github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/stores/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 { Helmet } from "react-helmet";
    14  import { connect } from "react-redux";
    15  import { RouteComponentProps, withRouter } from "react-router-dom";
    16  import { createSelector } from "reselect";
    17  
    18  import * as protos from "src/js/protos";
    19  import { storesRequestKey, refreshStores } from "src/redux/apiReducers";
    20  import { AdminUIState } from "src/redux/state";
    21  import { nodeIDAttr } from "src/util/constants";
    22  import EncryptionStatus from "src/views/reports/containers/stores/encryption";
    23  import Loading from "src/views/shared/components/loading";
    24  import { getMatchParamByName } from "src/util/query";
    25  
    26  interface StoresOwnProps {
    27    stores: protos.cockroach.server.serverpb.IStoreDetails[];
    28    loading: boolean;
    29    lastError: Error;
    30    refreshStores: typeof refreshStores;
    31  }
    32  
    33  type StoresProps = StoresOwnProps & RouteComponentProps;
    34  
    35  function storesRequestFromProps(props: StoresProps) {
    36    const nodeId = getMatchParamByName(props.match, nodeIDAttr);
    37    return new protos.cockroach.server.serverpb.StoresRequest({
    38      node_id: nodeId,
    39    });
    40  }
    41  
    42  /**
    43   * Renders the Stores Report page.
    44   */
    45  export class Stores extends React.Component<StoresProps, {}> {
    46    refresh(props = this.props) {
    47      props.refreshStores(storesRequestFromProps(props));
    48    }
    49  
    50    componentDidMount() {
    51      // Refresh nodes status query when mounting.
    52      this.refresh();
    53    }
    54  
    55    componentDidUpdate(prevProps: StoresProps) {
    56      if (!_.isEqual(this.props.location, prevProps.location)) {
    57        this.refresh(this.props);
    58      }
    59    }
    60  
    61    renderSimpleRow(header: string, value: string, title: string = "") {
    62      let realTitle = title;
    63      if (_.isEmpty(realTitle)) {
    64        realTitle = value;
    65      }
    66      return (
    67        <tr className="stores-table__row">
    68          <th className="stores-table__cell stores-table__cell--header">{header}</th>
    69          <td className="stores-table__cell" title={realTitle}>{value}</td>
    70        </tr>
    71      );
    72    }
    73  
    74    renderStore = (store: protos.cockroach.server.serverpb.IStoreDetails) => {
    75      return (
    76        <table key={store.store_id} className="stores-table">
    77          <tbody>
    78            { this.renderSimpleRow("Store ID", store.store_id.toString()) }
    79            { new EncryptionStatus({store: store}).getEncryptionRows() }
    80          </tbody>
    81        </table>
    82      );
    83    }
    84  
    85    renderContent = () => {
    86      const { stores, match } = this.props;
    87  
    88      const nodeID = getMatchParamByName(match, nodeIDAttr);
    89      if (_.isEmpty(stores)) {
    90        return (
    91          <h2 className="base-heading">No stores were found on node {nodeID}.</h2>
    92        );
    93      }
    94  
    95      return _.map(this.props.stores,  this.renderStore);
    96    }
    97  
    98    render() {
    99      const nodeID = getMatchParamByName(this.props.match, nodeIDAttr);
   100      let header: string = null;
   101      if (_.isNaN(parseInt(nodeID, 10))) {
   102        header = "Local Node";
   103      } else {
   104        header = `Node ${nodeID}`;
   105      }
   106  
   107      return (
   108        <div className="section">
   109          <Helmet title="Stores | Debug" />
   110          <h1 className="base-heading">Stores</h1>
   111          <h2 className="base-heading">{header} stores</h2>
   112          <Loading
   113            loading={this.props.loading}
   114            error={this.props.lastError}
   115            render={this.renderContent}
   116          />
   117        </div>
   118      );
   119    }
   120  }
   121  
   122  function selectStoresState(state: AdminUIState, props: StoresProps) {
   123    const nodeIDKey = storesRequestKey(storesRequestFromProps(props));
   124    return state.cachedData.stores[nodeIDKey];
   125  }
   126  
   127  const selectStoresLoading = createSelector(
   128    selectStoresState,
   129    (stores) => _.isEmpty(stores) || _.isEmpty(stores.data),
   130  );
   131  
   132  const selectSortedStores = createSelector(
   133    selectStoresLoading,
   134    selectStoresState,
   135    (loading, stores) => {
   136      if (loading) {
   137        return null;
   138      }
   139      return _.sortBy(stores.data.stores, (store) => store.store_id);
   140    },
   141  );
   142  
   143  const selectStoresLastError = createSelector(
   144    selectStoresLoading,
   145    selectStoresState,
   146    (loading, stores) => {
   147      if (loading) {
   148        return null;
   149      }
   150      return stores.lastError;
   151    },
   152  );
   153  
   154  const mapStateToProps = (state: AdminUIState, props: StoresProps) => ({
   155    stores: selectSortedStores(state, props),
   156    loading: selectStoresLoading(state, props),
   157    lastError: selectStoresLastError(state, props),
   158  });
   159  
   160  const mapDispatchToProps = {
   161    refreshStores,
   162  };
   163  
   164  export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Stores));