This is an automated email from the ASF dual-hosted git repository.

erikrit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 2e76fbb  chore: type FilterableTable (#10073)
2e76fbb is described below

commit 2e76fbb7e56f4551d3733db82ce5a2e04ad6cd8d
Author: Erik Ritter <erik.rit...@airbnb.com>
AuthorDate: Thu Jun 18 21:57:11 2020 -0700

    chore: type FilterableTable (#10073)
---
 superset-frontend/package-lock.json                |  26 +++
 superset-frontend/package.json                     |   2 +
 ...ableTable_spec.jsx => FilterableTable_spec.tsx} |   8 +-
 .../{FilterableTable.jsx => FilterableTable.tsx}   | 207 +++++++++++++++------
 4 files changed, 180 insertions(+), 63 deletions(-)

diff --git a/superset-frontend/package-lock.json 
b/superset-frontend/package-lock.json
index f80cb17..cc3557c 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -9753,6 +9753,14 @@
         }
       }
     },
+    "@types/cheerio": {
+      "version": "0.22.18",
+      "resolved": 
"https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.18.tgz";,
+      "integrity": 
"sha512-Fq7R3fINAPSdUEhOyjG4iVxgHrOnqDJbY0/BUuiN0pvD/rfmZWekVZnv+vcs8TtpA2XF50uv50LaE4EnpEL/Hw==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/classnames": {
       "version": "2.2.9",
       "resolved": 
"https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz";,
@@ -9852,6 +9860,15 @@
         "@types/node": "*"
       }
     },
+    "@types/enzyme": {
+      "version": "3.10.5",
+      "resolved": 
"https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.5.tgz";,
+      "integrity": 
"sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA==",
+      "requires": {
+        "@types/cheerio": "*",
+        "@types/react": "*"
+      }
+    },
     "@types/eslint-visitor-keys": {
       "version": "1.0.0",
       "resolved": 
"https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz";,
@@ -10123,6 +10140,15 @@
         "@types/react": "*"
       }
     },
