Skip to content

Commit

Permalink
ui: add data distribution screen (aka replica matrix)
Browse files Browse the repository at this point in the history
with collapsible tree matrix widget
  • Loading branch information
Pete Vilter committed Apr 17, 2018
1 parent 63de506 commit da3cc35
Show file tree
Hide file tree
Showing 11 changed files with 1,169 additions and 1 deletion.
2 changes: 2 additions & 0 deletions pkg/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Layout from "src/views/app/containers/layout";
import { DatabaseTablesList, DatabaseGrantsList } from "src/views/databases/containers/databases";
import TableDetails from "src/views/databases/containers/tableDetails";
import { EventPage } from "src/views/cluster/containers/events";
import DataDistributionPage from "src/views/cluster/containers/dataDistribution";
import Raft from "src/views/devtools/containers/raft";
import RaftRanges from "src/views/devtools/containers/raftRanges";
import RaftMessages from "src/views/devtools/containers/raftMessages";
Expand Down Expand Up @@ -136,6 +137,7 @@ ReactDOM.render(
<Route path="network" component={ Network } />
<Route path="nodes" component={ Nodes } />
<Route path="settings" component={ Settings } />
<Route path="datadistribution" component={ DataDistributionPage } />
<Route path={`certificates/:${nodeIDAttr}`} component={ Certificates } />
<Route path={`range/:${rangeIDAttr}`} component={ Range } />
<Route path={`range/:${rangeIDAttr}/cmdqueue`} component={ CommandQueue } />
Expand Down
5 changes: 5 additions & 0 deletions pkg/ui/src/redux/apiReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ export const settingsReducerObj = new CachedDataReducer(
);
export const refreshSettings = settingsReducerObj.refresh;

const replicaMatrixReducerObj = new CachedDataReducer(api.getReplicaMatrix, "replicaMatrix", moment.duration(1, "m"));
export const refreshReplicaMatrix = replicaMatrixReducerObj.refresh;

export interface APIReducersState {
cluster: CachedDataReducerState<api.ClusterResponseMessage>;
events: CachedDataReducerState<api.EventsResponseMessage>;
Expand All @@ -252,6 +255,7 @@ export interface APIReducersState {
rangeLog: KeyedCachedDataReducerState<api.RangeLogResponseMessage>;
commandQueue: KeyedCachedDataReducerState<api.CommandQueueResponseMessage>;
settings: CachedDataReducerState<api.SettingsResponseMessage>;
replicaMatrix: CachedDataReducerState<api.ReplicaMatrixResponseMessage>;
}

export const apiReducersReducer = combineReducers<APIReducersState>({
Expand All @@ -278,6 +282,7 @@ export const apiReducersReducer = combineReducers<APIReducersState>({
[rangeLogReducerObj.actionNamespace]: rangeLogReducerObj.reducer,
[commandQueueReducerObj.actionNamespace]: commandQueueReducerObj.reducer,
[settingsReducerObj.actionNamespace]: settingsReducerObj.reducer,
[replicaMatrixReducerObj.actionNamespace]: replicaMatrixReducerObj.reducer,
});

export { CachedDataReducerState, KeyedCachedDataReducerState };
9 changes: 9 additions & 0 deletions pkg/ui/src/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export type CommandQueueResponseMessage = protos.cockroach.server.serverpb.Comma
export type SettingsRequestMessage = protos.cockroach.server.serverpb.SettingsRequest;
export type SettingsResponseMessage = protos.cockroach.server.serverpb.SettingsResponse;

export type ReplicaMatrixRequestMessage = protos.cockroach.server.serverpb.ReplicaMatrixRequest;
export type ReplicaMatrixResponseMessage = protos.cockroach.server.serverpb.ReplicaMatrixResponse;

// API constants

export const API_PREFIX = "_admin/v1";
Expand Down Expand Up @@ -309,3 +312,9 @@ export function getCommandQueue(req: CommandQueueRequestMessage, timeout?: momen
export function getSettings(_req: SettingsRequestMessage, timeout?: moment.Duration): Promise<SettingsResponseMessage> {
return timeoutFetch(serverpb.SettingsResponse, `${API_PREFIX}/settings`, null, timeout);
}

// getReplicaMatrix returns data distribution information
// TODO(vilterp): rename to DataDistribution?
export function getReplicaMatrix(timeout?: moment.Duration): Promise<ReplicaMatrixResponseMessage> {
return timeoutFetch(serverpb.ReplicaMatrixResponse, `${API_PREFIX}/replica_matrix`, null, timeout);
}
19 changes: 19 additions & 0 deletions pkg/ui/src/views/cluster/containers/dataDistribution/index.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.replica-matrix
background-color white
border-collapse collapse
border-bottom 1px solid lightgrey
border-top 1px solid lightgrey

thead
border-bottom 1px solid black

th, td
padding 5px
border-left 1px solid lightgrey
border-right 1px solid lightgrey

td.table-name
text-align left

td.value
text-align center
180 changes: 180 additions & 0 deletions pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import _ from "lodash";
import React from "react";
import { Link } from "react-router";
import { connect } from "react-redux";

import Loading from "src/views/shared/components/loading";
import spinner from "assets/spinner.gif";
import { cockroach } from "src/js/protos";
import { NodeStatus$Properties } from "src/util/proto";
import { AdminUIState } from "src/redux/state";
import docsURL from "src/util/docs";
import { refreshReplicaMatrix, refreshNodes } from "src/redux/apiReducers";
import Matrix from "./matrix";
import ZoneConfigList from "./zoneConfigList";
import { TreeNode, setAtPath, TreePath } from "./tree";
import "./index.styl";

type ReplicaMatrixResponse = cockroach.server.serverpb.ReplicaMatrixResponse;
type NodeDescriptor = cockroach.roachpb.NodeDescriptor$Properties;

interface TableDesc {
dbName: string;
tableName?: string;
}

function makeNodeTree(nodes: NodeDescriptor[]): TreeNode<NodeDescriptor> {
const root: TreeNode<NodeDescriptor> = {
name: "Cluster",
data: {},
children: [],
};

nodes.forEach((node) => {
const path = node.locality.tiers.map((tier) => `${tier.key}=${tier.value}`);
setAtPath(root, path, {
name: `n${node.node_id.toString()}`,
data: node,
});
});
return root;
}

const ZONE_CONFIGS_DOCS_URL = docsURL("configure-replication-zones.html");

class ReplicaMatrix extends Matrix<TableDesc, NodeDescriptor> {}

interface DataDistributionProps {
replicaMatrix: ReplicaMatrixResponse;
nodes: NodeStatus$Properties[];
refreshReplicaMatrix: typeof refreshReplicaMatrix;
refreshNodes: typeof refreshNodes;
}

class DataDistribution extends React.Component<DataDistributionProps> {

render() {
// TODO(vilterp): use locality tree selector
const nodeTree = makeNodeTree(this.props.nodes.map((n) => n.desc));
const databaseInfo = this.props.replicaMatrix.database_info;

const dbTree: TreeNode<TableDesc> = {
name: "Cluster",
data: null,
children: _.map(databaseInfo, (dbInfo, dbName) => ({
name: dbName,
data: { dbName },
children: _.map(dbInfo.table_info, (_, tableName) => ({
name: tableName,
data: { dbName, tableName },
})),
})),
};

function getValue(dbPath: TreePath, nodePath: TreePath): number {
const [dbName, tableName] = dbPath;
// TODO(vilterp): substring is to get rid of the "n" prefix; find a different way
const nodeID = nodePath[nodePath.length - 1].substr(1);

const res = databaseInfo[dbName].table_info[tableName].replica_count_by_node_id[nodeID];
if (!res) {
return 0;
}
return res.toInt();
}

return (
<table>
<tbody>
<tr>
<td style={{verticalAlign: "top", paddingRight: 20}}>
<h2>
Zone Configs{" "}
<a href={ZONE_CONFIGS_DOCS_URL}>
<small>(?)</small>
</a>
</h2>
<ZoneConfigList/>
</td>
<td style={{verticalAlign: "top"}}>
<ReplicaMatrix
label={<em># Replicas</em>}
cols={nodeTree}
rows={dbTree}
colNodeLabel={(_, path, isPlaceholder) => (
isPlaceholder ? "" : path[path.length - 1]
)}
colLeafLabel={(node, path, isPlaceholder) => (
isPlaceholder
? ""
: node === null
? path[path.length - 1]
: `n${node.node_id.toString()}`
)}
rowNodeLabel={(row: TableDesc) => (`DB: ${row.dbName}`)}
rowLeafLabel={(row: TableDesc) => (row.tableName)}
getValue={getValue}
/>
</td>
</tr>
</tbody>
</table>
);
}
}

class DataDistributionPage extends React.Component<DataDistributionProps> {

componentDidMount() {
this.props.refreshReplicaMatrix();
this.props.refreshNodes();
}

componentDidUpdate() {
this.props.refreshReplicaMatrix();
this.props.refreshNodes();
}

render() {
return (
<div>
<section className="section parent-link">
<Link to="/debug">&lt; Back to Debug</Link>
</section>
<section className="section">
<h1>Data Distribution</h1>
</section>
<section className="section">
<Loading
className="loading-image loading-image__spinner-left"
loading={!this.props.replicaMatrix || !this.props.nodes}
image={spinner}
>
<DataDistribution
nodes={this.props.nodes}
refreshNodes={this.props.refreshNodes}
replicaMatrix={this.props.replicaMatrix}
refreshReplicaMatrix={this.props.refreshReplicaMatrix}
/>
</Loading>
</section>
</div>
);
}
}

// tslint:disable-next-line:variable-name
const DataDistributionPageConnected = connect(
(state: AdminUIState) => {
return {
replicaMatrix: state.cachedData.replicaMatrix.data,
nodes: state.cachedData.nodes.data,
};
},
{
refreshReplicaMatrix,
refreshNodes,
},
)(DataDistributionPage);

export default DataDistributionPageConnected;
14 changes: 14 additions & 0 deletions pkg/ui/src/views/cluster/containers/dataDistribution/matrix.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.matrix-row.node
cursor pointer

.matrix-row.node:hover
background-color rgb(240, 240, 240)

.matrix-column-header
font-weight bold

.matrix-column-header.toggleable
cursor pointer

.matrix-column-header.toggleable:hover
background-color rgb(240, 240, 240)
Loading

0 comments on commit da3cc35

Please sign in to comment.