mirror of https://github.com/apache/druid.git
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
This commit is contained in:
parent
2814729d32
commit
30e646308a
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.table-column-selection {
|
||||
|
||||
float: right;
|
||||
|
||||
.pt-popover-content {
|
||||
padding: 10px 10px 1px 10px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
|
|
|
@ -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()}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue