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  }