Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: Add per-node option to Custom Graphs #23771

Merged
merged 1 commit into from
Mar 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions pkg/ui/src/redux/nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as protos from "src/js/protos";
import {
nodeDisplayNameByIDSelector,
selectCommissionedNodeStatuses,
selectStoreIDsByNodeID,
LivenessStatus,
sumNodeStats,
} from "./nodes";
Expand Down Expand Up @@ -110,6 +111,43 @@ describe("node data selectors", function() {
assert.deepEqual(nodeDisplayNameByIDSelector(store.getState()), {});
});
});

describe("store IDs by node ID", function() {
it("correctly creates storeID map", function() {
const data = [
{
desc: { node_id: 1 },
store_statuses: [
{ desc: { store_id: 1 }},
{ desc: { store_id: 2 }},
{ desc: { store_id: 3 }},
],
},
{
desc: { node_id: 2 },
store_statuses: [
{ desc: { store_id: 4 }},
],
},
{
desc: { node_id: 3 },
store_statuses: [
{ desc: { store_id: 5 }},
{ desc: { store_id: 6 }},
],
},
];
const store = createAdminUIStore();
store.dispatch(nodesReducerObj.receiveData(data));
const state = store.getState();

assert.deepEqual(selectStoreIDsByNodeID(state), {
1: ["1", "2", "3"],
2: ["4"],
3: ["5", "6"],
});
});
});
});

