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

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


The following commit(s) were added to refs/heads/master by this push:
     new 30e6463  Add table column selection in druid console to allow 
hiding/showing of columns (#7292)
30e6463 is described below

commit 30e646308abc9e73e5f2548d95e75feca0a4d7f4
Author: Qi Shu <shuqi...@gmail.com>
AuthorDate: Wed Mar 20 13:11:00 2019 -0700

    Add table column selection in druid console to allow hiding/showing of 
columns (#7292)
    
    * Add table column selections to all tables to allow user to hide/show 
columns
    
    * Small change for re-rendering
    
    * Use column selection handler class to process all column hiding/showing
    
    * dereference table handler function at the start; use more specific file 
name for table.tsx
---
 .../table-column-selection.scss}                   | 17 +++--
 .../src/components/table-column-selection.tsx      | 68 +++++++++++++++++++
 web-console/src/utils/index.tsx                    |  1 +
 .../src/utils/table-column-selection-handler.tsx   | 61 +++++++++++++++++
 web-console/src/views/datasource-view.tsx          | 46 ++++++++++---
 web-console/src/views/lookups-view.tsx             | 39 +++++++++--
 web-console/src/views/segments-view.tsx            | 58 ++++++++++++----
 web-console/src/views/servers-view.tsx             | 77 ++++++++++++++++++----
 web-console/src/views/tasks-view.tsx               | 76 +++++++++++++++++----
 9 files changed, 381 insertions(+), 62 deletions(-)

diff --git a/web-console/src/utils/index.tsx 
b/web-console/src/components/table-column-selection.scss
similarity index 84%
copy from web-console/src/utils/index.tsx
copy to web-console/src/components/table-column-selection.scss
index d231058..ddce873 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/components/table-column-selection.scss
@@ -16,7 +16,16 @@
  * limitations under the License.
  */
 
-export * from './general';
-export * from './druid-query';
-export * from './query-manager';
-export * from './rune-decoder';
+.table-column-selection {
+
+  float: right;
+
+  .pt-popover-content {
+    padding: 10px 10px 1px 10px;
+  }
+
+  .form-group {
+    margin-bottom: 0;
+  }
+
+}
diff --git a/web-console/src/components/table-column-selection.tsx 
b/web-console/src/components/table-column-selection.tsx
new file mode 100644
index 0000000..bd86fe7
--- /dev/null
+++ b/web-console/src/components/table-column-selection.tsx
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Button, Checkbox, Popover, Position } from "@blueprintjs/core";
+import * as React from 'react';
+
+import { FormGroup, IconNames } from "./filler";
+
+import "./table-column-selection.scss";
+
+interface TableColumnSelectionProps extends React.Props<any> {
+  columns: string[];
+  onChange: (column: string) => void;
+  tableColumnsHidden: string[];
+}
+
+interface TableColumnSelectionState {
+
+}
+
+export class TableColumnSelection extends 
React.Component<TableColumnSelectionProps, TableColumnSelectionState> {
+
+  constructor(props: TableColumnSelectionProps) {
+    super(props);
+    this.state = {
+
+    };
+  }
+
+  render() {
+    const { columns, onChange, tableColumnsHidden } = this.props;
+    const checkboxes = <FormGroup>
+      {
+        columns.map(column => {
+          return <Checkbox
+            label={column}
+            key={column}
+            checked={!tableColumnsHidden.includes(column)}
+            onChange={() => onChange(column)}
+          />;
+        })
+      }
+    </FormGroup>;
+    return <Popover
+      className={"table-column-selection"}
+      content={checkboxes}
+      position={Position.BOTTOM_RIGHT}
+      inline
+    >
+      <Button rightIconName={IconNames.CARET_DOWN} text={"Columns"} />
+    </Popover>;
+  }
+}
diff --git a/web-console/src/utils/index.tsx b/web-console/src/utils/index.tsx
index d231058..2bbc9b3 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/utils/index.tsx
@@ -20,3 +20,4 @@ export * from './general';
 export * from './druid-query';
 export * from './query-manager';
 export * from './rune-decoder';
