Browse Source

Explore: Use react-table as table component

- adds react-table as dependency
- replaces custom table component in Explore
- vendors react-table styles and overrides them (currently in
  explore.scss)
David Kaltschmidt 7 years ago
parent
commit
ff67213b42

+ 1 - 0
package.json

@@ -160,6 +160,7 @@
     "react-redux": "^5.0.7",
     "react-select": "2.1.0",
     "react-sizeme": "^2.3.6",
+    "react-table": "^6.8.6",
     "react-transition-group": "^2.2.1",
     "redux": "^4.0.0",
     "redux-logger": "^3.0.6",

+ 3 - 1
public/app/features/explore/Explore.tsx

@@ -644,7 +644,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
                   />
                 )}
               {supportsTable && showingTable ? (
-                <Table className="m-t-3" data={tableResult} loading={loading} onClickCell={this.onClickTableCell} />
+                <div className="panel-container">
+                  <Table className="m-t-3" data={tableResult} loading={loading} onClickCell={this.onClickTableCell} />
+                </div>
               ) : null}
               {supportsLogs && showingLogs ? <Logs data={logsResult} loading={loading} /> : null}
             </main>

+ 32 - 62
public/app/features/explore/Table.tsx

@@ -1,4 +1,7 @@
+import _ from 'lodash';
 import React, { PureComponent } from 'react';
+import ReactTable from 'react-table';
+
 import TableModel from 'app/core/table_model';
 
 const EMPTY_TABLE = new TableModel();
@@ -10,75 +13,42 @@ interface TableProps {
   onClickCell?: (columnKey: string, rowValue: string) => void;
 }
 
