github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/enqueueRange/index.tsx (about)

     1  // Copyright 2020 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 React, { Fragment } from "react";
    12  import Helmet from "react-helmet";
    13  import { RouteComponentProps, withRouter } from "react-router-dom";
    14  import moment from "moment";
    15  
    16  import { enqueueRange } from "src/util/api";
    17  import { cockroach } from "src/js/protos";
    18  import Print from "src/views/reports/containers/range/print";
    19  import "./index.styl";
    20  
    21  import EnqueueRangeRequest = cockroach.server.serverpb.EnqueueRangeRequest;
    22  import EnqueueRangeResponse = cockroach.server.serverpb.EnqueueRangeResponse;
    23  
    24  const QUEUES = [
    25    "replicate",
    26    "gc",
    27    "merge",
    28    "split",
    29    "replicaGC",
    30    "raftlog",
    31    "raftsnapshot",
    32    "consistencyChecker",
    33    "timeSeriesMaintenance",
    34  ];
    35  
    36  interface EnqueueRangeProps {
    37    handleEnqueueRange: (queue: string, rangeID: number, nodeID: number, skipShouldQueue: boolean) => Promise<EnqueueRangeResponse>;
    38  }
    39  
    40  interface EnqueueRangeState {
    41    queue: string;
    42    rangeID: string;
    43    nodeID: string;
    44    skipShouldQueue: boolean;
    45    response: EnqueueRangeResponse;
    46    error: Error;
    47  }
    48  
    49  export class EnqueueRange extends React.Component<EnqueueRangeProps & RouteComponentProps, EnqueueRangeState> {
    50    state: EnqueueRangeState = {
    51      queue: QUEUES[0],
    52      rangeID: "",
    53      nodeID: "",
    54      skipShouldQueue: false,
    55      response: null,
    56      error: null,
    57    };
    58  
    59    handleUpdateQueue = (evt: React.FormEvent<{ value: string }>) => {
    60      this.setState({
    61        queue: evt.currentTarget.value,
    62      });
    63    }
    64  
    65    handleUpdateRangeID = (evt: React.FormEvent<{ value: string }>) => {
    66      this.setState({
    67        rangeID: evt.currentTarget.value,
    68      });
    69    }
    70  
    71    handleUpdateNodeID = (evt: React.FormEvent<{ value: string }>) => {
    72      this.setState({
    73        nodeID: evt.currentTarget.value,
    74      });
    75    }
    76  
    77    handleEnqueueRange = (queue: string, rangeID: number, nodeID: number, skipShouldQueue: boolean) => {
    78      const req = new EnqueueRangeRequest({
    79        queue: queue,
    80        range_id: rangeID,
    81        node_id: nodeID,
    82        skip_should_queue: skipShouldQueue,
    83      });
    84      return enqueueRange(req, moment.duration({ hours: 1 }));
    85    }
    86  
    87    handleSubmit = (evt: React.FormEvent<any>) => {
    88      evt.preventDefault();
    89  
    90      this.handleEnqueueRange(
    91        this.state.queue,
    92        // These parseInts should succeed because <input type="number" />
    93        // enforces numeric input. Otherwise they're NaN.
    94        parseInt(this.state.rangeID, 10),
    95        parseInt(this.state.nodeID, 10),
    96        this.state.skipShouldQueue,
    97      ).then(
    98        response => {
    99          this.setState({ response, error: null });
   100        },
   101        error => {
   102          this.setState({ response: null, error });
   103        },
   104      );
   105    }
   106  
   107    renderNodeResponse(details: EnqueueRangeResponse.IDetails) {
   108      return (
   109        <Fragment>
   110          <p>
   111            {details.error
   112              ? <Fragment><b>Error:</b> {details.error}</Fragment>
   113              : "Call succeeded"}
   114          </p>
   115          <table className="enqueue-range-table">
   116            <thead>
   117              <tr className="enqueue-range-table__row enqueue-range-table__row--header">
   118                <th className="enqueue-range-table__cell enqueue-range-table__cell--header">Timestamp</th>
   119                <th className="enqueue-range-table__cell enqueue-range-table__cell--header">Message</th>
   120              </tr>
   121            </thead>
   122            <tbody>
   123              {details.events.map((event) => (
   124                <tr className="enqueue-range-table__row--body">
   125                  <td className="enqueue-range-table__cell enqueue-range-table__cell--date">
   126                    {Print.Timestamp(event.time)}
   127                  </td>
   128                  <td className="enqueue-range-table__cell">
   129                    <pre>{event.message}</pre>
   130                  </td>
   131                </tr>
   132              ))}
   133            </tbody>
   134          </table>
   135        </Fragment>
   136      );
   137    }
   138  
   139    renderResponse() {
   140      const { response } = this.state;
   141  
   142      if (!response) {
   143        return null;
   144      }
   145  
   146      return (
   147        <Fragment>
   148          <h2 className="base-heading">Enqueue Range Output</h2>
   149          {response.details.map((details) => (
   150            <div>
   151              <h3>Node n{details.node_id}</h3>
   152  
   153              {this.renderNodeResponse(details)}
   154            </div>
   155          ))}
   156        </Fragment>
   157      );
   158    }
   159  
   160    renderError() {
   161      const { error } = this.state;
   162  
   163      if (!error) {
   164        return null;
   165      }
   166  
   167      return (
   168        <Fragment>Error running EnqueueRange: {error.message}</Fragment>
   169      );
   170    }
   171  
   172    render() {
   173      return (
   174        <Fragment>
   175          <Helmet title="Enqueue Range" />
   176          <div className="content">
   177            <section className="section">
   178              <div className="form-container">
   179                <h1 className="base-heading heading">Manually enqueue range in a replica queue</h1>
   180                <br />
   181                <form onSubmit={this.handleSubmit} className="form-internal" method="post">
   182                  <label>
   183                    Queue:{" "}
   184                    <select onChange={this.handleUpdateQueue}>
   185                      {QUEUES.map((queue) => (
   186                        <option key={queue} value={queue}>{queue}</option>
   187                      ))}
   188                    </select>
   189                  </label>
   190                  <br />
   191                  <label>
   192                    RangeID:{" "}
   193                    <input
   194                      type="number"
   195                      name="rangeID"
   196                      className="input-text"
   197                      onChange={this.handleUpdateRangeID}
   198                      value={this.state.rangeID}
   199                      placeholder="RangeID"
   200                    />
   201                  </label>
   202                  <br />
   203                  <label>
   204                    NodeID:{" "}
   205                    <input
   206                      type="number"
   207                      name="nodeID"
   208                      className="input-text"
   209                      onChange={this.handleUpdateNodeID}
   210                      value={this.state.nodeID}
   211                      placeholder="NodeID (optional)"
   212                    />
   213                    &nbsp;If not specified, we'll attempt to enqueue on all the nodes.
   214                  </label>
   215                  <br />
   216                  <label>
   217                    SkipShouldQueue:{" "}
   218                    <input
   219                      type="checkbox"
   220                      checked={this.state.skipShouldQueue}
   221                      name="skipShouldQueue"
   222                      onChange={() => this.setState({ skipShouldQueue: !this.state.skipShouldQueue })}
   223                    />
   224                  </label>
   225                  <br />
   226                  <input
   227                    type="submit"
   228                    className="submit-button"
   229                    value="Submit"
   230                  />
   231                </form>
   232              </div>
   233            </section>
   234          </div>
   235          <section className="section">
   236            {this.renderResponse()}
   237            {this.renderError()}
   238          </section>
   239        </Fragment>
   240      );
   241    }
   242  }
   243  
   244  const EnqueueRangeConnected = withRouter(EnqueueRange);
   245  
   246  export default EnqueueRangeConnected;