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 Jun 12, 2018
1 parent 4756ea8 commit 43768b9
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 @@ -33,6 +33,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 @@ -141,6 +142,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
9 changes: 9 additions & 0 deletions pkg/ui/src/redux/apiReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ const storesReducerObj = new KeyedCachedDataReducer(
);
export const refreshStores = storesReducerObj.refresh;

const dataDistributionReducerObj = new CachedDataReducer(
api.getDataDistribution,
"dataDistribution",
moment.duration(1, "m"),
);
export const refreshDataDistribution = dataDistributionReducerObj.refresh;

export interface APIReducersState {
cluster: CachedDataReducerState<api.ClusterResponseMessage>;
events: CachedDataReducerState<api.EventsResponseMessage>;
Expand All @@ -265,6 +272,7 @@ export interface APIReducersState {
commandQueue: KeyedCachedDataReducerState<api.CommandQueueResponseMessage>;
settings: CachedDataReducerState<api.SettingsResponseMessage>;
stores: KeyedCachedDataReducerState<api.StoresResponseMessage>;
dataDistribution: CachedDataReducerState<api.DataDistributionResponseMessage>;
}

export const apiReducersReducer = combineReducers<APIReducersState>({
Expand Down Expand Up @@ -292,6 +300,7 @@ export const apiReducersReducer = combineReducers<APIReducersState>({
[commandQueueReducerObj.actionNamespace]: commandQueueReducerObj.reducer,
[settingsReducerObj.actionNamespace]: settingsReducerObj.reducer,
[storesReducerObj.actionNamespace]: storesReducerObj.reducer,
[dataDistributionReducerObj.actionNamespace]: dataDistributionReducerObj.reducer,
});

export { CachedDataReducerState, KeyedCachedDataReducerState };
7 changes: 7 additions & 0 deletions pkg/ui/src/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export type StoresResponseMessage = protos.cockroach.server.serverpb.StoresRespo

export type UserLogoutResponseMessage = protos.cockroach.server.serverpb.UserLogoutResponse;

export type DataDistributionResponseMessage = protos.cockroach.server.serverpb.DataDistributionResponse;

// API constants

export const API_PREFIX = "_admin/v1";
Expand Down Expand Up @@ -330,3 +332,8 @@ export function userLogout(timeout?: moment.Duration): Promise<UserLogoutRespons
export function getStores(req: StoresRequestMessage, timeout?: moment.Duration): Promise<StoresResponseMessage> {
return timeoutFetch(serverpb.StoresResponse, `${STATUS_PREFIX}/stores/${req.node_id}`, null, timeout);
}

// getDataDistribution returns information about how replicas are distributed across nodes.
export function getDataDistribution(timeout?: moment.Duration): Promise<DataDistributionResponseMessage> {
return timeoutFetch(serverpb.DataDistributionResponse, `${API_PREFIX}/data_distribution`, null, timeout);
}
1 change: 1 addition & 0 deletions pkg/ui/src/util/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const startFlags = docsURL("start-a-node.html#flags");
export const pauseJob = docsURL("pause-job.html");
export const cancelJob = docsURL("cancel-job.html");
export const enableNodeMap = docsURL("enable-node-map.html");
export const zoneConfigs = docsURL("configure-replication-zones.html");

// Note that these explicitly don't use the current version, since we want to
// link to the most up-to-date documentation available.
Expand Down
11 changes: 11 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,11 @@
.data-distribution
display: flex

&__zone-config-sidebar
padding-right 20px

.zone-config
padding-top 10px

&__raw-yaml
padding-top 5px
204 changes: 204 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,204 @@
import _ from "lodash";
import React from "react";
import { connect } from "react-redux";

import Loading from "src/views/shared/components/loading";
import spinner from "assets/spinner.gif";
import { ToolTipWrapper } from "src/views/shared/components/toolTip";
import * as docsURL from "src/util/docs";
import { FixLong } from "src/util/fixLong";
import { cockroach } from "src/js/protos";
import { AdminUIState } from "src/redux/state";
import { refreshDataDistribution, refreshNodes, refreshLiveness } from "src/redux/apiReducers";
import { LocalityTree, selectLocalityTree } from "src/redux/localities";
import ReplicaMatrix, { SchemaObject } from "./replicaMatrix";
import { TreeNode, TreePath } from "./tree";
import "./index.styl";

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

const ZONE_CONFIG_TEXT = (
<span>
Zone configurations (<a href={docsURL.zoneConfigs} target="_blank">see documentation</a>)
control how CockroachDB distributes data across nodes.
</span>
);

interface DataDistributionProps {
dataDistribution: DataDistributionResponse;
localityTree: LocalityTree;
}

class DataDistribution extends React.Component<DataDistributionProps> {

renderZoneConfigs() {
const zoneConfigs = this.props.dataDistribution.zone_configs;
const sortedIDs = Object.keys(zoneConfigs);
sortedIDs.sort();

return (
<div className="zone-config-list">
<ul>
{sortedIDs.map((zcId) => {
const zoneConfig = zoneConfigs[zcId];
return (
<li key={zcId} className="zone-config">
<h3>{zoneConfig.cli_specifier}</h3>
<pre className="zone-config__raw-yaml">
{zoneConfig.config_yaml}
</pre>
</li>
);
})}
</ul>
</div>
);
}

getCellValue = (dbPath: TreePath, nodePath: TreePath): number => {
const [dbName, tableName] = dbPath;
const nodeID = nodePath[nodePath.length - 1];
const databaseInfo = this.props.dataDistribution.database_info;

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

render() {
const nodeTree = nodeTreeFromLocalityTree("Cluster", this.props.localityTree);
const databaseInfo = this.props.dataDistribution.database_info;

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

return (
<div className="data-distribution">
<div className="data-distribution__zone-config-sidebar">
<h2>
Zone Configs{" "}
<div className="section-heading__tooltip">
<ToolTipWrapper text={ZONE_CONFIG_TEXT}>
<div className="section-heading__tooltip-hover-area">
<div className="section-heading__info-icon">i</div>
</div>
</ToolTipWrapper>
</div>
</h2>
{this.renderZoneConfigs()}
</div>
<div>
<ReplicaMatrix
cols={nodeTree}
rows={dbTree}
getValue={this.getCellValue}
/>
</div>
</div>
);
}
}

interface DataDistributionPageProps {
dataDistribution: DataDistributionResponse;
localityTree: LocalityTree;
refreshDataDistribution: typeof refreshDataDistribution;
refreshNodes: typeof refreshNodes;
refreshLiveness: typeof refreshLiveness;
}

class DataDistributionPage extends React.Component<DataDistributionPageProps> {

componentDidMount() {
this.props.refreshDataDistribution();
this.props.refreshNodes();
this.props.refreshLiveness();
}

componentWillReceiveProps() {
this.props.refreshDataDistribution();
this.props.refreshNodes();
this.props.refreshLiveness();
}

render() {
return (
<div>
<section className="section">
<h1>Data Distribution</h1>
</section>
<section className="section">
<Loading
className="loading-image loading-image__spinner-left"
loading={!this.props.dataDistribution || !this.props.localityTree}
image={spinner}
>
<DataDistribution
localityTree={this.props.localityTree}
dataDistribution={this.props.dataDistribution}
/>
</Loading>
</section>
</div>
);
}
}

// tslint:disable-next-line:variable-name
const DataDistributionPageConnected = connect(
(state: AdminUIState) => {
return {
dataDistribution: state.cachedData.dataDistribution.data,
localityTree: selectLocalityTree(state),
};
},
{
refreshDataDistribution,
refreshNodes,
refreshLiveness,
},
)(DataDistributionPage);

export default DataDistributionPageConnected;

// Helpers

function nodeTreeFromLocalityTree(
rootName: string,
localityTree: LocalityTree,
): TreeNode<NodeDescriptor> {
const children: TreeNode<any>[] = [];

// Add child localities.
_.forEach(localityTree.localities, (valuesForKey, key) => {
_.forEach(valuesForKey, (subLocalityTree, value) => {
children.push(nodeTreeFromLocalityTree(`${key}=${value}`, subLocalityTree));
});
});

// Add child nodes.
_.forEach(localityTree.nodes, (node) => {
children.push({
name: node.desc.node_id.toString(),
data: node.desc,
});
});

return {
name: rootName,
children: children,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.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.value
text-align center

&__metric-label
font-style italic

&__row--node
cursor pointer

&__row--node:hover
background-color rgb(240, 240, 240)

&__row-label
text-align left

&__row-label--node
font-weight bold

&__column-header
font-weight bold

&__column-header--node
cursor pointer

&__column-header--node:hover
background-color rgb(240, 240, 240)

&__cell-value
text-align right
Loading

0 comments on commit 43768b9

Please sign in to comment.