This is an automated email from the ASF dual-hosted git repository.
cwylie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git
The following commit(s) were added to refs/heads/master by this push:
new 96b9b30 Web console: Adding a server view that can display all
servers (#7770)
96b9b30 is described below
commit 96b9b3073cb350e6ee44625327134384de899820
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Tue May 28 17:12:50 2019 -0700
Web console: Adding a server view that can display all servers (#7770)
* adding a server view that can see all servers
* fixing action-cell snapshot test
* dynamic resize on sql view
* remove extra word
* update home view server tile also
* offer more context options
* pass context to explain also
---
web-console/package.json | 2 +-
web-console/src/bootstrap/react-table-defaults.tsx | 4 +-
.../__snapshots__/action-cell.spec.tsx.snap | 22 +-
.../components/action-cell/action-cell.spec.tsx | 8 +-
.../src/components/action-cell/action-cell.tsx | 27 +-
.../__snapshots__/header-bar.spec.tsx.snap | 2 +-
.../src/components/header-bar/header-bar.scss | 4 +-
.../src/components/header-bar/header-bar.tsx | 2 +-
.../__snapshots__/sql-control.spec.tsx.snap | 134 ++--
.../src/components/sql-control/sql-control.scss | 24 +-
.../components/sql-control/sql-control.spec.tsx | 15 +-
.../src/components/sql-control/sql-control.tsx | 129 ++--
.../view-control-bar/view-control-bar.scss | 4 +-
web-console/src/console-application.scss | 2 +-
web-console/src/entry.scss | 2 +
web-console/src/utils/general.tsx | 12 +-
web-console/src/utils/ingestion-spec.tsx | 41 +-
web-console/src/utils/local-storage-keys.tsx | 4 +-
web-console/src/variables.scss | 2 +
.../__snapshots__/datasource-view.spec.tsx.snap | 2 +-
.../src/views/datasource-view/datasource-view.scss | 4 +-
.../src/views/datasource-view/datasource-view.tsx | 30 +-
.../__snapshots__/home-view.spec.tsx.snap | 2 +-
web-console/src/views/home-view/home-view.tsx | 297 ++++----
.../src/views/load-data-view/load-data-view.tsx | 9 +-
.../__snapshots__/lookups-view.spec.tsx.snap | 2 +-
.../src/views/lookups-view/lookups-view.tsx | 26 +-
.../__snapshots__/segments-view.spec.tsx.snap | 2 +-
.../src/views/segments-view/segments-view.scss | 4 +-
.../src/views/segments-view/segments-view.tsx | 3 +-
.../__snapshots__/servers-view.spec.tsx.snap | 773 ++++++++-------------
.../src/views/servers-view/servers-view.scss | 18 +-
.../src/views/servers-view/servers-view.tsx | 512 +++++++-------
.../sql-view/__snapshots__/sql-view.spec.tsx.snap | 297 ++++----
web-console/src/views/sql-view/sql-view.scss | 36 +-
web-console/src/views/sql-view/sql-view.tsx | 107 +--
.../__snapshots__/tasks-view.spec.tsx.snap | 6 +-
web-console/src/views/task-view/tasks-view.scss | 36 +-
web-console/src/views/task-view/tasks-view.tsx | 104 ++-
39 files changed, 1298 insertions(+), 1412 deletions(-)
diff --git a/web-console/package.json b/web-console/package.json
index 2191296..4cf6311 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -31,7 +31,7 @@
"run": "./script/run",
"test": "jest --silent 2>&1",
"coverage": "jest --coverage",
- "update": "jest -u",
+ "update-snapshots": "jest -u",
"tslint": "./node_modules/.bin/tslint -c tslint.json --project
tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter
'src/**/*.ts?(x)'",
"tslint-fix": "npm run tslint -- --fix",
"tslint-changed-only": "git diff --diff-filter=ACMR --cached --name-only |
grep -E \\.tsx\\?$ | xargs ./node_modules/.bin/tslint -c tslint.json --project
tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter",
diff --git a/web-console/src/bootstrap/react-table-defaults.tsx
b/web-console/src/bootstrap/react-table-defaults.tsx
index b4dde83..d68912b 100644
--- a/web-console/src/bootstrap/react-table-defaults.tsx
+++ b/web-console/src/bootstrap/react-table-defaults.tsx
@@ -37,6 +37,7 @@ class NoData extends React.Component {
/* tslint:enable:max-classes-per-file */
Object.assign(ReactTableDefaults, {
+ className: '-striped -highlight',
defaultFilterMethod: (filter: Filter, row: any, column: any) => {
const id = filter.pivotId || filter.id;
return booleanCustomTableFilter(filter, row[id]);
@@ -51,5 +52,6 @@ Object.assign(ReactTableDefaults, {
const previewValues = subRows.filter((d: any) => typeof d[column.id] !==
'undefined').map((row: any) => row[column.id]);
const previewCount = countBy(previewValues);
return <span>{Object.keys(previewCount).sort().map(v => `${v}
(${previewCount[v]})`).join(', ')}</span>;
- }
+ },
+ defaultPageSize: 20
});
diff --git
a/web-console/src/components/action-cell/__snapshots__/action-cell.spec.tsx.snap
b/web-console/src/components/action-cell/__snapshots__/action-cell.spec.tsx.snap
index f3a8876..e1c819e 100644
---
a/web-console/src/components/action-cell/__snapshots__/action-cell.spec.tsx.snap
+++
b/web-console/src/components/action-cell/__snapshots__/action-cell.spec.tsx.snap
@@ -4,8 +4,24 @@ exports[`describe action cell action cell snapshot 1`] = `
<div
class="action-cell"
>
- <div>
- hello world
- </div>
+ <span
+ class="bp3-icon bp3-icon-search-template"
+ icon="search-template"
+ >
+ <svg
+ data-icon="search-template"
+ height="16"
+ viewBox="0 0 16 16"
+ width="16"
+ >
+ <desc>
+ search-template
+ </desc>
+ <path
+ d="M15.55 13.43l-2.67-2.67c.7-1.09 1.11-2.38 1.11-3.77
0-3.87-3.13-7-7-7s-7 3.13-7 7 3.13 7 7 7c1.39 0 2.68-.41 3.77-1.11l2.67
2.67a1.498 1.498 0 1 0 2.12-2.12zm-8.56-1.44c-2.76 0-5-2.24-5-5s2.24-5 5-5 5
2.24 5 5-2.24 5-5 5zm2.5-6h-5c-.28 0-.5.22-.5.5s.22.5.5.5h5c.28 0
.5-.22.5-.5s-.22-.5-.5-.5zm0-2h-5c-.28 0-.5.22-.5.5s.22.5.5.5h5c.28 0
.5-.22.5-.5s-.22-.5-.5-.5zm0 4h-5c-.28 0-.5.22-.5.5s.22.5.5.5h5c.28 0
.5-.22.5-.5s-.22-.5-.5-.5z"
+ fill-rule="evenodd"
+ />
+ </svg>
+ </span>
</div>
`;
diff --git a/web-console/src/components/action-cell/action-cell.spec.tsx
b/web-console/src/components/action-cell/action-cell.spec.tsx
index 4683591..94d6471 100644
--- a/web-console/src/components/action-cell/action-cell.spec.tsx
+++ b/web-console/src/components/action-cell/action-cell.spec.tsx
@@ -25,10 +25,10 @@ import { ActionCell} from './action-cell';
describe('describe action cell', () => {
it('action cell snapshot', () => {
- const actionCell =
- <ActionCell >
- <div>hello world</div>
- </ActionCell>;
+ const actionCell = <ActionCell
+ onDetail={() => null}
+ actions={[]}
+ />;
const { container, getByText } = render(actionCell);
expect(container.firstChild).toMatchSnapshot();
});
diff --git a/web-console/src/components/action-cell/action-cell.tsx
b/web-console/src/components/action-cell/action-cell.tsx
index e37b2c9..5252132 100644
--- a/web-console/src/components/action-cell/action-cell.tsx
+++ b/web-console/src/components/action-cell/action-cell.tsx
@@ -16,21 +16,46 @@
* limitations under the License.
*/
+import { Icon, Popover, Position } from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
import * as React from 'react';
+import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
+
import './action-cell.scss';
export interface ActionCellProps extends React.Props<any> {
+ onDetail?: () => void;
+ actions?: BasicAction[];
}
export class ActionCell extends React.Component<ActionCellProps, {}> {
+ static COLUMN_ID = 'actions';
+ static COLUMN_LABEL = 'Actions';
+ static COLUMN_WIDTH = 70;
+
constructor(props: ActionCellProps, context: any) {
super(props, context);
}
render() {
+ const { onDetail, actions } = this.props;
+ const actionsMenu = actions ? basicActionsToMenu(actions) : null;
+
return <div className="action-cell">
- {this.props.children}
+ {
+ onDetail &&
+ <Icon
+ icon={IconNames.SEARCH_TEMPLATE}
+ onClick={() => onDetail()}
+ />
+ }
+ {
+ actionsMenu &&
+ <Popover content={actionsMenu} position={Position.BOTTOM_RIGHT}>
+ <Icon icon={IconNames.WRENCH}/>
+ </Popover>
+ }
</div>;
}
}
diff --git
a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
index 29b24e3..64769a7 100644
---
a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
+++
b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
@@ -99,7 +99,7 @@ exports[`describe header bar header bar snapshot 1`] = `
href="#servers"
icon="database"
minimal={true}
- text="Data servers"
+ text="Servers"
/>
<Blueprint3.NavbarDivider />
<Blueprint3.AnchorButton
diff --git a/web-console/src/components/header-bar/header-bar.scss
b/web-console/src/components/header-bar/header-bar.scss
index 77360c7..9ac063c 100644
--- a/web-console/src/components/header-bar/header-bar.scss
+++ b/web-console/src/components/header-bar/header-bar.scss
@@ -16,13 +16,15 @@
* limitations under the License.
*/
+@import "../../variables";
+
.header-bar {
overflow: hidden;
.logo {
position: relative;
width: 100px;
- height: 50px;
+ height: $header-bar-height;
svg {
position: absolute;
diff --git a/web-console/src/components/header-bar/header-bar.tsx
b/web-console/src/components/header-bar/header-bar.tsx
index be8bbfe..6792db2 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -163,7 +163,7 @@ export class HeaderBar extends
React.Component<HeaderBarProps, HeaderBarState> {
<AnchorButton minimal active={active === 'datasources'}
icon={IconNames.MULTI_SELECT} text="Datasources" href="#datasources" />
<AnchorButton minimal active={active === 'segments'}
icon={IconNames.STACKED_CHART} text="Segments" href="#segments" />
<AnchorButton minimal active={active === 'tasks'}
icon={IconNames.GANTT_CHART} text="Tasks" href="#tasks" />
- <AnchorButton minimal active={active === 'servers'}
icon={IconNames.DATABASE} text="Data servers" href="#servers" />
+ <AnchorButton minimal active={active === 'servers'}
icon={IconNames.DATABASE} text="Servers" href="#servers" />
<NavbarDivider/>
<AnchorButton minimal active={active === 'query'}
icon={IconNames.APPLICATION} text="Query" href="#query" />
diff --git
a/web-console/src/components/sql-control/__snapshots__/sql-control.spec.tsx.snap
b/web-console/src/components/sql-control/__snapshots__/sql-control.spec.tsx.snap
index 1fb9aa3..033a1b0 100644
---
a/web-console/src/components/sql-control/__snapshots__/sql-control.spec.tsx.snap
+++
b/web-console/src/components/sql-control/__snapshots__/sql-control.spec.tsx.snap
@@ -5,92 +5,96 @@ exports[`describe sql control sql control snapshot 1`] = `
class="sql-control"
>
<div
- class=" ace_editor ace-solarized-dark ace_dark ace_focus"
- id="ace-editor"
- style="width: 100%; height: 30vh; font-size: 14px;"
+ class="ace-container"
>
- <textarea
- autocapitalize="off"
- autocorrect="off"
- class="ace_text-input"
- spellcheck="false"
- style="opacity: 0; position: fixed; top: 0px;"
- wrap="off"
- />
<div
- aria-hidden="true"
- class="ace_gutter"
+ class=" ace_editor ace-solarized-dark ace_dark ace_focus"
+ id="ace-editor"
+ style="width: 100%; height: 200px; font-size: 14px;"
>
- <div
- class="ace_layer ace_gutter-layer ace_folding-enabled"
+ <textarea
+ autocapitalize="off"
+ autocorrect="off"
+ class="ace_text-input"
+ spellcheck="false"
+ style="opacity: 0; position: fixed; top: 0px;"
+ wrap="off"
/>
<div
- class="ace_gutter-active-line"
- />
- </div>
- <div
- class="ace_scroller"
- >
+ aria-hidden="true"
+ class="ace_gutter"
+ >
+ <div
+ class="ace_layer ace_gutter-layer ace_folding-enabled"
+ />
+ <div
+ class="ace_gutter-active-line"
+ />
+ </div>
<div
- class="ace_content"
+ class="ace_scroller"
>
<div
- class="ace_layer ace_print-margin-layer"
+ class="ace_content"
>
<div
- class="ace_print-margin"
- style="left: 4px; visibility: hidden;"
+ class="ace_layer ace_print-margin-layer"
+ >
+ <div
+ class="ace_print-margin"
+ style="left: 4px; visibility: hidden;"
+ />
+ </div>
+ <div
+ class="ace_layer ace_marker-layer"
+ />
+ <div
+ class="ace_layer ace_text-layer"
+ style="padding: 0px 4px;"
/>
+ <div
+ class="ace_layer ace_marker-layer"
+ />
+ <div
+ class="ace_layer ace_cursor-layer"
+ >
+ <div
+ class="ace_cursor"
+ />
+ </div>
</div>
+ </div>
+ <div
+ class="ace_scrollbar ace_scrollbar-v"
+ style="display: none; width: 20px;"
+ >
<div
- class="ace_layer ace_marker-layer"
+ class="ace_scrollbar-inner"
+ style="width: 20px;"
/>
+ </div>
+ <div
+ class="ace_scrollbar ace_scrollbar-h"
+ style="display: none; height: 20px;"
+ >
<div
- class="ace_layer ace_text-layer"
- style="padding: 0px 4px;"
+ class="ace_scrollbar-inner"
+ style="height: 20px;"
/>
+ </div>
+ <div
+ style="height: auto; width: auto; top: 0px; left: 0px; visibility:
hidden; position: absolute; white-space: pre; overflow: hidden;"
+ >
<div
- class="ace_layer ace_marker-layer"
+ style="height: auto; width: auto; top: 0px; left: 0px; visibility:
hidden; position: absolute; white-space: pre; overflow: visible;"
/>
<div
- class="ace_layer ace_cursor-layer"
+ style="height: auto; width: auto; top: 0px; left: 0px; visibility:
hidden; position: absolute; white-space: pre; overflow: visible;"
>
- <div
- class="ace_cursor"
- />
+
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
</div>
</div>
</div>
- <div
- class="ace_scrollbar ace_scrollbar-v"
- style="display: none; width: 20px;"
- >
- <div
- class="ace_scrollbar-inner"
- style="width: 20px;"
- />
- </div>
- <div
- class="ace_scrollbar ace_scrollbar-h"
- style="display: none; height: 20px;"
- >
- <div
- class="ace_scrollbar-inner"
- style="height: 20px;"
- />
- </div>
- <div
- style="height: auto; width: auto; top: 0px; left: 0px; visibility:
hidden; position: absolute; white-space: pre; overflow: hidden;"
- >
- <div
- style="height: auto; width: auto; top: 0px; left: 0px; visibility:
hidden; position: absolute; white-space: pre; overflow: visible;"
- />
- <div
- style="height: auto; width: auto; top: 0px; left: 0px; visibility:
hidden; position: absolute; white-space: pre; overflow: visible;"
- >
-
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- </div>
- </div>
</div>
<div
class="buttons"
@@ -163,9 +167,7 @@ exports[`describe sql control sql control snapshot 1`] = `
<span
class="query-elapsed"
>
- Last query took
- 0.00
- seconds
+ Last query took 0.00 seconds
</span>
</div>
</div>
diff --git a/web-console/src/components/sql-control/sql-control.scss
b/web-console/src/components/sql-control/sql-control.scss
index 2fe4036..694de36 100644
--- a/web-console/src/components/sql-control/sql-control.scss
+++ b/web-console/src/components/sql-control/sql-control.scss
@@ -16,21 +16,31 @@
* limitations under the License.
*/
+@import "../../variables";
.sql-control {
+ .ace-container {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ bottom: 30px + $standard-padding;
- .ace_scroller {
- background-color: #232C35;
- }
+ .ace_scroller {
+ background-color: #232C35;
+ }
- .ace_gutter-layer {
- background-color: #27313c;
+ .ace_gutter-layer {
+ background-color: #27313c;
+ }
}
.buttons {
- position: relative;
+ position: absolute;
+ width: 100%;
+ bottom: 0;
+ height: 30px;
- button{
+ button {
margin-right: 15px;
}
diff --git a/web-console/src/components/sql-control/sql-control.spec.tsx
b/web-console/src/components/sql-control/sql-control.spec.tsx
index 73f7902..8b6bcb4 100644
--- a/web-console/src/components/sql-control/sql-control.spec.tsx
+++ b/web-console/src/components/sql-control/sql-control.spec.tsx
@@ -21,16 +21,15 @@ import { render } from 'react-testing-library';
import {SqlControl} from './sql-control';
-
describe('describe sql control', () => {
it('sql control snapshot', () => {
- const sqlControl =
- <SqlControl
- initSql={'test'}
- onRun={(query: string, bypassCache: boolean, wrapQuery: boolean) => {}}
- onExplain={(sqlQuery: string) => {}}
- queryElapsed={2}
- />
+ const sqlControl = <SqlControl
+ initSql={'test'}
+ onRun={(query, context, wrapQuery) => {}}
+ onExplain={(sqlQuery, context) => {}}
+ queryElapsed={2}
+ />;
+
const { container, getByText } = render(sqlControl);
expect(container.firstChild).toMatchSnapshot();
});
diff --git a/web-console/src/components/sql-control/sql-control.tsx
b/web-console/src/components/sql-control/sql-control.tsx
index 06f7e80..69d7a00 100644
--- a/web-console/src/components/sql-control/sql-control.tsx
+++ b/web-console/src/components/sql-control/sql-control.tsx
@@ -19,11 +19,11 @@
import {
Button,
ButtonGroup,
- Intent,
+ Intent, IResizeEntry,
Menu,
MenuItem,
Popover,
- Position
+ Position, ResizeSensor
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import axios from 'axios';
@@ -32,7 +32,6 @@ import 'brace/ext/language_tools';
import 'brace/mode/hjson';
import 'brace/mode/sql';
import 'brace/theme/solarized_dark';
-import * as classNames from 'classnames';
import * as Hjson from 'hjson';
import * as React from 'react';
import AceEditor from 'react-ace';
@@ -46,7 +45,6 @@ import { MenuCheckbox } from
'./../menu-checkbox/menu-checkbox';
import './sql-control.scss';
-
function validHjson(query: string) {
try {
Hjson.parse(query);
@@ -60,8 +58,8 @@ const langTools = ace.acequire('ace/ext/language_tools');
export interface SqlControlProps extends React.Props<any> {
initSql: string | null;
- onRun: (query: string, bypassCache: boolean, wrapQuery: boolean) => void;
- onExplain: (sqlQuery: string) => void;
+ onRun: (query: string, context: Record<string, any>, wrapQuery: boolean) =>
void;
+ onExplain: (sqlQuery: string, context: Record<string, any>) => void;
queryElapsed: number | null;
}
@@ -69,8 +67,14 @@ export interface SqlControlState {
query: string;
autoComplete: boolean;
autoCompleteLoading: boolean;
- bypassCache: boolean;
wrapQuery: boolean;
+ useApproximateCountDistinct: boolean;
+ useApproximateTopN: boolean;
+ useCache: boolean;
+
+ // For reasons (https://github.com/securingsincity/react-ace/issues/415)
react ace editor needs an explicit height
+ // Since this component will grown and shrink dynamically we will measure
its height and then set it.
+ editorHeight: number;
}
export class SqlControl extends React.Component<SqlControlProps,
SqlControlState> {
@@ -80,8 +84,12 @@ export class SqlControl extends
React.Component<SqlControlProps, SqlControlState
query: props.initSql || '',
autoComplete: true,
autoCompleteLoading: false,
- bypassCache: false,
- wrapQuery: true
+ wrapQuery: true,
+ useApproximateCountDistinct: true,
+ useApproximateTopN: true,
+ useCache: true,
+
+ editorHeight: 200
};
}
@@ -209,6 +217,26 @@ export class SqlControl extends
React.Component<SqlControlProps, SqlControlState
this.addCompleters();
}
+ getContext(): Record<string, any> {
+ const { useCache, useApproximateCountDistinct, useApproximateTopN } =
this.state;
+ const context: Record<string, any> = {};
+
+ if (useCache === false) {
+ context.useCache = false;
+ context.populateCache = false;
+ }
+
+ if (useApproximateCountDistinct === false) {
+ context.useApproximateCountDistinct = false;
+ }
+
+ if (useApproximateTopN === false) {
+ context.useApproximateTopN = false;
+ }
+
+ return context;
+ }
+
private handleChange = (newValue: string): void => {
this.setState({
query: newValue
@@ -217,13 +245,18 @@ export class SqlControl extends
React.Component<SqlControlProps, SqlControlState
private onRunClick = () => {
const { onRun } = this.props;
- const { query, bypassCache, wrapQuery } = this.state;
- onRun(query, bypassCache, wrapQuery);
+ const { query, wrapQuery } = this.state;
+ onRun(query, this.getContext(), wrapQuery);
+ }
+
+ private handleAceContainerResize = (entries: IResizeEntry[]) => {
+ if (entries.length !== 1) return;
+ this.setState({ editorHeight: entries[0].contentRect.height });
}
renderExtraMenu(isRune: boolean) {
const { onExplain } = this.props;
- const { query, autoComplete, bypassCache, wrapQuery } = this.state;
+ const { query, autoComplete, useCache, wrapQuery,
useApproximateCountDistinct, useApproximateTopN } = this.state;
return <Menu>
<MenuItem
@@ -238,7 +271,7 @@ export class SqlControl extends
React.Component<SqlControlProps, SqlControlState
<MenuItem
icon={IconNames.CLEAN}
text="Explain"
- onClick={() => onExplain(query)}
+ onClick={() => onExplain(query, this.getContext())}
/>
<MenuCheckbox
checked={wrapQuery}
@@ -250,45 +283,59 @@ export class SqlControl extends
React.Component<SqlControlProps, SqlControlState
label="Auto complete"
onChange={() => this.setState({autoComplete: !autoComplete})}
/>
+ <MenuCheckbox
+ checked={useApproximateCountDistinct}
+ label="Use approximate COUNT(DISTINCT)"
+ onChange={() => this.setState({useApproximateCountDistinct:
!useApproximateCountDistinct})}
+ />
+ <MenuCheckbox
+ checked={useApproximateTopN}
+ label="Use approximate TopN"
+ onChange={() => this.setState({useApproximateTopN:
!useApproximateTopN})}
+ />
</>
}
<MenuCheckbox
- checked={bypassCache}
- label="Bypass cache"
- onChange={() => this.setState({bypassCache: !bypassCache})}
+ checked={useCache}
+ label="Use cache"
+ onChange={() => this.setState({useCache: !useCache})}
/>
</Menu>;
}
render() {
const { queryElapsed } = this.props;
- const { query, autoComplete, wrapQuery } = this.state;
+ const { query, autoComplete, wrapQuery, editorHeight } = this.state;
const isRune = query.trim().startsWith('{');
// Set the key in the AceEditor to force a rebind and prevent an error
that happens otherwise
return <div className="sql-control">
- <AceEditor
- key={isRune ? 'hjson' : 'sql'}
- mode={isRune ? 'hjson' : 'sql'}
- theme="solarized_dark"
- name="ace-editor"
- onChange={this.handleChange}
- focus
- fontSize={14}
- width="100%"
- height="30vh"
- showPrintMargin={false}
- value={query}
- editorProps={{
- $blockScrolling: Infinity
- }}
- setOptions={{
- enableBasicAutocompletion: isRune ? false : autoComplete,
- enableLiveAutocompletion: isRune ? false : autoComplete,
- showLineNumbers: true,
- tabSize: 2
- }}
- />
+ <ResizeSensor onResize={this.handleAceContainerResize}>
+ <div className="ace-container">
+ <AceEditor
+ key={isRune ? 'hjson' : 'sql'}
+ mode={isRune ? 'hjson' : 'sql'}
+ theme="solarized_dark"
+ name="ace-editor"
+ onChange={this.handleChange}
+ focus
+ fontSize={14}
+ width="100%"
+ height={`${editorHeight}px`}
+ showPrintMargin={false}
+ value={query}
+ editorProps={{
+ $blockScrolling: Infinity
+ }}
+ setOptions={{
+ enableBasicAutocompletion: isRune ? false : autoComplete,
+ enableLiveAutocompletion: isRune ? false : autoComplete,
+ showLineNumbers: true,
+ tabSize: 2
+ }}
+ />
+ </div>
+ </ResizeSensor>
<div className="buttons">
<ButtonGroup>
<Button
@@ -303,7 +350,9 @@ export class SqlControl extends
React.Component<SqlControlProps, SqlControlState
</ButtonGroup>
{
queryElapsed &&
- <span className="query-elapsed"> Last query took {(queryElapsed /
1000).toFixed(2)} seconds</span>
+ <span className="query-elapsed">
+ {`Last query took ${(queryElapsed / 1000).toFixed(2)} seconds`}
+ </span>
}
</div>
</div>;
diff --git a/web-console/src/components/view-control-bar/view-control-bar.scss
b/web-console/src/components/view-control-bar/view-control-bar.scss
index 40cb7f2..5b7aa89 100644
--- a/web-console/src/components/view-control-bar/view-control-bar.scss
+++ b/web-console/src/components/view-control-bar/view-control-bar.scss
@@ -16,8 +16,10 @@
* limitations under the License.
*/
+@import "../../variables";
+
.view-control-bar {
- height: 30px;
+ height: $view-control-bar-height;
white-space: nowrap;
& > * {
diff --git a/web-console/src/console-application.scss
b/web-console/src/console-application.scss
index 0c702e6..c17a48b 100644
--- a/web-console/src/console-application.scss
+++ b/web-console/src/console-application.scss
@@ -25,7 +25,7 @@
.view-container {
position: absolute;
- top: 50px;
+ top: $header-bar-height;
left: 0;
right: 0;
bottom: 0;
diff --git a/web-console/src/entry.scss b/web-console/src/entry.scss
index ed31ec4..fcea2e2 100644
--- a/web-console/src/entry.scss
+++ b/web-console/src/entry.scss
@@ -72,3 +72,5 @@ svg {
}
}
}
+
+// Some SplitterLayout globals
diff --git a/web-console/src/utils/general.tsx
b/web-console/src/utils/general.tsx
index 87d5e24..64ec28c 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -122,11 +122,15 @@ export function countBy<T>(array: T[], fn: (x: T, index:
number) => string = Str
return counts;
}
-export function lookupBy<T>(array: T[], fn: (x: T, index: number) => string =
String): Record<string, T> {
- const lookup: Record<string, T> = {};
+function identity(x: any): any {
+ return x;
+}
+
+export function lookupBy<T, Q>(array: T[], keyFn: (x: T, index: number) =>
string = String, valueFn: (x: T, index: number) => Q = identity):
Record<string, Q> {
+ const lookup: Record<string, Q> = {};
for (let i = 0; i < array.length; i++) {
- const key = fn(array[i], i);
- lookup[key] = array[i];
+ const a = array[i];
+ lookup[keyFn(a, i)] = valueFn(a, i);
}
return lookup;
}
diff --git a/web-console/src/utils/ingestion-spec.tsx
b/web-console/src/utils/ingestion-spec.tsx
index 8c83f67..355dc39 100644
--- a/web-console/src/utils/ingestion-spec.tsx
+++ b/web-console/src/utils/ingestion-spec.tsx
@@ -841,51 +841,50 @@ export function
getIoConfigTuningFormFields(ingestionComboType: IngestionComboTy
case 'index:http':
case 'index:static-s3':
case 'index:static-google-blobstore':
- const objectType = ingestionComboType === 'index:http' ? 'http' : 'S3';
return [
{
- name: 'firehose.maxCacheCapacityBytes',
- label: 'Max cache capacity bytes',
+ name: 'firehose.fetchTimeout',
+ label: 'Fetch timeout',
type: 'number',
- defaultValue: 1073741824,
+ defaultValue: 60000,
info: <>
- <p>Maximum size of the cache space in bytes. 0 means disabling
cache. Cached files are not removed until the ingestion task completes.</p>
+ <p>Timeout for fetching the object.</p>
</>
},
{
- name: 'firehose.maxFetchCapacityBytes',
- label: 'Max fetch capacity bytes',
+ name: 'firehose.maxFetchRetry',
+ label: 'Max fetch retry',
type: 'number',
- defaultValue: 1073741824,
+ defaultValue: 3,
info: <>
- <p>Maximum size of the fetch space in bytes. 0 means disabling
prefetch. Prefetched files are removed immediately once they are read.</p>
+ <p>Maximum retry for fetching the object.</p>
</>
},
{
- name: 'firehose.prefetchTriggerBytes',
- label: 'Prefetch trigger bytes',
+ name: 'firehose.maxCacheCapacityBytes',
+ label: 'Max cache capacity bytes',
type: 'number',
+ defaultValue: 1073741824,
info: <>
- <p>Threshold to trigger prefetching {objectType} objects.</p>
- <p>Default: maxFetchCapacityBytes / 2</p>
+ <p>Maximum size of the cache space in bytes. 0 means disabling
cache. Cached files are not removed until the ingestion task completes.</p>
</>
},
{
- name: 'firehose.fetchTimeout',
- label: 'Fetch timeout',
+ name: 'firehose.maxFetchCapacityBytes',
+ label: 'Max fetch capacity bytes',
type: 'number',
- defaultValue: 60000,
+ defaultValue: 1073741824,
info: <>
- <p>Timeout for fetching a http object.</p>
+ <p>Maximum size of the fetch space in bytes. 0 means disabling
prefetch. Prefetched files are removed immediately once they are read.</p>
</>
},
{
- name: 'firehose.maxFetchRetry',
- label: 'Max fetch retry',
+ name: 'firehose.prefetchTriggerBytes',
+ label: 'Prefetch trigger bytes',
type: 'number',
- defaultValue: 3,
+ placeholder: 'maxFetchCapacityBytes / 2',
info: <>
- <p>Maximum retry for fetching a {objectType} object.</p>
+ <p>Threshold to trigger prefetching the objects.</p>
</>
}
];
diff --git a/web-console/src/utils/local-storage-keys.tsx
b/web-console/src/utils/local-storage-keys.tsx
index 50bbd51..7ed711a 100644
--- a/web-console/src/utils/local-storage-keys.tsx
+++ b/web-console/src/utils/local-storage-keys.tsx
@@ -23,12 +23,10 @@ export const LocalStorageKeys = {
SUPERVISOR_TABLE_COLUMN_SELECTION: 'supervisor-table-column-selection' as
'supervisor-table-column-selection',
TASK_TABLE_COLUMN_SELECTION: 'task-table-column-selection' as
'task-table-column-selection',
SERVER_TABLE_COLUMN_SELECTION: 'historical-table-column-selection' as
'historical-table-column-selection',
- MIDDLEMANAGER_TABLE_COLUMN_SELECTION: 'middleManager-table-column-selection'
as 'middleManager-table-column-selection',
LOOKUP_TABLE_COLUMN_SELECTION: 'lookup-table-column-selection' as
'lookup-table-column-selection',
QUERY_KEY: 'druid-console-query' as 'druid-console-query',
TASKS_VIEW_PANE_SIZE: 'tasks-view-pane-size' as 'tasks-view-pane-size',
- SERVERS_VIEW_PANE_SIZE: 'servers-view-pane-size' as 'servers-view-pane-size'
-
+ QUERY_VIEW_PANE_SIZE: 'query-view-pane-size' as 'query-view-pane-size'
};
export type LocalStorageKeys = typeof LocalStorageKeys[keyof typeof
LocalStorageKeys];
diff --git a/web-console/src/variables.scss b/web-console/src/variables.scss
index 6a867c8..ef3c770 100644
--- a/web-console/src/variables.scss
+++ b/web-console/src/variables.scss
@@ -16,4 +16,6 @@
* limitations under the License.
*/
+$header-bar-height: 50px;
+$view-control-bar-height: 30px;
$standard-padding: 15px;
diff --git
a/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
b/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
index edd0a39..516c111 100644
---
a/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
+++
b/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
@@ -57,7 +57,7 @@ exports[`describe data source view data source view snapshot
1`] = `
TrComponent={[Function]}
TrGroupComponent={[Function]}
aggregatedKey="_aggregated"
- className="-striped -highlight"
+ className=""
collapseOnDataChange={true}
collapseOnPageChange={true}
collapseOnSortingChange={true}
diff --git a/web-console/src/views/datasource-view/datasource-view.scss
b/web-console/src/views/datasource-view/datasource-view.scss
index b552485..62878d6 100644
--- a/web-console/src/views/datasource-view/datasource-view.scss
+++ b/web-console/src/views/datasource-view/datasource-view.scss
@@ -16,6 +16,8 @@
* limitations under the License.
*/
+@import "../../variables";
+
.data-sources-view {
height: 100%;
width: 100%;
@@ -26,7 +28,7 @@
.ReactTable {
position: absolute;
- top: 50px;
+ top: $view-control-bar-height + $standard-padding;
bottom: 0;
width: 100%;
}
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx
b/web-console/src/views/datasource-view/datasource-view.tsx
index 674d421..35d46f9 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -22,8 +22,8 @@ import axios from 'axios';
import * as React from 'react';
import ReactTable, { Filter } from 'react-table';
-import { ActionCell, RuleEditor, TableColumnSelection, ViewControlBar} from
'../../components/index';
-import { AsyncActionDialog, CompactionDialog, RetentionDialog } from
'../../dialogs/index';
+import { ActionCell, RuleEditor, TableColumnSelection, ViewControlBar} from
'../../components';
+import { AsyncActionDialog, CompactionDialog, RetentionDialog } from
'../../dialogs';
import { AppToaster } from '../../singletons/toaster';
import {
addFilter,
@@ -36,12 +36,12 @@ import {
queryDruidSql,
QueryManager, TableColumnSelectionHandler
} from '../../utils';
-import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
+import { BasicAction } from '../../utils/basic-action';
import './datasource-view.scss';
-const tableColumns: string[] = ['Datasource', 'Availability', 'Retention',
'Compaction', 'Size', 'Num rows', 'Actions'];
-const tableColumnsNoSql: string[] = ['Datasource', 'Availability',
'Retention', 'Compaction', 'Size', 'Actions'];
+const tableColumns: string[] = ['Datasource', 'Availability', 'Retention',
'Compaction', 'Size', 'Num rows', ActionCell.COLUMN_LABEL];
+const tableColumnsNoSql: string[] = ['Datasource', 'Availability',
'Retention', 'Compaction', 'Size', ActionCell.COLUMN_LABEL];
export interface DatasourcesViewProps extends React.Props<any> {
goToSql: (initSql: string) => void;
@@ -609,31 +609,21 @@ GROUP BY 1`);
show: !noSqlMode && tableColumnSelectionHandler.showColumn('Num
rows')
},
{
- Header: 'Actions',
+ Header: ActionCell.COLUMN_LABEL,
accessor: 'datasource',
- id: 'actions',
- width: 70,
+ id: ActionCell.COLUMN_ID,
+ width: ActionCell.COLUMN_WIDTH,
filterable: false,
Cell: row => {
const datasource = row.value;
const { disabled } = row.original;
const datasourceActions = this.getDatasourceActions(datasource,
disabled);
- const datasourceMenu = basicActionsToMenu(datasourceActions);
-
- return <ActionCell>
- {
- datasourceMenu &&
- <Popover content={datasourceMenu}
position={Position.BOTTOM_RIGHT}>
- <Icon icon={IconNames.WRENCH}/>
- </Popover>
- }
- </ActionCell>;
+ return <ActionCell actions={datasourceActions}/>;
},
- show: tableColumnSelectionHandler.showColumn('Actions')
+ show:
tableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL)
}
]}
defaultPageSize={50}
- className="-striped -highlight"
/>
{this.renderDropDataAction()}
{this.renderEnableAction()}
diff --git
a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
index 68a390f..b62f0f4 100644
--- a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
+++ b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
@@ -124,7 +124,7 @@ exports[`describe home view home viexw snapshot 1`] = `
icon="database"
/>
- Data servers
+ Servers
</Component>
<p>
Loading...
diff --git a/web-console/src/views/home-view/home-view.tsx
b/web-console/src/views/home-view/home-view.tsx
index 61c0540..ff113c3 100644
--- a/web-console/src/views/home-view/home-view.tsx
+++ b/web-console/src/views/home-view/home-view.tsx
@@ -22,7 +22,7 @@ import axios from 'axios';
import * as React from 'react';
import { UrlBaser } from '../../singletons/url-baser';
-import { getHeadProp, pluralIfNeeded, queryDruidSql, QueryManager } from
'../../utils';
+import { getHeadProp, lookupBy, pluralIfNeeded, queryDruidSql, QueryManager }
from '../../utils';
import './home-view.scss';
@@ -65,13 +65,15 @@ export interface HomeViewState {
waitingTaskCount: number;
taskCountError: string | null;
- dataServerCountLoading: boolean;
- dataServerCount: number;
- dataServerCountError: string | null;
-
- middleManagerCountLoading: boolean;
+ serverCountLoading: boolean;
+ coordinatorCount: number;
+ overlordCount: number;
+ routerCount: number;
+ brokerCount: number;
+ historicalCount: number;
middleManagerCount: number;
- middleManagerCountError: string | null;
+ peonCount: number;
+ serverCountError: string | null;
}
export class HomeView extends React.Component<HomeViewProps, HomeViewState> {
@@ -80,8 +82,7 @@ export class HomeView extends React.Component<HomeViewProps,
HomeViewState> {
private segmentQueryManager: QueryManager<string, any>;
private supervisorQueryManager: QueryManager<string, any>;
private taskQueryManager: QueryManager<string, any>;
- private dataServerQueryManager: QueryManager<string, any>;
- private middleManagerQueryManager: QueryManager<string, any>;
+ private serverQueryManager: QueryManager<string, any>;
constructor(props: HomeViewProps, context: any) {
super(props, context);
@@ -111,13 +112,15 @@ export class HomeView extends
React.Component<HomeViewProps, HomeViewState> {
waitingTaskCount: 0,
taskCountError: null,
- dataServerCountLoading: false,
- dataServerCount: 0,
- dataServerCountError: null,
-
- middleManagerCountLoading: false,
+ serverCountLoading: false,
+ coordinatorCount: 0,
+ overlordCount: 0,
+ routerCount: 0,
+ brokerCount: 0,
+ historicalCount: 0,
middleManagerCount: 0,
- middleManagerCountError: null
+ peonCount: 0,
+ serverCountError: null
};
}
@@ -168,11 +171,7 @@ export class HomeView extends
React.Component<HomeViewProps, HomeViewState> {
this.segmentQueryManager = new QueryManager({
processQuery: async (query) => {
- if (!noSqlMode) {
- const segments = await queryDruidSql({ query });
- return getHeadProp(segments, 'count') || 0;
- } else {
-
+ if (noSqlMode) {
const loadstatusResp = await
axios.get('/druid/coordinator/v1/loadstatus?simple');
const loadstatus = loadstatusResp.data;
const unavailableSegmentNum = Object.keys(loadstatus).reduce((sum,
key) => {
@@ -186,8 +185,12 @@ export class HomeView extends
React.Component<HomeViewProps, HomeViewState> {
}, 0);
return availableSegmentNum + unavailableSegmentNum;
- }
+ } else {
+ const segments = await queryDruidSql({ query });
+ return getHeadProp(segments, 'count') || 0;
+
+ }
},
onStateChange: ({ result, loading, error }) => {
this.setState({
@@ -228,37 +231,33 @@ export class HomeView extends
React.Component<HomeViewProps, HomeViewState> {
this.taskQueryManager = new QueryManager({
processQuery: async (query) => {
- let taskCountsFromQuery: {status: string, count: number}[] = [];
- if (!noSqlMode) {
- taskCountsFromQuery = await queryDruidSql({ query });
- } else {
+ if (noSqlMode) {
const completeTasksResp = await
axios.get('/druid/indexer/v1/completeTasks');
const runningTasksResp = await
axios.get('/druid/indexer/v1/runningTasks');
- const waitingTasksResp = await
axios.get('/druid/indexer/v1/waitingTasks');
const pendingTasksResp = await
axios.get('/druid/indexer/v1/pendingTasks');
- taskCountsFromQuery.push(
- {status: 'SUCCESS', count: completeTasksResp.data.filter((d: any)
=> d.status === 'SUCCESS').length},
- {status: 'FAILED', count: completeTasksResp.data.filter((d: any)
=> d.status === 'FAILED').length},
- {status: 'RUNNING', count: runningTasksResp.data.length},
- {status: 'WAITING', count: waitingTasksResp.data.length},
- {status: 'PENDING', count: pendingTasksResp.data.length}
- );
+ const waitingTasksResp = await
axios.get('/druid/indexer/v1/waitingTasks');
+ return {
+ SUCCESS: completeTasksResp.data.filter((d: any) => d.status ===
'SUCCESS').length,
+ FAILED: completeTasksResp.data.filter((d: any) => d.status ===
'FAILED').length,
+ RUNNING: runningTasksResp.data.length,
+ PENDING: pendingTasksResp.data.length,
+ WAITING: waitingTasksResp.data.length
+ };
+
+ } else {
+ const taskCountsFromQuery: { status: string, count: number }[] =
await queryDruidSql({ query });
+ return lookupBy(taskCountsFromQuery, x => x.status, x => x.count);
+
}
- const taskCounts = taskCountsFromQuery.reduce((acc: any, curr: any) =>
{
- const status = curr.status.toLowerCase();
- const property = `${status}TaskCount`;
- return {...acc, [property]: curr.count};
- }, {});
- return taskCounts;
},
onStateChange: ({ result, loading, error }) => {
this.setState({
taskCountLoading: loading,
- successTaskCount: result ? result.successTaskCount : 0,
- failedTaskCount: result ? result.failedTaskCount : 0,
- runningTaskCount: result ? result.runningTaskCount : 0,
- pendingTaskCount: result ? result.pendingTaskCount : 0,
- waitingTaskCount: result ? result.waitingTaskCount : 0,
+ successTaskCount: result ? result.SUCCESS : 0,
+ failedTaskCount: result ? result.FAILED : 0,
+ runningTaskCount: result ? result.RUNNING : 0,
+ pendingTaskCount: result ? result.PENDING : 0,
+ waitingTaskCount: result ? result.WAITING : 0,
taskCountError: error
});
}
@@ -272,60 +271,48 @@ GROUP BY 1`);
// -------------------------
- this.dataServerQueryManager = new QueryManager({
+ this.serverQueryManager = new QueryManager({
processQuery: async (query) => {
- const getDataServerNum = async () => {
- const allServerResp = await
axios.get('/druid/coordinator/v1/servers?simple');
- const allServers = allServerResp.data;
- return allServers.filter((s: any) => s.type === 'historical').length;
- };
- if (!noSqlMode) {
- const dataServerCounts = await queryDruidSql({ query });
- const serverNum = getHeadProp(dataServerCounts, 'count') || 0;
- if (serverNum === 0) return await getDataServerNum();
- return serverNum;
- } else {
- return await getDataServerNum();
- }
- },
- onStateChange: ({ result, loading, error }) => {
- this.setState({
- dataServerCountLoading: loading,
- dataServerCount: result,
- dataServerCountError: error
- });
- }
- });
+ if (noSqlMode) {
+ const serversResp = await
axios.get('/druid/coordinator/v1/servers?simple');
+ const middleManagerResp = await
axios.get('/druid/indexer/v1/workers');
+ return {
+ historical: serversResp.data.filter((s: any) => s.type ===
'historical').length,
+ middle_manager: middleManagerResp.data.length,
+ peon: serversResp.data.filter((s: any) => s.type ===
'indexer-executor').length
+ };
- this.dataServerQueryManager.runQuery(`SELECT COUNT(*) as "count" FROM
sys.servers WHERE "server_type" = 'historical'`);
-
- // -------------------------
+ } else {
+ const serverCountsFromQuery: { server_type: string, count: number
}[] = await queryDruidSql({ query });
+ return lookupBy(serverCountsFromQuery, x => x.server_type, x =>
x.count);
- this.middleManagerQueryManager = new QueryManager({
- processQuery: async (query) => {
- const middleManagerResp = await axios.get('/druid/indexer/v1/workers');
- const middleManagerCount: number = middleManagerResp.data.length;
- return middleManagerCount;
+ }
},
onStateChange: ({ result, loading, error }) => {
this.setState({
- middleManagerCountLoading: loading,
- middleManagerCount: result,
- middleManagerCountError: error
+ serverCountLoading: loading,
+ coordinatorCount: result ? result.coordinator : 0,
+ overlordCount: result ? result.overlord : 0,
+ routerCount: result ? result.router : 0,
+ brokerCount: result ? result.broker : 0,
+ historicalCount: result ? result.historical : 0,
+ middleManagerCount: result ? result.middle_manager : 0,
+ peonCount: result ? result.peon : 0,
+ serverCountError: error
});
}
});
- this.middleManagerQueryManager.runQuery(`dummy`);
+ this.serverQueryManager.runQuery(`SELECT server_type, COUNT(*) as "count"
FROM sys.servers GROUP BY 1`);
}
componentWillUnmount(): void {
this.statusQueryManager.terminate();
this.datasourceQueryManager.terminate();
this.segmentQueryManager.terminate();
+ this.supervisorQueryManager.terminate();
this.taskQueryManager.terminate();
- this.dataServerQueryManager.terminate();
- this.middleManagerQueryManager.terminate();
+ this.serverQueryManager.terminate();
}
renderCard(cardOptions: CardOptions): JSX.Element {
@@ -341,76 +328,94 @@ GROUP BY 1`);
const state = this.state;
return <div className="home-view app-view">
- {this.renderCard({
- href: UrlBaser.base('/status'),
- icon: IconNames.GRAPH,
- title: 'Status',
- loading: state.statusLoading,
- content: state.status ? `Apache Druid is running version
${state.status.version}` : '',
- error: state.statusError
- })}
-
- {this.renderCard({
- href: '#datasources',
- icon: IconNames.MULTI_SELECT,
- title: 'Datasources',
- loading: state.datasourceCountLoading,
- content: pluralIfNeeded(state.datasourceCount, 'datasource'),
- error: state.datasourceCountError
- })}
-
- {this.renderCard({
- href: '#segments',
- icon: IconNames.STACKED_CHART,
- title: 'Segments',
- loading: state.segmentCountLoading,
- content: pluralIfNeeded(state.segmentCount, 'segment'),
- error: state.datasourceCountError
- })}
-
- {this.renderCard({
- href: '#tasks',
- icon: IconNames.LIST_COLUMNS,
- title: 'Supervisors',
- loading: state.supervisorCountLoading,
- content: <>
+ {
+ this.renderCard({
+ href: UrlBaser.base('/status'),
+ icon: IconNames.GRAPH,
+ title: 'Status',
+ loading: state.statusLoading,
+ content: state.status ? `Apache Druid is running version
${state.status.version}` : '',
+ error: state.statusError
+ })
+ }
+ {
+ this.renderCard({
+ href: '#datasources',
+ icon: IconNames.MULTI_SELECT,
+ title: 'Datasources',
+ loading: state.datasourceCountLoading,
+ content: pluralIfNeeded(state.datasourceCount, 'datasource'),
+ error: state.datasourceCountError
+ })
+ }
+ {
+ this.renderCard({
+ href: '#segments',
+ icon: IconNames.STACKED_CHART,
+ title: 'Segments',
+ loading: state.segmentCountLoading,
+ content: pluralIfNeeded(state.segmentCount, 'segment'),
+ error: state.datasourceCountError
+ })
+ }
+ {
+ this.renderCard({
+ href: '#tasks',
+ icon: IconNames.LIST_COLUMNS,
+ title: 'Supervisors',
+ loading: state.supervisorCountLoading,
+ content: <>
{!Boolean(state.runningSupervisorCount +
state.suspendedSupervisorCount) && <p>0 supervisors</p>}
{Boolean(state.runningSupervisorCount) &&
<p>{pluralIfNeeded(state.runningSupervisorCount, 'running supervisor')}</p>}
{Boolean(state.suspendedSupervisorCount) &&
<p>{pluralIfNeeded(state.suspendedSupervisorCount, 'suspended supervisor')}</p>}
</>,
- error: state.supervisorCountError
- })}
-
- {this.renderCard({
- href: '#tasks',
- icon: IconNames.GANTT_CHART,
- title: 'Tasks',
- loading: state.taskCountLoading,
- content: <>
- {Boolean(state.runningTaskCount) &&
<p>{pluralIfNeeded(state.runningTaskCount, 'running task')}</p>}
- {Boolean(state.pendingTaskCount) &&
<p>{pluralIfNeeded(state.pendingTaskCount, 'pending task')}</p>}
- {Boolean(state.successTaskCount) &&
<p>{pluralIfNeeded(state.successTaskCount, 'successful task')}</p>}
- {Boolean(state.waitingTaskCount) &&
<p>{pluralIfNeeded(state.waitingTaskCount, 'waiting task')}</p>}
- {Boolean(state.failedTaskCount) &&
<p>{pluralIfNeeded(state.failedTaskCount, 'failed task')}</p>}
- {!(Boolean(state.runningTaskCount) ||
Boolean(state.pendingTaskCount) || Boolean(state.successTaskCount) ||
- Boolean(state.waitingTaskCount) || Boolean(state.failedTaskCount))
&&
- <p>There are no tasks</p>
- }
+ error: state.supervisorCountError
+ })
+ }
+ {
+ this.renderCard({
+ href: '#tasks',
+ icon: IconNames.GANTT_CHART,
+ title: 'Tasks',
+ loading: state.taskCountLoading,
+ content: <>
+ {Boolean(state.runningTaskCount) &&
<p>{pluralIfNeeded(state.runningTaskCount, 'running task')}</p>}
+ {Boolean(state.pendingTaskCount) &&
<p>{pluralIfNeeded(state.pendingTaskCount, 'pending task')}</p>}
+ {Boolean(state.successTaskCount) &&
<p>{pluralIfNeeded(state.successTaskCount, 'successful task')}</p>}
+ {Boolean(state.waitingTaskCount) &&
<p>{pluralIfNeeded(state.waitingTaskCount, 'waiting task')}</p>}
+ {Boolean(state.failedTaskCount) &&
<p>{pluralIfNeeded(state.failedTaskCount, 'failed task')}</p>}
+ {
+ !(
+ Boolean(state.runningTaskCount) ||
+ Boolean(state.pendingTaskCount) ||
+ Boolean(state.successTaskCount) ||
+ Boolean(state.waitingTaskCount) ||
+ Boolean(state.failedTaskCount)
+ ) &&
+ <p>There are no tasks</p>
+ }
</>,
- error: state.taskCountError
- })}
-
- {this.renderCard({
- href: '#servers',
- icon: IconNames.DATABASE,
- title: 'Data servers',
- loading: state.dataServerCountLoading ||
state.middleManagerCountLoading,
- content: <>
- <p>{pluralIfNeeded(state.dataServerCount, 'historical')}</p>
- <p>{pluralIfNeeded(state.middleManagerCount, 'middlemanager')}</p>
- </>,
- error: state.dataServerCountError
- })}
+ error: state.taskCountError
+ })
+ }
+ {
+ this.renderCard({
+ href: '#servers',
+ icon: IconNames.DATABASE,
+ title: 'Servers',
+ loading: state.serverCountLoading,
+ content: <>
+ <p>{`${pluralIfNeeded(state.overlordCount, 'overlord')},
${pluralIfNeeded(state.coordinatorCount, 'coordinator')}`}</p>
+ <p>{`${pluralIfNeeded(state.routerCount, 'router')},
${pluralIfNeeded(state.brokerCount, 'broker')}`}</p>
+ <p>{`${pluralIfNeeded(state.historicalCount, 'historical')},
${pluralIfNeeded(state.middleManagerCount, 'middle manager')}`}</p>
+ {
+ Boolean(state.peonCount) &&
+ <p>{pluralIfNeeded(state.peonCount, 'peon')}</p>
+ }
+ </>,
+ error: state.serverCountError
+ })
+ }
</div>;
}
}
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx
b/web-console/src/views/load-data-view/load-data-view.tsx
index fdba39c..61bddda 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -31,8 +31,8 @@ import * as classNames from 'classnames';
import * as React from 'react';
import ReactTable from 'react-table';
-import { AutoForm, CenterMessage, ClearableInput, ExternalLink, JSONInput,
Loader, NullTableCell } from '../../components/index';
-import { AsyncActionDialog } from '../../dialogs/index';
+import { AutoForm, CenterMessage, ClearableInput, ExternalLink, JSONInput,
Loader, NullTableCell } from '../../components';
+import { AsyncActionDialog } from '../../dialogs';
import { AppToaster } from '../../singletons/toaster';
import {
filterMap,
@@ -782,7 +782,6 @@ export class LoadDataView extends
React.Component<LoadDataViewProps, LoadDataVie
defaultPageSize={50}
showPagination={false}
sortable={false}
- className="-striped -highlight"
/>
</div>;
}
@@ -1062,7 +1061,6 @@ export class LoadDataView extends
React.Component<LoadDataViewProps, LoadDataVie
defaultPageSize={50}
showPagination={false}
sortable={false}
- className="-striped -highlight"
/>
</div>;
}
@@ -1255,7 +1253,6 @@ export class LoadDataView extends
React.Component<LoadDataViewProps, LoadDataVie
defaultPageSize={50}
showPagination={false}
sortable={false}
- className="-striped -highlight"
/>
</div>;
}
@@ -1493,7 +1490,6 @@ export class LoadDataView extends
React.Component<LoadDataViewProps, LoadDataVie
defaultPageSize={50}
showPagination={false}
sortable={false}
- className="-striped -highlight"
/>
</div>;
}
@@ -1815,7 +1811,6 @@ export class LoadDataView extends
React.Component<LoadDataViewProps, LoadDataVie
defaultPageSize={50}
showPagination={false}
sortable={false}
- className="-striped -highlight"
/>
</div>;
}
diff --git
a/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
b/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
index 148a732..2ef65d6 100644
---
a/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
+++
b/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
@@ -50,7 +50,7 @@ exports[`describe lookups view lookups view snapshot 1`] = `
TrComponent={[Function]}
TrGroupComponent={[Function]}
aggregatedKey="_aggregated"
- className="-striped -highlight"
+ className=""
collapseOnDataChange={true}
collapseOnPageChange={true}
collapseOnSortingChange={true}
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx
b/web-console/src/views/lookups-view/lookups-view.tsx
index d72d189..4c1b0a1 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -36,7 +36,7 @@ import {BasicAction, basicActionsToMenu} from
'../../utils/basic-action';
import './lookups-view.scss';
-const tableColumns: string[] = ['Lookup name', 'Tier', 'Type', 'Version',
'Actions'];
+const tableColumns: string[] = ['Lookup name', 'Tier', 'Type', 'Version',
ActionCell.COLUMN_LABEL];
const DEFAULT_LOOKUP_TIER: string = '__default';
@@ -214,7 +214,7 @@ export class LookupsView extends
React.Component<LookupsViewProps, LookupsViewSt
}
}
- private getlookupActions(lookupTier: string, lookupId: string):
BasicAction[] {
+ private getLookupActions(lookupTier: string, lookupId: string):
BasicAction[] {
return [
{
icon: IconNames.EDIT,
@@ -304,31 +304,21 @@ export class LookupsView extends
React.Component<LookupsViewProps, LookupsViewSt
show: tableColumnSelectionHandler.showColumn('Version')
},
{
- Header: 'Actions',
- id: 'actions',
- width: 70,
+ Header: ActionCell.COLUMN_LABEL,
+ id: ActionCell.COLUMN_ID,
+ width: ActionCell.COLUMN_WIDTH,
accessor: row => ({id: row.id, tier: row.tier}),
filterable: false,
Cell: (row: any) => {
const lookupId = row.value.id;
const lookupTier = row.value.tier;
- const lookupActions = this.getlookupActions(lookupTier,
lookupId);
- const lookupMenu = basicActionsToMenu(lookupActions);
-
- return <ActionCell>
- {
- lookupMenu &&
- <Popover content={lookupMenu}
position={Position.BOTTOM_RIGHT}>
- <Icon icon={IconNames.WRENCH}/>
- </Popover>
- }
- </ActionCell>;
+ const lookupActions = this.getLookupActions(lookupTier,
lookupId);
+ return <ActionCell actions={lookupActions}/>;
},
- show: tableColumnSelectionHandler.showColumn('Actions')
+ show:
tableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL)
}
]}
defaultPageSize={50}
- className="-striped -highlight"
/>
</>;
}
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 ee2f486..ec973f4 100644
---
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
@@ -60,7 +60,7 @@ exports[`describe segments-view segments view snapshot 1`] = `
TrComponent={[Function]}
TrGroupComponent={[Function]}
aggregatedKey="_aggregated"
- className="-striped -highlight"
+ className=""
collapseOnDataChange={true}
collapseOnPageChange={true}
collapseOnSortingChange={true}
diff --git a/web-console/src/views/segments-view/segments-view.scss
b/web-console/src/views/segments-view/segments-view.scss
index 31816a4..aa7c1f4 100644
--- a/web-console/src/views/segments-view/segments-view.scss
+++ b/web-console/src/views/segments-view/segments-view.scss
@@ -16,13 +16,15 @@
* limitations under the License.
*/
+@import "../../variables";
+
.segments-view {
height: 100%;
width: 100%;
.ReactTable {
position: absolute;
- top: 50px;
+ top: $view-control-bar-height + $standard-padding;
bottom: 0;
width: 100%;
diff --git a/web-console/src/views/segments-view/segments-view.tsx
b/web-console/src/views/segments-view/segments-view.tsx
index 0cae93b..9c53d7f 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -24,7 +24,7 @@ import * as React from 'react';
import ReactTable from 'react-table';
import { Filter } from 'react-table';
-import { TableColumnSelection, ViewControlBar } from '../../components/index';
+import { TableColumnSelection, ViewControlBar } from '../../components';
import {
addFilter,
formatBytes,
@@ -363,7 +363,6 @@ export class SegmentsView extends
React.Component<SegmentsViewProps, SegmentsVie
}
]}
defaultPageSize={50}
- className="-striped -highlight"
SubComponent={rowInfo => {
const { original } = rowInfo;
const { payload } = rowInfo.original;
diff --git
a/web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap
b/web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap
index 3741b8c..dee904d 100644
---
a/web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap
+++
b/web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap
@@ -1,507 +1,308 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`describe servers view action servers view 1`] = `
-<t
- customClassName="servers-view app-view"
- onDragEnd={null}
- onDragStart={null}
- onSecondaryPaneSizeChange={[Function]}
- percentage={true}
- primaryIndex={0}
- primaryMinSize={30}
- secondaryInitialSize={60}
- secondaryMinSize={30}
- vertical={true}
+<div
+ className="servers-view app-view"
>
- <div
- className="top-pane"
+ <ViewControlBar
+ label="Servers"
>
- <ViewControlBar
- label="Historicals"
- >
+ <Component>
+ Group by
+ </Component>
+ <Blueprint3.ButtonGroup>
<Blueprint3.Button
- icon="refresh"
+ active={true}
onClick={[Function]}
- text="Refresh"
- />
+ >
+ None
+ </Blueprint3.Button>
<Blueprint3.Button
- icon="application"
+ active={false}
onClick={[Function]}
- text="Go to SQL"
- />
- <Blueprint3.Switch
- checked={false}
- label="Group by tier"
- onChange={[Function]}
- />
- <TableColumnSelection
- columns={
- Array [
- "Server",
- "Tier",
- "Curr size",
- "Max size",
- "Usage",
- "Load/drop queues",
- "Host",
- "Port",
- ]
- }
- 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="-striped -highlight"
- collapseOnDataChange={true}
- collapseOnPageChange={true}
- collapseOnSortingChange={true}
- column={
- Object {
- "Aggregated": undefined,
- "Cell": undefined,
- "Expander": undefined,
- "Filter": undefined,
- "Footer": undefined,
- "Header": undefined,
- "Pivot": undefined,
- "PivotValue": undefined,
- "aggregate": undefined,
- "className": "",
- "filterAll": false,
- "filterMethod": undefined,
- "filterable": undefined,
- "footerClassName": "",
- "footerStyle": Object {},
- "getFooterProps": [Function],
- "getHeaderProps": [Function],
- "getProps": [Function],
- "headerClassName": "",
- "headerStyle": Object {},
- "minWidth": 100,
- "resizable": undefined,
- "show": true,
- "sortMethod": undefined,
- "sortable": undefined,
- "style": Object {},
- }
- }
+ >
+ Type
+ </Blueprint3.Button>
+ <Blueprint3.Button
+ active={false}
+ onClick={[Function]}
+ >
+ Tier
+ </Blueprint3.Button>
+ </Blueprint3.ButtonGroup>
+ <Blueprint3.Button
+ icon="refresh"
+ onClick={[Function]}
+ text="Refresh"
+ />
+ <Blueprint3.Button
+ icon="application"
+ onClick={[Function]}
+ text="Go to SQL"
+ />
+ <TableColumnSelection
columns={
Array [
- Object {
- "Aggregated": [Function],
- "Header": "Server",
- "accessor": "server",
- "show": true,
- "width": 300,
- },
- Object {
- "Cell": [Function],
- "Header": "Tier",
- "accessor": "tier",
- "show": true,
- },
- Object {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Curr size",
- "accessor": "curr_size",
- "filterable": false,
- "id": "curr_size",
- "show": true,
- "width": 100,
- },
- Object {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Max size",
- "accessor": "max_size",
- "filterable": false,
- "id": "max_size",
- "show": true,
- "width": 100,
- },
- Object {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Usage",
- "accessor": [Function],
- "filterable": false,
- "id": "usage",
- "show": true,
- "width": 100,
- },
- Object {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Load/drop queues",
- "accessor": [Function],
- "filterable": false,
- "id": "queue",
- "show": true,
- "width": 400,
- },
- Object {
- "Aggregated": [Function],
- "Header": "Host",
- "accessor": "host",
- "show": true,
- },
- Object {
- "Aggregated": [Function],
- "Header": "Port",
- "accessor": [Function],
- "id": "port",
- "show": true,
- },
+ "Server",
+ "Type",
+ "Tier",
+ "Host",
+ "Port",
+ "Curr size",
+ "Max size",
+ "Usage",
+ "Detail",
+ "Actions",
]
}
- data={Array []}
- defaultExpanded={Object {}}
- defaultFilterMethod={[Function]}
- defaultFiltered={Array []}
- defaultPageSize={10}
- defaultResized={Array []}
- defaultSortDesc={false}
- defaultSortMethod={[Function]}
- defaultSorted={Array []}
- expanderDefaults={
+ 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,
+ "aggregate": undefined,
+ "className": "",
+ "filterAll": false,
+ "filterMethod": undefined,
+ "filterable": undefined,
+ "footerClassName": "",
+ "footerStyle": Object {},
+ "getFooterProps": [Function],
+ "getHeaderProps": [Function],
+ "getProps": [Function],
+ "headerClassName": "",
+ "headerStyle": Object {},
+ "minWidth": 100,
+ "resizable": undefined,
+ "show": true,
+ "sortMethod": undefined,
+ "sortable": undefined,
+ "style": Object {},
+ }
+ }
+ columns={
+ Array [
+ Object {
+ "Aggregated": [Function],
+ "Header": "Server",
+ "accessor": "server",
+ "show": true,
+ "width": 300,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "Type",
+ "accessor": "server_type",
+ "show": true,
+ "width": 150,
+ },
Object {
+ "Cell": [Function],
+ "Header": "Tier",
+ "accessor": "tier",
+ "show": true,
+ },
+ Object {
+ "Aggregated": [Function],
+ "Header": "Host",
+ "accessor": "host",
+ "show": true,
+ },
+ Object {
+ "Aggregated": [Function],
+ "Header": "Port",
+ "accessor": [Function],
+ "id": "port",
+ "show": true,
+ },
+ Object {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Curr size",
+ "accessor": "curr_size",
"filterable": false,
- "resizable": false,
- "sortable": false,
- "width": 35,
- }
- }
- filterable={true}
- filtered={Array []}
- 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..."
- multiSort={true}
- nestingLevelKey="_nestingLevel"
- nextText="Next"
- noDataText=""
- ofText="of"
- onFetchData={[Function]}
- onFilteredChange={[Function]}
- originalKey="_original"
- pageSizeOptions={
- Array [
- 5,
- 10,
- 20,
- 25,
- 50,
- 100,
- ]
- }
- pageText="Page"
- pivotBy={Array []}
- pivotDefaults={Object {}}
- pivotIDKey="_pivotID"
- pivotValKey="_pivotVal"
- previousText="Previous"
- resizable={true}
- resolveData={[Function]}
- rowsText="rows"
- showPageJump={true}
- showPageSizeOptions={true}
- showPagination={true}
- showPaginationBottom={true}
- showPaginationTop={false}
- sortable={true}
- style={Object {}}
- subRowsKey="_subRows"
- />
- </div>
- <div
- className="bottom-pane"
- >
- <ViewControlBar
- label="MiddleManagers"
- >
- <Blueprint3.Button
- icon="refresh"
- onClick={[Function]}
- text="Refresh"
- />
- <TableColumnSelection
- columns={
- Array [
- "Host",
- "Usage",
- "Availability groups",
- "Last completed task time",
- "Blacklisted until",
- "Status",
- "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]}
- SubComponent={[Function]}
- TableComponent={[Function]}
- TbodyComponent={[Function]}
- TdComponent={[Function]}
- TfootComponent={[Function]}
- ThComponent={[Function]}
- TheadComponent={[Function]}
- TrComponent={[Function]}
- TrGroupComponent={[Function]}
- aggregatedKey="_aggregated"
- className="-striped -highlight"
- collapseOnDataChange={true}
- collapseOnPageChange={true}
- collapseOnSortingChange={true}
- column={
+ "id": "curr_size",
+ "show": true,
+ "width": 100,
+ },
Object {
- "Aggregated": undefined,
- "Cell": undefined,
- "Expander": undefined,
- "Filter": undefined,
- "Footer": undefined,
- "Header": undefined,
- "Pivot": undefined,
- "PivotValue": undefined,
- "aggregate": undefined,
- "className": "",
- "filterAll": false,
- "filterMethod": undefined,
- "filterable": undefined,
- "footerClassName": "",
- "footerStyle": Object {},
- "getFooterProps": [Function],
- "getHeaderProps": [Function],
- "getProps": [Function],
- "headerClassName": "",
- "headerStyle": Object {},
- "minWidth": 100,
- "resizable": undefined,
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Max size",
+ "accessor": "max_size",
+ "filterable": false,
+ "id": "max_size",
"show": true,
- "sortMethod": undefined,
- "sortable": undefined,
- "style": Object {},
- }
- }
- columns={
- Array [
- Object {
- "Cell": [Function],
- "Header": "Host",
- "accessor": [Function],
- "id": "host",
- "show": true,
- },
- Object {
- "Header": "Usage",
- "accessor": [Function],
- "filterable": false,
- "id": "usage",
- "show": true,
- "width": 60,
- },
- Object {
- "Header": "Availability groups",
- "accessor": [Function],
- "filterable": false,
- "id": "availabilityGroups",
- "show": true,
- "width": 60,
- },
- Object {
- "Header": "Last completed task time",
- "accessor": "lastCompletedTaskTime",
- "show": true,
- },
- Object {
- "Header": "Blacklisted until",
- "accessor": "blacklistedUntil",
- "show": true,
- },
- Object {
- "Header": "Status",
- "accessor": [Function],
- "id": "status",
- "show": true,
- },
- Object {
- "Cell": [Function],
- "Header": "Actions",
- "accessor": [Function],
- "filterable": false,
- "id": "actions",
- "show": true,
- "width": 70,
- },
- ]
- }
- data={Array []}
- defaultExpanded={Object {}}
- defaultFilterMethod={[Function]}
- defaultFiltered={Array []}
- defaultPageSize={10}
- defaultResized={Array []}
- defaultSortDesc={false}
- defaultSortMethod={[Function]}
- defaultSorted={Array []}
- expanderDefaults={
+ "width": 100,
+ },
Object {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Usage",
+ "accessor": [Function],
"filterable": false,
- "resizable": false,
- "sortable": false,
- "width": 35,
- }
- }
- filterable={true}
- filtered={
- Array [
- Object {
- "id": "host",
- "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..."
- multiSort={true}
- nestingLevelKey="_nestingLevel"
- nextText="Next"
- noDataText=""
- ofText="of"
- onFetchData={[Function]}
- onFilteredChange={[Function]}
- originalKey="_original"
- pageSizeOptions={
- Array [
- 5,
- 10,
- 20,
- 25,
- 50,
- 100,
- ]
+ "id": "usage",
+ "show": true,
+ "width": 100,
+ },
+ Object {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Detail",
+ "accessor": [Function],
+ "filterable": false,
+ "id": "queue",
+ "show": true,
+ "width": 400,
+ },
+ Object {
+ "Cell": [Function],
+ "Header": "Actions",
+ "accessor": [Function],
+ "filterable": false,
+ "id": "actions",
+ "show": true,
+ "width": 70,
+ },
+ ]
+ }
+ data={Array []}
+ defaultExpanded={Object {}}
+ defaultFilterMethod={[Function]}
+ defaultFiltered={Array []}
+ defaultPageSize={50}
+ defaultResized={Array []}
+ defaultSortDesc={false}
+ defaultSortMethod={[Function]}
+ defaultSorted={Array []}
+ expanderDefaults={
+ Object {
+ "filterable": false,
+ "resizable": false,
+ "sortable": false,
+ "width": 35,
}
- pageText="Page"
- pivotDefaults={Object {}}
- pivotIDKey="_pivotID"
- pivotValKey="_pivotVal"
- previousText="Previous"
- resizable={true}
- resolveData={[Function]}
- rowsText="rows"
- showPageJump={true}
- showPageSizeOptions={true}
- showPagination={true}
- showPaginationBottom={true}
- showPaginationTop={false}
- sortable={true}
- style={Object {}}
- subRowsKey="_subRows"
- />
- <AsyncActionDialog
- action={null}
- confirmButtonText="Disable worker"
- failText="Could not disable worker"
- intent="danger"
- onClose={[Function]}
- successText="Worker has been disabled"
- >
- <p>
- Are you sure you want to disable worker 'null'?
- </p>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Enable worker"
- failText="Could not enable worker"
- intent="primary"
- onClose={[Function]}
- successText="Worker has been enabled"
- >
- <p>
- Are you sure you want to enable worker 'null'?
- </p>
- </AsyncActionDialog>
- </div>
-</t>
+ }
+ filterable={true}
+ filtered={Array []}
+ 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..."
+ multiSort={true}
+ nestingLevelKey="_nestingLevel"
+ nextText="Next"
+ noDataText=""
+ ofText="of"
+ onFetchData={[Function]}
+ onFilteredChange={[Function]}
+ originalKey="_original"
+ pageSizeOptions={
+ Array [
+ 5,
+ 10,
+ 20,
+ 25,
+ 50,
+ 100,
+ ]
+ }
+ pageText="Page"
+ pivotBy={Array []}
+ pivotDefaults={Object {}}
+ pivotIDKey="_pivotID"
+ pivotValKey="_pivotVal"
+ previousText="Previous"
+ resizable={true}
+ resolveData={[Function]}
+ rowsText="rows"
+ showPageJump={true}
+ showPageSizeOptions={true}
+ showPagination={true}
+ showPaginationBottom={true}
+ showPaginationTop={false}
+ sortable={true}
+ style={Object {}}
+ subRowsKey="_subRows"
+ />
+ <AsyncActionDialog
+ action={null}
+ confirmButtonText="Disable worker"
+ failText="Could not disable worker"
+ intent="danger"
+ onClose={[Function]}
+ successText="Worker has been disabled"
+ >
+ <p>
+ Are you sure you want to disable worker 'null'?
+ </p>
+ </AsyncActionDialog>
+ <AsyncActionDialog
+ action={null}
+ confirmButtonText="Enable worker"
+ failText="Could not enable worker"
+ intent="primary"
+ onClose={[Function]}
+ successText="Worker has been enabled"
+ >
+ <p>
+ Are you sure you want to enable worker 'null'?
+ </p>
+ </AsyncActionDialog>
+</div>
`;
diff --git a/web-console/src/views/servers-view/servers-view.scss
b/web-console/src/views/servers-view/servers-view.scss
index 9029e82..c7834f7 100644
--- a/web-console/src/views/servers-view/servers-view.scss
+++ b/web-console/src/views/servers-view/servers-view.scss
@@ -22,29 +22,17 @@
height: 100%;
width: 100%;
- .view-control-bar{
+ .view-control-bar {
margin-bottom: $standard-padding;
}
- .splitter-layout.splitter-layout-vertical > .layout-splitter{
- height: 2px;
- background-color: #6d8ea9;
- padding-top: 5px;
- }
-
.ReactTable{
position: absolute;
- top: 50px;
+ top: $view-control-bar-height + $standard-padding;
bottom: 0;
width: 100%;
}
- .bottom-pane {
- height: 100%;
- position: relative;
- padding-top: 15px;
- }
-
ul {
line-height: 20px;
}
@@ -52,7 +40,7 @@
.fill-indicator {
position: relative;
width: 100%;
- height: 100%;
+ height: 16px;
background-color: #dadada;
border-radius: 2px;
diff --git a/web-console/src/views/servers-view/servers-view.tsx
b/web-console/src/views/servers-view/servers-view.tsx
index 5dc78ae..3d9446e 100644
--- a/web-console/src/views/servers-view/servers-view.tsx
+++ b/web-console/src/views/servers-view/servers-view.tsx
@@ -16,12 +16,11 @@
* limitations under the License.
*/
-import { Button, Icon, Intent, Popover, Position, Switch } from
'@blueprintjs/core';
+import { Button, ButtonGroup, Intent, Label } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import axios from 'axios';
import { sum } from 'd3-array';
import * as React from 'react';
-import SplitterLayout from 'react-splitter-layout';
import ReactTable from 'react-table';
import { Filter } from 'react-table';
@@ -29,21 +28,21 @@ import {
ActionCell,
TableColumnSelection,
ViewControlBar
-} from '../../components/index';
-import { AsyncActionDialog } from '../../dialogs/index';
+} from '../../components';
+import { AsyncActionDialog } from '../../dialogs';
import {
addFilter,
formatBytes,
- formatBytesCompact, localStorageGet, LocalStorageKeys, localStorageSet,
+ formatBytesCompact, LocalStorageKeys, lookupBy,
queryDruidSql,
QueryManager, TableColumnSelectionHandler
} from '../../utils';
-import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
+import { BasicAction } from '../../utils/basic-action';
+import { deepGet } from '../../utils/object-change';
import './servers-view.scss';
-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', 'Status', 'Actions'];
+const serverTableColumns: string[] = ['Server', 'Type', 'Tier', 'Host',
'Port', 'Curr size', 'Max size', 'Usage', 'Detail', ActionCell.COLUMN_LABEL];
function formatQueues(segmentsToLoad: number, segmentsToLoadSize: number,
segmentsToDrop: number, segmentsToDropSize: number): string {
const queueParts: string[] = [];
@@ -53,7 +52,7 @@ function formatQueues(segmentsToLoad: number,
segmentsToLoadSize: number, segmen
if (segmentsToDrop) {
queueParts.push(`${segmentsToDrop} segments to drop
(${formatBytesCompact(segmentsToDropSize)})`);
}
- return queueParts.join(', ') || 'Empty queues';
+ return queueParts.join(', ') || 'Empty load/drop queues';
}
export interface ServersViewProps extends React.Props<any> {
@@ -68,29 +67,28 @@ export interface ServersViewState {
servers: any[] | null;
serversError: string | null;
serverFilter: Filter[];
- groupByTier: boolean;
-
- middleManagersLoading: boolean;
- middleManagers: any[] | null;
- middleManagersError: string | null;
- middleManagerFilter: Filter[];
+ groupServersBy: null | 'server_type' | 'tier';
middleManagerDisableWorkerHost: string | null;
middleManagerEnableWorkerHost: string | null;
}
interface ServerQueryResultRow {
+ server: string;
+ server_type: string;
+ tier: string;
curr_size: number;
host: string;
max_size: number;
plaintext_port: number;
- server: string;
- tier: string;
tls_port: number;
- segmentsToDrop?: number;
- segmentsToDropSize?: number;
- segmentsToLoad?: number;
- segmentsToLoadSize?: number;
+}
+
+interface LoadQueueStatus {
+ segmentsToDrop: number;
+ segmentsToDropSize: number;
+ segmentsToLoad: number;
+ segmentsToLoadSize: number;
}
interface MiddleManagerQueryResultRow {
@@ -99,14 +97,20 @@ interface MiddleManagerQueryResultRow {
currCapacityUsed: number;
lastCompletedTaskTime: string;
runningTasks: string[];
- worker: any;
+ worker: {
+ capacity: number;
+ host: string;
+ ip: string;
+ scheme: string;
+ version: string;
+ };
}
+interface ServerResultRow extends ServerQueryResultRow,
Partial<LoadQueueStatus>, Partial<MiddleManagerQueryResultRow> {}
+
export class ServersView extends React.Component<ServersViewProps,
ServersViewState> {
private serverQueryManager: QueryManager<string, ServerQueryResultRow[]>;
- private middleManagerQueryManager: QueryManager<string,
MiddleManagerQueryResultRow[]>;
private serverTableColumnSelectionHandler: TableColumnSelectionHandler;
- private middleManagerTableColumnSelectionHandler:
TableColumnSelectionHandler;
constructor(props: ServersViewProps, context: any) {
super(props, context);
@@ -115,12 +119,7 @@ export class ServersView extends
React.Component<ServersViewProps, ServersViewSt
servers: null,
serversError: null,
serverFilter: [],
- groupByTier: false,
-
- middleManagersLoading: true,
- middleManagers: null,
- middleManagersError: null,
- middleManagerFilter: props.middleManager ? [{ id: 'host', value:
props.middleManager }] : [],
+ groupServersBy: null,
middleManagerDisableWorkerHost: null,
middleManagerEnableWorkerHost: null
@@ -129,26 +128,20 @@ export class ServersView extends
React.Component<ServersViewProps, ServersViewSt
this.serverTableColumnSelectionHandler = new TableColumnSelectionHandler(
LocalStorageKeys.SERVER_TABLE_COLUMN_SELECTION, () => this.setState({})
);
-
- this.middleManagerTableColumnSelectionHandler = new
TableColumnSelectionHandler(
- LocalStorageKeys.MIDDLEMANAGER_TABLE_COLUMN_SELECTION, () =>
this.setState({})
- );
- if (!localStorageGet(LocalStorageKeys.SERVERS_VIEW_PANE_SIZE)) {
- localStorageSet(LocalStorageKeys.SERVERS_VIEW_PANE_SIZE, '60');
- }
}
- static getServers = async (): Promise<ServerQueryResultRow[]> => {
+ static async getServers(): Promise<ServerQueryResultRow[]> {
const allServerResp = await
axios.get('/druid/coordinator/v1/servers?simple');
const allServers = allServerResp.data;
- return allServers.filter((s: any) => s.type === 'historical').map((s: any)
=> {
+ return allServers.map((s: any) => {
return {
+ server: s.host,
+ server_type: s.type === 'indexer-executor' ? 'peon' : s.type,
+ tier: s.tier,
host: s.host.split(':')[0],
plaintext_port: parseInt(s.host.split(':')[1], 10),
- server: s.host,
curr_size: s.currSize,
max_size: s.maxSize,
- tier: s.tier,
tls_port: -1
};
});
@@ -161,21 +154,47 @@ export class ServersView extends
React.Component<ServersViewProps, ServersViewSt
let servers: ServerQueryResultRow[];
if (!noSqlMode) {
servers = await queryDruidSql({ query });
- if (servers.length === 0) {
- servers = await ServersView.getServers();
- }
} else {
servers = await ServersView.getServers();
}
+
const loadQueueResponse = await
axios.get('/druid/coordinator/v1/loadqueue?simple');
- const loadQueues = loadQueueResponse.data;
- return servers.map((s: any) => {
+ const loadQueues: Record<string, LoadQueueStatus> =
loadQueueResponse.data;
+ servers = servers.map((s: any) => {
const loadQueueInfo = loadQueues[s.server];
if (loadQueueInfo) {
s = Object.assign(s, loadQueueInfo);
}
return s;
});
+
+ let middleManagers: MiddleManagerQueryResultRow[];
+ try {
+ const middleManagerResponse = await
axios.get('/druid/indexer/v1/workers');
+ middleManagers = middleManagerResponse.data;
+ } catch (e) {
+ if (
+ e.response &&
+ typeof e.response.data === 'object' &&
+ e.response.data.error === 'Task Runner does not support worker
listing'
+ ) {
+ // Swallow this error because it simply a reflection of a local
task runner.
+ middleManagers = [];
+ } else {
+ // Otherwise re-throw.
+ throw e;
+ }
+ }
+
+ const middleManagersLookup = lookupBy(middleManagers, (m) =>
m.worker.host);
+
+ return servers.map((s: any) => {
+ const middleManagerInfo = middleManagersLookup[s.server];
+ if (middleManagerInfo) {
+ s = Object.assign(s, middleManagerInfo);
+ }
+ return s;
+ });
},
onStateChange: ({ result, loading, error }) => {
this.setState({
@@ -186,46 +205,48 @@ export class ServersView extends
React.Component<ServersViewProps, ServersViewSt
}
});
+ // Ranking
+ // coordinator => 7
+ // overlord => 6
+ // router => 5
+ // broker => 4
+ // historical => 3
+ // middle_manager => 2
+ // peon => 1
+
this.serverQueryManager.runQuery(`SELECT
- "tier", "server", "host", "plaintext_port", "tls_port", "curr_size",
"max_size"
+ "server", "server_type", "tier", "host", "plaintext_port", "tls_port",
"curr_size", "max_size",
+ (
+ CASE "server_type"
+ WHEN 'coordinator' THEN 7
+ WHEN 'overlord' THEN 6
+ WHEN 'router' THEN 5
+ WHEN 'broker' THEN 4
+ WHEN 'historical' THEN 3
+ WHEN 'middle_manager' THEN 2
+ WHEN 'peon' THEN 1
+ ELSE 0
+ END
+ ) AS "rank"
FROM sys.servers
-WHERE "server_type" = 'historical'`);
-
- this.middleManagerQueryManager = new QueryManager({
- processQuery: async (query: string) => {
- const resp = await axios.get('/druid/indexer/v1/workers');
- return resp.data;
- },
- onStateChange: ({ result, loading, error }) => {
- this.setState({
- middleManagers: result,
- middleManagersLoading: loading,
- middleManagersError: error
- });
- }
- });
-
- this.middleManagerQueryManager.runQuery('dummy');
+ORDER BY "rank" DESC, "server" DESC`);
}
componentWillUnmount(): void {
this.serverQueryManager.terminate();
- this.middleManagerQueryManager.terminate();
- }
-
- private onSecondaryPaneSizeChange(secondaryPaneSize: number) {
- localStorageSet(LocalStorageKeys.SERVERS_VIEW_PANE_SIZE,
String(secondaryPaneSize));
}
renderServersTable() {
- const { servers, serversLoading, serversError, serverFilter, groupByTier }
= this.state;
+ const { servers, serversLoading, serversError, serverFilter,
groupServersBy } = this.state;
const { serverTableColumnSelectionHandler } = this;
const fillIndicator = (value: number) => {
+ let formattedValue = (value * 100).toFixed(1);
+ if (formattedValue === '0.0' && value > 0) formattedValue = '~' +
formattedValue;
return <div className="fill-indicator">
<div className="bar" style={{ width: `${value * 100}%` }}/>
- <div className="label">{(value * 100).toFixed(1) + '%'}</div>
+ <div className="label">{formattedValue + '%'}</div>
</div>;
};
@@ -238,7 +259,8 @@ WHERE "server_type" = 'historical'`);
onFilteredChange={(filtered, column) => {
this.setState({ serverFilter: filtered });
}}
- pivotBy={groupByTier ? ['tier'] : []}
+ pivotBy={groupServersBy ? [groupServersBy] : []}
+ defaultPageSize={50}
columns={[
{
Header: 'Server',
@@ -248,6 +270,16 @@ WHERE "server_type" = 'historical'`);
show: serverTableColumnSelectionHandler.showColumn('Server')
},
{
+ Header: 'Type',
+ accessor: 'server_type',
+ width: 150,
+ Cell: row => {
+ const value = row.value;
+ return <a onClick={() => { this.setState({ serverFilter:
addFilter(serverFilter, 'server_type', value) }); }}>{value}</a>;
+ },
+ show: serverTableColumnSelectionHandler.showColumn('Type')
+ },
+ {
Header: 'Tier',
accessor: 'tier',
Cell: row => {
@@ -257,18 +289,41 @@ WHERE "server_type" = 'historical'`);
show: serverTableColumnSelectionHandler.showColumn('Tier')
},
{
+ Header: 'Host',
+ accessor: 'host',
+ Aggregated: () => '',
+ show: serverTableColumnSelectionHandler.showColumn('Host')
+ },
+ {
+ Header: 'Port',
+ id: 'port',
+ accessor: (row) => {
+ const ports: string[] = [];
+ if (row.plaintext_port !== -1) {
+ ports.push(`${row.plaintext_port} (plain)`);
+ }
+ if (row.tls_port !== -1) {
+ ports.push(`${row.tls_port} (TLS)`);
+ }
+ return ports.join(', ') || 'No port';
+ },
+ Aggregated: () => '',
+ show: serverTableColumnSelectionHandler.showColumn('Port')
+ },
+ {
Header: 'Curr size',
id: 'curr_size',
width: 100,
filterable: false,
accessor: 'curr_size',
Aggregated: row => {
+ if (row.row._pivotVal !== 'historical') return '';
const originals = row.subRows.map(r => r._original);
const totalCurr = sum(originals, s => s.curr_size);
return formatBytes(totalCurr);
},
Cell: row => {
- if (row.aggregated) return '';
+ if (row.aggregated || row.original.server_type !== 'historical')
return '';
if (row.value === null) return '';
return formatBytes(row.value);
},
@@ -281,12 +336,13 @@ WHERE "server_type" = 'historical'`);
filterable: false,
accessor: 'max_size',
Aggregated: row => {
+ if (row.row._pivotVal !== 'historical') return '';
const originals = row.subRows.map(r => r._original);
const totalMax = sum(originals, s => s.max_size);
return formatBytes(totalMax);
},
Cell: row => {
- if (row.aggregated) return '';
+ if (row.aggregated || row.original.server_type !== 'historical')
return '';
if (row.value === null) return '';
return formatBytes(row.value);
},
@@ -297,32 +353,92 @@ WHERE "server_type" = 'historical'`);
id: 'usage',
width: 100,
filterable: false,
- accessor: (row) => row.max_size ? (row.curr_size / row.max_size) :
null,
+ accessor: (row) => {
+ if (row.server_type === 'middle_manager') {
+ return row.worker ? row.currCapacityUsed / row.worker.capacity :
null;
+ } else {
+ return row.max_size ? (row.curr_size / row.max_size) : null;
+ }
+ },
Aggregated: row => {
- const originals = row.subRows.map(r => r._original);
- const totalCurr = sum(originals, s => s.curr_size);
- const totalMax = sum(originals, s => s.max_size);
- return fillIndicator(totalCurr / totalMax);
+ switch (row.row._pivotVal) {
+ case 'historical':
+ const originalHistoricals = row.subRows.map(r => r._original);
+ const totalCurr = sum(originalHistoricals, s => s.curr_size);
+ const totalMax = sum(originalHistoricals, s => s.max_size);
+ return fillIndicator(totalCurr / totalMax);
+
+ case 'middle_manager':
+ const originalMiddleManagers = row.subRows.map(r =>
r._original);
+ const totalCurrCapacityUsed = sum(originalMiddleManagers, s =>
s.currCapacityUsed || 0);
+ const totalWorkerCapacity = sum(originalMiddleManagers, s =>
deepGet(s, 'worker.capacity') || 0);
+ return `${totalCurrCapacityUsed} / ${totalWorkerCapacity}
(total slots)`;
+
+ default:
+ return '';
+ }
},
Cell: row => {
if (row.aggregated) return '';
- if (row.value === null) return '';
- return fillIndicator(row.value);
+ const { server_type } = row.original;
+ switch (server_type) {
+ case 'historical':
+ return fillIndicator(row.value);
+
+ case 'middle_manager':
+ const currCapacityUsed = deepGet(row,
'original.currCapacityUsed') || 0;
+ const capacity = deepGet(row, 'original.worker.capacity');
+ if (typeof capacity === 'number') {
+ return `${currCapacityUsed} / ${capacity} (slots)`;
+ } else {
+ return '- / -';
+ }
+
+ default:
+ return '';
+ }
},
show: serverTableColumnSelectionHandler.showColumn('Usage')
},
{
- Header: 'Load/drop queues',
+ Header: 'Detail',
id: 'queue',
width: 400,
filterable: false,
- accessor: (row) => (row.segmentsToLoad || 0) + (row.segmentsToDrop
|| 0),
+ accessor: (row) => {
+ if (row.server_type === 'middle_manager') {
+ if (deepGet(row, 'worker.version') === '') return 'Disabled';
+
+ const details: string[] = [];
+ if (row.lastCompletedTaskTime) {
+ details.push(`Last completed task:
${row.lastCompletedTaskTime}`);
+ }
+ if (row.blacklistedUntil) {
+ details.push(`Blacklisted until: ${row.blacklistedUntil}`);
+ }
+ return details.join(' ');
+
+ } else {
+ return (row.segmentsToLoad || 0) + (row.segmentsToDrop || 0);
+ }
+ },
Cell: (row => {
if (row.aggregated) return '';
- const { segmentsToLoad, segmentsToLoadSize, segmentsToDrop,
segmentsToDropSize } = row.original;
- return formatQueues(segmentsToLoad, segmentsToLoadSize,
segmentsToDrop, segmentsToDropSize);
+ const { server_type } = row.original;
+ switch (server_type) {
+ case 'historical':
+ const { segmentsToLoad, segmentsToLoadSize, segmentsToDrop,
segmentsToDropSize } = row.original;
+ return formatQueues(segmentsToLoad, segmentsToLoadSize,
segmentsToDrop, segmentsToDropSize);
+
+ case 'middle_manager':
+ return row.value;
+
+ default:
+ return '';
+ }
}),
Aggregated: row => {
+ if (row.row._pivotVal !== 'historical') return '';
const originals = row.subRows.map(r => r._original);
const segmentsToLoad = sum(originals, s => s.segmentsToLoad);
const segmentsToLoadSize = sum(originals, s =>
s.segmentsToLoadSize);
@@ -330,138 +446,26 @@ WHERE "server_type" = 'historical'`);
const segmentsToDropSize = sum(originals, s =>
s.segmentsToDropSize);
return formatQueues(segmentsToLoad, segmentsToLoadSize,
segmentsToDrop, segmentsToDropSize);
},
- show: serverTableColumnSelectionHandler.showColumn('Load/drop
queues')
- },
- {
- Header: 'Host',
- accessor: 'host',
- Aggregated: () => '',
- show: serverTableColumnSelectionHandler.showColumn('Host')
+ show: serverTableColumnSelectionHandler.showColumn('Detail')
},
{
- Header: 'Port',
- id: 'port',
- accessor: (row) => {
- const ports: string[] = [];
- if (row.plaintext_port !== -1) {
- ports.push(`${row.plaintext_port} (plain)`);
- }
- if (row.tls_port !== -1) {
- ports.push(`${row.tls_port} (TLS)`);
- }
- return ports.join(', ') || 'No port';
+ Header: ActionCell.COLUMN_LABEL,
+ id: ActionCell.COLUMN_ID,
+ width: ActionCell.COLUMN_WIDTH,
+ accessor: (row) => row.worker,
+ filterable: false,
+ Cell: row => {
+ if (!row.value) return null;
+ const disabled = row.value.version === '';
+ const workerActions = this.getWorkerActions(row.value.host,
disabled);
+ return <ActionCell actions={workerActions}/>;
},
- Aggregated: () => '',
- show: serverTableColumnSelectionHandler.showColumn('Port')
+ show:
serverTableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL)
}
]}
- defaultPageSize={10}
- className="-striped -highlight"
/>;
}
- renderMiddleManagerTable() {
- const { goToTask } = this.props;
- const { middleManagers, middleManagersLoading, middleManagersError,
middleManagerFilter } = this.state;
- const { middleManagerTableColumnSelectionHandler } = this;
-
- return <>
- <ReactTable
- data={middleManagers || []}
- loading={middleManagersLoading}
- noDataText={!middleManagersLoading && middleManagers &&
!middleManagers.length ? 'No MiddleManagers' : (middleManagersError || '')}
- filterable
- filtered={middleManagerFilter}
- onFilteredChange={(filtered, column) => {
- this.setState({ middleManagerFilter: filtered });
- }}
- columns={[
- {
- Header: 'Host',
- id: 'host',
- accessor: (row) => row.worker.host,
- Cell: row => {
- const value = row.value;
- return <a onClick={() => { this.setState({ middleManagerFilter:
addFilter(middleManagerFilter, 'host', value) }); }}>{value}</a>;
- },
- show: middleManagerTableColumnSelectionHandler.showColumn('Host')
- },
- {
- Header: 'Usage',
- id: 'usage',
- width: 60,
- accessor: (row) => `${row.currCapacityUsed} /
${row.worker.capacity}`,
- filterable: false,
- show: middleManagerTableColumnSelectionHandler.showColumn('Usage')
- },
- {
- Header: 'Availability groups',
- id: 'availabilityGroups',
- width: 60,
- accessor: (row) => row.availabilityGroups.length,
- filterable: false,
- show:
middleManagerTableColumnSelectionHandler.showColumn('Availability groups')
- },
- {
- Header: 'Last completed task time',
- accessor: 'lastCompletedTaskTime',
- show: middleManagerTableColumnSelectionHandler.showColumn('Last
completed task time')
- },
- {
- Header: 'Blacklisted until',
- accessor: 'blacklistedUntil',
- show:
middleManagerTableColumnSelectionHandler.showColumn('Blacklisted until')
- },
- {
- Header: 'Status',
- id: 'status',
- accessor: (row) => row.worker.version === '' ? 'Disabled' :
'Enabled',
- show: middleManagerTableColumnSelectionHandler.showColumn('Status')
- },
- {
- Header: 'Actions',
- id: 'actions',
- width: 70,
- accessor: (row) => row.worker,
- filterable: false,
- Cell: row => {
- const disabled = row.value.version === '';
- const workerActions = this.getWorkerActions(row.value.host,
disabled);
- const workerMenu = basicActionsToMenu(workerActions);
-
- return <ActionCell>
- {
- workerMenu &&
- <Popover content={workerMenu}
position={Position.BOTTOM_RIGHT}>
- <Icon icon={IconNames.WRENCH}/>
- </Popover>
- }
- </ActionCell>;
- },
- show:
middleManagerTableColumnSelectionHandler.showColumn('Actions')
- }
- ]}
- defaultPageSize={10}
- className="-striped -highlight"
- SubComponent={rowInfo => {
- const runningTasks = rowInfo.original.runningTasks;
- return <div style={{ padding: '20px' }}>
- {
- runningTasks.length ?
- <>
- <span>Running tasks:</span>
- <ul>{runningTasks.map((t: string) => <li key={t}>{t} <a
onClick={() => goToTask(t)}>➚</a></li>)}</ul>
- </> :
- <span>No running tasks</span>
- }
- </div>;
- }}
- />
- {this.renderDisableWorkerAction()}
- {this.renderEnableWorkerAction()}
- </>;
- }
-
private getWorkerActions(workerHost: string, disabled: boolean):
BasicAction[] {
if (disabled) {
return [
@@ -498,7 +502,7 @@ WHERE "server_type" = 'historical'`);
intent={Intent.DANGER}
onClose={(success) => {
this.setState({ middleManagerDisableWorkerHost: null });
- if (success) this.middleManagerQueryManager.rerunLastQuery();
+ if (success) this.serverQueryManager.rerunLastQuery();
}}
>
<p>
@@ -523,7 +527,7 @@ WHERE "server_type" = 'historical'`);
intent={Intent.PRIMARY}
onClose={(success) => {
this.setState({ middleManagerEnableWorkerHost: null });
- if (success) this.middleManagerQueryManager.rerunLastQuery();
+ if (success) this.serverQueryManager.rerunLastQuery();
}}
>
<p>
@@ -534,61 +538,39 @@ WHERE "server_type" = 'historical'`);
render() {
const { goToSql, noSqlMode } = this.props;
- const { groupByTier } = this.state;
- const { serverTableColumnSelectionHandler,
middleManagerTableColumnSelectionHandler } = this;
-
- return <SplitterLayout
- customClassName={'servers-view app-view'}
- vertical
- percentage
-
secondaryInitialSize={Number(localStorageGet(LocalStorageKeys.SERVERS_VIEW_PANE_SIZE)
as string)}
- primaryMinSize={30}
- secondaryMinSize={30}
- onSecondaryPaneSizeChange={this.onSecondaryPaneSizeChange}
- >
- <div className={'top-pane'}>
- <ViewControlBar label="Historicals">
- <Button
- icon={IconNames.REFRESH}
- text="Refresh"
- onClick={() => this.serverQueryManager.rerunLastQuery()}
- />
- {
- !noSqlMode &&
- <Button
- icon={IconNames.APPLICATION}
- text="Go to SQL"
- onClick={() => goToSql(this.serverQueryManager.getLastQuery())}
- />
- }
- <Switch
- checked={groupByTier}
- label="Group by tier"
- onChange={() => this.setState({ groupByTier: !groupByTier })}
- />
- <TableColumnSelection
- columns={serverTableColumns}
- onChange={(column) =>
serverTableColumnSelectionHandler.changeTableColumnSelection(column)}
-
tableColumnsHidden={serverTableColumnSelectionHandler.hiddenColumns}
- />
- </ViewControlBar>
- {this.renderServersTable()}
- </div>
- <div className={'bottom-pane'}>
- <ViewControlBar label="MiddleManagers">
+ const { groupServersBy } = this.state;
+ const { serverTableColumnSelectionHandler } = this;
+
+ return <div className="servers-view app-view">
+ <ViewControlBar label="Servers">
+ <Label>Group by</Label>
+ <ButtonGroup>
+ <Button active={groupServersBy === null} onClick={() =>
this.setState({ groupServersBy: null })}>None</Button>
+ <Button active={groupServersBy === 'server_type'} onClick={() =>
this.setState({ groupServersBy: 'server_type' })}>Type</Button>
+ <Button active={groupServersBy === 'tier'} onClick={() =>
this.setState({ groupServersBy: 'tier' })}>Tier</Button>
+ </ButtonGroup>
+ <Button
+ icon={IconNames.REFRESH}
+ text="Refresh"
+ onClick={() => this.serverQueryManager.rerunLastQuery()}
+ />
+ {
+ !noSqlMode &&
<Button
- icon={IconNames.REFRESH}
- text="Refresh"
- onClick={() => this.middleManagerQueryManager.rerunLastQuery()}
+ icon={IconNames.APPLICATION}
+ text="Go to SQL"
+ onClick={() => goToSql(this.serverQueryManager.getLastQuery())}
/>
- <TableColumnSelection
- columns={middleManagerTableColumns}
- onChange={(column) =>
middleManagerTableColumnSelectionHandler.changeTableColumnSelection(column)}
-
tableColumnsHidden={middleManagerTableColumnSelectionHandler.hiddenColumns}
- />
- </ViewControlBar>
- {this.renderMiddleManagerTable()}
- </div>
- </SplitterLayout>;
+ }
+ <TableColumnSelection
+ columns={serverTableColumns}
+ onChange={(column) =>
serverTableColumnSelectionHandler.changeTableColumnSelection(column)}
+ tableColumnsHidden={serverTableColumnSelectionHandler.hiddenColumns}
+ />
+ </ViewControlBar>
+ {this.renderServersTable()}
+ {this.renderDisableWorkerAction()}
+ {this.renderEnableWorkerAction()}
+ </div>;
}
}
diff --git
a/web-console/src/views/sql-view/__snapshots__/sql-view.spec.tsx.snap
b/web-console/src/views/sql-view/__snapshots__/sql-view.spec.tsx.snap
index 54973fc..fa514ed 100644
--- a/web-console/src/views/sql-view/__snapshots__/sql-view.spec.tsx.snap
+++ b/web-console/src/views/sql-view/__snapshots__/sql-view.spec.tsx.snap
@@ -1,147 +1,164 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`describe sql view sql view snapshot 1`] = `
-<div
- className="sql-view app-view"
+<t
+ customClassName="sql-view app-view"
+ onDragEnd={null}
+ onDragStart={null}
+ onSecondaryPaneSizeChange={[Function]}
+ percentage={true}
+ primaryIndex={0}
+ primaryMinSize={30}
+ secondaryInitialSize={60}
+ secondaryMinSize={30}
+ vertical={true}
>
- <SqlControl
- initSql="test"
- onExplain={[Function]}
- onRun={[Function]}
- queryElapsed={null}
- />
- <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="-striped -highlight"
- collapseOnDataChange={true}
- collapseOnPageChange={true}
- collapseOnSortingChange={true}
- column={
- Object {
- "Aggregated": undefined,
- "Cell": undefined,
- "Expander": undefined,
- "Filter": undefined,
- "Footer": undefined,
- "Header": undefined,
- "Pivot": undefined,
- "PivotValue": undefined,
- "aggregate": undefined,
- "className": "",
- "filterAll": false,
- "filterMethod": undefined,
- "filterable": undefined,
- "footerClassName": "",
- "footerStyle": Object {},
- "getFooterProps": [Function],
- "getHeaderProps": [Function],
- "getProps": [Function],
- "headerClassName": "",
- "headerStyle": Object {},
- "minWidth": 100,
- "resizable": undefined,
- "show": true,
- "sortMethod": undefined,
- "sortable": undefined,
- "style": Object {},
+ <div
+ className="top-pane"
+ >
+ <SqlControl
+ initSql="test"
+ onExplain={[Function]}
+ onRun={[Function]}
+ queryElapsed={null}
+ />
+ </div>
+ <div
+ className="bottom-pane"
+ >
+ <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,
+ "aggregate": undefined,
+ "className": "",
+ "filterAll": false,
+ "filterMethod": undefined,
+ "filterable": undefined,
+ "footerClassName": "",
+ "footerStyle": Object {},
+ "getFooterProps": [Function],
+ "getHeaderProps": [Function],
+ "getProps": [Function],
+ "headerClassName": "",
+ "headerStyle": Object {},
+ "minWidth": 100,
+ "resizable": undefined,
+ "show": true,
+ "sortMethod": undefined,
+ "sortable": undefined,
+ "style": Object {},
+ }
}
- }
- columns={Array []}
- data={Array []}
- defaultExpanded={Object {}}
- defaultFilterMethod={[Function]}
- defaultFiltered={Array []}
- defaultPageSize={10}
- defaultResized={Array []}
- defaultSortDesc={false}
- defaultSortMethod={[Function]}
- defaultSorted={Array []}
- expanderDefaults={
- Object {
- "filterable": false,
- "resizable": false,
- "sortable": false,
- "width": 35,
+ columns={Array []}
+ data={Array []}
+ defaultExpanded={Object {}}
+ defaultFilterMethod={[Function]}
+ defaultFiltered={Array []}
+ defaultPageSize={20}
+ defaultResized={Array []}
+ defaultSortDesc={false}
+ defaultSortMethod={[Function]}
+ defaultSorted={Array []}
+ expanderDefaults={
+ Object {
+ "filterable": false,
+ "resizable": false,
+ "sortable": false,
+ "width": 35,
+ }
}
- }
- filterable={false}
- 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={false}
- loadingText="Loading..."
- multiSort={true}
- nestingLevelKey="_nestingLevel"
- nextText="Next"
- noDataText=""
- ofText="of"
- onFetchData={[Function]}
- originalKey="_original"
- pageSizeOptions={
- Array [
- 5,
- 10,
- 20,
- 25,
- 50,
- 100,
- ]
- }
- pageText="Page"
- pivotDefaults={Object {}}
- pivotIDKey="_pivotID"
- pivotValKey="_pivotVal"
- previousText="Previous"
- resizable={true}
- resolveData={[Function]}
- rowsText="rows"
- showPageJump={true}
- showPageSizeOptions={true}
- showPagination={true}
- showPaginationBottom={true}
- showPaginationTop={false}
- sortable={false}
- style={Object {}}
- subRowsKey="_subRows"
- />
-</div>
+ filterable={false}
+ 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={false}
+ loadingText="Loading..."
+ multiSort={true}
+ nestingLevelKey="_nestingLevel"
+ nextText="Next"
+ noDataText=""
+ ofText="of"
+ onFetchData={[Function]}
+ originalKey="_original"
+ pageSizeOptions={
+ Array [
+ 5,
+ 10,
+ 20,
+ 25,
+ 50,
+ 100,
+ ]
+ }
+ pageText="Page"
+ pivotDefaults={Object {}}
+ pivotIDKey="_pivotID"
+ pivotValKey="_pivotVal"
+ previousText="Previous"
+ resizable={true}
+ resolveData={[Function]}
+ rowsText="rows"
+ showPageJump={true}
+ showPageSizeOptions={true}
+ showPagination={true}
+ showPaginationBottom={true}
+ showPaginationTop={false}
+ sortable={false}
+ style={Object {}}
+ subRowsKey="_subRows"
+ />
+ </div>
+</t>
`;
diff --git a/web-console/src/views/sql-view/sql-view.scss
b/web-console/src/views/sql-view/sql-view.scss
index 54c9117..f1ef90b 100644
--- a/web-console/src/views/sql-view/sql-view.scss
+++ b/web-console/src/views/sql-view/sql-view.scss
@@ -20,22 +20,38 @@
.sql-view {
height: 100%;
- display: flex;
- flex-direction: column;
+ width: 100%;
- .sql-control {
- textarea {
+ .top-pane {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ bottom: 10px;
+
+ .sql-control {
+ position: absolute;
width: 100%;
- min-height: 180px;
+ height: 100%;
}
+ }
- .buttons {
- padding: $standard-padding 0;
- }
+ &.splitter-layout.splitter-layout-vertical > .layout-splitter {
+ height: 3px;
+ background-color: #6d8ea9;
}
- .ReactTable {
- flex: 1;
+ .bottom-pane {
+ position: absolute;
+ width: 100%;
+ top: 10px;
+ bottom: 0;
+
+ .ReactTable {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ }
}
}
diff --git a/web-console/src/views/sql-view/sql-view.tsx
b/web-console/src/views/sql-view/sql-view.tsx
index 46be35ac..dbf0f0d 100644
--- a/web-console/src/views/sql-view/sql-view.tsx
+++ b/web-console/src/views/sql-view/sql-view.tsx
@@ -18,10 +18,11 @@
import * as Hjson from 'hjson';
import * as React from 'react';
+import SplitterLayout from 'react-splitter-layout';
import ReactTable from 'react-table';
-import { NullTableCell, SqlControl } from '../../components/index';
-import { QueryPlanDialog } from '../../dialogs/index';
+import { NullTableCell, SqlControl } from '../../components';
+import { QueryPlanDialog } from '../../dialogs';
import {
BasicQueryExplanation,
decodeRune,
@@ -35,9 +36,9 @@ import {
import './sql-view.scss';
-interface QueryWithFlags {
+interface QueryWithContext {
queryString: string;
- bypassCache?: boolean;
+ context?: Record<string, any>;
wrapQuery?: boolean;
}
@@ -62,9 +63,13 @@ interface SqlQueryResult {
}
export class SqlView extends React.Component<SqlViewProps, SqlViewState> {
+ static trimSemicolon(query: string): string {
+ // Trims out a trailing semicolon while preserving space
(https://bit.ly/1n1yfkJ)
+ return query.replace(/;+(\s*)$/, '$1');
+ }
- private sqlQueryManager: QueryManager<QueryWithFlags, SqlQueryResult>;
- private explainQueryManager: QueryManager<string, any>;
+ private sqlQueryManager: QueryManager<QueryWithContext, SqlQueryResult>;
+ private explainQueryManager: QueryManager<QueryWithContext,
BasicQueryExplanation | SemiJoinQueryExplanation | string>;
constructor(props: SqlViewProps, context: any) {
super(props, context);
@@ -82,20 +87,15 @@ export class SqlView extends React.Component<SqlViewProps,
SqlViewState> {
componentDidMount(): void {
this.sqlQueryManager = new QueryManager({
- processQuery: async (queryWithFlags: QueryWithFlags) => {
- const { queryString, bypassCache, wrapQuery } = queryWithFlags;
+ processQuery: async (queryWithContext: QueryWithContext) => {
+ const { queryString, context, wrapQuery } = queryWithContext;
const startTime = new Date();
if (queryString.trim().startsWith('{')) {
// Secret way to issue a native JSON "rune" query
const runeQuery = Hjson.parse(queryString);
- if (bypassCache) {
- runeQuery.context = runeQuery.context || {};
- runeQuery.context.useCache = false;
- runeQuery.context.populateCache = false;
- }
-
+ if (context) runeQuery.context = context;
const result = await queryDruidRune(runeQuery);
return {
queryResult: decodeRune(runeQuery, result),
@@ -104,7 +104,7 @@ export class SqlView extends React.Component<SqlViewProps,
SqlViewState> {
} else {
const actualQuery = wrapQuery ?
- `SELECT * FROM (${queryString.replace(/;+(\s*)$/, '$1')}) LIMIT
2000` :
+ `SELECT * FROM (${SqlView.trimSemicolon(queryString)}) LIMIT 2000`
:
queryString;
const queryPayload: Record<string, any> = {
@@ -113,13 +113,7 @@ export class SqlView extends React.Component<SqlViewProps,
SqlViewState> {
header: true
};
- if (wrapQuery) {
- queryPayload.context = {
- useCache: false,
- populateCache: false
- };
- }
-
+ if (context) queryPayload.context = context;
const result = await queryDruidSql(queryPayload);
return {
@@ -142,14 +136,17 @@ export class SqlView extends
React.Component<SqlViewProps, SqlViewState> {
});
this.explainQueryManager = new QueryManager({
- processQuery: async (query: string) => {
- const explainQuery = `explain plan for ${query}`;
- const result = await queryDruidSql({
- query: explainQuery,
+ processQuery: async (queryWithContext: QueryWithContext) => {
+ const { queryString, context } = queryWithContext;
+ const explainPayload: Record<string, any> = {
+ query: `EXPLAIN PLAN FOR (${SqlView.trimSemicolon(queryString)})`,
resultFormat: 'object'
- });
- const data: BasicQueryExplanation | SemiJoinQueryExplanation | string
= parseQueryPlan(result[0]['PLAN']);
- return data;
+ };
+
+ if (context) explainPayload.context = context;
+ const result = await queryDruidSql(explainPayload);
+
+ return parseQueryPlan(result[0]['PLAN']);
},
onStateChange: ({ result, loading, error }) => {
this.setState({
@@ -166,13 +163,8 @@ export class SqlView extends React.Component<SqlViewProps,
SqlViewState> {
this.explainQueryManager.terminate();
}
- getExplain = (q: string) => {
- this.setState({
- explainDialogOpen: true,
- loadingExplain: true,
- explainError: null
- });
- this.explainQueryManager.runQuery(q);
+ onSecondaryPaneSizeChange(secondaryPaneSize: number) {
+ localStorageSet(LocalStorageKeys.QUERY_VIEW_PANE_SIZE,
String(secondaryPaneSize));
}
renderExplainDialog() {
@@ -204,8 +196,6 @@ export class SqlView extends React.Component<SqlViewProps,
SqlViewState> {
};
})
}
- defaultPageSize={10}
- className="-striped -highlight"
/>;
}
@@ -213,18 +203,33 @@ export class SqlView extends
React.Component<SqlViewProps, SqlViewState> {
const { initSql } = this.props;
const { queryElapsed } = this.state;
- return <div className="sql-view app-view">
- <SqlControl
- initSql={initSql || localStorageGet(LocalStorageKeys.QUERY_KEY)}
- onRun={(queryString, bypassCache, wrapQuery) => {
- localStorageSet(LocalStorageKeys.QUERY_KEY, queryString);
- this.sqlQueryManager.runQuery({ queryString, bypassCache, wrapQuery
});
- }}
- onExplain={this.getExplain}
- queryElapsed={queryElapsed}
- />
- {this.renderResultTable()}
- {this.renderExplainDialog()}
- </div>;
+ return <SplitterLayout
+ customClassName="sql-view app-view"
+ vertical
+ percentage
+
secondaryInitialSize={Number(localStorageGet(LocalStorageKeys.QUERY_VIEW_PANE_SIZE)
as string) || 60}
+ primaryMinSize={30}
+ secondaryMinSize={30}
+ onSecondaryPaneSizeChange={this.onSecondaryPaneSizeChange}
+ >
+ <div className="top-pane">
+ <SqlControl
+ initSql={initSql || localStorageGet(LocalStorageKeys.QUERY_KEY)}
+ onRun={(queryString, context, wrapQuery) => {
+ localStorageSet(LocalStorageKeys.QUERY_KEY, queryString);
+ this.sqlQueryManager.runQuery({ queryString, context, wrapQuery });
+ }}
+ onExplain={(queryString, context) => {
+ this.setState({ explainDialogOpen: true });
+ this.explainQueryManager.runQuery({ queryString, context });
+ }}
+ queryElapsed={queryElapsed}
+ />
+ </div>
+ <div className="bottom-pane">
+ {this.renderResultTable()}
+ {this.renderExplainDialog()}
+ </div>
+ </SplitterLayout>;
}
}
diff --git
a/web-console/src/views/task-view/__snapshots__/tasks-view.spec.tsx.snap
b/web-console/src/views/task-view/__snapshots__/tasks-view.spec.tsx.snap
index 16b7b90..aaef769 100644
--- a/web-console/src/views/task-view/__snapshots__/tasks-view.spec.tsx.snap
+++ b/web-console/src/views/task-view/__snapshots__/tasks-view.spec.tsx.snap
@@ -63,7 +63,7 @@ exports[`describe tasks view tasks view snapshot 1`] = `
TrComponent={[Function]}
TrGroupComponent={[Function]}
aggregatedKey="_aggregated"
- className="-striped -highlight"
+ className=""
collapseOnDataChange={true}
collapseOnPageChange={true}
collapseOnSortingChange={true}
@@ -140,7 +140,7 @@ exports[`describe tasks view tasks view snapshot 1`] = `
defaultExpanded={Object {}}
defaultFilterMethod={[Function]}
defaultFiltered={Array []}
- defaultPageSize={5}
+ defaultPageSize={20}
defaultResized={Array []}
defaultSortDesc={false}
defaultSortMethod={[Function]}
@@ -392,7 +392,7 @@ exports[`describe tasks view tasks view snapshot 1`] = `
TrComponent={[Function]}
TrGroupComponent={[Function]}
aggregatedKey="_aggregated"
- className="-striped -highlight"
+ className=""
collapseOnDataChange={true}
collapseOnPageChange={true}
collapseOnSortingChange={true}
diff --git a/web-console/src/views/task-view/tasks-view.scss
b/web-console/src/views/task-view/tasks-view.scss
index c22ae9c..e7161b1 100644
--- a/web-console/src/views/task-view/tasks-view.scss
+++ b/web-console/src/views/task-view/tasks-view.scss
@@ -22,27 +22,33 @@
height: 100%;
width: 100%;
+ .top-pane {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ bottom: 0;
+
+ .view-control-bar {
+ margin-bottom: $standard-padding;
+ }
+ }
+
+ &.splitter-layout.splitter-layout-vertical > .layout-splitter {
+ height: 3px;
+ background-color: #6d8ea9;
+ }
- .view-control-bar{
- margin-bottom: $standard-padding;
+ .bottom-pane {
+ position: absolute;
+ width: 100%;
+ top: 12px;
+ bottom: 0;
}
.ReactTable {
position: absolute;
- top: 50px;
+ top: $view-control-bar-height + $standard-padding;
bottom: 0;
width: 100%;
}
-
-}
-
-.bottom-pane {
- position: relative;
- height: 100%;
- padding-top: 15px;
-}
-
-.splitter-layout.splitter-layout-vertical > .layout-splitter{
- height: 2px;
- background-color: #6d8ea9;
}
diff --git a/web-console/src/views/task-view/tasks-view.tsx
b/web-console/src/views/task-view/tasks-view.tsx
index e07c27b..942677e 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { Alert, Button, ButtonGroup, Icon, Intent, Label, Menu, MenuDivider,
MenuItem, Popover, Position } from '@blueprintjs/core';
+import { Alert, Button, ButtonGroup, Intent, Label, Menu, MenuItem, Popover,
Position } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import axios from 'axios';
import * as React from 'react';
@@ -24,8 +24,8 @@ import SplitterLayout from 'react-splitter-layout';
import ReactTable from 'react-table';
import { Filter } from 'react-table';
-import { ActionCell, TableColumnSelection, ViewControlBar} from
'../../components/index';
-import { AsyncActionDialog, SpecDialog, SupervisorTableActionDialog,
TaskTableActionDialog } from '../../dialogs/index';
+import { ActionCell, TableColumnSelection, ViewControlBar} from
'../../components';
+import { AsyncActionDialog, SpecDialog, SupervisorTableActionDialog,
TaskTableActionDialog } from '../../dialogs';
import { AppToaster } from '../../singletons/toaster';
import {
addFilter,
@@ -36,12 +36,12 @@ import {
queryDruidSql,
QueryManager, TableColumnSelectionHandler
} from '../../utils';
-import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
+import { BasicAction } from '../../utils/basic-action';
import './tasks-view.scss';
-const supervisorTableColumns: string[] = ['Datasource', 'Type',
'Topic/Stream', 'Status', 'Actions'];
-const taskTableColumns: string[] = ['Task ID', 'Type', 'Datasource',
'Location', 'Created time', 'Status', 'Duration', 'Actions'];
+const supervisorTableColumns: string[] = ['Datasource', 'Type',
'Topic/Stream', 'Status', ActionCell.COLUMN_LABEL];
+const taskTableColumns: string[] = ['Task ID', 'Type', 'Datasource',
'Location', 'Created time', 'Status', 'Duration', ActionCell.COLUMN_LABEL];
export interface TasksViewProps extends React.Props<any> {
taskId: string | null;
@@ -87,10 +87,10 @@ interface TaskQueryResultRow {
duration: number;
error_msg: string | null;
location: string | null;
- rank: number;
status: string;
task_id: string;
type: string;
+ rank: number;
}
interface SupervisorQueryResultRow {
@@ -115,6 +115,7 @@ export class TasksView extends
React.Component<TasksViewProps, TasksViewState> {
private supervisorTableColumnSelectionHandler: TableColumnSelectionHandler;
private taskTableColumnSelectionHandler: TableColumnSelectionHandler;
static statusRanking: Record<string, number> = {RUNNING: 4, PENDING: 3,
WAITING: 2, SUCCESS: 1, FAILED: 1};
+
constructor(props: TasksViewProps, context: any) {
super(props, context);
this.state = {
@@ -153,9 +154,6 @@ export class TasksView extends
React.Component<TasksViewProps, TasksViewState> {
this.taskTableColumnSelectionHandler = new TableColumnSelectionHandler(
LocalStorageKeys.TASK_TABLE_COLUMN_SELECTION, () => this.setState({})
);
- if (!localStorageGet(LocalStorageKeys.TASKS_VIEW_PANE_SIZE)) {
- localStorageSet(LocalStorageKeys.TASKS_VIEW_PANE_SIZE, '60');
- }
}
static parseTasks = (data: any[]): TaskQueryResultRow[] => {
@@ -166,10 +164,10 @@ export class TasksView extends
React.Component<TasksViewProps, TasksViewState> {
duration: d.duration ? d.duration : 0,
error_msg: d.errorMsg,
location: d.location.host ? `${d.location.host}:${d.location.port}` :
null,
- rank: (TasksView.statusRanking as any)[d.statusCode === 'RUNNING' ?
d.runnerStatusCode : d.statusCode],
status: d.statusCode === 'RUNNING' ? d.runnerStatusCode : d.statusCode,
task_id: d.id,
- type: d.typTasksView
+ type: d.typTasksView,
+ rank: TasksView.statusRanking[d.statusCode === 'RUNNING' ?
d.runnerStatusCode : d.statusCode]
};
});
}
@@ -226,12 +224,14 @@ export class TasksView extends
React.Component<TasksViewProps, TasksViewState> {
// FAILED => 1
this.taskQueryManager.runQuery(`SELECT
- "task_id", "type", "datasource", "created_time",
+ "task_id", "type", "datasource", "created_time", "location", "duration",
"error_msg",
CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS
"status",
- CASE WHEN "status" = 'RUNNING' THEN
- (CASE WHEN "runner_status" = 'RUNNING' THEN 4 WHEN "runner_status" =
'PENDING' THEN 3 ELSE 2 END)
- ELSE 1 END AS "rank",
- "location", "duration", "error_msg"
+ (
+ CASE WHEN "status" = 'RUNNING' THEN
+ (CASE "runner_status" WHEN 'RUNNING' THEN 4 WHEN 'PENDING' THEN 3 ELSE 2
END)
+ ELSE 1
+ END
+ ) AS "rank"
FROM sys.tasks
ORDER BY "rank" DESC, "created_time" DESC`);
@@ -479,39 +479,27 @@ ORDER BY "rank" DESC, "created_time" DESC`);
show: supervisorTableColumnSelectionHandler.showColumn('Status')
},
{
- Header: 'Actions',
- id: 'actions',
+ Header: ActionCell.COLUMN_LABEL,
+ id: ActionCell.COLUMN_ID,
accessor: 'id',
- width: 70,
+ width: ActionCell.COLUMN_WIDTH,
filterable: false,
Cell: row => {
const id = row.value;
const type = row.row.type;
const supervisorSuspended = row.original.spec.suspended;
const supervisorActions = this.getSupervisorActions(id,
supervisorSuspended, type);
- const supervisorMenu = basicActionsToMenu(supervisorActions);
-
- return <ActionCell>
- <Icon
- icon={IconNames.SEARCH_TEMPLATE}
- onClick={() => this.setState({
- supervisorTableActionDialogId: id,
- supervisorTableActionDialogActions: supervisorActions
- })}
- />
- {
- supervisorMenu &&
- <Popover content={supervisorMenu}
position={Position.BOTTOM_RIGHT}>
- <Icon icon={IconNames.WRENCH}/>
- </Popover>
- }
- </ActionCell>;
+ return <ActionCell
+ onDetail={() => this.setState({
+ supervisorTableActionDialogId: id,
+ supervisorTableActionDialogActions: supervisorActions
+ })}
+ actions={supervisorActions}
+ />;
},
- show: supervisorTableColumnSelectionHandler.showColumn('Actions')
+ show:
supervisorTableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL)
}
]}
- defaultPageSize={5}
- className="-striped -highlight"
/>
{this.renderResumeSupervisorAction()}
{this.renderSuspendSupervisorAction()}
@@ -675,10 +663,10 @@ ORDER BY "rank" DESC, "created_time" DESC`);
show: taskTableColumnSelectionHandler.showColumn('Duration')
},
{
- Header: 'Actions',
- id: 'actions',
+ Header: ActionCell.COLUMN_LABEL,
+ id: ActionCell.COLUMN_ID,
accessor: 'task_id',
- width: 70,
+ width: ActionCell.COLUMN_WIDTH,
filterable: false,
Cell: row => {
if (row.aggregated) return '';
@@ -686,30 +674,18 @@ ORDER BY "rank" DESC, "created_time" DESC`);
const type = row.row.type;
const { status } = row.original;
const taskActions = this.getTaskActions(id, status, type);
- const taskMenu = basicActionsToMenu(taskActions);
-
- return <ActionCell>
- <Icon
- icon={IconNames.SEARCH_TEMPLATE}
- onClick={() => this.setState({
- taskTableActionDialogId: id,
- taskTableActionDialogActions: taskActions
- })}
- />
- {
- taskMenu &&
- <Popover content={taskMenu} position={Position.BOTTOM_RIGHT}>
- <Icon icon={IconNames.WRENCH}/>
- </Popover>
- }
- </ActionCell>;
+ return <ActionCell
+ onDetail={() => this.setState({
+ taskTableActionDialogId: id,
+ taskTableActionDialogActions: taskActions
+ })}
+ actions={taskActions}
+ />;
},
Aggregated: row => '',
- show: taskTableColumnSelectionHandler.showColumn('Actions')
+ show:
taskTableColumnSelectionHandler.showColumn(ActionCell.COLUMN_LABEL)
}
]}
- defaultPageSize={20}
- className="-striped -highlight"
/>
{this.renderKillTaskAction()}
</>;
@@ -736,7 +712,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
customClassName={'tasks-view app-view'}
vertical
percentage
-
secondaryInitialSize={Number(localStorageGet(LocalStorageKeys.TASKS_VIEW_PANE_SIZE)
as string)}
+
secondaryInitialSize={Number(localStorageGet(LocalStorageKeys.TASKS_VIEW_PANE_SIZE)
as string) || 60}
primaryMinSize={30}
secondaryMinSize={30}
onSecondaryPaneSizeChange={this.onSecondaryPaneSizeChange}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]