You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by fj...@apache.org on 2019/06/26 03:14:15 UTC
[incubator-druid] branch master updated: Web-console: Add action
column to segments view (#7954)
This is an automated email from the ASF dual-hosted git repository.
fjy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git
The following commit(s) were added to refs/heads/master by this push:
new a171b4a Web-console: Add action column to segments view (#7954)
a171b4a is described below
commit a171b4a399d395b673a6f84854fef90d0f834a4e
Author: mcbrewster <37...@users.noreply.github.com>
AuthorDate: Tue Jun 25 20:14:06 2019 -0700
Web-console: Add action column to segments view (#7954)
* add actions column to segments view
* add sements action column
---
.idea/inspectionProfiles/Druid.xml | 2 +-
.../segment-table-action-dialog.spec.tsx.snap | 171 ++++++
.../segment-table-action-dialog.spec.tsx} | 28 +-
.../segment-table-action-dialog.tsx | 78 +++
web-console/src/utils/index.tsx | 1 -
.../src/utils/local-storage-backed-array.tsx | 65 +++
.../src/utils/table-column-selection-handler.tsx | 61 ---
.../src/views/datasource-view/datasource-view.tsx | 36 +-
.../src/views/lookups-view/lookups-view.tsx | 45 +-
.../__snapshots__/segments-view.spec.tsx.snap | 595 +++++++++++----------
.../src/views/segments-view/segments-view.tsx | 214 +++++---
.../src/views/servers-view/servers-view.tsx | 53 +-
web-console/src/views/task-view/tasks-view.tsx | 82 +--
13 files changed, 912 insertions(+), 519 deletions(-)
diff --git a/.idea/inspectionProfiles/Druid.xml b/.idea/inspectionProfiles/Druid.xml
index ed890c8..4de1e05 100644
--- a/.idea/inspectionProfiles/Druid.xml
+++ b/.idea/inspectionProfiles/Druid.xml
@@ -405,4 +405,4 @@
<option name="ADD_NONJAVA_TO_ENTRIES" value="true" />
</inspection_tool>
</profile>
-</component>
+</component>
\ No newline at end of file
diff --git a/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap
new file mode 100644
index 0000000..6e695a9
--- /dev/null
+++ b/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap
@@ -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>
+`;
diff --git a/web-console/src/utils/index.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
similarity index 55%
copy from web-console/src/utils/index.tsx
copy to web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
index 0a4c9f9..52decd2 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
@@ -16,10 +16,24 @@
* limitations under the License.
*/
-export * from './general';
-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';
+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();
+ });
+});
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
new file mode 100644
index 0000000..1ae592b
--- /dev/null
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
@@ -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>
+ );
+ }
+}
diff --git a/web-console/src/utils/index.tsx b/web-console/src/utils/index.tsx
index 0a4c9f9..f1d18b0 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/utils/index.tsx
@@ -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';
diff --git a/web-console/src/utils/local-storage-backed-array.tsx b/web-console/src/utils/local-storage-backed-array.tsx
new file mode 100644
index 0000000..daf227d
--- /dev/null
+++ b/web-console/src/utils/local-storage-backed-array.tsx
@@ -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);
+ }
+}
diff --git a/web-console/src/utils/table-column-selection-handler.tsx b/web-console/src/utils/table-column-selection-handler.tsx
deleted file mode 100644
index 4decef5..0000000
--- a/web-console/src/utils/table-column-selection-handler.tsx
+++ /dev/null
@@ -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);
- }
-}
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index bf7f60a..db14776 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -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: '',
+ hiddenColumns: new LocalStorageBackedArray<string>(
+ LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
+ ),
};
-
- this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
- 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()}
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx b/web-console/src/views/lookups-view/lookups-view.tsx
index 8efc35a..182ea93 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -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,
+
+ hiddenColumns: new LocalStorageBackedArray<string>(
+ LocalStorageKeys.LOOKUP_TABLE_COLUMN_SELECTION,
+ ),
};
- this.tableColumnSelectionHandler = new TableColumnSelectionHandler(
- 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()}
diff --git a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
index dae9fbe..9be7408 100755
--- a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
+++ b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
@@ -1,297 +1,324 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`segments-view matches snapshot 1`] = `
-<div
- className="segments-view app-view"
->
- <ViewControlBar
- label="Segments"
+<Fragment>
+ <div
+ className="segments-view app-view"
>
- <RefreshButton
- localStorageKey="segments-refresh-rate"
- onRefresh={[Function]}
- />
- <Blueprint3.Button
- hidden={false}
- icon="application"
- onClick={[Function]}
- text="Go to SQL"
- />
- <TableColumnSelector
+ <ViewControlBar
+ label="Segments"
+ >
+ <RefreshButton
+ localStorageKey="segments-refresh-rate"
+ onRefresh={[Function]}
+ />
+ <Blueprint3.Button
+ hidden={false}
+ icon="application"
+ onClick={[Function]}
+ text="Go to SQL"
+ />
+ <TableColumnSelector
+ columns={
+ Array [
+ "Segment ID",
+ "Datasource",
+ "Start",
+ "End",
+ "Version",
+ "Partition",
+ "Size",
+ "Num rows",
+ "Replicas",
+ "Is published",
+ "Is realtime",
+ "Is available",
+ "Is overshadowed",
+ "Actions",
+ ]
+ }
+ onChange={[Function]}
+ tableColumnsHidden={Array []}
+ />
+ </ViewControlBar>
+ <ReactTable
+ AggregatedComponent={[Function]}
+ ExpanderComponent={[Function]}
+ FilterComponent={[Function]}
+ LoadingComponent={[Function]}
+ NoDataComponent={[Function]}
+ PadRowComponent={[Function]}
+ PaginationComponent={[Function]}
+ PivotValueComponent={[Function]}
+ ResizerComponent={[Function]}
+ TableComponent={[Function]}
+ TbodyComponent={[Function]}
+ TdComponent={[Function]}
+ TfootComponent={[Function]}
+ ThComponent={[Function]}
+ TheadComponent={[Function]}
+ TrComponent={[Function]}
+ TrGroupComponent={[Function]}
+ aggregatedKey="_aggregated"
+ className=""
+ collapseOnDataChange={true}
+ collapseOnPageChange={true}
+ collapseOnSortingChange={true}
+ column={
+ Object {
+ "Aggregated": undefined,
+ "Cell": undefined,
+ "Expander": undefined,
+ "Filter": undefined,
+ "Footer": undefined,
+ "Header": undefined,
+ "Pivot": undefined,
+ "PivotValue": undefined,
+ "Placeholder": undefined,
+ "aggregate": undefined,
+ "className": "",
+ "filterAll": false,
+ "filterMethod": undefined,
+ "filterable": undefined,
+ "footerClassName": "",
+ "footerStyle": Object {},
+ "getFooterProps": [Function],
+ "getHeaderProps": [Function],
+ "getProps": [Function],
+ "headerClassName": "",
+ "headerStyle": Object {},
+ "minResizeWidth": 11,
+ "minWidth": 100,
+ "resizable": undefined,
+ "show": true,
+ "sortMethod": undefined,
+ "sortable": undefined,
+ "style": Object {},
+ }
+ }
columns={
Array [
- "Segment ID",
- "Datasource",
- "Start",
- "End",
- "Version",
- "Partition",
- "Size",
- "Num rows",
- "Replicas",
- "Is published",
- "Is realtime",
- "Is available",
- "Is overshadowed",
+ Object {
+ "Header": "Segment ID",
+ "accessor": "segment_id",
+ "show": true,
+ "width": 300,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "Datasource",
+ "accessor": "datasource",
+ "show": true,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "Start",
+ "accessor": "start",
+ "defaultSortDesc": true,
+ "show": true,
+ "width": 120,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "End",
+ "accessor": "end",
+ "defaultSortDesc": true,
+ "show": true,
+ "width": 120,
+ },
+ Object {
+ "Header": "Version",
+ "accessor": "version",
+ "defaultSortDesc": true,
+ "show": true,
+ "width": 120,
+ },
+ Object {
+ "Header": "Partition",
+ "accessor": "partition_num",
+ "filterable": false,
+ "show": true,
+ "width": 60,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "Size",
+ "accessor": "size",
+ "defaultSortDesc": true,
+ "filterable": false,
+ "show": true,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "Num rows",
+ "accessor": "num_rows",
+ "defaultSortDesc": true,
+ "filterable": false,
+ "show": true,
+ },
+ Object {
+ "Header": "Replicas",
+ "accessor": "num_replicas",
+ "defaultSortDesc": true,
+ "filterable": false,
+ "show": true,
+ "width": 60,
+ },
+ Object {
+ "Filter": [Function],
+ "Header": "Is published",
+ "accessor": [Function],
+ "id": "is_published",
+ "show": true,
+ },
+ Object {
+ "Filter": [Function],
+ "Header": "Is realtime",
+ "accessor": [Function],
+ "id": "is_realtime",
+ "show": true,
+ },
+ Object {
+ "Filter": [Function],
+ "Header": "Is available",
+ "accessor": [Function],
+ "id": "is_available",
+ "show": true,
+ },
+ Object {
+ "Filter": [Function],
+ "Header": "Is overshadowed",
+ "accessor": [Function],
+ "id": "is_overshadowed",
+ "show": true,
+ },
+ Object {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Actions",
+ "accessor": "segment_id",
+ "filterable": false,
+ "id": "actions",
+ "show": true,
+ "width": 70,
+ },
]
}
- onChange={[Function]}
- tableColumnsHidden={Array []}
- />
- </ViewControlBar>
- <ReactTable
- AggregatedComponent={[Function]}
- ExpanderComponent={[Function]}
- FilterComponent={[Function]}
- LoadingComponent={[Function]}
- NoDataComponent={[Function]}
- PadRowComponent={[Function]}
- PaginationComponent={[Function]}
- PivotValueComponent={[Function]}
- ResizerComponent={[Function]}
- SubComponent={[Function]}
- TableComponent={[Function]}
- TbodyComponent={[Function]}
- TdComponent={[Function]}
- TfootComponent={[Function]}
- ThComponent={[Function]}
- TheadComponent={[Function]}
- TrComponent={[Function]}
- TrGroupComponent={[Function]}
- aggregatedKey="_aggregated"
- className=""
- collapseOnDataChange={true}
- collapseOnPageChange={true}
- collapseOnSortingChange={true}
- column={
- Object {
- "Aggregated": undefined,
- "Cell": undefined,
- "Expander": undefined,
- "Filter": undefined,
- "Footer": undefined,
- "Header": undefined,
- "Pivot": undefined,
- "PivotValue": undefined,
- "Placeholder": undefined,
- "aggregate": undefined,
- "className": "",
- "filterAll": false,
- "filterMethod": undefined,
- "filterable": undefined,
- "footerClassName": "",
- "footerStyle": Object {},
- "getFooterProps": [Function],
- "getHeaderProps": [Function],
- "getProps": [Function],
- "headerClassName": "",
- "headerStyle": Object {},
- "minResizeWidth": 11,
- "minWidth": 100,
- "resizable": undefined,
- "show": true,
- "sortMethod": undefined,
- "sortable": undefined,
- "style": Object {},
+ data={Array []}
+ defaultExpanded={Object {}}
+ defaultFilterMethod={[Function]}
+ defaultFiltered={Array []}
+ defaultPage={0}
+ defaultPageSize={50}
+ defaultResized={Array []}
+ defaultSortDesc={false}
+ defaultSortMethod={[Function]}
+ defaultSorted={
+ Array [
+ Object {
+ "desc": true,
+ "id": "start",
+ },
+ ]
}
- }
- columns={
- Array [
- Object {
- "Header": "Segment ID",
- "accessor": "segment_id",
- "show": true,
- "width": 300,
- },
- Object {
- "Cell": [Function],
- "Header": "Datasource",
- "accessor": "datasource",
- "show": true,
- },
- Object {
- "Cell": [Function],
- "Header": "Start",
- "accessor": "start",
- "defaultSortDesc": true,
- "show": true,
- "width": 120,
- },
- Object {
- "Cell": [Function],
- "Header": "End",
- "accessor": "end",
- "defaultSortDesc": true,
- "show": true,
- "width": 120,
- },
- Object {
- "Header": "Version",
- "accessor": "version",
- "defaultSortDesc": true,
- "show": true,
- "width": 120,
- },
- Object {
- "Header": "Partition",
- "accessor": "partition_num",
- "filterable": false,
- "show": true,
- "width": 60,
- },
- Object {
- "Cell": [Function],
- "Header": "Size",
- "accessor": "size",
- "defaultSortDesc": true,
- "filterable": false,
- "show": true,
- },
- Object {
- "Cell": [Function],
- "Header": "Num rows",
- "accessor": "num_rows",
- "defaultSortDesc": true,
- "filterable": false,
- "show": true,
- },
+ expanderDefaults={
Object {
- "Header": "Replicas",
- "accessor": "num_replicas",
- "defaultSortDesc": true,
"filterable": false,
- "show": true,
- "width": 60,
- },
- Object {
- "Filter": [Function],
- "Header": "Is published",
- "accessor": [Function],
- "id": "is_published",
- "show": true,
- },
- Object {
- "Filter": [Function],
- "Header": "Is realtime",
- "accessor": [Function],
- "id": "is_realtime",
- "show": true,
- },
- Object {
- "Filter": [Function],
- "Header": "Is available",
- "accessor": [Function],
- "id": "is_available",
- "show": true,
- },
- Object {
- "Filter": [Function],
- "Header": "Is overshadowed",
- "accessor": [Function],
- "id": "is_overshadowed",
- "show": true,
- },
- ]
- }
- data={Array []}
- defaultExpanded={Object {}}
- defaultFilterMethod={[Function]}
- defaultFiltered={Array []}
- defaultPage={0}
- defaultPageSize={50}
- defaultResized={Array []}
- defaultSortDesc={false}
- defaultSortMethod={[Function]}
- defaultSorted={
- Array [
- Object {
- "desc": true,
- "id": "start",
- },
- ]
- }
- expanderDefaults={
- Object {
- "filterable": false,
- "resizable": false,
- "sortable": false,
- "width": 35,
+ "resizable": false,
+ "sortable": false,
+ "width": 35,
+ }
}
- }
- filterable={true}
- filtered={
- Array [
- Object {
- "id": "datasource",
- "value": "test",
- },
- ]
- }
- freezeWhenExpanded={false}
- getLoadingProps={[Function]}
- getNoDataProps={[Function]}
- getPaginationProps={[Function]}
- getProps={[Function]}
- getResizerProps={[Function]}
- getTableProps={[Function]}
- getTbodyProps={[Function]}
- getTdProps={[Function]}
- getTfootProps={[Function]}
- getTfootTdProps={[Function]}
- getTfootTrProps={[Function]}
- getTheadFilterProps={[Function]}
- getTheadFilterThProps={[Function]}
- getTheadFilterTrProps={[Function]}
- getTheadGroupProps={[Function]}
- getTheadGroupThProps={[Function]}
- getTheadGroupTrProps={[Function]}
- getTheadProps={[Function]}
- getTheadThProps={[Function]}
- getTheadTrProps={[Function]}
- getTrGroupProps={[Function]}
- getTrProps={[Function]}
- groupedByPivotKey="_groupedByPivot"
- indexKey="_index"
- loading={true}
- loadingText="Loading..."
- manual={true}
- multiSort={true}
- nestingLevelKey="_nestingLevel"
- nextText="Next"
- noDataText=""
- ofText=""
- onFetchData={[Function]}
- onFilteredChange={[Function]}
- originalKey="_original"
- pageJumpText="jump to page"
- pageSizeOptions={
- Array [
- 5,
- 10,
- 20,
- 25,
- 50,
- 100,
- ]
- }
- pageText="Page"
- pages={10000000}
- pivotDefaults={Object {}}
- pivotIDKey="_pivotID"
- pivotValKey="_pivotVal"
- previousText="Previous"
- resizable={true}
- resolveData={[Function]}
- rowsSelectorText="rows per page"
- rowsText="rows"
- showPageJump={false}
- showPageSizeOptions={true}
- showPagination={true}
- showPaginationBottom={true}
- showPaginationTop={false}
- sortable={true}
- style={Object {}}
- subRowsKey="_subRows"
- />
-</div>
+ filterable={true}
+ filtered={
+ Array [
+ Object {
+ "id": "datasource",
+ "value": "test",
+ },
+ ]
+ }
+ freezeWhenExpanded={false}
+ getLoadingProps={[Function]}
+ getNoDataProps={[Function]}
+ getPaginationProps={[Function]}
+ getProps={[Function]}
+ getResizerProps={[Function]}
+ getTableProps={[Function]}
+ getTbodyProps={[Function]}
+ getTdProps={[Function]}
+ getTfootProps={[Function]}
+ getTfootTdProps={[Function]}
+ getTfootTrProps={[Function]}
+ getTheadFilterProps={[Function]}
+ getTheadFilterThProps={[Function]}
+ getTheadFilterTrProps={[Function]}
+ getTheadGroupProps={[Function]}
+ getTheadGroupThProps={[Function]}
+ getTheadGroupTrProps={[Function]}
+ getTheadProps={[Function]}
+ getTheadThProps={[Function]}
+ getTheadTrProps={[Function]}
+ getTrGroupProps={[Function]}
+ getTrProps={[Function]}
+ groupedByPivotKey="_groupedByPivot"
+ indexKey="_index"
+ loading={true}
+ loadingText="Loading..."
+ manual={true}
+ multiSort={true}
+ nestingLevelKey="_nestingLevel"
+ nextText="Next"
+ noDataText=""
+ ofText=""
+ onFetchData={[Function]}
+ onFilteredChange={[Function]}
+ originalKey="_original"
+ pageJumpText="jump to page"
+ pageSizeOptions={
+ Array [
+ 5,
+ 10,
+ 20,
+ 25,
+ 50,
+ 100,
+ ]
+ }
+ pageText="Page"
+ pages={10000000}
+ pivotDefaults={Object {}}
+ pivotIDKey="_pivotID"
+ pivotValKey="_pivotVal"
+ previousText="Previous"
+ resizable={true}
+ resolveData={[Function]}
+ rowsSelectorText="rows per page"
+ rowsText="rows"
+ showPageJump={false}
+ showPageSizeOptions={true}
+ showPagination={true}
+ showPaginationBottom={true}
+ showPaginationTop={false}
+ sortable={true}
+ style={Object {}}
+ 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>
`;
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index 942caa2..5c0b397 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -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,89 +416,152 @@ 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>
- );
- }}
/>
);
}
+ 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;
- const { tableColumnSelectionHandler } = this;
return (
- <div className="segments-view app-view">
- <ViewControlBar label="Segments">
- <RefreshButton
- onRefresh={auto =>
- noSqlMode
- ? this.segmentsJsonQueryManager.rerunLastQueryInBackground(auto)
- : this.segmentsSqlQueryManager.rerunLastQueryInBackground(auto)
- }
- localStorageKey={LocalStorageKeys.SEGMENTS_REFRESH_RATE}
- />
- {!noSqlMode && (
- <Button
- icon={IconNames.APPLICATION}
- text="Go to SQL"
- hidden={noSqlMode}
- onClick={() => goToQuery(this.segmentsSqlQueryManager.getLastQuery().query)}
+ <>
+ <div className="segments-view app-view">
+ <ViewControlBar label="Segments">
+ <RefreshButton
+ onRefresh={auto =>
+ noSqlMode
+ ? this.segmentsJsonQueryManager.rerunLastQueryInBackground(auto)
+ : this.segmentsSqlQueryManager.rerunLastQueryInBackground(auto)
+ }
+ localStorageKey={LocalStorageKeys.SEGMENTS_REFRESH_RATE}
+ />
+ {!noSqlMode && (
+ <Button
+ icon={IconNames.APPLICATION}
+ text="Go to SQL"
+ hidden={noSqlMode}
+ onClick={() => goToQuery(this.segmentsSqlQueryManager.getLastQuery().query)}
+ />
+ )}
+ <TableColumnSelector
+ columns={noSqlMode ? tableColumnsNoSql : tableColumns}
+ onChange={column => this.setState({ hiddenColumns: hiddenColumns.toggle(column) })}
+ tableColumnsHidden={hiddenColumns.storedArray}
/>
- )}
- <TableColumnSelector
- columns={noSqlMode ? tableColumnsNoSql : tableColumns}
- onChange={column => tableColumnSelectionHandler.changeTableColumnSelector(column)}
- tableColumnsHidden={tableColumnSelectionHandler.hiddenColumns}
+ </ViewControlBar>
+ {this.renderSegmentsTable()}
+ </div>
+ {this.renderTerminateSegmentAction()}
+ {segmentTableActionDialogId && (
+ <SegmentTableActionDialog
+ segmentId={segmentTableActionDialogId}
+ dataSourceId={datasourceTableActionDialogId}
+ actions={actions}
+ onClose={() => this.setState({ segmentTableActionDialogId: null })}
+ isOpen
/>
- </ViewControlBar>
- {this.renderSegmentsTable()}
- </div>
+ )}
+ </>
);
}
}
diff --git a/web-console/src/views/servers-view/servers-view.tsx b/web-console/src/views/servers-view/servers-view.tsx
index af02784..e8a0433 100644
--- a/web-console/src/views/servers-view/servers-view.tsx
+++ b/web-console/src/views/servers-view/servers-view.tsx
@@ -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(
- LocalStorageKeys.SERVER_TABLE_COLUMN_SELECTION,
- () => this.setState({}),
- );
+ hiddenColumns: new LocalStorageBackedArray<string>(
+ LocalStorageKeys.SERVER_TABLE_COLUMN_SELECTION,
+ ),
+ };
}
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()}
diff --git a/web-console/src/views/task-view/tasks-view.tsx b/web-console/src/views/task-view/tasks-view.tsx
index 0f7505d..3ab7293 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -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(
- LocalStorageKeys.TASK_TABLE_COLUMN_SELECTION,
- () => this.setState({}),
- );
+ hiddenTaskColumns: new LocalStorageBackedArray<string>(
+ LocalStorageKeys.TASK_TABLE_COLUMN_SELECTION,
+ ),
+ 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()}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org