+export * from './table-column-selection-handler';
diff --git a/web-console/src/utils/table-column-selection-handler.tsx 
b/web-console/src/utils/table-column-selection-handler.tsx
new file mode 100644
index 0000000..0ecead7
--- /dev/null
+++ b/web-console/src/utils/table-column-selection-handler.tsx
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { localStorageGet, localStorageSet } from "./general";
+
+export class TableColumnSelectionHandler {
+  tableName: string;
+  hiddenColumns: string[];
+  updateComponent: () => void;
+
+  constructor(tableName: string, updateComponent: () => void) {
+    this.tableName = tableName;
+    this.updateComponent = updateComponent;
+    this.getHiddenTableColumns();
+  }
+
+  getHiddenTableColumns(): void {
+    const stringValue: string | null = localStorageGet(this.tableName);
+    try {
+      const selections = JSON.parse(String(stringValue));
+      if (!Array.isArray(selections)) {
+        this.hiddenColumns = [];
+      } else {
+        this.hiddenColumns = selections;
+      }
+    } catch (e) {
+      this.hiddenColumns = [];
+    }
+  }
+
+  changeTableColumnSelection(column: string): void {
+    let newSelections: string[];
+    if (this.hiddenColumns.includes(column)) {
+      newSelections = this.hiddenColumns.filter(c => c !== column);
+    } else {
+      newSelections = this.hiddenColumns.concat(column);
+    }
+    this.hiddenColumns = newSelections;
+    this.updateComponent();
+    localStorageSet(this.tableName, JSON.stringify(newSelections));
+  }
+
+  showColumn(column: string): boolean {
+    return !this.hiddenColumns.includes(column);
+  }
+}
diff --git a/web-console/src/views/datasource-view.tsx 
b/web-console/src/views/datasource-view.tsx
index 3ee04a2..696a8f5 100644
--- a/web-console/src/views/datasource-view.tsx
+++ b/web-console/src/views/datasource-view.tsx
@@ -23,6 +23,7 @@ import ReactTable, { Filter } from "react-table";
 
 import { IconNames } from "../components/filler";
 import { RuleEditor } from '../components/rule-editor';
+import { TableColumnSelection } from "../components/table-column-selection";
 import { AsyncActionDialog } from '../dialogs/async-action-dialog';
 import { CompactionDialog } from "../dialogs/compaction-dialog";
 import { RetentionDialog } from '../dialogs/retention-dialog';
@@ -34,11 +35,16 @@ import {
   formatNumber,
   getDruidErrorMessage,
   lookupBy,
-  pluralIfNeeded, queryDruidSql, QueryManager
+  pluralIfNeeded,
+  queryDruidSql,
+  QueryManager, TableColumnSelectionHandler
 } from "../utils";
 
 import "./datasource-view.scss";
 
+const datasourceTableColumnSelection = "datasource-table-column-selection";
+const tableColumns: string[] = ["Datasource", "Availability", "Retention", 
"Compaction", "Size", "Num rows", "Actions"];
+
 export interface DatasourcesViewProps extends React.Props<any> {
   goToSql: (initSql: string) => void;
   goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
@@ -64,6 +70,7 @@ export interface DatasourcesViewState {
   dropDataDatasource: string | null;
   enableDatasource: string | null;
   killDatasource: string | null;
+
 }
 
 export class DatasourcesView extends React.Component<DatasourcesViewProps, 
DatasourcesViewState> {
@@ -82,6 +89,7 @@ export class DatasourcesView extends 
React.Component<DatasourcesViewProps, Datas
   }
 
   private datasourceQueryManager: QueryManager<string, { tiers: string[], 
defaultRules: any[], datasources: Datasource[] }>;
+  private tableColumnSelectionHandler: TableColumnSelectionHandler;
 
   constructor(props: DatasourcesViewProps, context: any) {
     super(props, context);
@@ -99,7 +107,12 @@ export class DatasourcesView extends 
React.Component<DatasourcesViewProps, Datas
       dropDataDatasource: null,
       enableDatasource: null,
       killDatasource: null
+
     };
+
+    this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
+      datasourceTableColumnSelection, () => this.setState({})
+    );
   }
 
   componentDidMount(): void {
@@ -151,6 +164,7 @@ export class DatasourcesView extends 
React.Component<DatasourcesViewProps, Datas
   SUM("num_rows") AS num_rows
 FROM sys.segments
 GROUP BY 1`);
+
   }
 
   componentWillUnmount(): void {
@@ -342,12 +356,11 @@ GROUP BY 1`);
   renderDatasourceTable() {
     const { goToSegments } = this.props;
     const { datasources, defaultRules, datasourcesLoading, datasourcesError, 
datasourcesFilter, showDisabled } = this.state;
-
+    const { tableColumnSelectionHandler } = this;
     let data = datasources || [];
     if (!showDisabled) {
       data = data.filter(d => !d.disabled);
     }
-
     return <>
       <ReactTable
         data={data}
@@ -366,7 +379,8 @@ GROUP BY 1`);
             Cell: row => {
               const value = row.value;
               return <a onClick={() => { this.setState({ datasourcesFilter: 
addFilter(datasourcesFilter, 'datasource', value) }); }}>{value}</a>;
-            }
+            },
+            show: tableColumnSelectionHandler.showColumn("Datasource")
           },
           {
             Header: "Availability",
@@ -400,7 +414,8 @@ GROUP BY 1`);
                 </span>;
 
               }
-            }
+            },
+            show: tableColumnSelectionHandler.showColumn("Availability")
           },
           {
             Header: 'Retention',
@@ -423,7 +438,8 @@ GROUP BY 1`);
                 {text}&nbsp;
                 <a>&#x270E;</a>
               </span>;
-            }
+            },
+            show: tableColumnSelectionHandler.showColumn("Retention")
           },
           {
             Header: 'Compaction',
@@ -449,21 +465,24 @@ GROUP BY 1`);
                 {text}&nbsp;
                 <a>&#x270E;</a>
               </span>;
