github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 import _ from "lodash"; 12 import React from "react"; 13 import { createSelector } from "reselect"; 14 import { connect } from "react-redux"; 15 import Helmet from "react-helmet"; 16 import { withRouter } from "react-router-dom"; 17 18 import Loading from "src/views/shared/components/loading"; 19 import { ToolTipWrapper } from "src/views/shared/components/toolTip"; 20 import * as docsURL from "src/util/docs"; 21 import { FixLong } from "src/util/fixLong"; 22 import { cockroach } from "src/js/protos"; 23 import { AdminUIState } from "src/redux/state"; 24 import { 25 refreshDataDistribution, 26 refreshNodes, 27 refreshLiveness, 28 CachedDataReducerState, 29 } from "src/redux/apiReducers"; 30 import { LocalityTree, selectLocalityTree } from "src/redux/localities"; 31 import ReplicaMatrix, { SchemaObject } from "./replicaMatrix"; 32 import { TreeNode, TreePath } from "./tree"; 33 import "./index.styl"; 34 import {selectLivenessRequestStatus, selectNodeRequestStatus} from "src/redux/nodes"; 35 36 type DataDistributionResponse = cockroach.server.serverpb.DataDistributionResponse; 37 type NodeDescriptor = cockroach.roachpb.INodeDescriptor; 38 type ZoneConfig$Properties = cockroach.server.serverpb.DataDistributionResponse.IZoneConfig; 39 40 const ZONE_CONFIG_TEXT = ( 41 <span> 42 Zone configurations 43 (<a href={docsURL.configureReplicationZones} target="_blank">see documentation</a>) 44 control how CockroachDB distributes data across nodes. 45 </span> 46 ); 47 48 interface DataDistributionProps { 49 dataDistribution: CachedDataReducerState<DataDistributionResponse>; 50 localityTree: LocalityTree; 51 sortedZoneConfigs: ZoneConfig$Properties[]; 52 } 53 54 class DataDistribution extends React.Component<DataDistributionProps> { 55 56 renderZoneConfigs() { 57 return ( 58 <div className="zone-config-list"> 59 <ul> 60 {this.props.sortedZoneConfigs.map((zoneConfig) => ( 61 <li key={zoneConfig.target} className="zone-config"> 62 <pre className="zone-config__raw-sql"> 63 {zoneConfig.config_sql} 64 </pre> 65 </li> 66 ))} 67 </ul> 68 </div> 69 ); 70 } 71 72 getCellValue = (dbPath: TreePath, nodePath: TreePath): number => { 73 const [dbName, tableName] = dbPath; 74 const nodeID = nodePath[nodePath.length - 1]; 75 const databaseInfo = this.props.dataDistribution.data.database_info; 76 77 const res = databaseInfo[dbName].table_info[tableName].replica_count_by_node_id[nodeID]; 78 if (!res) { 79 return 0; 80 } 81 return FixLong(res).toInt(); 82 } 83 84 render() { 85 const nodeTree = nodeTreeFromLocalityTree("Cluster", this.props.localityTree); 86 87 const databaseInfo = this.props.dataDistribution.data.database_info; 88 const dbTree: TreeNode<SchemaObject> = { 89 name: "Cluster", 90 data: { 91 dbName: null, 92 tableName: null, 93 }, 94 children: _.map(databaseInfo, (dbInfo, dbName) => ({ 95 name: dbName, 96 data: { 97 dbName, 98 }, 99 children: _.map(dbInfo.table_info, (tableInfo, tableName) => ({ 100 name: tableName, 101 data: { 102 dbName, 103 tableName, 104 droppedAt: tableInfo.dropped_at, 105 }, 106 })), 107 })), 108 }; 109 110 return ( 111 <div className="data-distribution"> 112 <div className="data-distribution__zone-config-sidebar"> 113 <h2 className="base-heading"> 114 Zone Configs{" "} 115 <div className="section-heading__tooltip"> 116 <ToolTipWrapper text={ZONE_CONFIG_TEXT}> 117 <div className="section-heading__tooltip-hover-area"> 118 <div className="section-heading__info-icon">i</div> 119 </div> 120 </ToolTipWrapper> 121 </div> 122 </h2> 123 {this.renderZoneConfigs()} 124 <p style={{ maxWidth: 300, paddingTop: 10 }}> 125 Dropped tables appear <span className="table-label--dropped">greyed out</span>. 126 Their replicas will be garbage collected according to 127 the <code>gc.ttlseconds</code> setting in their zone configs. 128 </p> 129 </div> 130 <div> 131 <ReplicaMatrix 132 cols={nodeTree} 133 rows={dbTree} 134 getValue={this.getCellValue} 135 /> 136 </div> 137 </div> 138 ); 139 } 140 } 141 142 interface DataDistributionPageProps { 143 dataDistribution: CachedDataReducerState<DataDistributionResponse>; 144 localityTree: LocalityTree; 145 localityTreeErrors: Error[]; 146 sortedZoneConfigs: ZoneConfig$Properties[]; 147 refreshDataDistribution: typeof refreshDataDistribution; 148 refreshNodes: typeof refreshNodes; 149 refreshLiveness: typeof refreshLiveness; 150 } 151 152 export class DataDistributionPage extends React.Component<DataDistributionPageProps> { 153 154 componentDidMount() { 155 this.props.refreshDataDistribution(); 156 this.props.refreshNodes(); 157 this.props.refreshLiveness(); 158 } 159 160 componentDidUpdate() { 161 this.props.refreshDataDistribution(); 162 this.props.refreshNodes(); 163 this.props.refreshLiveness(); 164 } 165 166 render() { 167 return ( 168 <div> 169 <Helmet title="Data Distribution" /> 170 <section className="section"> 171 <h1 className="base-heading">Data Distribution</h1> 172 </section> 173 <section className="section"> 174 <Loading 175 loading={!this.props.dataDistribution.data || !this.props.localityTree} 176 error={[this.props.dataDistribution.lastError, ...this.props.localityTreeErrors]} 177 render={() => ( 178 <DataDistribution 179 localityTree={this.props.localityTree} 180 dataDistribution={this.props.dataDistribution} 181 sortedZoneConfigs={this.props.sortedZoneConfigs} 182 /> 183 )} 184 /> 185 </section> 186 </div> 187 ); 188 } 189 } 190 191 const sortedZoneConfigs = createSelector( 192 (state: AdminUIState) => state.cachedData.dataDistribution, 193 (dataDistributionState) => { 194 if (!dataDistributionState.data) { 195 return null; 196 } 197 return _.sortBy(dataDistributionState.data.zone_configs, (zc) => zc.target); 198 }, 199 ); 200 201 const localityTreeErrors = createSelector( 202 selectNodeRequestStatus, 203 selectLivenessRequestStatus, 204 (nodes, liveness) => [nodes.lastError, liveness.lastError], 205 ); 206 207 // tslint:disable-next-line:variable-name 208 const DataDistributionPageConnected = withRouter(connect( 209 (state: AdminUIState) => ({ 210 dataDistribution: state.cachedData.dataDistribution, 211 sortedZoneConfigs: sortedZoneConfigs(state), 212 localityTree: selectLocalityTree(state), 213 localityTreeErrors: localityTreeErrors(state), 214 }), 215 { 216 refreshDataDistribution, 217 refreshNodes, 218 refreshLiveness, 219 }, 220 )(DataDistributionPage)); 221 222 export default DataDistributionPageConnected; 223 224 // Helpers 225 226 function nodeTreeFromLocalityTree( 227 rootName: string, 228 localityTree: LocalityTree, 229 ): TreeNode<NodeDescriptor> { 230 const children: TreeNode<any>[] = []; 231 232 // Add child localities. 233 _.forEach(localityTree.localities, (valuesForKey, key) => { 234 _.forEach(valuesForKey, (subLocalityTree, value) => { 235 children.push(nodeTreeFromLocalityTree(`${key}=${value}`, subLocalityTree)); 236 }); 237 }); 238 239 // Add child nodes. 240 _.forEach(localityTree.nodes, (node) => { 241 children.push({ 242 name: node.desc.node_id.toString(), 243 data: node.desc, 244 }); 245 }); 246 247 return { 248 name: rootName, 249 children: children, 250 }; 251 }