github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/cluster/containers/dataDistribution/replicaMatrix.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, { Component } from "react";
    13  import classNames from "classnames";
    14  
    15  import {
    16    TreeNode,
    17    TreePath,
    18    layoutTreeHorizontal,
    19    flatten,
    20    sumValuesUnderPaths,
    21    LayoutCell,
    22    FlattenedNode,
    23  } from "./tree";
    24  import { ToolTipWrapper } from "src/views/shared/components/toolTip";
    25  import { TimestampToMoment } from "src/util/convert";
    26  
    27  import { cockroach } from "src/js/protos";
    28  import NodeDescriptor$Properties = cockroach.roachpb.INodeDescriptor;
    29  import { google } from "src/js/protos";
    30  import ITimestamp = google.protobuf.ITimestamp;
    31  
    32  import "./replicaMatrix.styl";
    33  
    34  const DOWN_ARROW = "▼";
    35  const SIDE_ARROW = "▶";
    36  
    37  interface ReplicaMatrixState {
    38    collapsedRows: TreePath[];
    39    collapsedCols: TreePath[];
    40  }
    41  
    42  interface ReplicaMatrixProps {
    43    cols: TreeNode<NodeDescriptor$Properties>;
    44    rows: TreeNode<SchemaObject>;
    45    getValue: (rowPath: TreePath, colPath: TreePath) => number;
    46  }
    47  
    48  // Amount to indent for a row each level of depth in the tree.
    49  const ROW_TREE_INDENT_PX = 18;
    50  // Margin for all rows in the matrix. Strangely, <th>s can't have margins
    51  // applied in CSS.
    52  const ROW_LEFT_MARGIN_PX = 5;
    53  
    54  class ReplicaMatrix extends Component<ReplicaMatrixProps, ReplicaMatrixState> {
    55  
    56    constructor(props: ReplicaMatrixProps) {
    57      super(props);
    58      this.state = {
    59        collapsedRows: [["system"], ["defaultdb"], ["postgres"]],
    60        collapsedCols: [],
    61      };
    62    }
    63  
    64    expandRow = (path: TreePath) => {
    65      this.setState({
    66        collapsedRows: this.state.collapsedRows.filter((tp) => !_.isEqual(tp, path)),
    67      });
    68    }
    69  
    70    collapseRow = (path: TreePath) => {
    71      this.setState({
    72        collapsedRows: [...this.state.collapsedRows, path],
    73      });
    74    }
    75  
    76    expandCol = (path: TreePath) => {
    77      this.setState({
    78        collapsedCols: this.state.collapsedCols.filter((tp) => !_.isEqual(tp, path)),
    79      });
    80    }
    81  
    82    collapseCol = (path: TreePath) => {
    83      this.setState({
    84        collapsedCols: [...this.state.collapsedCols, path],
    85      });
    86    }
    87  
    88    colLabel(col: LayoutCell<NodeDescriptor$Properties>): string {
    89      if (col.isPlaceholder) {
    90        return null;
    91      }
    92  
    93      if (col.isLeaf) {
    94        return `n${col.data.node_id}`;
    95      }
    96  
    97      const arrow = col.isCollapsed ? SIDE_ARROW : DOWN_ARROW;
    98      const localityLabel = col.path.length === 0 ? "Cluster" : col.path[col.path.length - 1];
    99      return `${arrow} ${localityLabel}`;
   100    }
   101  
   102    rowLabelText(row: FlattenedNode<SchemaObject>) {
   103      if (row.isLeaf) {
   104        return row.data.tableName;
   105      }
   106  
   107      const arrow = row.isCollapsed ? SIDE_ARROW : DOWN_ARROW;
   108      const label = row.data.dbName ? `DB: ${row.data.dbName}` : "Cluster";
   109  
   110      return `${arrow} ${label}`;
   111    }
   112  
   113    rowLabel(row: FlattenedNode<SchemaObject>) {
   114      const text = this.rowLabelText(row);
   115  
   116      const label = (
   117        <span className={classNames("table-label", { "table-label--dropped": !!row.data.droppedAt })}>
   118          {text}
   119        </span>
   120      );
   121  
   122      if (row.data.droppedAt) {
   123        return (
   124          <ToolTipWrapper
   125            text={
   126              <span>
   127                Dropped at {TimestampToMoment(row.data.droppedAt).format()}.
   128                Will eventually be garbage collected according to this schema
   129                object's GC TTL.
   130              </span>
   131            }
   132          >
   133            {label}
   134          </ToolTipWrapper>
   135        );
   136      } else {
   137        return label;
   138      }
   139    }
   140  
   141    render() {
   142      const {
   143        cols,
   144        rows,
   145        getValue,
   146      } = this.props;
   147      const {
   148        collapsedRows,
   149        collapsedCols,
   150      } = this.state;
   151  
   152      const flattenedRows = flatten(rows, collapsedRows, true /* includeNodes */);
   153      const headerRows = layoutTreeHorizontal(cols, collapsedCols);
   154      const flattenedCols = flatten(cols, collapsedCols, false /* includeNodes */);
   155  
   156      return (
   157        <table className="matrix">
   158          <thead>
   159            {headerRows.map((row, idx) => (
   160              <tr key={idx}>
   161                {idx === 0
   162                  ? <th className="matrix__metric-label"># Replicas</th>
   163                  : <th />}
   164                {row.map((col) => (
   165                  <th
   166                    key={col.path.join("/")}
   167                    colSpan={col.width}
   168                    className={classNames(
   169                      "matrix__column-header",
   170                      { "matrix__column-header--internal-node": !(col.isLeaf || col.isPlaceholder) },
   171                    )}
   172                    onClick={() => (
   173                      col.isCollapsed
   174                        ? this.expandCol(col.path)
   175                        : this.collapseCol(col.path)
   176                    )}
   177                  >
   178                    {this.colLabel(col)}
   179                  </th>
   180                ))}
   181              </tr>
   182            ))}
   183          </thead>
   184          <tbody>
   185            {flattenedRows.map((row) => {
   186              return (
   187                <tr
   188                  key={row.path.join("/")}
   189                  className={classNames(
   190                    "matrix__row",
   191                    { "matrix__row--internal-node": !row.isLeaf },
   192                  )}
   193                  onClick={() => (
   194                    row.isCollapsed
   195                      ? this.expandRow(row.path)
   196                      : this.collapseRow(row.path)
   197                  )}
   198                >
   199                  <th
   200                    className={classNames(
   201                      "matrix__row-header",
   202                      { "matrix__row-header--internal-node": !row.isLeaf },
   203                    )}
   204                    style={{ paddingLeft: row.depth * ROW_TREE_INDENT_PX + ROW_LEFT_MARGIN_PX }}
   205                  >
   206                    {this.rowLabel(row)}
   207                  </th>
   208                  {flattenedCols.map((col) => {
   209                    return (
   210                      <td
   211                        key={col.path.join("/")}
   212                        className="matrix__cell-value"
   213                      >
   214                        {row.isLeaf || row.isCollapsed
   215                          ? emptyIfZero(sumValuesUnderPaths(rows, cols, row.path, col.path, getValue))
   216                          : null}
   217                      </td>
   218                    );
   219                  })}
   220                </tr>
   221              );
   222            })}
   223          </tbody>
   224        </table>
   225      );
   226    }
   227  
   228  }
   229  
   230  function emptyIfZero(n: number): string {
   231    if (n === 0) {
   232      return "";
   233    }
   234    return `${n}`;
   235  }
   236  
   237  export default ReplicaMatrix;
   238  
   239  export interface SchemaObject {
   240    dbName?: string;
   241    tableName?: string;
   242    droppedAt?: ITimestamp;
   243  }