github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/problemRanges/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 Long from "long";
    13  import React from "react";
    14  import { Helmet } from "react-helmet";
    15  import { connect } from "react-redux";
    16  import { Link, RouteComponentProps, withRouter } from "react-router-dom";
    17  import * as protos from "src/js/protos";
    18  import { problemRangesRequestKey, refreshProblemRanges } from "src/redux/apiReducers";
    19  import { CachedDataReducerState } from "src/redux/cachedDataReducer";
    20  import { AdminUIState } from "src/redux/state";
    21  import { nodeIDAttr } from "src/util/constants";
    22  import { FixLong } from "src/util/fixLong";
    23  import ConnectionsTable from "src/views/reports/containers/problemRanges/connectionsTable";
    24  import Loading from "src/views/shared/components/loading";
    25  import { getMatchParamByName } from "src/util/query";
    26  
    27  type NodeProblems$Properties = protos.cockroach.server.serverpb.ProblemRangesResponse.INodeProblems;
    28  
    29  interface ProblemRangesOwnProps {
    30    problemRanges: CachedDataReducerState<protos.cockroach.server.serverpb.ProblemRangesResponse>;
    31    refreshProblemRanges: typeof refreshProblemRanges;
    32  }
    33  
    34  type ProblemRangesProps = ProblemRangesOwnProps & RouteComponentProps;
    35  
    36  function isLoading(state: CachedDataReducerState<any>) {
    37    return _.isNil(state) || (_.isNil(state.data) && _.isNil(state.lastError));
    38  }
    39  
    40  function ProblemRangeList(props: {
    41    name: string,
    42    problems: NodeProblems$Properties[],
    43    extract: (p: NodeProblems$Properties) => Long[],
    44  }) {
    45    const ids = _.chain(props.problems)
    46      .filter(problem => _.isEmpty(problem.error_message))
    47      .flatMap(problem => props.extract(problem))
    48      .map(id => FixLong(id))
    49      .sort((a, b) => a.compare(b))
    50      .map(id => id.toString())
    51      .sortedUniq()
    52      .value();
    53    if (_.isEmpty(ids)) {
    54      return null;
    55    }
    56    return (
    57      <div>
    58        <h2 className="base-heading">{props.name}</h2>
    59        <div className="problems-list">
    60          {
    61            _.map(ids, id => {
    62              return (
    63                <Link key={id} className="problems-link" to={`/reports/range/${id}`}>
    64                  {id}
    65                </Link>
    66              );
    67            })
    68          }
    69        </div>
    70      </div>
    71    );
    72  }
    73  
    74  function problemRangeRequestFromProps(props: ProblemRangesProps) {
    75    return new protos.cockroach.server.serverpb.ProblemRangesRequest({
    76      node_id: getMatchParamByName(props.match, nodeIDAttr),
    77    });
    78  }
    79  
    80  /**
    81   * Renders the Problem Ranges page.
    82   *
    83   * The problem ranges endpoint returns a list of known ranges with issues on a
    84   * per node basis. This page aggregates those lists together and displays all
    85   * unique range IDs that have problems.
    86   */
    87  export class ProblemRanges extends React.Component<ProblemRangesProps, {}> {
    88    refresh(props = this.props) {
    89      props.refreshProblemRanges(problemRangeRequestFromProps(props));
    90    }
    91  
    92    componentDidMount() {
    93      // Refresh nodes status query when mounting.
    94      this.refresh();
    95    }
    96  
    97    componentDidUpdate(prevProps: ProblemRangesProps) {
    98      if (!_.isEqual(this.props.location, prevProps.location)) {
    99        this.refresh(this.props);
   100      }
   101    }
   102  
   103    renderReportBody() {
   104      const { problemRanges, match } = this.props;
   105      const nodeId = getMatchParamByName(match, nodeIDAttr);
   106  
   107      if (isLoading(this.props.problemRanges)) {
   108        return null;
   109      }
   110  
   111      if (!_.isNil(problemRanges.lastError)) {
   112        if (nodeId === null) {
   113          return (
   114            <div>
   115              <h2 className="base-heading">Error loading Problem Ranges for the Cluster</h2>
   116              {problemRanges.lastError.toString()}
   117            </div>
   118          );
   119        } else {
   120          return (
   121            <div>
   122              <h2 className="base-heading">Error loading Problem Ranges for node n{nodeId}</h2>
   123              {problemRanges.lastError.toString()}
   124            </div>
   125          );
   126        }
   127      }
   128  
   129      const { data } = problemRanges;
   130  
   131      const validIDs = _.keys(_.pickBy(data.problems_by_node_id, d => {
   132        return _.isEmpty(d.error_message);
   133      }));
   134      if (validIDs.length === 0) {
   135        if (nodeId === null) {
   136          return <h2 className="base-heading">No nodes returned any results</h2>;
   137        } else {
   138          return <h2 className="base-heading">No results reported for node n{nodeId}</h2>;
   139        }
   140      }
   141  
   142      let titleText: string; // = "Problem Ranges for ";
   143      if (validIDs.length === 1) {
   144        const singleNodeID = _.keys(data.problems_by_node_id)[0];
   145        titleText = `Problem Ranges on Node n${singleNodeID}`;
   146      } else {
   147        titleText = "Problem Ranges on the Cluster";
   148      }
   149  
   150      const problems = _.values(data.problems_by_node_id);
   151      return (
   152        <div>
   153          <h2 className="base-heading">
   154            {titleText}
   155          </h2>
   156          <ProblemRangeList
   157            name="Unavailable"
   158            problems={problems}
   159            extract={(problem) => problem.unavailable_range_ids}
   160          />
   161          <ProblemRangeList
   162            name="No Raft Leader"
   163            problems={problems}
   164            extract={(problem) => problem.no_raft_leader_range_ids}
   165          />
   166          <ProblemRangeList
   167            name="Invalid Lease"
   168            problems={problems}
   169            extract={(problem) => problem.no_lease_range_ids}
   170          />
   171          <ProblemRangeList
   172            name="Raft Leader but not Lease Holder"
   173            problems={problems}
   174            extract={(problem) => problem.raft_leader_not_lease_holder_range_ids}
   175          />
   176          <ProblemRangeList
   177            name="Underreplicated (or slow)"
   178            problems={problems}
   179            extract={(problem) => problem.underreplicated_range_ids}
   180          />
   181          <ProblemRangeList
   182            name="Overreplicated"
   183            problems={problems}
   184            extract={(problem) => problem.overreplicated_range_ids}
   185          />
   186          <ProblemRangeList
   187            name="Quiescent equals ticking"
   188            problems={problems}
   189            extract={(problem) => problem.quiescent_equals_ticking_range_ids}
   190          />
   191        </div>
   192      );
   193    }
   194  
   195    render() {
   196      return (
   197        <div className="section">
   198          <Helmet title="Problem Ranges | Debug" />
   199          <h1 className="base-heading">Problem Ranges Report</h1>
   200          <Loading
   201            loading={isLoading(this.props.problemRanges)}
   202            error={this.props.problemRanges && this.props.problemRanges.lastError}
   203            render={() => (
   204              <div>
   205                {this.renderReportBody()}
   206                <ConnectionsTable problemRanges={this.props.problemRanges} />
   207              </div>
   208            )}
   209          />
   210        </div>
   211      );
   212    }
   213  }
   214  
   215  const mapStateToProps = (state: AdminUIState, props: ProblemRangesProps) => {
   216    const nodeIDKey = problemRangesRequestKey(problemRangeRequestFromProps(props));
   217    return {
   218      problemRanges: state.cachedData.problemRanges[nodeIDKey],
   219    };
   220  };
   221  
   222  const mapDispatchToProps = {
   223    // actionCreators returns objects with type and payload
   224    refreshProblemRanges,
   225  };
   226  
   227  export default withRouter(connect(
   228    mapStateToProps,
   229    mapDispatchToProps,
   230  )(ProblemRanges));