github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/statementDiagnosticsHistory/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 from "react";
    12  import { Helmet } from "react-helmet";
    13  import { connect } from "react-redux";
    14  import moment from "moment";
    15  import { Action, Dispatch } from "redux";
    16  import Long from "long";
    17  import { isUndefined } from "lodash";
    18  import { Link } from "react-router-dom";
    19  
    20  import {
    21    Button,
    22    ColumnsConfig,
    23    DownloadFile,
    24    DownloadFileRef,
    25    Table,
    26    Text,
    27    TextTypes,
    28    Tooltip,
    29  } from "src/components";
    30  import HeaderSection from "src/views/shared/components/headerSection";
    31  import { AdminUIState } from "src/redux/state";
    32  import { getStatementDiagnostics } from "src/util/api";
    33  import { trustIcon } from "src/util/trust";
    34  import DownloadIcon from "!!raw-loader!assets/download.svg";
    35  import {
    36    selectStatementByFingerprint,
    37    selectStatementDiagnosticsReports,
    38  } from "src/redux/statements/statementsSelectors";
    39  import {
    40    invalidateStatementDiagnosticsRequests,
    41    refreshStatementDiagnosticsRequests,
    42    refreshStatements,
    43  } from "src/redux/apiReducers";
    44  import { DiagnosticStatusBadge } from "src/views/statements/diagnostics/diagnosticStatusBadge";
    45  import "./statementDiagnosticsHistoryView.styl";
    46  import { cockroach } from "src/js/protos";
    47  import IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport;
    48  import StatementDiagnosticsRequest = cockroach.server.serverpb.StatementDiagnosticsRequest;
    49  import {
    50    getDiagnosticsStatus,
    51    sortByCompletedField,
    52    sortByRequestedAtField,
    53    sortByStatementFingerprintField,
    54  } from "src/views/statements/diagnostics";
    55  import { trackDownloadDiagnosticsBundle } from "src/util/analytics";
    56  import { shortStatement } from "src/views/statements/statementsTable";
    57  import { summarize } from "src/util/sql/summarize";
    58  
    59  type StatementDiagnosticsHistoryViewProps = MapStateToProps & MapDispatchToProps;
    60  
    61  const StatementColumn: React.FC<{ fingerprint: string }> = ({ fingerprint }) => {
    62    const summary = summarize(fingerprint);
    63    const shortenedStatement = shortStatement(summary, fingerprint);
    64    const showTooltip = fingerprint !== shortenedStatement;
    65  
    66    if (showTooltip) {
    67      return (
    68        <Text textType={TextTypes.Code}>
    69          <Tooltip
    70            placement="bottom"
    71            title={
    72              <pre className="cl-table-link__description">{ fingerprint }</pre>
    73            }
    74            overlayClassName="cl-table-link__statement-tooltip--fixed-width"
    75          >
    76            {shortenedStatement}
    77          </Tooltip>
    78        </Text>
    79      );
    80    }
    81    return (
    82      <Text textType={TextTypes.Code}>{shortenedStatement}</Text>
    83    );
    84  };
    85  
    86  class StatementDiagnosticsHistoryView extends React.Component<StatementDiagnosticsHistoryViewProps> {
    87    columns: ColumnsConfig<IStatementDiagnosticsReport> = [
    88      {
    89        key: "activatedOn",
    90        title: "Activated on",
    91        sorter: sortByRequestedAtField,
    92        defaultSortOrder: "descend",
    93        width: "240px",
    94        render: (_text, record) => {
    95          const timestamp = record.requested_at.seconds.toNumber() * 1000;
    96          return moment(timestamp).format("LL[ at ]h:mm a");
    97        },
    98      },
    99      {
   100        key: "statement",
   101        title: "statement",
   102        sorter: sortByStatementFingerprintField,
   103        render: (_text, record) => {
   104          const { getStatementByFingerprint } = this.props;
   105          const fingerprint = record.statement_fingerprint;
   106          const statement = getStatementByFingerprint(fingerprint);
   107          const { implicit_txn: implicitTxn = "true", query } = statement?.key?.key_data || {};
   108  
   109          if (isUndefined(query)) {
   110            return <StatementColumn fingerprint={fingerprint} />;
   111          }
   112  
   113          return (
   114            <Link
   115              to={ `/statement/${implicitTxn}/${encodeURIComponent(query)}` }
   116              className="crl-statements-diagnostics-view__statements-link"
   117            >
   118              <StatementColumn fingerprint={fingerprint} />
   119            </Link>
   120          );
   121        },
   122      },
   123      {
   124        key: "status",
   125        title: "status",
   126        sorter: sortByCompletedField,
   127        width: "160px",
   128        render: (_text, record) => (
   129          <Text>
   130            <DiagnosticStatusBadge
   131              status={getDiagnosticsStatus(record)}
   132            />
   133          </Text>
   134        ),
   135      },
   136      {
   137        key: "actions",
   138        title: "",
   139        sorter: false,
   140        width: "160px",
   141        render: (_text, record) => {
   142          if (record.completed) {
   143            return (
   144              <div className="crl-statements-diagnostics-view__actions-column cell--show-on-hover nodes-table__link">
   145                <a href={`_admin/v1/stmtbundle/${record.statement_diagnostics_id}`}
   146                   onClick={() => trackDownloadDiagnosticsBundle(record.statement_fingerprint)}>
   147                  <Button
   148                    size="small"
   149                    type="flat"
   150                    iconPosition="left"
   151                    icon={() => (
   152                      <span
   153                        className="crl-statements-diagnostics-view__icon"
   154                        dangerouslySetInnerHTML={ trustIcon(DownloadIcon) }
   155                      />
   156                    )}
   157                  >
   158                    Bundle (.zip)
   159                  </Button>
   160                </a>
   161              </div>
   162            );
   163          }
   164          return null;
   165        },
   166      },
   167    ];
   168  
   169    tablePageSize = 16;
   170  
   171    downloadRef = React.createRef<DownloadFileRef>();
   172  
   173    constructor(props: StatementDiagnosticsHistoryViewProps) {
   174      super(props);
   175      props.refresh();
   176    }
   177  
   178    renderTableTitle = () => {
   179      const { diagnosticsReports } = this.props;
   180      const totalCount = diagnosticsReports.length;
   181  
   182      if (totalCount === 0) {
   183        return null;
   184      }
   185  
   186      if (totalCount <= this.tablePageSize) {
   187        return (
   188          <div className="diagnostics-history-view__table-header">
   189            <Text>{`${totalCount} traces`}</Text>
   190          </div>
   191        );
   192      }
   193  
   194      return (
   195        <div className="diagnostics-history-view__table-header">
   196          <Text>{`${this.tablePageSize} of ${totalCount} traces`}</Text>
   197        </div>
   198      );
   199    }
   200  
   201    getStatementDiagnostics = async (diagnosticsId: Long) => {
   202      const request = new StatementDiagnosticsRequest({ statement_diagnostics_id: diagnosticsId });
   203      const response = await getStatementDiagnostics(request);
   204      const trace = response.diagnostics?.trace;
   205      this.downloadRef.current?.download("statement-diagnostics.json", "application/json", trace);
   206    }
   207  
   208    render() {
   209      const { diagnosticsReports } = this.props;
   210      const dataSource = diagnosticsReports.map((diagnosticsReport, idx) => ({
   211        ...diagnosticsReport,
   212        key: idx,
   213      }));
   214  
   215      return (
   216        <section className="section">
   217          <Helmet title="Statement diagnostics history | Debug" />
   218          <HeaderSection
   219            title="Statement diagnostics history"
   220            navigationBackConfig={{
   221              text: "Advanced Debug",
   222              path: "/debug",
   223            }}
   224          />
   225          { this.renderTableTitle() }
   226          <div className="diagnostics-history-view__table-container">
   227            <Table
   228              pageSize={this.tablePageSize}
   229              dataSource={dataSource}
   230              columns={this.columns}
   231            />
   232          </div>
   233          <DownloadFile ref={this.downloadRef}/>
   234        </section>
   235      );
   236    }
   237  }
   238  
   239  interface MapStateToProps {
   240    diagnosticsReports: IStatementDiagnosticsReport[];
   241    getStatementByFingerprint: (fingerprint: string) => ReturnType<typeof selectStatementByFingerprint>;
   242  }
   243  
   244  interface MapDispatchToProps {
   245    refresh: () => void;
   246  }
   247  
   248  const mapStateToProps = (state: AdminUIState): MapStateToProps => ({
   249    diagnosticsReports: selectStatementDiagnosticsReports(state) || [],
   250    getStatementByFingerprint: (fingerprint: string) => selectStatementByFingerprint(state, fingerprint),
   251  });
   252  
   253  const mapDispatchToProps = (dispatch: Dispatch<Action, AdminUIState>): MapDispatchToProps => ({
   254    refresh: () => {
   255      dispatch(invalidateStatementDiagnosticsRequests());
   256      dispatch(refreshStatementDiagnosticsRequests());
   257      dispatch(refreshStatements());
   258    },
   259  });
   260  
   261  export default connect<
   262    MapStateToProps,
   263    MapDispatchToProps,
   264    StatementDiagnosticsHistoryViewProps
   265    >(mapStateToProps, mapDispatchToProps)(StatementDiagnosticsHistoryView);