You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by cw...@apache.org on 2019/03/20 20:11:07 UTC
[incubator-druid] branch master updated: Add table column selection
in druid console to allow hiding/showing of columns (#7292)
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 <sh...@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}
<a>✎</a>
</span>;
- }
+ },
+ show: tableColumnSelectionHandler.showColumn("Retention")
},
{
Header: 'Compaction',
@@ -449,21 +465,24 @@ GROUP BY 1`);
{text}
<a>✎</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
<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>
<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-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org