+    "@types/react-virtualized": {
+      "version": "9.21.10",
+      "resolved": 
"https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.10.tgz";,
+      "integrity": 
"sha512-f5Ti3A7gGdLkPPFNHTrvKblpsPNBiQoSorOEOD+JPx72g/Ng2lOt4MYfhvQFQNgyIrAro+Z643jbcKafsMW2ag==",
+      "requires": {
+        "@types/prop-types": "*",
+        "@types/react": "*"
+      }
+    },
     "@types/react-window": {
       "version": "1.8.2",
       "resolved": 
"https://registry.npmjs.org/@types/react-window/-/react-window-1.8.2.tgz";,
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 7d60f6b..921cb81 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -101,9 +101,11 @@
     "@superset-ui/translation": "^0.13.27",
     "@superset-ui/validator": "^0.13.27",
     "@types/classnames": "^2.2.9",
+    "@types/enzyme": "^3.10.5",
     "@types/react-bootstrap": "^0.32.21",
     "@types/react-json-tree": "^0.6.11",
     "@types/react-select": "^3.0.12",
+    "@types/react-virtualized": "^9.21.10",
     "@types/react-window": "^1.8.2",
     "@types/redux-localstorage": "^1.0.8",
     "@types/rison": "0.0.6",
diff --git 
a/superset-frontend/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
 
b/superset-frontend/spec/javascripts/components/FilterableTable/FilterableTable_spec.tsx
similarity index 96%
rename from 
superset-frontend/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
rename to 
superset-frontend/spec/javascripts/components/FilterableTable/FilterableTable_spec.tsx
index 74cbf92..2d2b451 100644
--- 
a/superset-frontend/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
+++ 
b/superset-frontend/spec/javascripts/components/FilterableTable/FilterableTable_spec.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import React from 'react';
-import { mount } from 'enzyme';
+import { mount, ReactWrapper } from 'enzyme';
 import FilterableTable, {
   MAX_COLUMNS_FOR_TABLE,
 } from 'src/components/FilterableTable/FilterableTable';
@@ -32,7 +32,7 @@ describe('FilterableTable', () => {
     ],
     height: 500,
   };
-  let wrapper;
+  let wrapper: ReactWrapper;
   beforeEach(() => {
     wrapper = mount(<FilterableTable {...mockedProps} />);
   });
@@ -53,11 +53,11 @@ describe('FilterableTable', () => {
         (_, x) => `col_${x}`,
       ),
       data: [
-        Object.assign(
+        {
           ...Array.from(Array(wideTableColumns)).map((val, x) => ({
             [`col_${x}`]: x,
           })),
-        ),
+        },
       ],
       height: 500,
     };
diff --git 
a/superset-frontend/src/components/FilterableTable/FilterableTable.jsx 
b/superset-frontend/src/components/FilterableTable/FilterableTable.tsx
similarity index 77%
rename from superset-frontend/src/components/FilterableTable/FilterableTable.jsx
rename to superset-frontend/src/components/FilterableTable/FilterableTable.tsx
index 5b0e111..236f25d 100644
--- a/superset-frontend/src/components/FilterableTable/FilterableTable.jsx
+++ b/superset-frontend/src/components/FilterableTable/FilterableTable.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import { List } from 'immutable';
-import PropTypes from 'prop-types';
+// @ts-ignore
 import JSONbig from 'json-bigint';
 import React, { PureComponent } from 'react';
 import JSONTree from 'react-json-tree';
@@ -28,6 +28,7 @@ import {
   SortDirection,
   SortIndicator,
   Table,
+  SortDirectionType,
 } from 'react-virtualized';
 import { getMultipleTextDimensions } from '@superset-ui/dimension';
 import { t } from '@superset-ui/translation';
@@ -37,7 +38,9 @@ import CopyToClipboard from '../CopyToClipboard';
 import ModalTrigger from '../ModalTrigger';
 import TooltipWrapper from '../TooltipWrapper';
 
-function safeJsonObjectParse(data) {
+function safeJsonObjectParse(
+  data: unknown,
+): null | unknown[] | Record<string, unknown> {
   // First perform a cheap proxy to avoid calling JSON.parse on data that is 
clearly not a
   // JSON object or array
   if (
@@ -85,31 +88,43 @@ const JSON_TREE_THEME = {
 // when more than MAX_COLUMNS_FOR_TABLE are returned, switch from table to 
grid view
 export const MAX_COLUMNS_FOR_TABLE = 50;
 
-const propTypes = {
-  orderedColumnKeys: PropTypes.array.isRequired,
-  data: PropTypes.array.isRequired,
-  height: PropTypes.number.isRequired,
-  filterText: PropTypes.string,
-  headerHeight: PropTypes.number,
-  overscanColumnCount: PropTypes.number,
-  overscanRowCount: PropTypes.number,
-  rowHeight: PropTypes.number,
-  striped: PropTypes.bool,
-  expandedColumns: PropTypes.array,
-};
+type CellDataType = string | number | null;
+type Datum = Record<string, CellDataType>;
+
+interface FilterableTableProps {
+  orderedColumnKeys: string[];
+  data: Record<string, unknown>[];
+  height: number;
+  filterText: string;
+  headerHeight: number;
+  overscanColumnCount: number;
+  overscanRowCount: number;
+  rowHeight: number;
+  striped: boolean;
+  expandedColumns: string[];
+}
 
-const defaultProps = {
-  filterText: '',
-  headerHeight: 32,
-  overscanColumnCount: 10,
-  overscanRowCount: 10,
-  rowHeight: 32,
-  striped: true,
-  expandedColumns: [],
-};
+interface FilterableTableState {
+  sortBy?: string;
+  sortDirection: SortDirectionType;
+  fitted: boolean;
+}
 
-export default class FilterableTable extends PureComponent {
-  constructor(props) {
+export default class FilterableTable extends PureComponent<
+  FilterableTableProps,
+  FilterableTableState
+> {
+  static defaultProps = {
+    filterText: '',
+    headerHeight: 32,
+    overscanColumnCount: 10,
+    overscanRowCount: 10,
+    rowHeight: 32,
+    striped: true,
+    expandedColumns: [],
+  };
+
+  constructor(props: FilterableTableProps) {
     super(props);
     this.list = List(this.formatTableData(props.data));
     this.addJsonModal = this.addJsonModal.bind(this);
@@ -140,7 +155,6 @@ export default class FilterableTable extends PureComponent {
     this.totalTableHeight = props.height;
 
     this.state = {
-      sortBy: null,
       sortDirection: SortDirection.ASC,
       fitted: false,
     };
@@ -152,7 +166,7 @@ export default class FilterableTable extends PureComponent {
     this.fitTableToWidthIfNeeded();
   }
 
-  getDatum(list, index) {
+  getDatum(list: List<Datum>, index: number) {
     return list.get(index % list.size);
   }
 
@@ -162,9 +176,10 @@ export default class FilterableTable extends PureComponent 
{
     const cellContent = [].concat(
       ...this.props.orderedColumnKeys.map(key =>
         this.list
-          .map(data =>
+          .map((data: Datum) =>
             this.getCellContent({ cellData: data[key], columnKey: key }),
           )
+          // @ts-ignore
           .push(key)
           .toJS(),
       ),
@@ -191,7 +206,13 @@ export default class FilterableTable extends PureComponent 
{
     return widthsByColumnKey;
   }
 
-  getCellContent({ cellData, columnKey }) {
+  getCellContent({
+    cellData,
+    columnKey,
+  }: {
+    cellData: CellDataType;
+    columnKey: string;
+  }) {
     if (cellData === null) {
       return <i className="text-muted">NULL</i>;
     }
@@ -208,7 +229,14 @@ export default class FilterableTable extends PureComponent 
{
     return this.complexColumns[columnKey] ? truncated : content;
   }
 
-  formatTableData(data) {
+  list: List<Datum>;
+  complexColumns: Record<string, boolean>;
+  widthsForColumnsByKey: Record<string, number>;
+  totalTableWidth: number;
+  totalTableHeight: number;
+  container: React.RefObject<HTMLDivElement>;
+
+  formatTableData(data: Record<string, unknown>[]): Datum[] {
     const formattedData = data.map(row => {
       const newRow = {};
       for (const k in row) {
@@ -224,7 +252,7 @@ export default class FilterableTable extends PureComponent {
     return formattedData;
   }
 
-  hasMatch(text, row) {
+  hasMatch(text: string, row: Datum) {
     const values = [];
     for (const key in row) {
       if (row.hasOwnProperty(key)) {
@@ -243,7 +271,7 @@ export default class FilterableTable extends PureComponent {
     return values.some(v => v.includes(lowerCaseText));
   }
 
-  rowClassName({ index }) {
+  rowClassName({ index }: { index: number }) {
     let className = '';
     if (this.props.striped) {
       className = index % 2 === 0 ? 'even-row' : 'odd-row';
@@ -251,12 +279,18 @@ export default class FilterableTable extends 
PureComponent {
     return className;
   }
 
-  sort({ sortBy, sortDirection }) {
+  sort({
+    sortBy,
+    sortDirection,
+  }: {
+    sortBy: string;
+    sortDirection: SortDirectionType;
+  }) {
     this.setState({ sortBy, sortDirection });
   }
 
   fitTableToWidthIfNeeded() {
-    const containerWidth = this.container.clientWidth;
+    const containerWidth = this.container.current!.clientWidth;
     if (this.totalTableWidth < containerWidth) {
       // fit table width if content doesn't fill the width of the container
       this.totalTableWidth = containerWidth;
@@ -264,7 +298,11 @@ export default class FilterableTable extends PureComponent 
{
     this.setState({ fitted: true });
   }
 
-  addJsonModal(node, jsonObject, jsonString) {
+  addJsonModal(
+    node: React.ReactNode,
+    jsonObject: Record<string, unknown> | unknown[],
+    jsonString: CellDataType,
+  ) {
     return (
       <ModalTrigger
         modalBody={<JSONTree data={jsonObject} theme={JSON_TREE_THEME} />}
@@ -279,24 +317,36 @@ export default class FilterableTable extends 
PureComponent {
     );
   }
 
-  sortResults(sortBy, descending) {
-    return (a, b) => {
-      if (a[sortBy] === b[sortBy]) {
+  sortResults(sortBy: string, descending: boolean) {
+    return (a: Datum, b: Datum) => {
+      const aValue = a[sortBy];
+      const bValue = b[sortBy];
+      if (aValue === bValue) {
         // equal items sort equally
         return 0;
-      } else if (a[sortBy] === null) {
+      } else if (aValue === null) {
         // nulls sort after anything else
         return 1;
-      } else if (b[sortBy] === null) {
+      } else if (bValue === null) {
         return -1;
       } else if (descending) {
-        return a[sortBy] < b[sortBy] ? 1 : -1;
+        return aValue < bValue ? 1 : -1;
       }
-      return a[sortBy] < b[sortBy] ? -1 : 1;
+      return aValue < bValue ? -1 : 1;
     };
   }
 
-  renderTableHeader({ dataKey, label, sortBy, sortDirection }) {
+  renderTableHeader({
+    dataKey,
+    label,
+    sortBy,
+    sortDirection,
+  }: {
+    dataKey: string;
+    label: string;
+    sortBy: string;
+    sortDirection: SortDirectionType;
+  }) {
     const className =
       this.props.expandedColumns.indexOf(label) > -1
         ? 'header-style-disabled'
@@ -313,7 +363,15 @@ export default class FilterableTable extends PureComponent 
{
     );
   }
 
-  renderGridCellHeader({ columnIndex, key, style }) {
+  renderGridCellHeader({
+    columnIndex,
+    key,
+    style,
+  }: {
+    columnIndex: number;
+    key: string;
+    style: React.CSSProperties;
+  }) {
     const label = this.props.orderedColumnKeys[columnIndex];
     const className =
       this.props.expandedColumns.indexOf(label) > -1
@@ -322,7 +380,13 @@ export default class FilterableTable extends PureComponent 
{
     return (
       <TooltipWrapper key={key} label="header" tooltip={label}>
         <div
-          style={{ ...style, top: style.top - GRID_POSITION_ADJUSTMENT }}
+          style={{
+            ...style,
+            top:
+              typeof style.top === 'number'
+                ? style.top - GRID_POSITION_ADJUSTMENT
+                : style.top,
+          }}
           className={`${className} grid-cell grid-header-cell`}
         >
           {label}
@@ -331,14 +395,30 @@ export default class FilterableTable extends 
PureComponent {
     );
   }
 
-  renderGridCell({ columnIndex, key, rowIndex, style }) {
+  renderGridCell({
+    columnIndex,
+    key,
+    rowIndex,
+    style,
+  }: {
+    columnIndex: number;
+    key: string;
+    rowIndex: number;
+    style: React.CSSProperties;
+  }) {
     const columnKey = this.props.orderedColumnKeys[columnIndex];
     const cellData = this.list.get(rowIndex)[columnKey];
     const content = this.getCellContent({ cellData, columnKey });
     const cellNode = (
       <div
         key={key}
-        style={{ ...style, top: style.top - GRID_POSITION_ADJUSTMENT }}
+        style={{
+          ...style,
+          top:
+            typeof style.top === 'number'
+              ? style.top - GRID_POSITION_ADJUSTMENT
+              : style.top,
+        }}
         className={`grid-cell ${this.rowClassName({ index: rowIndex })}`}
       >
         {content}
@@ -362,14 +442,17 @@ export default class FilterableTable extends 
PureComponent {
 
     let { height } = this.props;
     let totalTableHeight = height;
-    if (this.container && this.totalTableWidth > this.container.clientWidth) {
+    if (
+      this.container.current &&
+      this.totalTableWidth > this.container.current.clientWidth
+    ) {
       // exclude the height of the horizontal scroll bar from the height of 
the table
       // and the height of the table container if the content overflows
       height -= SCROLL_BAR_HEIGHT;
       totalTableHeight -= SCROLL_BAR_HEIGHT;
     }
 
-    const getColumnWidth = ({ index }) =>
+    const getColumnWidth = ({ index }: { index: number }) =>
       this.widthsForColumnsByKey[orderedColumnKeys[index]];
 
     // fix height of filterable table
@@ -413,7 +496,13 @@ export default class FilterableTable extends PureComponent 
{
     );
   }
 
-  renderTableCell({ cellData, columnKey }) {
+  renderTableCell({
+    cellData,
+    columnKey,
+  }: {
+    cellData: CellDataType;
+    columnKey: string;
+  }) {
     const cellNode = this.getCellContent({ cellData, columnKey });
     const jsonObject = safeJsonObjectParse(cellData);
     if (jsonObject) {
@@ -432,30 +521,33 @@ export default class FilterableTable extends 
PureComponent {
       rowHeight,
     } = this.props;
 
-    let sortedAndFilteredList = this.list;
+    let sortedAndFilteredList: List<Datum> = this.list;
     // filter list
     if (filterText) {
-      sortedAndFilteredList = this.list.filter(row =>
+      sortedAndFilteredList = this.list.filter((row: Datum) =>
         this.hasMatch(filterText, row),
-      );
+      ) as List<Datum>;
     }
     // sort list
     if (sortBy) {
       sortedAndFilteredList = sortedAndFilteredList.sort(
         this.sortResults(sortBy, sortDirection === SortDirection.DESC),
-      );
+      ) as List<Datum>;
     }
 
     let { height } = this.props;
     let totalTableHeight = height;
-    if (this.container && this.totalTableWidth > this.container.clientWidth) {
+    if (
+      this.container.current &&
+      this.totalTableWidth > this.container.current.clientWidth
+    ) {
       // exclude the height of the horizontal scroll bar from the height of 
the table
       // and the height of the table container if the content overflows
       height -= SCROLL_BAR_HEIGHT;
       totalTableHeight -= SCROLL_BAR_HEIGHT;
     }
 
-    const rowGetter = ({ index }) =>
+    const rowGetter = ({ index }: { index: number }) =>
       this.getDatum(sortedAndFilteredList, index);
     return (
       <div
@@ -504,6 +596,3 @@ export default class FilterableTable extends PureComponent {
     return this.renderTable();
   }
 }
-
-FilterableTable.propTypes = propTypes;
-FilterableTable.defaultProps = defaultProps;

Reply via email to