-            }
+            },
+            show: tableColumnSelectionHandler.showColumn("Compaction")
           },
           {
             Header: 'Size',
             accessor: 'size',
             filterable: false,
             width: 100,
-            Cell: (row) => formatBytes(row.value)
+            Cell: (row) => formatBytes(row.value),
+            show: tableColumnSelectionHandler.showColumn("Size")
           },
           {
             Header: 'Num rows',
             accessor: 'num_rows',
             filterable: false,
             width: 100,
-            Cell: (row) => formatNumber(row.value)
+            Cell: (row) => formatNumber(row.value),
+            show: tableColumnSelectionHandler.showColumn("Num rows")
           },
           {
             Header: 'Actions',
@@ -484,7 +503,8 @@ GROUP BY 1`);
                   <a onClick={() => this.setState({ dropDataDatasource: 
datasource })}>Drop data</a>
                 </div>;
               }
-            }
+            },
+            show: tableColumnSelectionHandler.showColumn("Actions")
           }
         ]}
         defaultPageSize={50}
@@ -501,6 +521,7 @@ GROUP BY 1`);
   render() {
     const { goToSql } = this.props;
     const { showDisabled } = this.state;
+    const { tableColumnSelectionHandler } = this;
 
     return <div className="data-sources-view app-view">
       <div className="control-bar">
@@ -520,6 +541,11 @@ GROUP BY 1`);
           label="Show disabled"
           onChange={() => this.setState({ showDisabled: !showDisabled })}
         />
+        <TableColumnSelection
+          columns={tableColumns}
+          onChange={(column) => 
tableColumnSelectionHandler.changeTableColumnSelection(column)}
+          tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderDatasourceTable()}
     </div>;
diff --git a/web-console/src/views/lookups-view.tsx 
b/web-console/src/views/lookups-view.tsx
index 02a6d0e..32e6386 100644
--- a/web-console/src/views/lookups-view.tsx
+++ b/web-console/src/views/lookups-view.tsx
@@ -23,12 +23,20 @@ import * as React from 'react';
 import ReactTable from "react-table";
 import { Filter } from "react-table";
 
+import { TableColumnSelection } from "../components/table-column-selection";
 import { LookupEditDialog } from "../dialogs/lookup-edit-dialog";
 import { AppToaster } from "../singletons/toaster";
-import { getDruidErrorMessage, QueryManager } from "../utils";
+import {
+  getDruidErrorMessage,
+  QueryManager,
+  TableColumnSelectionHandler
+} from "../utils";
 
 import "./lookups-view.scss";
 
+const lookupTableColumnSelection = "lookup-table-column-selection";
+const tableColumns: string[] = ["Lookup Name", "Tier", "Type", "Version", 
"Config"];
+
 export interface LookupsViewProps extends React.Props<any> {
 
 }
@@ -49,6 +57,7 @@ export interface LookupsViewState {
 export class LookupsView extends React.Component<LookupsViewProps, 
LookupsViewState> {
   private lookupsGetQueryManager: QueryManager<string, {lookupEntries: any[], 
tiers: string[]}>;
   private lookupDeleteQueryManager: QueryManager<string, any[]>;
+  private tableColumnSelectionHandler: TableColumnSelectionHandler;
 
   constructor(props: LookupsViewProps, context: any) {
     super(props, context);
@@ -64,6 +73,9 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
       isEdit: false,
       allLookupTiers: []
     };
+    this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
+      lookupTableColumnSelection, () => this.setState({})
+    );
   }
 
   componentDidMount(): void {
@@ -205,7 +217,9 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
   }
 
   renderLookupsTable() {
-    const { lookups, loadingLookups, lookupsError} = this.state;
+    const { lookups, loadingLookups, lookupsError } = this.state;
+    const { tableColumnSelectionHandler } = this;
+
     if (lookupsError) {
       return <div className={"init-div"}>
         <Button
@@ -226,25 +240,29 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
             Header: "Lookup Name",
             id: "lookup_name",
             accessor: (row: any) => row.id,
-            filterable: true
+            filterable: true,
+            show: tableColumnSelectionHandler.showColumn("Lookup Name")
           },
           {
             Header: "Tier",
             id: "tier",
             accessor: (row: any) => row.tier,
-            filterable: true
+            filterable: true,
+            show: tableColumnSelectionHandler.showColumn("Tier")
           },
           {
             Header: "Type",
             id: "type",
             accessor: (row: any) => row.spec.type,
-            filterable: true
+            filterable: true,
+            show: tableColumnSelectionHandler.showColumn("Type")
           },
           {
             Header: "Version",
             id: "version",
             accessor: (row: any) => row.version,
-            filterable: true
+            filterable: true,
+            show: tableColumnSelectionHandler.showColumn("Version")
           },
           {
             Header: "Config",
@@ -259,7 +277,8 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
                 &nbsp;&nbsp;&nbsp;
                 <a onClick={() => this.deleteLookup(lookupTier, 
lookupId)}>Delete</a>
               </div>;
-            }
+            },
+            show: tableColumnSelectionHandler.showColumn("Config")
           }
         ]}
         defaultPageSize={50}
@@ -286,6 +305,7 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
   }
 
   render() {
+    const { tableColumnSelectionHandler } = this;
     return <div className="lookups-view app-view">
       <div className="control-bar">
         <div className="control-label">Lookups</div>
@@ -300,6 +320,11 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
           style={{display: this.state.lookupsError !== null ? 'none' : 
'inline'}}
           onClick={() => this.openLookupEditDialog("", "")}
         />
+        <TableColumnSelection
+          columns={tableColumns}
+          onChange={(column) => 
tableColumnSelectionHandler.changeTableColumnSelection(column)}
+          tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderLookupsTable()}
       {this.renderLookupEditDialog()}
diff --git a/web-console/src/views/segments-view.tsx 
b/web-console/src/views/segments-view.tsx
index 8320f05..c0713da 100644
--- a/web-console/src/views/segments-view.tsx
+++ b/web-console/src/views/segments-view.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Button } from "@blueprintjs/core";
+import { Button, Intent } from "@blueprintjs/core";
 import axios from 'axios';
 import * as classNames from 'classnames';
 import * as React from 'react';
@@ -24,6 +24,8 @@ import ReactTable from "react-table";
 import { Filter } from "react-table";
 
 import { H5, IconNames } from "../components/filler";
+import { TableColumnSelection } from "../components/table-column-selection";
+import { AppToaster } from "../singletons/toaster";
 import {
   addFilter,
   formatBytes,
@@ -31,11 +33,15 @@ import {
   makeBooleanFilter,
   parseList,
   queryDruidSql,
-  QueryManager
+  QueryManager, TableColumnSelectionHandler
 } from "../utils";
 
 import "./segments-view.scss";
 
+const segmentTableColumnSelection = "segment-table-column-selection";
+const tableColumns: string[] = ["Segment ID", "Datasource", "Start", "End", 
"Version", "Partition",
+  "Size", "Num rows", "Replicas", "Is published", "Is realtime", "Is 
available"];
+
 export interface SegmentsViewProps extends React.Props<any> {
   goToSql: (initSql: string) => void;
   datasource: string | null;
@@ -56,6 +62,7 @@ interface QueryAndSkip {
 
 export class SegmentsView extends React.Component<SegmentsViewProps, 
SegmentsViewState> {
   private segmentsQueryManager: QueryManager<QueryAndSkip, any[]>;
+  private tableColumnSelectionHandler: TableColumnSelectionHandler;
 
   constructor(props: SegmentsViewProps, context: any) {
     super(props, context);
@@ -91,6 +98,10 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
         });
       }
     });
+
+    this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
+      segmentTableColumnSelection, () => this.setState({})
+    );
   }
 
   componentWillUnmount(): void {
@@ -135,6 +146,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
 
   renderSegmentsTable() {
     const { segments, segmentsLoading, segmentsError, segmentFilter } = 
this.state;
+    const { tableColumnSelectionHandler } = this;
 
     return <ReactTable
       data={segments || []}
@@ -155,7 +167,8 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
         {
           Header: "Segment ID",
           accessor: "segment_id",
-          width: 300
+          width: 300,
+          show: tableColumnSelectionHandler.showColumn("Segment ID")
         },
         {
           Header: "Datasource",
@@ -163,7 +176,8 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           Cell: row => {
             const value = row.value;
             return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'datasource', value) }); }}>{value}</a>;
-          }
+          },
+          show: tableColumnSelectionHandler.showColumn("Datasource")
         },
         {
           Header: "Start",
@@ -173,7 +187,8 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           Cell: row => {
             const value = row.value;
             return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'start', value) }); }}>{value}</a>;
-          }
+          },
+          show: tableColumnSelectionHandler.showColumn("Start")
         },
         {
           Header: "End",
@@ -183,58 +198,67 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           Cell: row => {
             const value = row.value;
             return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'end', value) }); }}>{value}</a>;
-          }
+          },
+          show: tableColumnSelectionHandler.showColumn("End")
         },
         {
           Header: "Version",
           accessor: "version",
           defaultSortDesc: true,
-          width: 120
+          width: 120,
+          show: tableColumnSelectionHandler.showColumn("Version")
         },
         {
           Header: "Partition",
           accessor: "partition_num",
           width: 60,
-          filterable: false
+          filterable: false,
+          show: tableColumnSelectionHandler.showColumn("Partition")
         },
         {
           Header: "Size",
           accessor: "size",
           filterable: false,
           defaultSortDesc: true,
-          Cell: row => formatBytes(row.value)
+          Cell: row => formatBytes(row.value),
+          show: tableColumnSelectionHandler.showColumn("Size")
         },
         {
           Header: "Num rows",
           accessor: "num_rows",
           filterable: false,
           defaultSortDesc: true,
-          Cell: row => formatNumber(row.value)
+          Cell: row => formatNumber(row.value),
+          show: tableColumnSelectionHandler.showColumn("Num rows")
         },
         {
           Header: "Replicas",
           accessor: "num_replicas",
           width: 60,
           filterable: false,
-          defaultSortDesc: true
+          defaultSortDesc: true,
+          show: tableColumnSelectionHandler.showColumn("Replicas")
         },
         {
           Header: "Is published",
           id: "is_published",
           accessor: (row) => String(Boolean(row.is_published)),
-          Filter: makeBooleanFilter()
+          Filter: makeBooleanFilter(),
+          show: tableColumnSelectionHandler.showColumn("Is published")
         },
         {
           Header: "Is realtime",
           id: "is_realtime",
           accessor: (row) => String(Boolean(row.is_realtime)),
-          Filter: makeBooleanFilter()
+          Filter: makeBooleanFilter(),
+          show: tableColumnSelectionHandler.showColumn("Is realtime")
         },
         {
           Header: "Is available",
           id: "is_available",
           accessor: (row) => String(Boolean(row.is_available)),
-          Filter: makeBooleanFilter()
+          Filter: makeBooleanFilter(),
+          show: tableColumnSelectionHandler.showColumn("Is available")
         }
       ]}
       defaultPageSize={50}
@@ -258,6 +282,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
 
   render() {
     const { goToSql } = this.props;
+    const { tableColumnSelectionHandler } = this;
 
     return <div className="segments-view app-view">
       <div className="control-bar">
@@ -272,6 +297,11 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           text="Go to SQL"
           onClick={() => 
goToSql(this.segmentsQueryManager.getLastQuery().query)}
         />
+        <TableColumnSelection
+          columns={tableColumns}
+          onChange={(column) => 
tableColumnSelectionHandler.changeTableColumnSelection(column)}
+          tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderSegmentsTable()}
     </div>;
diff --git a/web-console/src/views/servers-view.tsx 
b/web-console/src/views/servers-view.tsx
index e8598be..5e0f631 100644
--- a/web-console/src/views/servers-view.tsx
+++ b/web-console/src/views/servers-view.tsx
@@ -25,10 +25,22 @@ import ReactTable from "react-table";
 import { Filter } from "react-table";
 
 import { IconNames } from '../components/filler';
-import { addFilter, formatBytes, formatBytesCompact, queryDruidSql, 
QueryManager } from "../utils";
+import { TableColumnSelection } from "../components/table-column-selection";
+import {
+  addFilter,
+  formatBytes,
+  formatBytesCompact,
+  queryDruidSql,
+  QueryManager, TableColumnSelectionHandler
+} from "../utils";
 
 import "./servers-view.scss";
 
+const serverTableColumnSelection = "historical-table-column-selection";
+const middleManagerTableColumnSelection = 
"middleManager-table-column-selection";
+const serverTableColumns: string[] = ["Server", "Tier", "Curr size", "Max 
size", "Usage", "Load/drop queues", "Host", "Port"];
+const middleManagerTableColumns: string[] = ["Host", "Usage", "Availability 
groups", "Last completed task time", "Blacklisted until"];
+
 function formatQueues(segmentsToLoad: number, segmentsToLoadSize: number, 
segmentsToDrop: number, segmentsToDropSize: number): string {
   const queueParts: string[] = [];
   if (segmentsToLoad) {
@@ -62,6 +74,8 @@ export interface ServersViewState {
 export class ServersView extends React.Component<ServersViewProps, 
ServersViewState> {
   private serverQueryManager: QueryManager<string, any[]>;
   private middleManagerQueryManager: QueryManager<string, any[]>;
+  private serverTableColumnSelectionHandler: TableColumnSelectionHandler;
+  private middleManagerTableColumnSelectionHandler: 
TableColumnSelectionHandler;
 
   constructor(props: ServersViewProps, context: any) {
     super(props, context);
@@ -77,6 +91,14 @@ export class ServersView extends 
React.Component<ServersViewProps, ServersViewSt
       middleManagersError: null,
       middleManagerFilter: props.middleManager ? [{ id: 'host', value: 
props.middleManager }] : []
     };
+
+    this.serverTableColumnSelectionHandler = new TableColumnSelectionHandler(
+      serverTableColumnSelection, () => this.setState({})
+    );
+
+    this.middleManagerTableColumnSelectionHandler = new 
TableColumnSelectionHandler(
+      middleManagerTableColumnSelection, () => this.setState({})
+    );
   }
 
   componentDidMount(): void {
@@ -124,6 +146,7 @@ WHERE "server_type" = 'historical'`);
     });
 
     this.middleManagerQueryManager.runQuery('dummy');
