Allow the resource detail page to display traffic from more than one resource type (#2108)

As mentioned in #2006, the resource detail page currently only shows inbound/outbound traffic from resources that match the type being viewed (e.g. if we are on the page for deploy/voting, inbound/outbound traffic to a daemonset won't be shown).

This branch updates the ResourceDetail code to display traffic from more than one resource type (this applies to the Octopus Graph as well).

For things that comprise pods, e.g deployments and daemonsets, we omit showing authorities,
services and pods

This PR also updates the Metric Table to handle the display of a table of multiple different resource types.
This commit is contained in:
Risha Mars 2019-01-22 12:37:36 -08:00 committed by GitHub
parent eacc09b7ba
commit db33a60b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 35 deletions

View File

@ -1,5 +1,4 @@
import { friendlyTitle, metricToFormatter, numericSort } from './util/Utils.js';
import { displayName, friendlyTitle, metricToFormatter, numericSort } from './util/Utils.js';
import BaseTable from './BaseTable.jsx';
import ErrorModal from './ErrorModal.jsx';
import GrafanaLink from './GrafanaLink.jsx';
@ -17,6 +16,8 @@ import { withContext } from './util/AppContext.jsx';
const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
let isAuthorityTable = resource === "authority";
let isMultiResourceTable = resource === "multi_resource";
let getResourceDisplayName = isMultiResourceTable ? displayName : d => d.name;
let nsColumn = [
{
@ -38,7 +39,7 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
let columns = [
{
title: friendlyTitle(resource).singular,
title: isMultiResourceTable ? "Resource" : friendlyTitle(resource).singular,
dataIndex: "name",
isNumeric: false,
render: d => {
@ -46,11 +47,11 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
if (resource === "namespace") {
nameContents = <PrefixedLink to={"/namespaces/" + d.name}>{d.name}</PrefixedLink>;
} else if (!d.added || isAuthorityTable) {
nameContents = d.name;
nameContents = getResourceDisplayName(d);
} else {
nameContents = (
<PrefixedLink to={"/namespaces/" + d.namespace + "/" + resource + "s/" + d.name}>
{d.name}
<PrefixedLink to={"/namespaces/" + d.namespace + "/" + d.type + "s/" + d.name}>
{getResourceDisplayName(d)}
</PrefixedLink>
);
}
@ -58,11 +59,11 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
<Grid container alignItems="center" spacing={8}>
<Grid item>{nameContents}</Grid>
{ _isEmpty(d.errors) ? null :
<Grid item><ErrorModal errors={d.errors} resourceName={d.name} resourceType={resource} /></Grid>}
<Grid item><ErrorModal errors={d.errors} resourceName={d.name} resourceType={d.type} /></Grid>}
</Grid>
);
},
sorter: (a, b) => (a.name || "").localeCompare(b.name)
sorter: (a, b) => (getResourceDisplayName(a) || "").localeCompare(getResourceDisplayName(b))
},
{
title: "Success Rate",
@ -121,7 +122,7 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
<GrafanaLink
name={row.name}
namespace={row.namespace}
resource={resource}
resource={row.type}
PrefixedLink={PrefixedLink} />
);
}

View File

@ -17,9 +17,9 @@ import _floor from 'lodash/floor';
import _get from 'lodash/get';
import _isEmpty from 'lodash/isEmpty';
import _isNil from 'lodash/isNil';
import _orderBy from 'lodash/orderBy';
import _size from 'lodash/size';
import _slice from 'lodash/slice';
import _sortBy from 'lodash/sortBy';
import _take from 'lodash/take';
import _times from 'lodash/times';
import { getSuccessRateClassification } from './util/MetricUtils.jsx' ;
@ -62,8 +62,11 @@ class Octopus extends React.Component {
getNeighborDisplayData = neighbors => {
// only display maxNumNeighbors neighboring nodes in the octopus graph,
// otherwise it will be really tall
let upstreams = _orderBy(neighbors.upstream, n => n.successRate);
let downstreams = _orderBy(neighbors.downstream, n => n.successRate);
// even though _sortBy is a stable sort, the order that this data is returned by the API
// can change, so the original order of items can change; this means we have to sort by
// name and by SR to ensure an actual stable sort
let upstreams = _sortBy(_sortBy(neighbors.upstream, displayName), n => n.successRate);
let downstreams = _sortBy(_sortBy(neighbors.downstream, displayName), n => n.successRate);
let display = {
upstreams: {
@ -103,7 +106,7 @@ class Octopus extends React.Component {
let Progress = StyledProgress(classification);
return (
<Grid item key={resource.name} >
<Grid item key={resource.type + "-" + resource.name} >
<Card className={type === "neighbor" ? classes.neighborNode : classes.centerNode} title={display}>
<CardContent>

View File

@ -1,5 +1,5 @@
import 'whatwg-fetch';
import { emptyMetric, processSingleResourceRollup } from './util/MetricUtils.jsx';
import { emptyMetric, processMultiResourceRollup, processSingleResourceRollup } from './util/MetricUtils.jsx';
import { resourceTypeToCamelCase, singularResource } from './util/Utils.js';
import AddResources from './AddResources.jsx';
import ErrorBanner from './ErrorBanner.jsx';
@ -71,10 +71,8 @@ export class ResourceDetailBase extends React.Component {
pollingInterval: 2000,
resourceMetrics: [],
podMetrics: [], // metrics for all pods whose owner is this resource
neighborMetrics: {
upstream: [],
downstream: []
},
upstreamMetrics: {}, // metrics for resources who send traffic to this resource
downstreamMetrics: {}, // metrics for resources who this resouce sends traffic to
unmeshedSources: {},
resourceIsMeshed: true,
pendingRequests: false,
@ -102,6 +100,21 @@ export class ResourceDetailBase extends React.Component {
this.api.cancelCurrentRequests();
}
getDisplayMetrics(metricsByResource) {
// if we're displaying a pod detail page, only display pod metrics
// if we're displaying another type of resource page, display metrics for
// rcs, deploys, replicasets, etc but not pods or authorities
let shouldExclude = this.state.resourceType === "pod" ?
r => r !== "pod" :
r => r === "pod" || r === "authority" || r === "service";
return _reduce(metricsByResource, (mem, resourceMetrics, resource) => {
if (shouldExclude(resource)) {
return mem;
}
return mem.concat(resourceMetrics);
}, []);
}
loadFromServer() {
if (this.state.pendingRequests) {
return; // don't make more requests if the ones we sent haven't completed
@ -123,11 +136,11 @@ export class ResourceDetailBase extends React.Component {
),
// upstream resources of this resource (meshed traffic only)
this.api.fetchMetrics(
`${this.api.urlsForResource(resource.type)}&to_name=${resource.name}&to_type=${resource.type}&to_namespace=${resource.namespace}`
`${this.api.urlsForResource("all")}&to_name=${resource.name}&to_type=${resource.type}&to_namespace=${resource.namespace}`
),
// downstream resources of this resource (meshed traffic only)
this.api.fetchMetrics(
`${this.api.urlsForResource(resource.type)}&from_name=${resource.name}&from_type=${resource.type}&from_namespace=${resource.namespace}`
`${this.api.urlsForResource("all")}&from_name=${resource.name}&from_type=${resource.type}&from_namespace=${resource.namespace}`
)
]);
@ -135,8 +148,8 @@ export class ResourceDetailBase extends React.Component {
.then(([resourceRsp, podListRsp, podMetricsRsp, upstreamRsp, downstreamRsp]) => {
let resourceMetrics = processSingleResourceRollup(resourceRsp);
let podMetrics = processSingleResourceRollup(podMetricsRsp);
let upstreamMetrics = processSingleResourceRollup(upstreamRsp);
let downstreamMetrics = processSingleResourceRollup(downstreamRsp);
let upstreamMetrics = processMultiResourceRollup(upstreamRsp);
let downstreamMetrics = processMultiResourceRollup(downstreamRsp);
// INEFFICIENT: get metrics for all the pods belonging to this resource.
// Do this by querying for metrics for all pods in this namespace and then filtering
@ -165,10 +178,8 @@ export class ResourceDetailBase extends React.Component {
resourceMetrics,
resourceIsMeshed,
podMetrics: podMetricsForResource,
neighborMetrics: {
upstream: upstreamMetrics,
downstream: downstreamMetrics
},
upstreamMetrics,
downstreamMetrics,
lastMetricReceivedTime,
loaded: true,
pendingRequests: false,
@ -217,7 +228,6 @@ export class ResourceDetailBase extends React.Component {
resourceMetrics,
unmeshedSources,
resourceIsMeshed,
neighborMetrics,
lastMetricReceivedTime
} = this.state;
@ -227,7 +237,7 @@ export class ResourceDetailBase extends React.Component {
namespace
};
let unmeshed = _filter(unmeshedSources, d => d.type === resourceType)
let unmeshed = _filter(unmeshedSources, d => d.type !== "pod")
.map(d => _merge({}, emptyMetric, d, {
unmeshed: true,
pods: {
@ -236,7 +246,10 @@ export class ResourceDetailBase extends React.Component {
}
}));
let upstreams = neighborMetrics.upstream.concat(unmeshed);
let upstreamMetrics = this.getDisplayMetrics(this.state.upstreamMetrics);
let downstreamMetrics = this.getDisplayMetrics(this.state.downstreamMetrics);
let upstreams = upstreamMetrics.concat(unmeshed);
let showNoTrafficMsg = resourceIsMeshed && (Date.now() - lastMetricReceivedTime > showNoTrafficMsgDelayMs);
@ -268,7 +281,7 @@ export class ResourceDetailBase extends React.Component {
<Octopus
resource={resourceMetrics[0]}
neighbors={neighborMetrics}
neighbors={{ upstream: upstreamMetrics, downstream: downstreamMetrics }}
unmeshedSources={Object.values(unmeshedSources)}
api={this.api} />
@ -282,18 +295,18 @@ export class ResourceDetailBase extends React.Component {
<React.Fragment>
<Typography variant="h5">Inbound</Typography>
<MetricsTable
resource={this.state.resource.type}
metrics={upstreams} />
resource="multi_resource"
metrics={upstreamMetrics} />
</React.Fragment>
)
}
{ _isEmpty(this.state.neighborMetrics.downstream) ? null : (
{ _isEmpty(this.state.downstreamMetrics) ? null : (
<React.Fragment>
<Typography variant="h5">Outbound</Typography>
<MetricsTable
resource={this.state.resource.type}
metrics={this.state.neighborMetrics.downstream} />
resource="multi_resource"
metrics={downstreamMetrics} />
</React.Fragment>
)
}