-interface SFCCellProps {
-  columnIndex: number;
-  onClickCell?: (columnKey: string, rowValue: string, columnIndex: number, rowIndex: number, table: TableModel) => void;
-  rowIndex: number;
-  table: TableModel;
-  value: string;
+function prepareRows(rows, columnNames) {
+  return rows.map(cells => _.zipObject(columnNames, cells));
 }
 
-function Cell(props: SFCCellProps) {
-  const { columnIndex, rowIndex, table, value, onClickCell } = props;
-  const column = table.columns[columnIndex];
-  if (column && column.filterable && onClickCell) {
-    const onClick = event => {
-      event.preventDefault();
-      onClickCell(column.text, value, columnIndex, rowIndex, table);
+export default class Table extends PureComponent<TableProps, {}> {
+  getCellProps = (state, rowInfo, column) => {
+    return {
+      onClick: () => {
+        const columnKey = column.Header;
+        const rowValue = rowInfo.row[columnKey];
+        this.props.onClickCell(columnKey, rowValue);
+      },
     };
-    return (
-      <td>
-        <a className="link" onClick={onClick}>
-          {value}
-        </a>
-      </td>
-    );
-  }
-  return <td>{value}</td>;
-}
+  };
 
-export default class Table extends PureComponent<TableProps, {}> {
   render() {
-    const { className = '', data, loading, onClickCell } = this.props;
+    const { data, loading } = this.props;
     const tableModel = data || EMPTY_TABLE;
-    if (!loading && data && data.rows.length === 0) {
-      return (
-        <table className={`${className} filter-table`}>
-          <thead>
-            <tr>
-              <th>Table</th>
-            </tr>
-          </thead>
-          <tbody>
-            <tr>
-              <td className="muted">The queries returned no data for a table.</td>
-            </tr>
-          </tbody>
-        </table>
-      );
-    }
+    const columnNames = tableModel.columns.map(({ text }) => text);
+    const columns = tableModel.columns.map(({ filterable, text }) => ({
+      Header: text,
+      accessor: text,
+      show: text !== 'Time',
+      Cell: row => <span className={filterable ? 'link' : ''}>{row.value}</span>,
+    }));
+
     return (
-      <table className={`${className} filter-table`}>
-        <thead>
-          <tr>{tableModel.columns.map(col => <th key={col.text}>{col.text}</th>)}</tr>
-        </thead>
-        <tbody>
-          {tableModel.rows.map((row, i) => (
-            <tr key={i}>
-              {row.map((value, j) => (
-                <Cell
-                  key={j}
-                  columnIndex={j}
-                  rowIndex={i}
-                  value={String(value)}
-                  table={data}
-                  onClickCell={onClickCell}
-                />
-              ))}
-            </tr>
-          ))}
-        </tbody>
-      </table>
+      <ReactTable
+        columns={columns}
+        data={tableModel.rows}
+        getTdProps={this.getCellProps}
+        loading={loading}
+        minRows={0}
+        noDataText="No data returned from query."
+        resolveData={data => prepareRows(data, columnNames)}
+      />
     );
   }
 }

+ 1 - 0
public/sass/_grafana.scss

@@ -2,6 +2,7 @@
 @import '../vendor/css/timepicker.css';
 @import '../vendor/css/spectrum.css';
 @import '../vendor/css/rc-cascader.scss';
+@import '../vendor/css/react-table.css';
 
 // MIXINS
 @import 'mixins/mixins';

+ 61 - 0
public/sass/pages/_explore.scss

@@ -111,6 +111,15 @@
   .link {
     text-decoration: underline;
   }
+
+  // React table
+  .explore-table-wrapper {
+    margin-top: 2 * $panel-margin;
+  }
+
+  .rt-tr .rt-td:last-child {
+    text-align: right;
+  }
 }
 
 .explore + .explore {
@@ -186,3 +195,55 @@
     margin: 0.25em 0.5em 0.5em;
   }
 }
+
+// ReactTable basic overrides (does not include pivot/groups/filters)
+// When integrating ReactTable as new panel plugin, move to _panel_table.scss
+
+.ReactTable {
+  border: none;
+}
+
+.ReactTable .rt-thead.-header {
+  box-shadow: none;
+  background: $list-item-bg;
+  border-top: 2px solid $body-bg;
+  border-bottom: 2px solid $body-bg;
+  height: 2em;
+}
+.ReactTable .rt-thead.-header .rt-th {
+  text-align: left;
+  color: $blue;
+  font-weight: 500;
+}
+.ReactTable .rt-thead .rt-td,
+.ReactTable .rt-thead .rt-th {
+  padding: 0.45em 0 0.45em 1.1em;
+  border-right: none;
+  box-shadow: none;
+}
+.ReactTable .rt-tbody .rt-td {
+  padding: 0.45em 0 0.45em 1.1em;
+  border-bottom: 2px solid $body-bg;
+  border-right: 2px solid $body-bg;
+}
+.ReactTable .rt-tbody .rt-td:last-child {
+  border-right: none;
+}
+.ReactTable .-pagination .-btn {
+  color: $blue;
+  background: $list-item-bg;
+}
+.ReactTable .-pagination input,
+.ReactTable .-pagination select {
+  color: $input-color;
+  background-color: $input-bg;
+}
+.ReactTable .-loading {
+  background: $input-bg;
+}
+.ReactTable .-loading.-active {
+  opacity: 0.8;
+}
+.ReactTable .-loading > div {
+  color: $input-color;
+}

+ 421 - 0
public/vendor/css/react-table.css

@@ -0,0 +1,421 @@
+.ReactTable {
+  position: relative;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  border: 1px solid rgba(0,0,0,0.1);
+}
+.ReactTable * {
+  box-sizing: border-box;
+}
+.ReactTable .rt-table {
+  -webkit-box-flex: 1;
+  -ms-flex: auto 1;
+  flex: auto 1;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  -webkit-box-align: stretch;
+  -ms-flex-align: stretch;
+  align-items: stretch;
+  width: 100%;
+  border-collapse: collapse;
+  overflow: auto;
+}
+.ReactTable .rt-thead {
+  -webkit-box-flex: 1;
+  -ms-flex: 1 0 auto;
+  flex: 1 0 auto;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.ReactTable .rt-thead.-headerGroups {
+  background: rgba(0,0,0,0.03);
+  border-bottom: 1px solid rgba(0,0,0,0.05);
+}
+.ReactTable .rt-thead.-filters {
+  border-bottom: 1px solid rgba(0,0,0,0.05);
+}
+.ReactTable .rt-thead.-filters input,
+.ReactTable .rt-thead.-filters select {
+  border: 1px solid rgba(0,0,0,0.1);
+  background: #fff;
+  padding: 5px 7px;
+  font-size: inherit;
+  border-radius: 3px;
+  font-weight: normal;
+  outline: none;
+}
+.ReactTable .rt-thead.-filters .rt-th {
+  border-right: 1px solid rgba(0,0,0,0.02);
+}
+.ReactTable .rt-thead.-header {
+  box-shadow: 0 2px 15px 0 rgba(0,0,0,0.15);
+}
+.ReactTable .rt-thead .rt-tr {
+  text-align: center;
+}
+.ReactTable .rt-thead .rt-td,
+.ReactTable .rt-thead .rt-th {
+  padding: 5px;
+  line-height: normal;
+  position: relative;
+  border-right: 1px solid rgba(0,0,0,0.05);
+  transition: box-shadow 0.3s cubic-bezier(.175,.885,.32,1.275);
+  box-shadow: inset 0 0 0 0 transparent;
+}
+.ReactTable .rt-thead .rt-td.-sort-asc,
+.ReactTable .rt-thead .rt-th.-sort-asc {
+  box-shadow: inset 0 3px 0 0 rgba(0,0,0,0.6);
+}
+.ReactTable .rt-thead .rt-td.-sort-desc,
+.ReactTable .rt-thead .rt-th.-sort-desc {
+  box-shadow: inset 0 -3px 0 0 rgba(0,0,0,0.6);
+}
+.ReactTable .rt-thead .rt-td.-cursor-pointer,
+.ReactTable .rt-thead .rt-th.-cursor-pointer {
+  cursor: pointer;
+}
+.ReactTable .rt-thead .rt-td:last-child,
+.ReactTable .rt-thead .rt-th:last-child {
+  border-right: 0;
+}
+.ReactTable .rt-thead .rt-resizable-header {
+  overflow: visible;
+}
+.ReactTable .rt-thead .rt-resizable-header:last-child {
+  overflow: hidden;
+}
+.ReactTable .rt-thead .rt-resizable-header-content {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.ReactTable .rt-thead .rt-header-pivot {
+  border-right-color: #f7f7f7;
+}
+.ReactTable .rt-thead .rt-header-pivot:after,
+.ReactTable .rt-thead .rt-header-pivot:before {
+  left: 100%;
+  top: 50%;
+  border: solid transparent;
+  content: " ";
+  height: 0;
+  width: 0;
+  position: absolute;
+  pointer-events: none;
+}
+.ReactTable .rt-thead .rt-header-pivot:after {
+  border-color: rgba(255,255,255,0);
+  border-left-color: #fff;
+  border-width: 8px;
+  margin-top: -8px;
+}
+.ReactTable .rt-thead .rt-header-pivot:before {
+  border-color: rgba(102,102,102,0);
+  border-left-color: #f7f7f7;
+  border-width: 10px;
+  margin-top: -10px;
+}
+.ReactTable .rt-tbody {
+  -webkit-box-flex: 99999;
+  -ms-flex: 99999 1 auto;
+  flex: 99999 1 auto;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  overflow: auto;
+}
+.ReactTable .rt-tbody .rt-tr-group {
+  border-bottom: solid 1px rgba(0,0,0,0.05);
+}
+.ReactTable .rt-tbody .rt-tr-group:last-child {
+  border-bottom: 0;
+}
+.ReactTable .rt-tbody .rt-td {
+  border-right: 1px solid rgba(0,0,0,0.02);
+}
+.ReactTable .rt-tbody .rt-td:last-child {
+  border-right: 0;
+}
+.ReactTable .rt-tbody .rt-expandable {
+  cursor: pointer;
+  text-overflow: clip;
+}
+.ReactTable .rt-tr-group {
+  -webkit-box-flex: 1;
+  -ms-flex: 1 0 auto;
+  flex: 1 0 auto;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  -webkit-box-align: stretch;
+  -ms-flex-align: stretch;
+  align-items: stretch;
+}
+.ReactTable .rt-tr {
+  -webkit-box-flex: 1;
+  -ms-flex: 1 0 auto;
+  flex: 1 0 auto;
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+}
+.ReactTable .rt-td,
+.ReactTable .rt-th {
+  -webkit-box-flex: 1;
+  -ms-flex: 1 0 0;
+  flex: 1 0 0;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding: 7px 5px;
+  overflow: hidden;
+  transition: 0.3s ease;
+  transition-property: width,min-width,padding,opacity;
+}
+.ReactTable .rt-td.-hidden,
+.ReactTable .rt-th.-hidden {
+  width: 0 !important;
+  min-width: 0 !important;
+  padding: 0 !important;
+  border: 0 !important;
+  opacity: 0 !important;
+}
+.ReactTable .rt-expander {
+  display: inline-block;
+  position: relative;
+  margin: 0;
+  color: transparent;
+  margin: 0 10px;
+}
+.ReactTable .rt-expander:after {
+  content: '';
+  position: absolute;
+  width: 0;
+  height: 0;
+  top: 50%;
+  left: 50%;
+  -webkit-transform: translate(-50%,-50%) rotate(-90deg);
+  transform: translate(-50%,-50%) rotate(-90deg);
+  border-left: 5.04px solid transparent;
+  border-right: 5.04px solid transparent;
+  border-top: 7px solid rgba(0,0,0,0.8);
+  transition: all 0.3s cubic-bezier(.175,.885,.32,1.275);
+  cursor: pointer;
+}
+.ReactTable .rt-expander.-open:after {
+  -webkit-transform: translate(-50%,-50%) rotate(0);
+  transform: translate(-50%,-50%) rotate(0);
+}
+.ReactTable .rt-resizer {
+  display: inline-block;
+  position: absolute;
+  width: 36px;
+  top: 0;
+  bottom: 0;
+  right: -18px;
+  cursor: col-resize;
+  z-index: 10;
+}
+.ReactTable .rt-tfoot {
+  -webkit-box-flex: 1;
+  -ms-flex: 1 0 auto;
+  flex: 1 0 auto;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  box-shadow: 0 0 15px 0 rgba(0,0,0,0.15);
+}
+.ReactTable .rt-tfoot .rt-td {
+  border-right: 1px solid rgba(0,0,0,0.05);
+}
+.ReactTable .rt-tfoot .rt-td:last-child {
+  border-right: 0;
+}
+.ReactTable.-striped .rt-tr.-odd {
+  background: rgba(0,0,0,0.03);
+}
+.ReactTable.-highlight .rt-tbody .rt-tr:not(.-padRow):hover {
+  background: rgba(0,0,0,0.05);
+}
+.ReactTable .-pagination {
+  z-index: 1;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: justify;
+  -ms-flex-pack: justify;
+  justify-content: space-between;
+  -webkit-box-align: stretch;
+  -ms-flex-align: stretch;
+  align-items: stretch;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  padding: 3px;
+  box-shadow: 0 0 15px 0 rgba(0,0,0,0.1);
+  border-top: 2px solid rgba(0,0,0,0.1);
+}
+.ReactTable .-pagination input,
+.ReactTable .-pagination select {
+  border: 1px solid rgba(0,0,0,0.1);
+  background: #fff;
+  padding: 5px 7px;
+  font-size: inherit;
+  border-radius: 3px;
+  font-weight: normal;
+  outline: none;
+}
+.ReactTable .-pagination .-btn {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+  display: block;
+  width: 100%;
+  height: 100%;
+  border: 0;
+  border-radius: 3px;
+  padding: 6px;
+  font-size: 1em;
+  color: rgba(0,0,0,0.6);
+  background: rgba(0,0,0,0.1);
+  transition: all 0.1s ease;
+  cursor: pointer;
+  outline: none;
+}
+.ReactTable .-pagination .-btn[disabled] {
+  opacity: 0.5;
+  cursor: default;
+}
+.ReactTable .-pagination .-btn:not([disabled]):hover {
+  background: rgba(0,0,0,0.3);
+  color: #fff;
+}
+.ReactTable .-pagination .-next,
+.ReactTable .-pagination .-previous {
+  -webkit-box-flex: 1;
+  -ms-flex: 1;
+  flex: 1;
+  text-align: center;
+}
+.ReactTable .-pagination .-center {
+  -webkit-box-flex: 1.5;
+  -ms-flex: 1.5;
+  flex: 1.5;
+  text-align: center;
+  margin-bottom: 0;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: horizontal;
+  -webkit-box-direction: normal;
+  -ms-flex-direction: row;
+  flex-direction: row;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  -ms-flex-pack: distribute;
+  justify-content: space-around;
+}
+.ReactTable .-pagination .-pageInfo {
+  display: inline-block;
+  margin: 3px 10px;
+  white-space: nowrap;
+}
+.ReactTable .-pagination .-pageJump {
+  display: inline-block;
+}
+.ReactTable .-pagination .-pageJump input {
+  width: 70px;
+  text-align: center;
+}
+.ReactTable .-pagination .-pageSizeOptions {
+  margin: 3px 10px;
+}
+.ReactTable .rt-noData {
+  display: block;
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  -webkit-transform: translate(-50%,-50%);
+  transform: translate(-50%,-50%);
+  background: rgba(255,255,255,0.8);
+  transition: all 0.3s ease;
+  z-index: 1;
+  pointer-events: none;
+  padding: 20px;
+  color: rgba(0,0,0,0.5);
+}
+.ReactTable .-loading {
+  display: block;
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  background: rgba(255,255,255,0.8);
+  transition: all 0.3s ease;
+  z-index: -1;
+  opacity: 0;
+  pointer-events: none;
+}
+.ReactTable .-loading > div {
+  position: absolute;
+  display: block;
+  text-align: center;
+  width: 100%;
+  top: 50%;
+  left: 0;
+  font-size: 15px;
+  color: rgba(0,0,0,0.6);
+  -webkit-transform: translateY(-52%);
+  transform: translateY(-52%);
+  transition: all 0.3s cubic-bezier(.25,.46,.45,.94);
+}
+.ReactTable .-loading.-active {
+  opacity: 1;
+  z-index: 2;
+  pointer-events: all;
+}
+.ReactTable .-loading.-active > div {
+  -webkit-transform: translateY(50%);
+  transform: translateY(50%);
+}
+.ReactTable .rt-resizing .rt-td,
+.ReactTable .rt-resizing .rt-th {
+  transition: none !important;
+  cursor: col-resize;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}

File diff suppressed because it is too large
+ 0 - 176
yarn.lock


Some files were not shown because too many files changed in this diff