github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/devtools/containers/raftMessages/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 { connect } from "react-redux";
    14  import { createSelector } from "reselect";
    15  import {RouteComponentProps, withRouter} from "react-router-dom";
    16  
    17  import { refreshLiveness, refreshNodes } from "src/redux/apiReducers";
    18  import { hoverOff as hoverOffAction, hoverOn as hoverOnAction, hoverStateSelector, HoverState } from "src/redux/hover";
    19  import { NodesSummary, nodesSummarySelector } from "src/redux/nodes";
    20  import { AdminUIState } from "src/redux/state";
    21  import { nodeIDAttr } from "src/util/constants";
    22  import { GraphDashboardProps, storeIDsForNode } from "src/views/cluster/containers/nodeGraphs/dashboards/dashboardUtils";
    23  import TimeScaleDropdown from "src/views/cluster/containers/timescale";
    24  import Dropdown, { DropdownOption } from "src/views/shared/components/dropdown";
    25  import { PageConfig, PageConfigItem } from "src/views/shared/components/pageconfig";
    26  import { MetricsDataProvider } from "src/views/shared/containers/metricDataProvider";
    27  import messagesDashboard from "./messages";
    28  import { getMatchParamByName } from "src/util/query";
    29  
    30  interface NodeGraphsOwnProps {
    31    refreshNodes: typeof refreshNodes;
    32    refreshLiveness: typeof refreshLiveness;
    33    hoverOn: typeof hoverOnAction;
    34    hoverOff: typeof hoverOffAction;
    35    nodesQueryValid: boolean;
    36    livenessQueryValid: boolean;
    37    nodesSummary: NodesSummary;
    38    hoverState: HoverState;
    39  }
    40  
    41  type RaftMessagesProps = NodeGraphsOwnProps & RouteComponentProps;
    42  
    43  export class RaftMessages extends React.Component<RaftMessagesProps> {
    44    /**
    45     * Selector to compute node dropdown options from the current node summary
    46     * collection.
    47     */
    48    private nodeDropdownOptions = createSelector(
    49      (summary: NodesSummary) => summary.nodeStatuses,
    50      (summary: NodesSummary) => summary.nodeDisplayNameByID,
    51      (nodeStatuses, nodeDisplayNameByID): DropdownOption[] => {
    52        const base = [{value: "", label: "Cluster"}];
    53        return base.concat(_.map(nodeStatuses, (ns) => {
    54          return {
    55            value: ns.desc.node_id.toString(),
    56            label: nodeDisplayNameByID[ns.desc.node_id],
    57          };
    58        }));
    59      },
    60    );
    61  
    62    refresh(props = this.props) {
    63      if (!props.nodesQueryValid) {
    64        props.refreshNodes();
    65      }
    66      if (!props.livenessQueryValid) {
    67        props.refreshLiveness();
    68      }
    69    }
    70  
    71    setClusterPath(nodeID: string) {
    72      const push = this.props.history.push;
    73      if (!_.isString(nodeID) || nodeID === "") {
    74        push("/raft/messages/all/");
    75      } else {
    76        push(`/raft/messages/node/${nodeID}`);
    77      }
    78    }
    79  
    80    nodeChange = (selected: DropdownOption) => {
    81      this.setClusterPath(selected.value);
    82    }
    83  
    84    componentDidMount() {
    85      this.refresh();
    86    }
    87  
    88    componentDidUpdate(props: RaftMessagesProps) {
    89      this.refresh(props);
    90    }
    91  
    92    render() {
    93      const { match, nodesSummary, hoverState, hoverOn, hoverOff } = this.props;
    94  
    95      const selectedNode = getMatchParamByName(match, nodeIDAttr) || "";
    96      const nodeSources = (selectedNode !== "") ? [selectedNode] : null;
    97  
    98      // When "all" is the selected source, some graphs display a line for every
    99      // node in the cluster using the nodeIDs collection. However, if a specific
   100      // node is already selected, these per-node graphs should only display data
   101      // only for the selected node.
   102      const nodeIDs = nodeSources ? nodeSources : nodesSummary.nodeIDs;
   103  
   104      // If a single node is selected, we need to restrict the set of stores
   105      // queried for per-store metrics (only stores that belong to that node will
   106      // be queried).
   107      const storeSources = nodeSources ? storeIDsForNode(nodesSummary, nodeSources[0]) : null;
   108  
   109      // tooltipSelection is a string used in tooltips to reference the currently
   110      // selected nodes. This is a prepositional phrase, currently either "across
   111      // all nodes" or "on node X".
   112      const tooltipSelection = (nodeSources && nodeSources.length === 1)
   113                                ? `on node ${nodeSources[0]}`
   114                                : "across all nodes";
   115  
   116      const dashboardProps: GraphDashboardProps = {
   117        nodeIDs,
   118        nodesSummary,
   119        nodeSources,
   120        storeSources,
   121        tooltipSelection,
   122      };
   123  
   124      // Generate graphs for the current dashboard, wrapping each one in a
   125      // MetricsDataProvider with a unique key.
   126      const graphs = messagesDashboard(dashboardProps);
   127      const graphComponents = _.map(graphs, (graph, idx) => {
   128        const key = `nodes.raftMessages.${idx}`;
   129        return (
   130          <div key={key}>
   131            <MetricsDataProvider id={key}>
   132              { React.cloneElement(graph, { hoverOn, hoverOff, hoverState }) }
   133            </MetricsDataProvider>
   134          </div>
   135        );
   136      });
   137  
   138      return (
   139        <div>
   140          <PageConfig>
   141            <PageConfigItem>
   142              <Dropdown
   143                title="Graph"
   144                options={this.nodeDropdownOptions(this.props.nodesSummary)}
   145                selected={selectedNode}
   146                onChange={this.nodeChange}
   147              />
   148            </PageConfigItem>
   149            <PageConfigItem>
   150              <TimeScaleDropdown />
   151            </PageConfigItem>
   152          </PageConfig>
   153          <div className="section l-columns">
   154            <div className="chart-group l-columns__left">
   155              { graphComponents }
   156            </div>
   157          </div>
   158        </div>
   159      );
   160    }
   161  }
   162  
   163  const mapStateToProps = (state: AdminUIState) => ({ // RootState contains declaration for whole state
   164    nodesSummary: nodesSummarySelector(state),
   165    nodesQueryValid: state.cachedData.nodes.valid,
   166    livenessQueryValid: state.cachedData.nodes.valid,
   167    hoverState: hoverStateSelector(state),
   168  });
   169  
   170  const mapDispatchToProps = {
   171    refreshNodes,
   172    refreshLiveness,
   173    hoverOn: hoverOnAction,
   174    hoverOff: hoverOffAction,
   175  };
   176  
   177  export default withRouter(connect(mapStateToProps, mapDispatchToProps)(RaftMessages));