diff --git a/web/app/css/styles.css b/web/app/css/styles.css index 03db3162d..d6523d28c 100644 --- a/web/app/css/styles.css +++ b/web/app/css/styles.css @@ -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); } diff --git a/web/app/js/components/GrafanaLink.jsx b/web/app/js/components/GrafanaLink.jsx index a788f72be..f45eb53a4 100644 --- a/web/app/js/components/GrafanaLink.jsx +++ b/web/app/js/components/GrafanaLink.jsx @@ -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.name}   + {this.props.displayName || this.props.name}   ); } diff --git a/web/app/js/components/MetricsTable.jsx b/web/app/js/components/MetricsTable.jsx index 0a53850a5..86a53d198 100644 --- a/web/app/js/components/MetricsTable.jsx +++ b/web/app/js/components/MetricsTable.jsx @@ -20,54 +20,76 @@ const withTooltip = (d, metricName) => { ); }; +const narrowColumnWidth = 100; +const formatLongTitles = title => { + let words = title.split(" "); + if (words.length === 2) { + return (
{words[0]}
{words[1]}
); + } 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 ? - : row.name + render: row => row.added ? : 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"] } diff --git a/web/app/js/components/PodsList.jsx b/web/app/js/components/PodsList.jsx index 72b5ea5e3..fce657d63 100644 --- a/web/app/js/components/PodsList.jsx +++ b/web/app/js/components/PodsList.jsx @@ -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: '' diff --git a/web/app/js/components/StatusTable.jsx b/web/app/js/components/StatusTable.jsx index 0f0e1551c..c61395399 100644 --- a/web/app/js/components/StatusTable.jsx +++ b/web/app/js/components/StatusTable.jsx @@ -44,8 +44,16 @@ const columns = { return { title: "Deployment", key: "name", - render: row => shouldLink && row.added ? - : row.name + render: row => { + let ownerInfo = row.name.split("/"); + return shouldLink && row.added ? + : row.name; + } }; }, pods: { diff --git a/web/app/js/components/util/MetricUtils.js b/web/app/js/components/util/MetricUtils.js index d8e14594a..8ad8254c8 100644 --- a/web/app/js/components/util/MetricUtils.js +++ b/web/app/js/components/util/MetricUtils.js @@ -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), diff --git a/web/app/test/GrafanaLinkTest.jsx b/web/app/test/GrafanaLinkTest.jsx new file mode 100644 index 000000000..aacfeee5a --- /dev/null +++ b/web/app/test/GrafanaLinkTest.jsx @@ -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); + }); +}); diff --git a/web/app/test/MetricUtilsTest.js b/web/app/test/MetricUtilsTest.js index 34ad78492..3da89aeba 100644 --- a/web/app/test/MetricUtilsTest.js +++ b/web/app/test/MetricUtilsTest.js @@ -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"); }); }); });