+
   }
 
   componentWillUnmount(): void {
@@ -133,6 +156,7 @@ WHERE "server_type" = 'historical'`);
 
   renderServersTable() {
     const { servers, serversLoading, serversError, serverFilter, groupByTier } 
= this.state;
+    const { serverTableColumnSelectionHandler } = this;
 
     const fillIndicator = (value: number) => {
       return <div className="fill-indicator">
@@ -156,7 +180,8 @@ WHERE "server_type" = 'historical'`);
           Header: "Server",
           accessor: "server",
           width: 300,
-          Aggregated: row => ''
+          Aggregated: row => '',
+          show: serverTableColumnSelectionHandler.showColumn("Server")
         },
         {
           Header: "Tier",
@@ -164,7 +189,8 @@ WHERE "server_type" = 'historical'`);
           Cell: row => {
             const value = row.value;
             return <a onClick={() => { this.setState({ serverFilter: 
addFilter(serverFilter, 'tier', value) }); }}>{value}</a>;
-          }
+          },
+          show: serverTableColumnSelectionHandler.showColumn("Tier")
         },
         {
           Header: "Curr size",
@@ -181,7 +207,8 @@ WHERE "server_type" = 'historical'`);
             if (row.aggregated) return '';
             if (row.value === null) return '';
             return formatBytes(row.value);
-          }
+          },
+          show: serverTableColumnSelectionHandler.showColumn("Curr size")
         },
         {
           Header: "Max size",
@@ -198,7 +225,8 @@ WHERE "server_type" = 'historical'`);
             if (row.aggregated) return '';
             if (row.value === null) return '';
             return formatBytes(row.value);
-          }
+          },
+          show: serverTableColumnSelectionHandler.showColumn("Max size")
         },
         {
           Header: "Usage",
@@ -216,7 +244,8 @@ WHERE "server_type" = 'historical'`);
             if (row.aggregated) return '';
             if (row.value === null) return '';
             return fillIndicator(row.value);
-          }
+          },
+          show: serverTableColumnSelectionHandler.showColumn("Usage")
         },
         {
           Header: "Load/drop queues",
@@ -236,12 +265,14 @@ WHERE "server_type" = 'historical'`);
             const segmentsToDrop = sum(originals, s => s.segmentsToDrop);
             const segmentsToDropSize = sum(originals, s => 
s.segmentsToDropSize);
             return formatQueues(segmentsToLoad, segmentsToLoadSize, 
segmentsToDrop, segmentsToDropSize);
-          }
+          },
+          show: serverTableColumnSelectionHandler.showColumn("Load/drop 
queues")
         },
         {
           Header: "Host",
           accessor: "host",
-          Aggregated: () => ''
+          Aggregated: () => '',
+          show: serverTableColumnSelectionHandler.showColumn("Host")
         },
         {
           Header: "Port",
@@ -256,7 +287,8 @@ WHERE "server_type" = 'historical'`);
             }
             return ports.join(', ') || 'No port';
           },
