Web-console: Add action column to segments view (#7954)

* add actions column to segments view

* add sements action column
This commit is contained in:
mcbrewster 2019-06-25 20:14:06 -07:00 committed by Fangjin Yang
parent 9d925f5086
commit a171b4a399
13 changed files with 935 additions and 517 deletions

View File

@ -0,0 +1,171 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`task table action dialog matches snapshot 1`] = `
<div
class="bp3-portal"
>
<div
class="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
>
<div
class="bp3-dialog table-action-dialog"
>
<div
class="bp3-dialog-header"
>
<h4
class="bp3-heading"
>
Segment: test
</h4>
<button
aria-label="Close"
class="bp3-button bp3-minimal bp3-dialog-close-button"
type="button"
>
<span
class="bp3-icon bp3-icon-small-cross"
icon="small-cross"
>
<svg
data-icon="small-cross"
height="20"
viewBox="0 0 20 20"
width="20"
>
<desc>
small-cross
</desc>
<path
d="M11.41 10l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L10 8.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L8.59 10 5.3 13.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71l3.29-3.3 3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L11.41 10z"
fill-rule="evenodd"
/>
</svg>
</span>
</button>
</div>
<div
class="bp3-dialog-body"
>
<div
class="side-bar"
>
<button
class="bp3-button bp3-intent-primary tab-button"
type="button"
>
<span
class="bp3-icon bp3-icon-manually-entered-data"
icon="manually-entered-data"
>
<svg
data-icon="manually-entered-data"
height="20"
viewBox="0 0 20 20"
width="20"
>
<desc>
manually-entered-data
</desc>
<path
d="M1 12h4.34l2-2H1c-.55 0-1 .45-1 1s.45 1 1 1zm16.77-3.94l1.65-1.65c.36-.36.58-.86.58-1.41 0-1.1-.9-2-2-2-.55 0-1.05.22-1.41.59l-1.65 1.65 2.83 2.82zM1 4h12.34l2-2H1c-.55 0-1 .45-1 1s.45 1 1 1zM0 15c0 .55.45 1 1 1h.34l2-2H1c-.55 0-1 .45-1 1zm1-7h8.34l2-2H1c-.55 0-1 .45-1 1s.45 1 1 1zm18 2h-.34l-2 2H19c.55 0 1-.45 1-1s-.45-1-1-1zm0 4h-4.34l-2 2H19c.55 0 1-.45 1-1s-.45-1-1-1zM4 19l4.41-1.59-2.81-2.79L4 19zM14.23 5.94l-7.65 7.65 2.83 2.83 7.65-7.65-2.83-2.83z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
Metadata
</span>
</button>
</div>
<div
class="main-section"
>
<div
class="show-json"
>
<div
class="top-actions"
>
<div
class="bp3-button-group right-buttons"
>
<button
class="bp3-button bp3-minimal"
type="button"
>
<span
class="bp3-button-text"
>
Save
</span>
</button>
<button
class="bp3-button bp3-minimal"
type="button"
>
<span
class="bp3-button-text"
>
Copy
</span>
</button>
<button
class="bp3-button bp3-minimal"
type="button"
>
<span
class="bp3-button-text"
>
View raw
</span>
</button>
</div>
</div>
<div
class="main-area"
>
<textarea
class="bp3-input"
readonly=""
/>
</div>
</div>
</div>
</div>
<div
class="bp3-dialog-footer"
>
<div
class="footer-actions-left"
/>
<div
class="bp3-dialog-footer-actions"
>
<button
class="bp3-button bp3-intent-primary"
type="button"
>
<span
class="bp3-button-text"
>
Close
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,39 @@
/*
* 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 React from 'react';
import { render } from 'react-testing-library';
import { SegmentTableActionDialog } from './segment-table-action-dialog';
const basicAction = { title: 'test', onAction: () => null };
describe('task table action dialog', () => {
it('matches snapshot', () => {
const taskTableActionDialog = (
<SegmentTableActionDialog
dataSourceId="test"
segmentId="test"
actions={[]}
onClose={() => null}
isOpen
/>
);
const { container } = render(taskTableActionDialog, { container: document.body });
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@ -0,0 +1,78 @@
/*
* 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 { IDialogProps, TextArea } from '@blueprintjs/core';
import React from 'react';
import { ShowJson } from '../../components';
import { BasicAction, basicActionsToButtons } from '../../utils/basic-action';
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
interface SegmentTableActionDialogProps extends IDialogProps {
segmentId: string | null;
dataSourceId: string | null;
actions: BasicAction[];
onClose: () => void;
}
interface SegmentTableActionDialogState {
activeTab: 'metadata';
}
export class SegmentTableActionDialog extends React.PureComponent<
SegmentTableActionDialogProps,
SegmentTableActionDialogState
> {
constructor(props: SegmentTableActionDialogProps) {
super(props);
this.state = {
activeTab: 'metadata',
};
}
render(): React.ReactNode {
const { segmentId, onClose, dataSourceId, actions } = this.props;
const { activeTab } = this.state;
const taskTableSideButtonMetadata: SideButtonMetaData[] = [
{
icon: 'manually-entered-data',
text: 'Metadata',
active: activeTab === 'metadata',
onClick: () => this.setState({ activeTab: 'metadata' }),
},
];
return (
<TableActionDialog
isOpen
sideButtonMetadata={taskTableSideButtonMetadata}
onClose={onClose}
title={`Segment: ${segmentId}`}
bottomButtons={basicActionsToButtons(actions)}
>
{activeTab === 'metadata' && (
<ShowJson
endpoint={`/druid/coordinator/v1/metadata/datasources/${dataSourceId}/segments/${segmentId}`}
downloadFilename={`Segment-metadata-${segmentId}.json`}
/>
)}
</TableActionDialog>
);
}
}

View File

@ -21,5 +21,4 @@ export * from './druid-query';
export * from './query-manager';
export * from './query-state';
export * from './rune-decoder';
export * from './table-column-selection-handler';
export * from './local-storage-keys';

View File

@ -0,0 +1,65 @@
/*
* 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, LocalStorageKeys, localStorageSet } from '../utils';
export class LocalStorageBackedArray<T> {
key: LocalStorageKeys;
storedArray: T[];
constructor(key: LocalStorageKeys, array?: T[]) {
this.key = key;
if (!Array.isArray(array)) {
this.getDataFromStorage();
} else {
this.storedArray = array;
this.setDataInStorage();
}
}
private getDataFromStorage(): void {
let possibleArray: any;
try {
possibleArray = JSON.parse(String(localStorageGet(this.key)));
} catch {
// show all columns by default
possibleArray = [];
}
if (!Array.isArray(possibleArray)) possibleArray = [];
this.storedArray = possibleArray;
}
private setDataInStorage(): void {
localStorageSet(this.key, JSON.stringify(this.storedArray));
}
toggle(value: T): LocalStorageBackedArray<T> {
let toggledArray;
if (this.storedArray.includes(value)) {
toggledArray = this.storedArray.filter(c => c !== value);
} else {
toggledArray = this.storedArray.concat(value);
}
return new LocalStorageBackedArray<T>(this.key, toggledArray);
}
exists(value: T): boolean {
return !this.storedArray.includes(value);
}
}

View File

@ -1,61 +0,0 @@
/*
* 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, LocalStorageKeys, localStorageSet } from '../utils';
export class TableColumnSelectionHandler {
tableName: LocalStorageKeys;
hiddenColumns: string[];
updateComponent: () => void;
constructor(tableName: LocalStorageKeys, 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 = [];
}
}
changeTableColumnSelector(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);
}
}

View File

@ -52,9 +52,9 @@ import {
pluralIfNeeded,
queryDruidSql,
QueryManager,
TableColumnSelectionHandler,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
import './datasource-view.scss';
@ -113,6 +113,7 @@ export interface DatasourcesViewState {
dropReloadDatasource: string | null;
dropReloadAction: 'drop' | 'reload';
dropReloadInterval: string;
hiddenColumns: LocalStorageBackedArray<string>;
}
export class DatasourcesView extends React.PureComponent<
@ -137,7 +138,6 @@ export class DatasourcesView extends React.PureComponent<
string,
{ tiers: string[]; defaultRules: any[]; datasources: Datasource[] }
>;
private tableColumnSelectionHandler: TableColumnSelectionHandler;
constructor(props: DatasourcesViewProps, context: any) {
super(props, context);
@ -158,16 +158,15 @@ export class DatasourcesView extends React.PureComponent<
dropReloadDatasource: null,
dropReloadAction: 'drop',
dropReloadInterval: '',
};
this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
hiddenColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
() => this.setState({}),
);
),
};
}
componentDidMount(): void {
const { noSqlMode } = this.props;
const { hiddenColumns } = this.state;
this.datasourceQueryManager = new QueryManager({
processQuery: async (query: string) => {
@ -564,8 +563,8 @@ GROUP BY 1`);
datasourcesError,
datasourcesFilter,
showDisabled,
hiddenColumns,
} = this.state;
const { tableColumnSelectionHandler } = this;
let data = datasources || [];
if (!showDisabled) {
data = data.filter(d => !d.disabled);
@ -604,7 +603,7 @@ GROUP BY 1`);
</a>
);
},
show: tableColumnSelectionHandler.showColumn('Datasource'),
show: hiddenColumns.exists('Datasource'),
},
{
Header: 'Availability',
@ -668,7 +667,7 @@ GROUP BY 1`);
const percentAvailable2 = d2.num_available / d2.num_total;
return percentAvailable1 - percentAvailable2 || d1.num_total - d2.num_total;
},
show: tableColumnSelectionHandler.showColumn('Availability'),
show: hiddenColumns.exists('Availability'),
},
{
Header: 'Retention',
@ -701,7 +700,7 @@ GROUP BY 1`);
</span>
);
},
show: tableColumnSelectionHandler.showColumn('Retention'),
show: hiddenColumns.exists('Retention'),
},
{
Header: 'Compaction',
@ -730,7 +729,7 @@ GROUP BY 1`);
</span>
);
},
show: tableColumnSelectionHandler.showColumn('Compaction'),
show: hiddenColumns.exists('Compaction'),
},
{
Header: 'Size',
@ -738,7 +737,7 @@ GROUP BY 1`);
filterable: false,
width: 100,
Cell: row => formatBytes(row.value),
show: tableColumnSelectionHandler.showColumn('Size'),
show: hiddenColumns.exists('Size'),
},
{
Header: 'Num rows',
@ -746,7 +745,7 @@ GROUP BY 1`);
filterable: false,
width: 100,
Cell: row => formatNumber(row.value),
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Num rows'),
show: !noSqlMode && hiddenColumns.exists('Num rows'),
},
{
Header: ActionCell.COLUMN_LABEL,
@ -760,7 +759,7 @@ GROUP BY 1`);
const datasourceActions = this.getDatasourceActions(datasource, disabled);
return <ActionCell actions={datasourceActions} />;
},
show: tableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL),
show: hiddenColumns.exists(ActionCell.COLUMN_LABEL),
},
]}
defaultPageSize={50}
@ -777,8 +776,7 @@ GROUP BY 1`);
render() {
const { goToQuery, noSqlMode } = this.props;
const { showDisabled } = this.state;
const { tableColumnSelectionHandler } = this;
const { showDisabled, hiddenColumns } = this.state;
return (
<div className="data-sources-view app-view">
@ -801,8 +799,8 @@ GROUP BY 1`);
/>
<TableColumnSelector
columns={noSqlMode ? tableColumnsNoSql : tableColumns}
onChange={column => tableColumnSelectionHandler.changeTableColumnSelector(column)}
tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
onChange={column => this.setState({ hiddenColumns: hiddenColumns.toggle(column) })}
tableColumnsHidden={hiddenColumns.storedArray}
/>
</ViewControlBar>
{this.renderDatasourceTable()}

View File

@ -26,13 +26,9 @@ import ReactTable from 'react-table';
import { ActionCell, TableColumnSelector, ViewControlBar } from '../../components';
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
import { AppToaster } from '../../singletons/toaster';
import {
getDruidErrorMessage,
LocalStorageKeys,
QueryManager,
TableColumnSelectionHandler,
} from '../../utils';
import { getDruidErrorMessage, LocalStorageKeys, QueryManager } from '../../utils';
import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
import './lookups-view.scss';
@ -57,11 +53,12 @@ export interface LookupsViewState {
deleteLookupName: string | null;
deleteLookupTier: string | null;
hiddenColumns: LocalStorageBackedArray<string>;
}
export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsViewState> {
private lookupsGetQueryManager: QueryManager<string, { lookupEntries: any[]; tiers: string[] }>;
private tableColumnSelectionHandler: TableColumnSelectionHandler;
constructor(props: LookupsViewProps, context: any) {
super(props, context);
@ -80,11 +77,11 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
deleteLookupTier: null,
deleteLookupName: null,
};
this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
hiddenColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.LOOKUP_TABLE_COLUMN_SELECTION,
() => this.setState({}),
);
),
};
}
componentDidMount(): void {
@ -266,8 +263,13 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
}
renderLookupsTable() {
const { lookups, loadingLookups, lookupsError, lookupsUninitialized } = this.state;
const { tableColumnSelectionHandler } = this;
const {
lookups,
loadingLookups,
lookupsError,
lookupsUninitialized,
hiddenColumns,
} = this.state;
if (lookupsUninitialized) {
return (
@ -296,28 +298,28 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
id: 'lookup_name',
accessor: (row: any) => row.id,
filterable: true,
show: tableColumnSelectionHandler.showColumn('Lookup name'),
show: hiddenColumns.exists('Lookup name'),
},
{
Header: 'Tier',
id: 'tier',
accessor: (row: any) => row.tier,
filterable: true,
show: tableColumnSelectionHandler.showColumn('Tier'),
show: hiddenColumns.exists('Tier'),
},
{
Header: 'Type',
id: 'type',
accessor: (row: any) => row.spec.type,
filterable: true,
show: tableColumnSelectionHandler.showColumn('Type'),
show: hiddenColumns.exists('Type'),
},
{
Header: 'Version',
id: 'version',
accessor: (row: any) => row.version,
filterable: true,
show: tableColumnSelectionHandler.showColumn('Version'),
show: hiddenColumns.exists('Version'),
},
{
Header: ActionCell.COLUMN_LABEL,
@ -331,7 +333,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
const lookupActions = this.getLookupActions(lookupTier, lookupId);
return <ActionCell actions={lookupActions} />;
},
show: tableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL),
show: hiddenColumns.exists(ActionCell.COLUMN_LABEL),
},
]}
defaultPageSize={50}
@ -368,8 +370,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
}
render() {
const { lookupsError } = this.state;
const { tableColumnSelectionHandler } = this;
const { lookupsError, hiddenColumns } = this.state;
return (
<div className="lookups-view app-view">
@ -388,8 +389,8 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
)}
<TableColumnSelector
columns={tableColumns}
onChange={column => tableColumnSelectionHandler.changeTableColumnSelector(column)}
tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
onChange={column => this.setState({ hiddenColumns: hiddenColumns.toggle(column) })}
tableColumnsHidden={hiddenColumns.storedArray}
/>
</ViewControlBar>
{this.renderLookupsTable()}

View File

@ -1,6 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`segments-view matches snapshot 1`] = `
<Fragment>
<div
className="segments-view app-view"
>
@ -33,6 +34,7 @@ exports[`segments-view matches snapshot 1`] = `
"Is realtime",
"Is available",
"Is overshadowed",
"Actions",
]
}
onChange={[Function]}
@ -49,7 +51,6 @@ exports[`segments-view matches snapshot 1`] = `
PaginationComponent={[Function]}
PivotValueComponent={[Function]}
ResizerComponent={[Function]}
SubComponent={[Function]}
TableComponent={[Function]}
TbodyComponent={[Function]}
TdComponent={[Function]}
@ -191,6 +192,16 @@ exports[`segments-view matches snapshot 1`] = `
"id": "is_overshadowed",
"show": true,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Actions",
"accessor": "segment_id",
"filterable": false,
"id": "actions",
"show": true,
"width": 70,
},
]
}
data={Array []}
@ -294,4 +305,20 @@ exports[`segments-view matches snapshot 1`] = `
subRowsKey="_subRows"
/>
</div>
<AsyncActionDialog
action={null}
confirmButtonText="Drop Segment"
failText="Could not drop segment"
intent="danger"
onClose={[Function]}
successText="Segment drop request acknowledged, next time the coordinator runs segment will be dropped"
>
<p>
Are you sure you want to drop segment 'null'?
</p>
<p>
This action is not reversible.
</p>
</AsyncActionDialog>
</Fragment>
`;

View File

@ -17,14 +17,15 @@
*/
import { Button, Intent } from '@blueprintjs/core';
import { H5 } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import axios from 'axios';
import React from 'react';
import ReactTable from 'react-table';
import { Filter } from 'react-table';
import { RefreshButton, TableColumnSelector, ViewControlBar } from '../../components';
import { ActionCell, RefreshButton, TableColumnSelector, ViewControlBar } from '../../components';
import { AsyncActionDialog } from '../../dialogs';
import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-dialog/segment-table-action-dialog';
import {
addFilter,
formatBytes,
@ -35,8 +36,9 @@ import {
queryDruidSql,
QueryManager,
sqlQueryCustomTableFilter,
TableColumnSelectionHandler,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
import './segments-view.scss';
@ -54,6 +56,7 @@ const tableColumns: string[] = [
'Is realtime',
'Is available',
'Is overshadowed',
ActionCell.COLUMN_LABEL,
];
const tableColumnsNoSql: string[] = [
'Segment ID',
@ -78,6 +81,12 @@ export interface SegmentsViewState {
segmentsError: string | null;
segmentFilter: Filter[];
allSegments?: SegmentQueryResultRow[] | null;
segmentTableActionDialogId: string | null;
datasourceTableActionDialogId: string | null;
actions: BasicAction[];
terminateSegmentId: string | null;
terminateDatasourceId: string | null;
hiddenColumns: LocalStorageBackedArray<string>;
}
interface QueryAndSkip {
@ -105,7 +114,6 @@ interface SegmentQueryResultRow {
export class SegmentsView extends React.PureComponent<SegmentsViewProps, SegmentsViewState> {
private segmentsSqlQueryManager: QueryManager<QueryAndSkip, SegmentQueryResultRow[]>;
private segmentsJsonQueryManager: QueryManager<any, SegmentQueryResultRow[]>;
private tableColumnSelectionHandler: TableColumnSelectionHandler;
constructor(props: SegmentsViewProps, context: any) {
super(props, context);
@ -115,10 +123,18 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
if (props.onlyUnavailable) segmentFilter.push({ id: 'is_available', value: 'false' });
this.state = {
segmentTableActionDialogId: null,
datasourceTableActionDialogId: null,
actions: [],
terminateSegmentId: null,
terminateDatasourceId: null,
segmentsLoading: true,
segments: null,
segmentsError: null,
segmentFilter,
hiddenColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.SEGMENT_TABLE_COLUMN_SELECTION,
),
};
this.segmentsSqlQueryManager = new QueryManager({
@ -185,11 +201,6 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
});
},
});
this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
LocalStorageKeys.SEGMENT_TABLE_COLUMN_SELECTION,
() => this.setState({}),
);
}
componentDidMount(): void {
@ -274,10 +285,20 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
});
};
private getSegmentActions(id: string, datasource: string): BasicAction[] {
const actions: BasicAction[] = [];
actions.push({
icon: IconNames.IMPORT,
title: 'Drop segment (disable)',
intent: Intent.DANGER,
onAction: () => this.setState({ terminateSegmentId: id, terminateDatasourceId: datasource }),
});
return actions;
}
renderSegmentsTable() {
const { segments, segmentsLoading, segmentsError, segmentFilter } = this.state;
const { segments, segmentsLoading, segmentsError, segmentFilter, hiddenColumns } = this.state;
const { noSqlMode } = this.props;
const { tableColumnSelectionHandler } = this;
return (
<ReactTable
@ -302,7 +323,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
Header: 'Segment ID',
accessor: 'segment_id',
width: 300,
show: tableColumnSelectionHandler.showColumn('Segment ID'),
show: hiddenColumns.exists('Segment ID'),
},
{
Header: 'Datasource',
@ -319,7 +340,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
</a>
);
},
show: tableColumnSelectionHandler.showColumn('Datasource'),
show: hiddenColumns.exists('Datasource'),
},
{
Header: 'Start',
@ -338,7 +359,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
</a>
);
},
show: tableColumnSelectionHandler.showColumn('Start'),
show: hiddenColumns.exists('Start'),
},
{
Header: 'End',
@ -357,21 +378,21 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
</a>
);
},
show: tableColumnSelectionHandler.showColumn('End'),
show: hiddenColumns.exists('End'),
},
{
Header: 'Version',
accessor: 'version',
defaultSortDesc: true,
width: 120,
show: tableColumnSelectionHandler.showColumn('Version'),
show: hiddenColumns.exists('Version'),
},
{
Header: 'Partition',
accessor: 'partition_num',
width: 60,
filterable: false,
show: tableColumnSelectionHandler.showColumn('Partition'),
show: hiddenColumns.exists('Partition'),
},
{
Header: 'Size',
@ -379,7 +400,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
filterable: false,
defaultSortDesc: true,
Cell: row => formatBytes(row.value),
show: tableColumnSelectionHandler.showColumn('Size'),
show: hiddenColumns.exists('Size'),
},
{
Header: 'Num rows',
@ -387,7 +408,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
filterable: false,
defaultSortDesc: true,
Cell: row => formatNumber(row.value),
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Num rows'),
show: !noSqlMode && hiddenColumns.exists('Num rows'),
},
{
Header: 'Replicas',
@ -395,63 +416,115 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
width: 60,
filterable: false,
defaultSortDesc: true,
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Replicas'),
show: !noSqlMode && hiddenColumns.exists('Replicas'),
},
{
Header: 'Is published',
id: 'is_published',
accessor: row => String(Boolean(row.is_published)),
Filter: makeBooleanFilter(),
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Is published'),
show: !noSqlMode && hiddenColumns.exists('Is published'),
},
{
Header: 'Is realtime',
id: 'is_realtime',
accessor: row => String(Boolean(row.is_realtime)),
Filter: makeBooleanFilter(),
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Is realtime'),
show: !noSqlMode && hiddenColumns.exists('Is realtime'),
},
{
Header: 'Is available',
id: 'is_available',
accessor: row => String(Boolean(row.is_available)),
Filter: makeBooleanFilter(),
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Is available'),
show: !noSqlMode && hiddenColumns.exists('Is available'),
},
{
Header: 'Is overshadowed',
id: 'is_overshadowed',
accessor: row => String(Boolean(row.is_overshadowed)),
Filter: makeBooleanFilter(),
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Is overshadowed'),
show: !noSqlMode && hiddenColumns.exists('Is overshadowed'),
},
{
Header: ActionCell.COLUMN_LABEL,
id: ActionCell.COLUMN_ID,
accessor: 'segment_id',
width: ActionCell.COLUMN_WIDTH,
filterable: false,
Cell: row => {
if (row.aggregated) return '';
const id = row.value;
const datasource = row.row.datasource;
const dimensions = parseList(row.original.payload.dimensions);
const metrics = parseList(row.original.payload.metrics);
return (
<ActionCell
onDetail={() => {
this.setState({
segmentTableActionDialogId: id,
datasourceTableActionDialogId: datasource,
actions: this.getSegmentActions(id, datasource),
});
}}
actions={this.getSegmentActions(id, datasource)}
/>
);
},
Aggregated: row => '',
show: hiddenColumns.exists(ActionCell.COLUMN_LABEL),
},
]}
defaultPageSize={50}
SubComponent={rowInfo => {
const { original } = rowInfo;
const { payload } = rowInfo.original;
const dimensions = parseList(payload.dimensions);
const metrics = parseList(payload.metrics);
return (
<div className="segment-detail">
<H5>Segment ID</H5>
<p>{original.segment_id}</p>
<H5>{`Dimensions (${dimensions.length})`}</H5>
<p>{dimensions.join(', ') || 'No dimension'}</p>
<H5>{`Metrics (${metrics.length})`}</H5>
<p>{metrics.join(', ') || 'No metrics'}</p>
</div>
);
}}
/>
);
}
render() {
const { goToQuery, noSqlMode } = this.props;
const { tableColumnSelectionHandler } = this;
renderTerminateSegmentAction() {
const { terminateSegmentId, terminateDatasourceId } = this.state;
return (
<AsyncActionDialog
action={
terminateSegmentId
? async () => {
const resp = await axios.delete(
`/druid/coordinator/v1/datasources/${terminateDatasourceId}/segments/${terminateSegmentId}`,
{},
);
return resp.data;
}
: null
}
confirmButtonText="Drop Segment"
successText="Segment drop request acknowledged, next time the coordinator runs segment will be dropped"
failText="Could not drop segment"
intent={Intent.DANGER}
onClose={success => {
this.setState({ terminateSegmentId: null });
if (success) {
this.segmentsJsonQueryManager.rerunLastQuery();
this.segmentsSqlQueryManager.rerunLastQuery();
}
}}
>
<p>{`Are you sure you want to drop segment '${terminateSegmentId}'?`}</p>
<p>This action is not reversible.</p>
</AsyncActionDialog>
);
}
render() {
const {
segmentTableActionDialogId,
datasourceTableActionDialogId,
actions,
hiddenColumns,
} = this.state;
const { goToQuery, noSqlMode } = this.props;
return (
<>
<div className="segments-view app-view">
<ViewControlBar label="Segments">
<RefreshButton
@ -472,12 +545,23 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
)}
<TableColumnSelector
columns={noSqlMode ? tableColumnsNoSql : tableColumns}
onChange={column => tableColumnSelectionHandler.changeTableColumnSelector(column)}
tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
onChange={column => this.setState({ hiddenColumns: hiddenColumns.toggle(column) })}
tableColumnsHidden={hiddenColumns.storedArray}
/>
</ViewControlBar>
{this.renderSegmentsTable()}
</div>
{this.renderTerminateSegmentAction()}
{segmentTableActionDialogId && (
<SegmentTableActionDialog
segmentId={segmentTableActionDialogId}
dataSourceId={datasourceTableActionDialogId}
actions={actions}
onClose={() => this.setState({ segmentTableActionDialogId: null })}
isOpen
/>
)}
</>
);
}
}

View File

@ -34,9 +34,9 @@ import {
lookupBy,
queryDruidSql,
QueryManager,
TableColumnSelectionHandler,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
import { deepGet } from '../../utils/object-change';
import './servers-view.scss';
@ -90,6 +90,8 @@ export interface ServersViewState {
middleManagerDisableWorkerHost: string | null;
middleManagerEnableWorkerHost: string | null;
hiddenColumns: LocalStorageBackedArray<string>;
}
interface ServerQueryResultRow {
@ -132,7 +134,6 @@ interface ServerResultRow
export class ServersView extends React.PureComponent<ServersViewProps, ServersViewState> {
private serverQueryManager: QueryManager<string, ServerQueryResultRow[]>;
private serverTableColumnSelectionHandler: TableColumnSelectionHandler;
constructor(props: ServersViewProps, context: any) {
super(props, context);
@ -145,12 +146,11 @@ export class ServersView extends React.PureComponent<ServersViewProps, ServersVi
middleManagerDisableWorkerHost: null,
middleManagerEnableWorkerHost: null,
};
this.serverTableColumnSelectionHandler = new TableColumnSelectionHandler(
hiddenColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.SERVER_TABLE_COLUMN_SELECTION,
() => this.setState({}),
);
),
};
}
static async getServers(): Promise<ServerQueryResultRow[]> {
@ -172,6 +172,8 @@ export class ServersView extends React.PureComponent<ServersViewProps, ServersVi
componentDidMount(): void {
const { noSqlMode } = this.props;
const { hiddenColumns } = this.state;
this.serverQueryManager = new QueryManager({
processQuery: async (query: string) => {
let servers: ServerQueryResultRow[];
@ -260,8 +262,14 @@ ORDER BY "rank" DESC, "server" DESC`);
}
renderServersTable() {
const { servers, serversLoading, serversError, serverFilter, groupServersBy } = this.state;
const { serverTableColumnSelectionHandler } = this;
const {
servers,
serversLoading,
serversError,
serverFilter,
groupServersBy,
hiddenColumns,
} = this.state;
const fillIndicator = (value: number) => {
let formattedValue = (value * 100).toFixed(1);
@ -294,7 +302,7 @@ ORDER BY "rank" DESC, "server" DESC`);
accessor: 'server',
width: 300,
Aggregated: row => '',
show: serverTableColumnSelectionHandler.showColumn('Server'),
show: hiddenColumns.exists('Server'),
},
{
Header: 'Type',
@ -312,7 +320,7 @@ ORDER BY "rank" DESC, "server" DESC`);
</a>
);
},
show: serverTableColumnSelectionHandler.showColumn('Type'),
show: hiddenColumns.exists('Type'),
},
{
Header: 'Tier',
@ -329,13 +337,13 @@ ORDER BY "rank" DESC, "server" DESC`);
</a>
);
},
show: serverTableColumnSelectionHandler.showColumn('Tier'),
show: hiddenColumns.exists('Tier'),
},
{
Header: 'Host',
accessor: 'host',
Aggregated: () => '',
show: serverTableColumnSelectionHandler.showColumn('Host'),
show: hiddenColumns.exists('Host'),
},
{
Header: 'Port',
@ -351,7 +359,7 @@ ORDER BY "rank" DESC, "server" DESC`);
return ports.join(', ') || 'No port';
},
Aggregated: () => '',
show: serverTableColumnSelectionHandler.showColumn('Port'),
show: hiddenColumns.exists('Port'),
},
{
Header: 'Curr size',
@ -370,7 +378,7 @@ ORDER BY "rank" DESC, "server" DESC`);
if (row.value === null) return '';
return formatBytes(row.value);
},
show: serverTableColumnSelectionHandler.showColumn('Curr size'),
show: hiddenColumns.exists('Curr size'),
},
{
Header: 'Max size',
@ -389,7 +397,7 @@ ORDER BY "rank" DESC, "server" DESC`);
if (row.value === null) return '';
return formatBytes(row.value);
},
show: serverTableColumnSelectionHandler.showColumn('Max size'),
show: hiddenColumns.exists('Max size'),
},
{
Header: 'Usage',
@ -447,7 +455,7 @@ ORDER BY "rank" DESC, "server" DESC`);
return '';
}
},
show: serverTableColumnSelectionHandler.showColumn('Usage'),
show: hiddenColumns.exists('Usage'),
},
{
Header: 'Detail',
@ -509,7 +517,7 @@ ORDER BY "rank" DESC, "server" DESC`);
segmentsToDropSize,
);
},
show: serverTableColumnSelectionHandler.showColumn('Detail'),
show: hiddenColumns.exists('Detail'),
},
{
Header: ActionCell.COLUMN_LABEL,
@ -523,7 +531,7 @@ ORDER BY "rank" DESC, "server" DESC`);
const workerActions = this.getWorkerActions(row.value.host, disabled);
return <ActionCell actions={workerActions} />;
},
show: serverTableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL),
show: hiddenColumns.exists(ActionCell.COLUMN_LABEL),
},
]}
/>
@ -612,8 +620,7 @@ ORDER BY "rank" DESC, "server" DESC`);
render() {
const { goToQuery, noSqlMode } = this.props;
const { groupServersBy } = this.state;
const { serverTableColumnSelectionHandler } = this;
const { groupServersBy, hiddenColumns } = this.state;
return (
<div className="servers-view app-view">
@ -652,8 +659,8 @@ ORDER BY "rank" DESC, "server" DESC`);
)}
<TableColumnSelector
columns={serverTableColumns}
onChange={column => serverTableColumnSelectionHandler.changeTableColumnSelector(column)}
tableColumnsHidden={serverTableColumnSelectionHandler.hiddenColumns}
onChange={column => this.setState({ hiddenColumns: hiddenColumns.toggle(column) })}
tableColumnsHidden={hiddenColumns.storedArray}
/>
</ViewControlBar>
{this.renderServersTable()}

View File

@ -53,9 +53,9 @@ import {
localStorageSet,
queryDruidSql,
QueryManager,
TableColumnSelectionHandler,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
import './tasks-view.scss';
@ -114,6 +114,8 @@ export interface TasksViewState {
taskTableActionDialogActions: BasicAction[];
supervisorTableActionDialogId: string | null;
supervisorTableActionDialogActions: BasicAction[];
hiddenTaskColumns: LocalStorageBackedArray<string>;
hiddenSupervisorColumns: LocalStorageBackedArray<string>;
}
interface TaskQueryResultRow {
@ -172,8 +174,6 @@ function stateToColor(status: string): string {
export class TasksView extends React.PureComponent<TasksViewProps, TasksViewState> {
private supervisorQueryManager: QueryManager<string, SupervisorQueryResultRow[]>;
private taskQueryManager: QueryManager<string, TaskQueryResultRow[]>;
private supervisorTableColumnSelectionHandler: TableColumnSelectionHandler;
private taskTableColumnSelectionHandler: TableColumnSelectionHandler;
static statusRanking: Record<string, number> = {
RUNNING: 4,
PENDING: 3,
@ -192,6 +192,7 @@ export class TasksView extends React.PureComponent<TasksViewProps, TasksViewStat
resumeSupervisorId: null,
suspendSupervisorId: null,
resetSupervisorId: null,
supervisorTableActionDialogId: null,
terminateSupervisorId: null,
tasksLoading: true,
@ -210,19 +211,15 @@ export class TasksView extends React.PureComponent<TasksViewProps, TasksViewStat
taskTableActionDialogId: null,
taskTableActionDialogStatus: null,
taskTableActionDialogActions: [],
supervisorTableActionDialogId: null,
supervisorTableActionDialogActions: [],
};
this.supervisorTableColumnSelectionHandler = new TableColumnSelectionHandler(
LocalStorageKeys.SUPERVISOR_TABLE_COLUMN_SELECTION,
() => this.setState({}),
);
this.taskTableColumnSelectionHandler = new TableColumnSelectionHandler(
hiddenTaskColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.TASK_TABLE_COLUMN_SELECTION,
() => this.setState({}),
);
),
hiddenSupervisorColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.SUPERVISOR_TABLE_COLUMN_SELECTION,
),
};
}
static parseTasks = (data: any[]): TaskQueryResultRow[] => {
@ -248,6 +245,7 @@ export class TasksView extends React.PureComponent<TasksViewProps, TasksViewStat
componentDidMount(): void {
const { noSqlMode } = this.props;
this.supervisorQueryManager = new QueryManager({
processQuery: async (query: string) => {
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
@ -521,8 +519,12 @@ ORDER BY "rank" DESC, "created_time" DESC`);
}
renderSupervisorTable() {
const { supervisors, supervisorsLoading, supervisorsError } = this.state;
const { supervisorTableColumnSelectionHandler } = this;
const {
supervisors,
supervisorsLoading,
supervisorsError,
hiddenSupervisorColumns,
} = this.state;
return (
<>
<ReactTable
@ -540,7 +542,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
id: 'datasource',
accessor: 'id',
width: 300,
show: supervisorTableColumnSelectionHandler.showColumn('Datasource'),
show: hiddenSupervisorColumns.exists('Datasource'),
},
{
Header: 'Type',
@ -552,7 +554,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
if (!tuningConfig) return '';
return tuningConfig.type;
},
show: supervisorTableColumnSelectionHandler.showColumn('Type'),
show: hiddenSupervisorColumns.exists('Type'),
},
{
Header: 'Topic/Stream',
@ -564,7 +566,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
if (!ioConfig) return '';
return ioConfig.topic || ioConfig.stream || '';
},
show: supervisorTableColumnSelectionHandler.showColumn('Topic/Stream'),
show: hiddenSupervisorColumns.exists('Topic/Stream'),
},
{
Header: 'Status',
@ -582,7 +584,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
</span>
);
},
show: supervisorTableColumnSelectionHandler.showColumn('Status'),
show: hiddenSupervisorColumns.exists('Status'),
},
{
Header: ActionCell.COLUMN_LABEL,
@ -607,7 +609,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
/>
);
},
show: supervisorTableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL),
show: hiddenSupervisorColumns.exists(ActionCell.COLUMN_LABEL),
},
]}
/>
@ -668,8 +670,14 @@ ORDER BY "rank" DESC, "created_time" DESC`);
renderTaskTable() {
const { goToMiddleManager } = this.props;
const { tasks, tasksLoading, tasksError, taskFilter, groupTasksBy } = this.state;
const { taskTableColumnSelectionHandler } = this;
const {
tasks,
tasksLoading,
tasksError,
taskFilter,
groupTasksBy,
hiddenTaskColumns,
} = this.state;
return (
<>
@ -690,7 +698,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
accessor: 'task_id',
width: 300,
Aggregated: row => '',
show: taskTableColumnSelectionHandler.showColumn('Task ID'),
show: hiddenTaskColumns.exists('Task ID'),
},
{
Header: 'Type',
@ -707,7 +715,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
</a>
);
},
show: taskTableColumnSelectionHandler.showColumn('Type'),
show: hiddenTaskColumns.exists('Type'),
},
{
Header: 'Datasource',
@ -724,8 +732,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
</a>
);
},
show: taskTableColumnSelectionHandler.showColumn('Datasource'),
show: hiddenTaskColumns.exists('Datasource'),
},
{
Header: 'Location',
accessor: 'location',
@ -733,14 +742,14 @@ ORDER BY "rank" DESC, "created_time" DESC`);
filterMethod: (filter: Filter, row: any) => {
return booleanCustomTableFilter(filter, row.location);
},
show: taskTableColumnSelectionHandler.showColumn('Location'),
show: hiddenTaskColumns.exists('Location'),
},
{
Header: 'Created time',
accessor: 'created_time',
width: 120,
Aggregated: row => '',
show: taskTableColumnSelectionHandler.showColumn('Created time'),
show: hiddenTaskColumns.exists('Created time'),
},
{
Header: 'Status',
@ -800,7 +809,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
filterMethod: (filter: Filter, row: any) => {
return booleanCustomTableFilter(filter, row.status.status);
},
show: taskTableColumnSelectionHandler.showColumn('Status'),
show: hiddenTaskColumns.exists('Status'),
},
{
Header: 'Duration',
@ -808,7 +817,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
filterable: false,
Cell: row => (row.value > 0 ? formatDuration(row.value) : ''),
Aggregated: () => '',
show: taskTableColumnSelectionHandler.showColumn('Duration'),
show: hiddenTaskColumns.exists('Duration'),
},
{
Header: ActionCell.COLUMN_LABEL,
@ -836,7 +845,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
);
},
Aggregated: row => '',
show: taskTableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL),
show: hiddenTaskColumns.exists(ActionCell.COLUMN_LABEL),
},
]}
/>
@ -857,8 +866,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
supervisorTableActionDialogId,
supervisorTableActionDialogActions,
taskTableActionDialogStatus,
hiddenSupervisorColumns,
hiddenTaskColumns,
} = this.state;
const { supervisorTableColumnSelectionHandler, taskTableColumnSelectionHandler } = this;
const submitSupervisorMenu = (
<Menu>
@ -915,9 +925,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
<TableColumnSelector
columns={supervisorTableColumns}
onChange={column =>
supervisorTableColumnSelectionHandler.changeTableColumnSelector(column)
this.setState({ hiddenSupervisorColumns: hiddenSupervisorColumns.toggle(column) })
}
tableColumnsHidden={supervisorTableColumnSelectionHandler.hiddenColumns}
tableColumnsHidden={hiddenSupervisorColumns.storedArray}
/>
</ViewControlBar>
{this.renderSupervisorTable()}
@ -968,9 +978,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
<TableColumnSelector
columns={taskTableColumns}
onChange={column =>
taskTableColumnSelectionHandler.changeTableColumnSelector(column)
this.setState({ hiddenTaskColumns: hiddenTaskColumns.toggle(column) })
}
tableColumnsHidden={taskTableColumnSelectionHandler.hiddenColumns}
tableColumnsHidden={hiddenTaskColumns.storedArray}
/>
</ViewControlBar>
{this.renderTaskTable()}