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} <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-unsubscr...@druid.apache.org For additional commands, e-mail: commits-h...@druid.apache.org