-          Aggregated: () => ''
+          Aggregated: () => '',
+          show: serverTableColumnSelectionHandler.showColumn("Port")
         }
       ]}
       defaultPageSize={10}
@@ -267,6 +299,7 @@ WHERE "server_type" = 'historical'`);
   renderMiddleManagerTable() {
     const { goToTask } = this.props;
     const { middleManagers, middleManagersLoading, middleManagersError, 
middleManagerFilter } = this.state;
+    const { middleManagerTableColumnSelectionHandler } = this;
 
     return <ReactTable
       data={middleManagers || []}
@@ -285,29 +318,34 @@ WHERE "server_type" = 'historical'`);
           Cell: row => {
             const value = row.value;
             return <a onClick={() => { this.setState({ middleManagerFilter: 
addFilter(middleManagerFilter, 'host', value) }); }}>{value}</a>;
-          }
+          },
+          show: middleManagerTableColumnSelectionHandler.showColumn("Host")
         },
         {
           Header: "Usage",
           id: "usage",
           width: 60,
           accessor: (row) => `${row.currCapacityUsed} / 
${row.worker.capacity}`,
-          filterable: false
+          filterable: false,
+          show: middleManagerTableColumnSelectionHandler.showColumn("Usage")
         },
         {
           Header: "Availability groups",
           id: "availabilityGroups",
           width: 60,
           accessor: (row) => row.availabilityGroups.length,
-          filterable: false
+          filterable: false,
+          show: 
middleManagerTableColumnSelectionHandler.showColumn("Availability groups")
         },
         {
           Header: "Last completed task time",
-          accessor: "lastCompletedTaskTime"
+          accessor: "lastCompletedTaskTime",
+          show: middleManagerTableColumnSelectionHandler.showColumn("Last 
completed task time")
         },
         {
           Header: "Blacklisted until",
-          accessor: "blacklistedUntil"
+          accessor: "blacklistedUntil",
+          show: 
middleManagerTableColumnSelectionHandler.showColumn("Blacklisted until")
         }
       ]}
       defaultPageSize={10}
@@ -331,6 +369,7 @@ WHERE "server_type" = 'historical'`);
   render() {
     const { goToSql } = this.props;
     const { groupByTier } = this.state;
+    const { serverTableColumnSelectionHandler, 
middleManagerTableColumnSelectionHandler } = this;
 
     return <div className="servers-view app-view">
       <div className="control-bar">
@@ -350,6 +389,11 @@ WHERE "server_type" = 'historical'`);
           label="Group by tier"
           onChange={() => this.setState({ groupByTier: !groupByTier })}
         />
