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 './druid-query';
|
||||||
export * from './query-manager';
|
export * from './query-manager';
|
||||||
export * from './rune-decoder';
|
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 { IconNames } from "../components/filler";
|
||||||
import { RuleEditor } from '../components/rule-editor';
|
import { RuleEditor } from '../components/rule-editor';
|
||||||
|
import { TableColumnSelection } from "../components/table-column-selection";
|
||||||
import { AsyncActionDialog } from '../dialogs/async-action-dialog';
|
import { AsyncActionDialog } from '../dialogs/async-action-dialog';
|
||||||
import { CompactionDialog } from "../dialogs/compaction-dialog";
|
import { CompactionDialog } from "../dialogs/compaction-dialog";
|
||||||
import { RetentionDialog } from '../dialogs/retention-dialog';
|
import { RetentionDialog } from '../dialogs/retention-dialog';
|
||||||
|
@ -34,11 +35,16 @@ import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
getDruidErrorMessage,
|
getDruidErrorMessage,
|
||||||
lookupBy,
|
lookupBy,
|
||||||
pluralIfNeeded, queryDruidSql, QueryManager
|
pluralIfNeeded,
|
||||||
|
queryDruidSql,
|
||||||
|
QueryManager, TableColumnSelectionHandler
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import "./datasource-view.scss";
|
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> {
|
export interface DatasourcesViewProps extends React.Props<any> {
|
||||||
goToSql: (initSql: string) => void;
|
goToSql: (initSql: string) => void;
|
||||||
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
||||||
|
@ -64,6 +70,7 @@ export interface DatasourcesViewState {
|
||||||
dropDataDatasource: string | null;
|
dropDataDatasource: string | null;
|
||||||
enableDatasource: string | null;
|
enableDatasource: string | null;
|
||||||
killDatasource: string | null;
|
killDatasource: string | null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DatasourcesView extends React.Component<DatasourcesViewProps, DatasourcesViewState> {
|
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 datasourceQueryManager: QueryManager<string, { tiers: string[], defaultRules: any[], datasources: Datasource[] }>;
|
||||||
|
private tableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
|
||||||
constructor(props: DatasourcesViewProps, context: any) {
|
constructor(props: DatasourcesViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -99,7 +107,12 @@ export class DatasourcesView extends React.Component<DatasourcesViewProps, Datas
|
||||||
dropDataDatasource: null,
|
dropDataDatasource: null,
|
||||||
enableDatasource: null,
|
enableDatasource: null,
|
||||||
killDatasource: null
|
killDatasource: null
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
datasourceTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
@ -151,6 +164,7 @@ export class DatasourcesView extends React.Component<DatasourcesViewProps, Datas
|
||||||
SUM("num_rows") AS num_rows
|
SUM("num_rows") AS num_rows
|
||||||
FROM sys.segments
|
FROM sys.segments
|
||||||
GROUP BY 1`);
|
GROUP BY 1`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -342,12 +356,11 @@ GROUP BY 1`);
|
||||||
renderDatasourceTable() {
|
renderDatasourceTable() {
|
||||||
const { goToSegments } = this.props;
|
const { goToSegments } = this.props;
|
||||||
const { datasources, defaultRules, datasourcesLoading, datasourcesError, datasourcesFilter, showDisabled } = this.state;
|
const { datasources, defaultRules, datasourcesLoading, datasourcesError, datasourcesFilter, showDisabled } = this.state;
|
||||||
|
const { tableColumnSelectionHandler } = this;
|
||||||
let data = datasources || [];
|
let data = datasources || [];
|
||||||
if (!showDisabled) {
|
if (!showDisabled) {
|
||||||
data = data.filter(d => !d.disabled);
|
data = data.filter(d => !d.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ReactTable
|
<ReactTable
|
||||||
data={data}
|
data={data}
|
||||||
|
@ -366,7 +379,8 @@ GROUP BY 1`);
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ datasourcesFilter: addFilter(datasourcesFilter, 'datasource', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ datasourcesFilter: addFilter(datasourcesFilter, 'datasource', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Datasource")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Availability",
|
Header: "Availability",
|
||||||
|
@ -400,7 +414,8 @@ GROUP BY 1`);
|
||||||
</span>;
|
</span>;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Availability")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Retention',
|
Header: 'Retention',
|
||||||
|
@ -423,7 +438,8 @@ GROUP BY 1`);
|
||||||
{text}
|
{text}
|
||||||
<a>✎</a>
|
<a>✎</a>
|
||||||
</span>;
|
</span>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Retention")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Compaction',
|
Header: 'Compaction',
|
||||||
|
@ -449,21 +465,24 @@ GROUP BY 1`);
|
||||||
{text}
|
{text}
|
||||||
<a>✎</a>
|
<a>✎</a>
|
||||||
</span>;
|
</span>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Compaction")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Size',
|
Header: 'Size',
|
||||||
accessor: 'size',
|
accessor: 'size',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
width: 100,
|
width: 100,
|
||||||
Cell: (row) => formatBytes(row.value)
|
Cell: (row) => formatBytes(row.value),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Size")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Num rows',
|
Header: 'Num rows',
|
||||||
accessor: 'num_rows',
|
accessor: 'num_rows',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
width: 100,
|
width: 100,
|
||||||
Cell: (row) => formatNumber(row.value)
|
Cell: (row) => formatNumber(row.value),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Num rows")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Actions',
|
Header: 'Actions',
|
||||||
|
@ -484,7 +503,8 @@ GROUP BY 1`);
|
||||||
<a onClick={() => this.setState({ dropDataDatasource: datasource })}>Drop data</a>
|
<a onClick={() => this.setState({ dropDataDatasource: datasource })}>Drop data</a>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Actions")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={50}
|
defaultPageSize={50}
|
||||||
|
@ -501,6 +521,7 @@ GROUP BY 1`);
|
||||||
render() {
|
render() {
|
||||||
const { goToSql } = this.props;
|
const { goToSql } = this.props;
|
||||||
const { showDisabled } = this.state;
|
const { showDisabled } = this.state;
|
||||||
|
const { tableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <div className="data-sources-view app-view">
|
return <div className="data-sources-view app-view">
|
||||||
<div className="control-bar">
|
<div className="control-bar">
|
||||||
|
@ -520,6 +541,11 @@ GROUP BY 1`);
|
||||||
label="Show disabled"
|
label="Show disabled"
|
||||||
onChange={() => this.setState({ showDisabled: !showDisabled })}
|
onChange={() => this.setState({ showDisabled: !showDisabled })}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={tableColumns}
|
||||||
|
onChange={(column) => tableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderDatasourceTable()}
|
{this.renderDatasourceTable()}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -23,12 +23,20 @@ import * as React from 'react';
|
||||||
import ReactTable from "react-table";
|
import ReactTable from "react-table";
|
||||||
import { Filter } from "react-table";
|
import { Filter } from "react-table";
|
||||||
|
|
||||||
|
import { TableColumnSelection } from "../components/table-column-selection";
|
||||||
import { LookupEditDialog } from "../dialogs/lookup-edit-dialog";
|
import { LookupEditDialog } from "../dialogs/lookup-edit-dialog";
|
||||||
import { AppToaster } from "../singletons/toaster";
|
import { AppToaster } from "../singletons/toaster";
|
||||||
import { getDruidErrorMessage, QueryManager } from "../utils";
|
import {
|
||||||
|
getDruidErrorMessage,
|
||||||
|
QueryManager,
|
||||||
|
TableColumnSelectionHandler
|
||||||
|
} from "../utils";
|
||||||
|
|
||||||
import "./lookups-view.scss";
|
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> {
|
export interface LookupsViewProps extends React.Props<any> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -49,6 +57,7 @@ export interface LookupsViewState {
|
||||||
export class LookupsView extends React.Component<LookupsViewProps, LookupsViewState> {
|
export class LookupsView extends React.Component<LookupsViewProps, LookupsViewState> {
|
||||||
private lookupsGetQueryManager: QueryManager<string, {lookupEntries: any[], tiers: string[]}>;
|
private lookupsGetQueryManager: QueryManager<string, {lookupEntries: any[], tiers: string[]}>;
|
||||||
private lookupDeleteQueryManager: QueryManager<string, any[]>;
|
private lookupDeleteQueryManager: QueryManager<string, any[]>;
|
||||||
|
private tableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
|
||||||
constructor(props: LookupsViewProps, context: any) {
|
constructor(props: LookupsViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -64,6 +73,9 @@ export class LookupsView extends React.Component<LookupsViewProps, LookupsViewSt
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
allLookupTiers: []
|
allLookupTiers: []
|
||||||
};
|
};
|
||||||
|
this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
lookupTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
@ -205,7 +217,9 @@ export class LookupsView extends React.Component<LookupsViewProps, LookupsViewSt
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLookupsTable() {
|
renderLookupsTable() {
|
||||||
const { lookups, loadingLookups, lookupsError} = this.state;
|
const { lookups, loadingLookups, lookupsError } = this.state;
|
||||||
|
const { tableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
if (lookupsError) {
|
if (lookupsError) {
|
||||||
return <div className={"init-div"}>
|
return <div className={"init-div"}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -226,25 +240,29 @@ export class LookupsView extends React.Component<LookupsViewProps, LookupsViewSt
|
||||||
Header: "Lookup Name",
|
Header: "Lookup Name",
|
||||||
id: "lookup_name",
|
id: "lookup_name",
|
||||||
accessor: (row: any) => row.id,
|
accessor: (row: any) => row.id,
|
||||||
filterable: true
|
filterable: true,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Lookup Name")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Tier",
|
Header: "Tier",
|
||||||
id: "tier",
|
id: "tier",
|
||||||
accessor: (row: any) => row.tier,
|
accessor: (row: any) => row.tier,
|
||||||
filterable: true
|
filterable: true,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Tier")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Type",
|
Header: "Type",
|
||||||
id: "type",
|
id: "type",
|
||||||
accessor: (row: any) => row.spec.type,
|
accessor: (row: any) => row.spec.type,
|
||||||
filterable: true
|
filterable: true,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Type")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Version",
|
Header: "Version",
|
||||||
id: "version",
|
id: "version",
|
||||||
accessor: (row: any) => row.version,
|
accessor: (row: any) => row.version,
|
||||||
filterable: true
|
filterable: true,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Version")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Config",
|
Header: "Config",
|
||||||
|
@ -259,7 +277,8 @@ export class LookupsView extends React.Component<LookupsViewProps, LookupsViewSt
|
||||||
|
|
||||||
<a onClick={() => this.deleteLookup(lookupTier, lookupId)}>Delete</a>
|
<a onClick={() => this.deleteLookup(lookupTier, lookupId)}>Delete</a>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Config")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={50}
|
defaultPageSize={50}
|
||||||
|
@ -286,6 +305,7 @@ export class LookupsView extends React.Component<LookupsViewProps, LookupsViewSt
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { tableColumnSelectionHandler } = this;
|
||||||
return <div className="lookups-view app-view">
|
return <div className="lookups-view app-view">
|
||||||
<div className="control-bar">
|
<div className="control-bar">
|
||||||
<div className="control-label">Lookups</div>
|
<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'}}
|
style={{display: this.state.lookupsError !== null ? 'none' : 'inline'}}
|
||||||
onClick={() => this.openLookupEditDialog("", "")}
|
onClick={() => this.openLookupEditDialog("", "")}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={tableColumns}
|
||||||
|
onChange={(column) => tableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderLookupsTable()}
|
{this.renderLookupsTable()}
|
||||||
{this.renderLookupEditDialog()}
|
{this.renderLookupEditDialog()}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button } from "@blueprintjs/core";
|
import { Button, Intent } from "@blueprintjs/core";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as classNames from 'classnames';
|
import * as classNames from 'classnames';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
@ -24,6 +24,8 @@ import ReactTable from "react-table";
|
||||||
import { Filter } from "react-table";
|
import { Filter } from "react-table";
|
||||||
|
|
||||||
import { H5, IconNames } from "../components/filler";
|
import { H5, IconNames } from "../components/filler";
|
||||||
|
import { TableColumnSelection } from "../components/table-column-selection";
|
||||||
|
import { AppToaster } from "../singletons/toaster";
|
||||||
import {
|
import {
|
||||||
addFilter,
|
addFilter,
|
||||||
formatBytes,
|
formatBytes,
|
||||||
|
@ -31,11 +33,15 @@ import {
|
||||||
makeBooleanFilter,
|
makeBooleanFilter,
|
||||||
parseList,
|
parseList,
|
||||||
queryDruidSql,
|
queryDruidSql,
|
||||||
QueryManager
|
QueryManager, TableColumnSelectionHandler
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import "./segments-view.scss";
|
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> {
|
export interface SegmentsViewProps extends React.Props<any> {
|
||||||
goToSql: (initSql: string) => void;
|
goToSql: (initSql: string) => void;
|
||||||
datasource: string | null;
|
datasource: string | null;
|
||||||
|
@ -56,6 +62,7 @@ interface QueryAndSkip {
|
||||||
|
|
||||||
export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsViewState> {
|
export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsViewState> {
|
||||||
private segmentsQueryManager: QueryManager<QueryAndSkip, any[]>;
|
private segmentsQueryManager: QueryManager<QueryAndSkip, any[]>;
|
||||||
|
private tableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
|
||||||
constructor(props: SegmentsViewProps, context: any) {
|
constructor(props: SegmentsViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -91,6 +98,10 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
segmentTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -135,6 +146,7 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
|
|
||||||
renderSegmentsTable() {
|
renderSegmentsTable() {
|
||||||
const { segments, segmentsLoading, segmentsError, segmentFilter } = this.state;
|
const { segments, segmentsLoading, segmentsError, segmentFilter } = this.state;
|
||||||
|
const { tableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <ReactTable
|
return <ReactTable
|
||||||
data={segments || []}
|
data={segments || []}
|
||||||
|
@ -155,7 +167,8 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
{
|
{
|
||||||
Header: "Segment ID",
|
Header: "Segment ID",
|
||||||
accessor: "segment_id",
|
accessor: "segment_id",
|
||||||
width: 300
|
width: 300,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Segment ID")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Datasource",
|
Header: "Datasource",
|
||||||
|
@ -163,7 +176,8 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ segmentFilter: addFilter(segmentFilter, 'datasource', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ segmentFilter: addFilter(segmentFilter, 'datasource', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Datasource")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Start",
|
Header: "Start",
|
||||||
|
@ -173,7 +187,8 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ segmentFilter: addFilter(segmentFilter, 'start', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ segmentFilter: addFilter(segmentFilter, 'start', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Start")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "End",
|
Header: "End",
|
||||||
|
@ -183,58 +198,67 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ segmentFilter: addFilter(segmentFilter, 'end', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ segmentFilter: addFilter(segmentFilter, 'end', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: tableColumnSelectionHandler.showColumn("End")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Version",
|
Header: "Version",
|
||||||
accessor: "version",
|
accessor: "version",
|
||||||
defaultSortDesc: true,
|
defaultSortDesc: true,
|
||||||
width: 120
|
width: 120,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Version")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Partition",
|
Header: "Partition",
|
||||||
accessor: "partition_num",
|
accessor: "partition_num",
|
||||||
width: 60,
|
width: 60,
|
||||||
filterable: false
|
filterable: false,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Partition")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Size",
|
Header: "Size",
|
||||||
accessor: "size",
|
accessor: "size",
|
||||||
filterable: false,
|
filterable: false,
|
||||||
defaultSortDesc: true,
|
defaultSortDesc: true,
|
||||||
Cell: row => formatBytes(row.value)
|
Cell: row => formatBytes(row.value),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Size")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Num rows",
|
Header: "Num rows",
|
||||||
accessor: "num_rows",
|
accessor: "num_rows",
|
||||||
filterable: false,
|
filterable: false,
|
||||||
defaultSortDesc: true,
|
defaultSortDesc: true,
|
||||||
Cell: row => formatNumber(row.value)
|
Cell: row => formatNumber(row.value),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Num rows")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Replicas",
|
Header: "Replicas",
|
||||||
accessor: "num_replicas",
|
accessor: "num_replicas",
|
||||||
width: 60,
|
width: 60,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
defaultSortDesc: true
|
defaultSortDesc: true,
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Replicas")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Is published",
|
Header: "Is published",
|
||||||
id: "is_published",
|
id: "is_published",
|
||||||
accessor: (row) => String(Boolean(row.is_published)),
|
accessor: (row) => String(Boolean(row.is_published)),
|
||||||
Filter: makeBooleanFilter()
|
Filter: makeBooleanFilter(),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Is published")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Is realtime",
|
Header: "Is realtime",
|
||||||
id: "is_realtime",
|
id: "is_realtime",
|
||||||
accessor: (row) => String(Boolean(row.is_realtime)),
|
accessor: (row) => String(Boolean(row.is_realtime)),
|
||||||
Filter: makeBooleanFilter()
|
Filter: makeBooleanFilter(),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Is realtime")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Is available",
|
Header: "Is available",
|
||||||
id: "is_available",
|
id: "is_available",
|
||||||
accessor: (row) => String(Boolean(row.is_available)),
|
accessor: (row) => String(Boolean(row.is_available)),
|
||||||
Filter: makeBooleanFilter()
|
Filter: makeBooleanFilter(),
|
||||||
|
show: tableColumnSelectionHandler.showColumn("Is available")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={50}
|
defaultPageSize={50}
|
||||||
|
@ -258,6 +282,7 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { goToSql } = this.props;
|
const { goToSql } = this.props;
|
||||||
|
const { tableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <div className="segments-view app-view">
|
return <div className="segments-view app-view">
|
||||||
<div className="control-bar">
|
<div className="control-bar">
|
||||||
|
@ -272,6 +297,11 @@ export class SegmentsView extends React.Component<SegmentsViewProps, SegmentsVie
|
||||||
text="Go to SQL"
|
text="Go to SQL"
|
||||||
onClick={() => goToSql(this.segmentsQueryManager.getLastQuery().query)}
|
onClick={() => goToSql(this.segmentsQueryManager.getLastQuery().query)}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={tableColumns}
|
||||||
|
onChange={(column) => tableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderSegmentsTable()}
|
{this.renderSegmentsTable()}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -25,10 +25,22 @@ import ReactTable from "react-table";
|
||||||
import { Filter } from "react-table";
|
import { Filter } from "react-table";
|
||||||
|
|
||||||
import { IconNames } from '../components/filler';
|
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";
|
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 {
|
function formatQueues(segmentsToLoad: number, segmentsToLoadSize: number, segmentsToDrop: number, segmentsToDropSize: number): string {
|
||||||
const queueParts: string[] = [];
|
const queueParts: string[] = [];
|
||||||
if (segmentsToLoad) {
|
if (segmentsToLoad) {
|
||||||
|
@ -62,6 +74,8 @@ export interface ServersViewState {
|
||||||
export class ServersView extends React.Component<ServersViewProps, ServersViewState> {
|
export class ServersView extends React.Component<ServersViewProps, ServersViewState> {
|
||||||
private serverQueryManager: QueryManager<string, any[]>;
|
private serverQueryManager: QueryManager<string, any[]>;
|
||||||
private middleManagerQueryManager: QueryManager<string, any[]>;
|
private middleManagerQueryManager: QueryManager<string, any[]>;
|
||||||
|
private serverTableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
private middleManagerTableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
|
||||||
constructor(props: ServersViewProps, context: any) {
|
constructor(props: ServersViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -77,6 +91,14 @@ export class ServersView extends React.Component<ServersViewProps, ServersViewSt
|
||||||
middleManagersError: null,
|
middleManagersError: null,
|
||||||
middleManagerFilter: props.middleManager ? [{ id: 'host', value: props.middleManager }] : []
|
middleManagerFilter: props.middleManager ? [{ id: 'host', value: props.middleManager }] : []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.serverTableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
serverTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.middleManagerTableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
middleManagerTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
@ -124,6 +146,7 @@ WHERE "server_type" = 'historical'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.middleManagerQueryManager.runQuery('dummy');
|
this.middleManagerQueryManager.runQuery('dummy');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -133,6 +156,7 @@ WHERE "server_type" = 'historical'`);
|
||||||
|
|
||||||
renderServersTable() {
|
renderServersTable() {
|
||||||
const { servers, serversLoading, serversError, serverFilter, groupByTier } = this.state;
|
const { servers, serversLoading, serversError, serverFilter, groupByTier } = this.state;
|
||||||
|
const { serverTableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
const fillIndicator = (value: number) => {
|
const fillIndicator = (value: number) => {
|
||||||
return <div className="fill-indicator">
|
return <div className="fill-indicator">
|
||||||
|
@ -156,7 +180,8 @@ WHERE "server_type" = 'historical'`);
|
||||||
Header: "Server",
|
Header: "Server",
|
||||||
accessor: "server",
|
accessor: "server",
|
||||||
width: 300,
|
width: 300,
|
||||||
Aggregated: row => ''
|
Aggregated: row => '',
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Server")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Tier",
|
Header: "Tier",
|
||||||
|
@ -164,7 +189,8 @@ WHERE "server_type" = 'historical'`);
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ serverFilter: addFilter(serverFilter, 'tier', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ serverFilter: addFilter(serverFilter, 'tier', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Tier")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Curr size",
|
Header: "Curr size",
|
||||||
|
@ -181,7 +207,8 @@ WHERE "server_type" = 'historical'`);
|
||||||
if (row.aggregated) return '';
|
if (row.aggregated) return '';
|
||||||
if (row.value === null) return '';
|
if (row.value === null) return '';
|
||||||
return formatBytes(row.value);
|
return formatBytes(row.value);
|
||||||
}
|
},
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Curr size")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Max size",
|
Header: "Max size",
|
||||||
|
@ -198,7 +225,8 @@ WHERE "server_type" = 'historical'`);
|
||||||
if (row.aggregated) return '';
|
if (row.aggregated) return '';
|
||||||
if (row.value === null) return '';
|
if (row.value === null) return '';
|
||||||
return formatBytes(row.value);
|
return formatBytes(row.value);
|
||||||
}
|
},
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Max size")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Usage",
|
Header: "Usage",
|
||||||
|
@ -216,7 +244,8 @@ WHERE "server_type" = 'historical'`);
|
||||||
if (row.aggregated) return '';
|
if (row.aggregated) return '';
|
||||||
if (row.value === null) return '';
|
if (row.value === null) return '';
|
||||||
return fillIndicator(row.value);
|
return fillIndicator(row.value);
|
||||||
}
|
},
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Usage")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Load/drop queues",
|
Header: "Load/drop queues",
|
||||||
|
@ -236,12 +265,14 @@ WHERE "server_type" = 'historical'`);
|
||||||
const segmentsToDrop = sum(originals, s => s.segmentsToDrop);
|
const segmentsToDrop = sum(originals, s => s.segmentsToDrop);
|
||||||
const segmentsToDropSize = sum(originals, s => s.segmentsToDropSize);
|
const segmentsToDropSize = sum(originals, s => s.segmentsToDropSize);
|
||||||
return formatQueues(segmentsToLoad, segmentsToLoadSize, segmentsToDrop, segmentsToDropSize);
|
return formatQueues(segmentsToLoad, segmentsToLoadSize, segmentsToDrop, segmentsToDropSize);
|
||||||
}
|
},
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Load/drop queues")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Host",
|
Header: "Host",
|
||||||
accessor: "host",
|
accessor: "host",
|
||||||
Aggregated: () => ''
|
Aggregated: () => '',
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Host")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Port",
|
Header: "Port",
|
||||||
|
@ -256,7 +287,8 @@ WHERE "server_type" = 'historical'`);
|
||||||
}
|
}
|
||||||
return ports.join(', ') || 'No port';
|
return ports.join(', ') || 'No port';
|
||||||
},
|
},
|
||||||
Aggregated: () => ''
|
Aggregated: () => '',
|
||||||
|
show: serverTableColumnSelectionHandler.showColumn("Port")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
|
@ -267,6 +299,7 @@ WHERE "server_type" = 'historical'`);
|
||||||
renderMiddleManagerTable() {
|
renderMiddleManagerTable() {
|
||||||
const { goToTask } = this.props;
|
const { goToTask } = this.props;
|
||||||
const { middleManagers, middleManagersLoading, middleManagersError, middleManagerFilter } = this.state;
|
const { middleManagers, middleManagersLoading, middleManagersError, middleManagerFilter } = this.state;
|
||||||
|
const { middleManagerTableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <ReactTable
|
return <ReactTable
|
||||||
data={middleManagers || []}
|
data={middleManagers || []}
|
||||||
|
@ -285,29 +318,34 @@ WHERE "server_type" = 'historical'`);
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ middleManagerFilter: addFilter(middleManagerFilter, 'host', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ middleManagerFilter: addFilter(middleManagerFilter, 'host', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: middleManagerTableColumnSelectionHandler.showColumn("Host")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Usage",
|
Header: "Usage",
|
||||||
id: "usage",
|
id: "usage",
|
||||||
width: 60,
|
width: 60,
|
||||||
accessor: (row) => `${row.currCapacityUsed} / ${row.worker.capacity}`,
|
accessor: (row) => `${row.currCapacityUsed} / ${row.worker.capacity}`,
|
||||||
filterable: false
|
filterable: false,
|
||||||
|
show: middleManagerTableColumnSelectionHandler.showColumn("Usage")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Availability groups",
|
Header: "Availability groups",
|
||||||
id: "availabilityGroups",
|
id: "availabilityGroups",
|
||||||
width: 60,
|
width: 60,
|
||||||
accessor: (row) => row.availabilityGroups.length,
|
accessor: (row) => row.availabilityGroups.length,
|
||||||
filterable: false
|
filterable: false,
|
||||||
|
show: middleManagerTableColumnSelectionHandler.showColumn("Availability groups")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Last completed task time",
|
Header: "Last completed task time",
|
||||||
accessor: "lastCompletedTaskTime"
|
accessor: "lastCompletedTaskTime",
|
||||||
|
show: middleManagerTableColumnSelectionHandler.showColumn("Last completed task time")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Blacklisted until",
|
Header: "Blacklisted until",
|
||||||
accessor: "blacklistedUntil"
|
accessor: "blacklistedUntil",
|
||||||
|
show: middleManagerTableColumnSelectionHandler.showColumn("Blacklisted until")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
|
@ -331,6 +369,7 @@ WHERE "server_type" = 'historical'`);
|
||||||
render() {
|
render() {
|
||||||
const { goToSql } = this.props;
|
const { goToSql } = this.props;
|
||||||
const { groupByTier } = this.state;
|
const { groupByTier } = this.state;
|
||||||
|
const { serverTableColumnSelectionHandler, middleManagerTableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <div className="servers-view app-view">
|
return <div className="servers-view app-view">
|
||||||
<div className="control-bar">
|
<div className="control-bar">
|
||||||
|
@ -350,6 +389,11 @@ WHERE "server_type" = 'historical'`);
|
||||||
label="Group by tier"
|
label="Group by tier"
|
||||||
onChange={() => this.setState({ groupByTier: !groupByTier })}
|
onChange={() => this.setState({ groupByTier: !groupByTier })}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={serverTableColumns}
|
||||||
|
onChange={(column) => serverTableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={serverTableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderServersTable()}
|
{this.renderServersTable()}
|
||||||
|
|
||||||
|
@ -362,6 +406,11 @@ WHERE "server_type" = 'historical'`);
|
||||||
text="Refresh"
|
text="Refresh"
|
||||||
onClick={() => this.middleManagerQueryManager.rerunLastQuery()}
|
onClick={() => this.middleManagerQueryManager.rerunLastQuery()}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={middleManagerTableColumns}
|
||||||
|
onChange={(column) => middleManagerTableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={middleManagerTableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderMiddleManagerTable()}
|
{this.renderMiddleManagerTable()}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -24,13 +24,26 @@ import ReactTable from "react-table";
|
||||||
import { Filter } from "react-table";
|
import { Filter } from "react-table";
|
||||||
|
|
||||||
import { ButtonGroup, IconNames, Label } from "../components/filler";
|
import { ButtonGroup, IconNames, Label } from "../components/filler";
|
||||||
|
import { TableColumnSelection } from "../components/table-column-selection";
|
||||||
import { AsyncActionDialog } from "../dialogs/async-action-dialog";
|
import { AsyncActionDialog } from "../dialogs/async-action-dialog";
|
||||||
import { SpecDialog } from "../dialogs/spec-dialog";
|
import { SpecDialog } from "../dialogs/spec-dialog";
|
||||||
import { AppToaster } from '../singletons/toaster';
|
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";
|
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> {
|
export interface TasksViewProps extends React.Props<any> {
|
||||||
taskId: string | null;
|
taskId: string | null;
|
||||||
goToSql: (initSql: string) => void;
|
goToSql: (initSql: string) => void;
|
||||||
|
@ -74,6 +87,8 @@ function statusToColor(status: string): string {
|
||||||
export class TasksView extends React.Component<TasksViewProps, TasksViewState> {
|
export class TasksView extends React.Component<TasksViewProps, TasksViewState> {
|
||||||
private supervisorQueryManager: QueryManager<string, any[]>;
|
private supervisorQueryManager: QueryManager<string, any[]>;
|
||||||
private taskQueryManager: QueryManager<string, any[]>;
|
private taskQueryManager: QueryManager<string, any[]>;
|
||||||
|
private supervisorTableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
private taskTableColumnSelectionHandler: TableColumnSelectionHandler;
|
||||||
|
|
||||||
constructor(props: TasksViewProps, context: any) {
|
constructor(props: TasksViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -98,7 +113,16 @@ export class TasksView extends React.Component<TasksViewProps, TasksViewState> {
|
||||||
supervisorSpecDialogOpen: false,
|
supervisorSpecDialogOpen: false,
|
||||||
taskSpecDialogOpen: false,
|
taskSpecDialogOpen: false,
|
||||||
alertErrorMsg: null
|
alertErrorMsg: null
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.supervisorTableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
supervisorTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.taskTableColumnSelectionHandler = new TableColumnSelectionHandler(
|
||||||
|
taskTableColumnSelection, () => this.setState({})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
@ -147,6 +171,7 @@ export class TasksView extends React.Component<TasksViewProps, TasksViewState> {
|
||||||
"location", "duration", "error_msg"
|
"location", "duration", "error_msg"
|
||||||
FROM sys.tasks
|
FROM sys.tasks
|
||||||
ORDER BY "rank" DESC, "created_time" DESC`);
|
ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -295,6 +320,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
|
|
||||||
renderSupervisorTable() {
|
renderSupervisorTable() {
|
||||||
const { supervisors, supervisorsLoading, supervisorsError } = this.state;
|
const { supervisors, supervisorsLoading, supervisorsError } = this.state;
|
||||||
|
const { supervisorTableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ReactTable
|
<ReactTable
|
||||||
|
@ -307,7 +333,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
Header: "Datasource",
|
Header: "Datasource",
|
||||||
id: 'datasource',
|
id: 'datasource',
|
||||||
accessor: "id",
|
accessor: "id",
|
||||||
width: 300
|
width: 300,
|
||||||
|
show: supervisorTableColumnSelectionHandler.showColumn("Datasource")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Type',
|
Header: 'Type',
|
||||||
|
@ -318,7 +345,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
const { tuningConfig } = spec;
|
const { tuningConfig } = spec;
|
||||||
if (!tuningConfig) return '';
|
if (!tuningConfig) return '';
|
||||||
return tuningConfig.type;
|
return tuningConfig.type;
|
||||||
}
|
},
|
||||||
|
show: supervisorTableColumnSelectionHandler.showColumn("Type")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Topic/Stream',
|
Header: 'Topic/Stream',
|
||||||
|
@ -329,7 +357,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
const { ioConfig } = spec;
|
const { ioConfig } = spec;
|
||||||
if (!ioConfig) return '';
|
if (!ioConfig) return '';
|
||||||
return ioConfig.topic || ioConfig.stream || '';
|
return ioConfig.topic || ioConfig.stream || '';
|
||||||
}
|
},
|
||||||
|
show: supervisorTableColumnSelectionHandler.showColumn("Topic/Stream")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Status",
|
Header: "Status",
|
||||||
|
@ -345,7 +374,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
</span>
|
</span>
|
||||||
{value}
|
{value}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
},
|
||||||
|
show: supervisorTableColumnSelectionHandler.showColumn("Status")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Actions',
|
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({ resetSupervisorId: id })}>Reset</a>
|
||||||
<a onClick={() => this.setState({ terminateSupervisorId: id })}>Terminate</a>
|
<a onClick={() => this.setState({ terminateSupervisorId: id })}>Terminate</a>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
},
|
||||||
|
show: supervisorTableColumnSelectionHandler.showColumn("Actions")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
|
@ -411,6 +442,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
renderTaskTable() {
|
renderTaskTable() {
|
||||||
const { goToMiddleManager } = this.props;
|
const { goToMiddleManager } = this.props;
|
||||||
const { tasks, tasksLoading, tasksError, taskFilter, groupTasksBy } = this.state;
|
const { tasks, tasksLoading, tasksError, taskFilter, groupTasksBy } = this.state;
|
||||||
|
const { taskTableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ReactTable
|
<ReactTable
|
||||||
|
@ -429,7 +461,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
Header: "Task ID",
|
Header: "Task ID",
|
||||||
accessor: "task_id",
|
accessor: "task_id",
|
||||||
width: 300,
|
width: 300,
|
||||||
Aggregated: row => ''
|
Aggregated: row => '',
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Task ID")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Type",
|
Header: "Type",
|
||||||
|
@ -437,7 +470,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ taskFilter: addFilter(taskFilter, 'type', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ taskFilter: addFilter(taskFilter, 'type', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Type")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Datasource",
|
Header: "Datasource",
|
||||||
|
@ -445,13 +479,15 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const value = row.value;
|
const value = row.value;
|
||||||
return <a onClick={() => { this.setState({ taskFilter: addFilter(taskFilter, 'datasource', value) }); }}>{value}</a>;
|
return <a onClick={() => { this.setState({ taskFilter: addFilter(taskFilter, 'datasource', value) }); }}>{value}</a>;
|
||||||
}
|
},
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Datasource")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Created time",
|
Header: "Created time",
|
||||||
accessor: "created_time",
|
accessor: "created_time",
|
||||||
width: 120,
|
width: 120,
|
||||||
Aggregated: row => ''
|
Aggregated: row => '',
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Created time")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Status",
|
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 previewValues = subRows.filter((d: any) => typeof d[column.id] !== 'undefined').map((row: any) => row._original[column.id]);
|
||||||
const previewCount = countBy(previewValues);
|
const previewCount = countBy(previewValues);
|
||||||
return <span>{Object.keys(previewCount).sort().map(v => `${v} (${previewCount[v]})`).join(', ')}</span>;
|
return <span>{Object.keys(previewCount).sort().map(v => `${v} (${previewCount[v]})`).join(', ')}</span>;
|
||||||
}
|
},
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Status")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Duration",
|
Header: "Duration",
|
||||||
accessor: "duration",
|
accessor: "duration",
|
||||||
filterable: false,
|
filterable: false,
|
||||||
Cell: (row) => row.value > 0 ? formatDuration(row.value) : '',
|
Cell: (row) => row.value > 0 ? formatDuration(row.value) : '',
|
||||||
Aggregated: () => ''
|
Aggregated: () => '',
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Duration")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Actions',
|
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>}
|
{(status === 'RUNNING' || status === 'WAITING' || status === 'PENDING') && <a onClick={() => this.setState({ killTaskId: id })}>Kill</a>}
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
Aggregated: row => ''
|
Aggregated: row => '',
|
||||||
|
show: taskTableColumnSelectionHandler.showColumn("Actions")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
defaultPageSize={20}
|
defaultPageSize={20}
|
||||||
|
@ -525,6 +564,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
render() {
|
render() {
|
||||||
const { goToSql } = this.props;
|
const { goToSql } = this.props;
|
||||||
const { groupTasksBy, supervisorSpecDialogOpen, taskSpecDialogOpen, alertErrorMsg } = this.state;
|
const { groupTasksBy, supervisorSpecDialogOpen, taskSpecDialogOpen, alertErrorMsg } = this.state;
|
||||||
|
const { supervisorTableColumnSelectionHandler, taskTableColumnSelectionHandler } = this;
|
||||||
|
|
||||||
return <div className="tasks-view app-view">
|
return <div className="tasks-view app-view">
|
||||||
<div className="control-bar">
|
<div className="control-bar">
|
||||||
|
@ -539,6 +579,11 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
text="Submit supervisor"
|
text="Submit supervisor"
|
||||||
onClick={() => this.setState({ supervisorSpecDialogOpen: true })}
|
onClick={() => this.setState({ supervisorSpecDialogOpen: true })}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={supervisorTableColumns}
|
||||||
|
onChange={(column) => supervisorTableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={supervisorTableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderSupervisorTable()}
|
{this.renderSupervisorTable()}
|
||||||
|
|
||||||
|
@ -568,6 +613,11 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
||||||
text="Submit task"
|
text="Submit task"
|
||||||
onClick={() => this.setState({ taskSpecDialogOpen: true })}
|
onClick={() => this.setState({ taskSpecDialogOpen: true })}
|
||||||
/>
|
/>
|
||||||
|
<TableColumnSelection
|
||||||
|
columns={taskTableColumns}
|
||||||
|
onChange={(column) => taskTableColumnSelectionHandler.changeTableColumnSelection(column)}
|
||||||
|
tableColumnsHidden={taskTableColumnSelectionHandler.hiddenColumns}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderTaskTable()}
|
{this.renderTaskTable()}
|
||||||
{ supervisorSpecDialogOpen ? <SpecDialog
|
{ supervisorSpecDialogOpen ? <SpecDialog
|
||||||
|
|
Loading…
Reference in New Issue