Add a namespace column to the metrics tables (#854)

* Add a namespace column to the metrics tables, support long resource names

* Add a test for GrafanaLink

* Change the PodList.jsx component to not use the ListPods api
This commit is contained in:
Risha Mars 2018-04-26 16:34:59 -07:00 committed by GitHub
parent 97bf4fcdf2
commit 4661aaf30d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 111 additions and 43 deletions

View File

@ -241,6 +241,19 @@ a.button.primary:active {
padding-right: 0;
}
& tr > th.long-header > span {
display: inline-block;
margin-right: 0;
& div {
float: left;
&.ant-table-column-sorter {
padding-top: 12px;
}
}
}
& .ant-table-tbody > tr:hover > td {
background: rgba(47, 128, 237, .1);
}

View File

@ -4,15 +4,13 @@ export default class GrafanaLink extends React.Component {
render() {
let resourceVariableName = this.props.resource.toLowerCase().replace(" ", "_");
let dashboardName = this.props.resource.toLowerCase().replace(" ", "-");
let ownerInfo = this.props.name.split("/");
let namespace = ownerInfo[0];
let name = ownerInfo[1];
return (
<this.props.conduitLink
to={`/dashboard/db/conduit-${dashboardName}?var-namespace=${namespace}&var-${resourceVariableName}=${name}`}
to={`/dashboard/db/conduit-${dashboardName}?var-namespace=${this.props.namespace}&var-${resourceVariableName}=${this.props.name}`}
deployment={"grafana"}
targetBlank={true}>
{this.props.name}&nbsp;&nbsp;<i className="fa fa-external-link" />
{this.props.displayName || this.props.name}&nbsp;&nbsp;<i className="fa fa-external-link" />
</this.props.conduitLink>
);
}

View File

@ -20,54 +20,76 @@ const withTooltip = (d, metricName) => {
);
};
const narrowColumnWidth = 100;
const formatLongTitles = title => {
let words = title.split(" ");
if (words.length === 2) {
return (<div className="table-long-title">{words[0]}<br />{words[1]}</div>);
} else {
return words;
}
};
const columnDefinitions = (sortable = true, resource, ConduitLink) => {
return [
{
title: "Namespace",
key: "namespace",
dataIndex: "namespace",
sorter: sortable ? (a, b) => (a.namespace || "").localeCompare(b.namespace) : false
},
{
title: resource,
key: "name",
defaultSortOrder: 'ascend',
width: 150,
sorter: sortable ? (a, b) => (a.name || "").localeCompare(b.name) : false,
render: row => row.added ?
<GrafanaLink name={row.name} resource={resource} conduitLink={ConduitLink} /> : row.name
render: row => row.added ? <GrafanaLink
name={row.name}
namespace={row.namespace}
resource={resource}
conduitLink={ConduitLink} /> : row.name
},
{
title: "Success Rate",
title: formatLongTitles("Success Rate"),
dataIndex: "successRate",
key: "successRateRollup",
className: "numeric",
className: "numeric long-header",
width: narrowColumnWidth,
sorter: sortable ? (a, b) => numericSort(a.successRate, b.successRate) : false,
render: d => metricToFormatter["SUCCESS_RATE"](d)
},
{
title: "Request Rate",
title: formatLongTitles("Request Rate"),
dataIndex: "requestRate",
key: "requestRateRollup",
className: "numeric",
className: "numeric long-header",
width: narrowColumnWidth,
sorter: sortable ? (a, b) => numericSort(a.requestRate, b.requestRate) : false,
render: d => withTooltip(d, "REQUEST_RATE")
},
{
title: "P50 Latency",
title: formatLongTitles("P50 Latency"),
dataIndex: "P50",
key: "p50LatencyRollup",
className: "numeric",
className: "numeric long-header",
width: narrowColumnWidth,
sorter: sortable ? (a, b) => numericSort(a.P50, b.P50) : false,
render: metricToFormatter["LATENCY"]
},
{
title: "P95 Latency",
title: formatLongTitles("P95 Latency"),
dataIndex: "P95",
key: "p95LatencyRollup",
className: "numeric",
className: "numeric long-header",
width: narrowColumnWidth,
sorter: sortable ? (a, b) => numericSort(a.P95, b.P95) : false,
render: metricToFormatter["LATENCY"]
},
{
title: "P99 Latency",
title: formatLongTitles("P99 Latency"),
dataIndex: "P99",
key: "p99LatencyRollup",
className: "numeric",
className: "numeric long-header",
width: narrowColumnWidth,
sorter: sortable ? (a, b) => numericSort(a.P99, b.P99) : false,
render: metricToFormatter["LATENCY"]
}

View File

@ -34,17 +34,6 @@ export default class PodsList extends React.Component {
this.api.cancelCurrentRequests();
}
filterPods(pods, metrics) {
let podsByName = _.keyBy(pods, 'name');
return _.compact(_.map(metrics, metric => {
let pod = podsByName[metric.name];
if (pod && !pod.controlPlane) {
metric.added = pod.added;
return metric;
}
}));
}
loadFromServer() {
if (this.state.pendingRequests) {
return; // don't make more requests if the ones we sent haven't completed
@ -52,17 +41,15 @@ export default class PodsList extends React.Component {
this.setState({ pendingRequests: true });
this.api.setCurrentRequests([
this.api.fetchMetrics(this.api.urlsForResource["pod"].url().rollup),
this.api.fetchPods()
this.api.fetchMetrics(this.api.urlsForResource["pod"].url().rollup)
]);
Promise.all(this.api.getCurrentPromises())
.then(([rollup, pods]) => {
let meshPods = processRollupMetrics(rollup);
let combinedMetrics = this.filterPods(pods.pods, meshPods);
.then(([rollup]) => {
let processedMetrics = processRollupMetrics(rollup);
this.setState({
metrics: combinedMetrics,
metrics: processedMetrics,
loaded: true,
pendingRequests: false,
error: ''

View File

@ -44,8 +44,16 @@ const columns = {
return {
title: "Deployment",
key: "name",
render: row => shouldLink && row.added ?
<GrafanaLink name={row.name} resource="deployment" conduitLink={ConduitLink} /> : row.name
render: row => {
let ownerInfo = row.name.split("/");
return shouldLink && row.added ?
<GrafanaLink
name={ownerInfo[1]}
namespace={ownerInfo[0]}
displayName={row.name}
resource="deployment"
conduitLink={ConduitLink} /> : row.name;
}
};
},
pods: {

View File

@ -107,7 +107,8 @@ export const processRollupMetrics = (rawMetrics, controllerNamespace) => {
return null;
}
return {
name: row.resource.namespace + "/" + row.resource.name,
name: row.resource.name,
namespace: row.resource.namespace,
requestRate: getRequestRate(row),
successRate: getSuccessRate(row),
latency: getLatency(row),

View File

@ -0,0 +1,34 @@
import Adapter from 'enzyme-adapter-react-16';
import { ApiHelpers } from '../js/components/util/ApiHelpers.jsx';
import Enzyme from 'enzyme';
import { expect } from 'chai';
import GrafanaLink from '../js/components/GrafanaLink.jsx';
import { mount } from 'enzyme';
import { routerWrap } from './testHelpers.jsx';
import sinon from 'sinon';
import sinonStubPromise from 'sinon-stub-promise';
Enzyme.configure({ adapter: new Adapter() });
sinonStubPromise(sinon);
describe('GrafanaLink', () => {
it('makes a link', () => {
let api = ApiHelpers('');
let linkProps = {
resource: "Replication Controller",
name: "aldksf-3409823049823",
namespace: "myns",
conduitLink: api.ConduitLink
};
let component = mount(routerWrap(GrafanaLink, linkProps));
let expectedDashboardNameStr = "/conduit-replication-controller";
let expectedNsStr = "var-namespace=myns";
let expectedVarNameStr = "var-replication_controller=aldksf-3409823049823";
expect(component.find("GrafanaLink")).to.have.length(1);
expect(component.html()).to.contain(expectedDashboardNameStr);
expect(component.html()).to.contain(expectedNsStr);
expect(component.html()).to.contain(expectedVarNameStr);
});
});

View File

@ -9,7 +9,8 @@ describe('MetricUtils', () => {
let result = processRollupMetrics(deployRollupFixtures);
let expectedResult = [
{
name: 'emojivoto/voting',
name: 'voting',
namespace: 'emojivoto',
requestRate: 2.5,
successRate: 0.9,
latency: {
@ -26,10 +27,14 @@ describe('MetricUtils', () => {
it('Extracts and sorts multiple deploys from a single response', () => {
let result = processRollupMetrics(multiDeployRollupFixtures);
expect(result).to.have.length(4);
expect(result[0].name).to.equal("emojivoto/emoji");
expect(result[1].name).to.equal("emojivoto/vote-bot");
expect(result[2].name).to.equal("emojivoto/voting");
expect(result[3].name).to.equal("emojivoto/web");
expect(result[0].name).to.equal("emoji");
expect(result[0].namespace).to.equal("emojivoto");
expect(result[1].name).to.equal("vote-bot");
expect(result[1].namespace).to.equal("emojivoto");
expect(result[2].name).to.equal("voting");
expect(result[2].namespace).to.equal("emojivoto");
expect(result[3].name).to.equal("web");
expect(result[3].namespace).to.equal("emojivoto");
});
});
});