+        <TableColumnSelection
+          columns={serverTableColumns}
+          onChange={(column) => 
serverTableColumnSelectionHandler.changeTableColumnSelection(column)}
+          tableColumnsHidden={serverTableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderServersTable()}
 
@@ -362,6 +406,11 @@ WHERE "server_type" = 'historical'`);
           text="Refresh"
           onClick={() => this.middleManagerQueryManager.rerunLastQuery()}
         />
+        <TableColumnSelection
+          columns={middleManagerTableColumns}
+          onChange={(column) => 
middleManagerTableColumnSelectionHandler.changeTableColumnSelection(column)}
+          
tableColumnsHidden={middleManagerTableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderMiddleManagerTable()}
     </div>;
diff --git a/web-console/src/views/tasks-view.tsx 
b/web-console/src/views/tasks-view.tsx
index 0816c4c..02fd615 100644
--- a/web-console/src/views/tasks-view.tsx
+++ b/web-console/src/views/tasks-view.tsx
@@ -24,13 +24,26 @@ import ReactTable from "react-table";
 import { Filter } from "react-table";
 
 import { ButtonGroup, IconNames, Label } from "../components/filler";
+import { TableColumnSelection } from "../components/table-column-selection";
 import { AsyncActionDialog } from "../dialogs/async-action-dialog";
 import { SpecDialog } from "../dialogs/spec-dialog";
 import { AppToaster } from '../singletons/toaster';
-import { addFilter, countBy, formatDuration, getDruidErrorMessage, 
queryDruidSql, QueryManager } from "../utils";
+import {
+  addFilter,
+  countBy,
+  formatDuration,
+  getDruidErrorMessage,
+  queryDruidSql,
+  QueryManager, TableColumnSelectionHandler
+} from "../utils";
 
 import "./tasks-view.scss";
 
+const supervisorTableColumnSelection = "supervisor-table-column-selection";
+const taskTableColumnSelection = "task-table-column-selection";
+const supervisorTableColumns: string[] = ["Datasource", "Type", 
"Topic/Stream", "Status", "Actions"];
+const taskTableColumns: string[] = ["Task ID", "Type", "Datasource", "Created 
time", "Status", "Duration", "Actions"];
+
 export interface TasksViewProps extends React.Props<any> {
   taskId: string | null;
   goToSql: (initSql: string) => void;
@@ -74,6 +87,8 @@ function statusToColor(status: string): string {
 export class TasksView extends React.Component<TasksViewProps, TasksViewState> 
{
   private supervisorQueryManager: QueryManager<string, any[]>;
   private taskQueryManager: QueryManager<string, any[]>;
+  private supervisorTableColumnSelectionHandler: TableColumnSelectionHandler;
+  private taskTableColumnSelectionHandler: TableColumnSelectionHandler;
 
   constructor(props: TasksViewProps, context: any) {
     super(props, context);
@@ -98,7 +113,16 @@ export class TasksView extends 
React.Component<TasksViewProps, TasksViewState> {
       supervisorSpecDialogOpen: false,
       taskSpecDialogOpen: false,
       alertErrorMsg: null
+
     };
+
+    this.supervisorTableColumnSelectionHandler = new 
TableColumnSelectionHandler(
+      supervisorTableColumnSelection, () => this.setState({})
+    );
+
+    this.taskTableColumnSelectionHandler = new TableColumnSelectionHandler(
+      taskTableColumnSelection, () => this.setState({})
+    );
   }
 
   componentDidMount(): void {
@@ -147,6 +171,7 @@ export class TasksView extends 
React.Component<TasksViewProps, TasksViewState> {
   "location", "duration", "error_msg"
 FROM sys.tasks
 ORDER BY "rank" DESC, "created_time" DESC`);
+
   }
 
   componentWillUnmount(): void {
@@ -295,6 +320,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
 
   renderSupervisorTable() {
     const { supervisors, supervisorsLoading, supervisorsError } = this.state;
+    const { supervisorTableColumnSelectionHandler } = this;
 
     return <>
       <ReactTable
@@ -307,7 +333,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             Header: "Datasource",
             id: 'datasource',
             accessor: "id",
-            width: 300
+            width: 300,
+            show: 
supervisorTableColumnSelectionHandler.showColumn("Datasource")
           },
           {
             Header: 'Type',
@@ -318,7 +345,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
               const { tuningConfig } = spec;
               if (!tuningConfig) return '';
               return tuningConfig.type;
-            }
+            },
+            show: supervisorTableColumnSelectionHandler.showColumn("Type")
           },
           {
             Header: 'Topic/Stream',
@@ -329,7 +357,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
               const { ioConfig } = spec;
               if (!ioConfig) return '';
               return ioConfig.topic || ioConfig.stream || '';
-            }
+            },
+            show: 
supervisorTableColumnSelectionHandler.showColumn("Topic/Stream")
           },
           {
             Header: "Status",
@@ -345,7 +374,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
                 </span>
                 {value}
               </span>;
-            }
+            },
+            show: supervisorTableColumnSelectionHandler.showColumn("Status")
           },
           {
             Header: 'Actions',
@@ -368,7 +398,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
                 <a onClick={() => this.setState({ resetSupervisorId: id 
})}>Reset</a>&nbsp;&nbsp;&nbsp;
                 <a onClick={() => this.setState({ terminateSupervisorId: id 
})}>Terminate</a>
               </div>;
-            }
+            },
+            show: supervisorTableColumnSelectionHandler.showColumn("Actions")
           }
         ]}
         defaultPageSize={10}