describe("selectCommissionedNodeStatuses", function() {
Expand Down
17 changes: 16 additions & 1 deletion pkg/ui/src/redux/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,19 @@ export const nodeDisplayNameByIDSelector = createSelector(
},
);

// selectStoreIDsByNodeID returns a map from node ID to a list of store IDs for
// that node. Like nodeIDsSelector, the store ids are converted to strings.
export const selectStoreIDsByNodeID = createSelector(
nodeStatusesSelector,
(nodeStatuses) => {
const result: {[key: string]: string[]} = {};
_.each(nodeStatuses, ns =>
result[ns.desc.node_id] = _.map(ns.store_statuses, ss => ss.desc.store_id.toString()),
);
return result;
},
);

/**
* nodesSummarySelector returns a directory object containing a variety of
* computed information based on the current nodes. This object is easy to
Expand All @@ -249,7 +262,8 @@ export const nodesSummarySelector = createSelector(
nodeDisplayNameByIDSelector,
livenessStatusByNodeIDSelector,
livenessByNodeIDSelector,
(nodeStatuses, nodeIDs, nodeStatusByID, nodeSums, nodeDisplayNameByID, livenessStatusByNodeID, livenessByNodeID) => {
selectStoreIDsByNodeID,
(nodeStatuses, nodeIDs, nodeStatusByID, nodeSums, nodeDisplayNameByID, livenessStatusByNodeID, livenessByNodeID, storeIDsByNodeID) => {
return {
nodeStatuses,
nodeIDs,
Expand All @@ -258,6 +272,7 @@ export const nodesSummarySelector = createSelector(
nodeDisplayNameByID,
livenessStatusByNodeID,
livenessByNodeID,
storeIDsByNodeID,
};
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import _ from "lodash";

import { NodesSummary } from "src/redux/nodes";

/**
Expand Down Expand Up @@ -45,9 +43,5 @@ export function nodeDisplayName(nodesSummary: NodesSummary, nid: string) {
}

export function storeIDsForNode(nodesSummary: NodesSummary, nid: string): string[] {
const ns = nodesSummary.nodeStatusByID[nid];
if (!ns) {
return [];
}
return _.map(ns.store_statuses, (ss) => ss.desc.store_id.toString());
return nodesSummary.storeIDsByNodeID[nid] || [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { DropdownOption } from "src/views/shared/components/dropdown";
import TimeSeriesQueryAggregator = protos.cockroach.ts.tspb.TimeSeriesQueryAggregator;
import TimeSeriesQueryDerivative = protos.cockroach.ts.tspb.TimeSeriesQueryDerivative;

const aggregatorOptions: DropdownOption[] = [
const downsamplerOptions: DropdownOption[] = [
TimeSeriesQueryAggregator.AVG,
TimeSeriesQueryAggregator.MAX,
TimeSeriesQueryAggregator.MIN,
TimeSeriesQueryAggregator.SUM,
].map(agg => ({ label: TimeSeriesQueryAggregator[agg], value: agg.toString() }));

const aggregatorOptions = downsamplerOptions;

const derivativeOptions: DropdownOption[] = [
{ label: "Normal", value: TimeSeriesQueryDerivative.NONE.toString() },
{ label: "Rate", value: TimeSeriesQueryDerivative.DERIVATIVE.toString() },
Expand All @@ -26,6 +28,7 @@ export class CustomMetricState {
downsampler = TimeSeriesQueryAggregator.AVG;
aggregator = TimeSeriesQueryAggregator.SUM;
derivative = TimeSeriesQueryDerivative.NONE;
perNode = false;
source = "";
}

Expand Down Expand Up @@ -73,6 +76,12 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
});
}

changePerNode = (selection: React.FormEvent<HTMLInputElement>) => {
this.changeState({
perNode: selection.currentTarget.checked,
});
}

deleteOption = () => {
this.props.onDelete(this.props.index);
}
Expand All @@ -81,7 +90,7 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
const {
metricOptions,
nodeOptions,
rowState: { metric, downsampler, aggregator, derivative, source },
rowState: { metric, downsampler, aggregator, derivative, source, perNode },
} = this.props;

return (
Expand All @@ -107,7 +116,7 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
clearable={false}
searchable={false}
value={downsampler.toString()}
options={aggregatorOptions}
options={downsamplerOptions}
onChange={this.changeDownsampler}
/>
</div>
Expand Down Expand Up @@ -148,6 +157,9 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
/>
</div>
</td>
<td className="metric-table__cell">
<input type="checkbox" checked={perNode} onChange={this.changePerNode} />
</td>
<td>
<button className="metric-edit-button" onClick={this.deleteOption}>Remove</button>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@
background inherit
padding 0

&__cell
text-align center

50 changes: 38 additions & 12 deletions pkg/ui/src/views/reports/containers/customgraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { PageConfig, PageConfigItem } from "src/views/shared/components/pageconf
import { CustomMetricState, CustomMetricRow } from "./customMetric";
import "./customgraph.styl";

import { NodeStatus$Properties } from "../../../../util/proto";

const axisUnitsOptions: DropdownOption[] = [
AxisUnits.Count,
AxisUnits.Bytes,
Expand Down Expand Up @@ -66,7 +68,7 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {

return _.keys(nodeStatuses[0].metrics).map(k => {
const fullMetricName =
_.has(nodeStatuses[0].store_statuses[0].metrics, k)
isStoreMetric(nodeStatuses[0], k)
? "cr.store." + k
: "cr.node." + k;

Expand Down Expand Up @@ -148,6 +150,7 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {
renderGraph() {
const metrics = this.currentMetrics();
const units = this.currentAxisUnits();
const { nodesSummary } = this.props;
if (_.isEmpty(metrics)) {
return (
<section className="section">
Expand All @@ -164,17 +167,35 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {
{
metrics.map((m, i) => {
if (m.metric !== "") {
return (
<Metric
key={i}
title={m.metric}
name={m.metric}
aggregator={m.aggregator}
downsampler={m.downsampler}
derivative={m.derivative}
sources={m.source === "" ? [] : [m.source]}
/>
);
if (m.perNode) {
return _.map(nodesSummary.nodeIDs, (nodeID) => (
<Metric
key={"${i}${nodeID}"}
title={`${nodeID}: ${m.metric} (${i})`}
name={m.metric}
aggregator={m.aggregator}
downsampler={m.downsampler}
derivative={m.derivative}
sources={
isStoreMetric(nodesSummary.nodeStatuses[0], m.metric)
? _.map(nodesSummary.storeIDsByNodeID[nodeID] || [], n => n.toString())
: [nodeID]
}
/>
));
} else {
return (
<Metric
key={i}
title={`${m.metric} (${i}) `}
name={m.metric}
aggregator={m.aggregator}
downsampler={m.downsampler}
derivative={m.derivative}
sources={m.source === "" ? [] : [m.source]}
/>
);
}
}
return "";
})
Expand Down Expand Up @@ -202,6 +223,7 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {
<td className="metric-table__header">Aggregator</td>
<td className="metric-table__header">Rate</td>
<td className="metric-table__header">Source</td>
<td className="metric-table__header">Per Node</td>
<td className="metric-table__header metric-table__header--no-title"></td>
</tr>
</thead>
Expand Down Expand Up @@ -266,3 +288,7 @@ const mapDispatchToProps = {
};

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(CustomGraph));

function isStoreMetric(nodeStatus: NodeStatus$Properties, metricName: string) {
return _.has(nodeStatus.store_statuses[0].metrics, metricName);
}