@@ -411,6 +442,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
   renderTaskTable() {
     const { goToMiddleManager } = this.props;
     const { tasks, tasksLoading, tasksError, taskFilter, groupTasksBy } = 
this.state;
+    const { taskTableColumnSelectionHandler } = this;
 
     return <>
       <ReactTable
@@ -429,7 +461,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             Header: "Task ID",
             accessor: "task_id",
             width: 300,
-            Aggregated: row => ''
+            Aggregated: row => '',
+            show: taskTableColumnSelectionHandler.showColumn("Task ID")
           },
           {
             Header: "Type",
@@ -437,7 +470,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             Cell: row => {
               const value = row.value;
               return <a onClick={() => { this.setState({ taskFilter: 
addFilter(taskFilter, 'type', value) }); }}>{value}</a>;
-            }
+            },
+            show: taskTableColumnSelectionHandler.showColumn("Type")
           },
           {
             Header: "Datasource",
@@ -445,13 +479,15 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             Cell: row => {
               const value = row.value;
               return <a onClick={() => { this.setState({ taskFilter: 
addFilter(taskFilter, 'datasource', value) }); }}>{value}</a>;
-            }
+            },
+            show: taskTableColumnSelectionHandler.showColumn("Datasource")
           },
           {
             Header: "Created time",
             accessor: "created_time",
             width: 120,
-            Aggregated: row => ''
+            Aggregated: row => '',
+            show: taskTableColumnSelectionHandler.showColumn("Created time")
           },
           {
             Header: "Status",
@@ -484,14 +520,16 @@ ORDER BY "rank" DESC, "created_time" DESC`);
               const previewValues = subRows.filter((d: any) => typeof 
d[column.id] !== 'undefined').map((row: any) => row._original[column.id]);
               const previewCount = countBy(previewValues);
               return <span>{Object.keys(previewCount).sort().map(v => `${v} 
(${previewCount[v]})`).join(', ')}</span>;
-            }
+            },
+            show: taskTableColumnSelectionHandler.showColumn("Status")
           },
           {
             Header: "Duration",
             accessor: "duration",
             filterable: false,
             Cell: (row) => row.value > 0 ? formatDuration(row.value) : '',
-            Aggregated: () => ''
+            Aggregated: () => '',
+            show: taskTableColumnSelectionHandler.showColumn("Duration")
           },
           {
             Header: 'Actions',
@@ -512,7 +550,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
                 {(status === 'RUNNING' || status === 'WAITING' || status === 
'PENDING') && <a onClick={() => this.setState({ killTaskId: id })}>Kill</a>}
               </div>;
             },
-            Aggregated: row => ''
+            Aggregated: row => '',
+            show: taskTableColumnSelectionHandler.showColumn("Actions")
           }
         ]}
         defaultPageSize={20}
@@ -525,6 +564,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
   render() {
     const { goToSql } = this.props;
     const { groupTasksBy, supervisorSpecDialogOpen, taskSpecDialogOpen, 
alertErrorMsg } = this.state;
+    const { supervisorTableColumnSelectionHandler, 
taskTableColumnSelectionHandler } = this;
 
     return <div className="tasks-view app-view">
       <div className="control-bar">
@@ -539,6 +579,11 @@ ORDER BY "rank" DESC, "created_time" DESC`);
           text="Submit supervisor"
           onClick={() => this.setState({ supervisorSpecDialogOpen: true })}
         />
+        <TableColumnSelection
+          columns={supervisorTableColumns}
+          onChange={(column) => 
supervisorTableColumnSelectionHandler.changeTableColumnSelection(column)}
+          
tableColumnsHidden={supervisorTableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderSupervisorTable()}
 
@@ -568,6 +613,11 @@ ORDER BY "rank" DESC, "created_time" DESC`);
           text="Submit task"
           onClick={() => this.setState({ taskSpecDialogOpen: true })}
         />
+        <TableColumnSelection
+          columns={taskTableColumns}
+          onChange={(column) => 
taskTableColumnSelectionHandler.changeTableColumnSelection(column)}
+          tableColumnsHidden={taskTableColumnSelectionHandler.hiddenColumns}
+        />
       </div>
       {this.renderTaskTable()}
       { supervisorSpecDialogOpen ? <SpecDialog


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@druid.apache.org
For additional commands, e-mail: commits-h...@druid.apache.org

Reply via email to