This is an automated email from the ASF dual-hosted git repository.
fjy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git
The following commit(s) were added to refs/heads/master by this push:
new 8493bd5 Web console: added bulk supervisor actions (#8028)
8493bd5 is described below
commit 8493bd5c1ccbd54c2c6d873591b1a44179655f5e
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Tue Jul 9 17:31:23 2019 -0700
Web console: added bulk supervisor actions (#8028)
* added bulk supervisor actions to the web console
* improve async action dialog
* fix tests
---
web-console/package.json | 1 -
web-console/src/console-application.tsx | 4 +-
.../async-action-dialog.spec.tsx.snap | 2 +-
.../async-action-dialog/async-action-dialog.scss | 23 ++
.../async-action-dialog/async-action-dialog.tsx | 110 ++++++----
web-console/src/utils/general.tsx | 6 +
web-console/src/utils/query-manager.tsx | 11 +-
.../__snapshots__/datasource-view.spec.tsx.snap | 59 -----
.../src/views/datasource-view/datasource-view.tsx | 112 +++++-----
.../__snapshots__/lookups-view.spec.tsx.snap | 12 -
.../src/views/lookups-view/lookups-view.tsx | 21 +-
.../__snapshots__/segments-view.spec.tsx.snap | 15 --
.../src/views/segments-view/segments-view.tsx | 33 ++-
.../__snapshots__/servers-view.spec.tsx.snap | 24 --
.../src/views/servers-view/servers-view.tsx | 52 +++--
.../__snapshots__/tasks-view.spec.tsx.snap | 118 +++++-----
web-console/src/views/task-view/tasks-view.tsx | 244 +++++++++++++++------
17 files changed, 448 insertions(+), 399 deletions(-)
diff --git a/web-console/package.json b/web-console/package.json
index edb85f2..0ec61cb 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -33,7 +33,6 @@
"endOfLine": "lf"
},
"scripts": {
- "watch": "./script/watch",
"compile": "./script/build",
"pretest": "./script/build",
"run": "./script/run",
diff --git a/web-console/src/console-application.tsx
b/web-console/src/console-application.tsx
index 8485a07..268fb92 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -62,7 +62,7 @@ export class ConsoleApplication extends React.PureComponent<
> {
static MESSAGE_KEY = 'druid-console-message';
static MESSAGE_DISMISSED = 'dismissed';
- private capabilitiesQueryManager: QueryManager<string, Capabilities>;
+ private capabilitiesQueryManager: QueryManager<null, Capabilities>;
static async discoverCapabilities(): Promise<Capabilities> {
try {
@@ -151,7 +151,7 @@ export class ConsoleApplication extends React.PureComponent<
}
componentDidMount(): void {
- this.capabilitiesQueryManager.runQuery('dummy');
+ this.capabilitiesQueryManager.runQuery(null);
}
componentWillUnmount(): void {
diff --git
a/web-console/src/dialogs/async-action-dialog/__snapshots__/async-action-dialog.spec.tsx.snap
b/web-console/src/dialogs/async-action-dialog/__snapshots__/async-action-dialog.spec.tsx.snap
index a2df6e9..811a405 100644
---
a/web-console/src/dialogs/async-action-dialog/__snapshots__/async-action-dialog.spec.tsx.snap
+++
b/web-console/src/dialogs/async-action-dialog/__snapshots__/async-action-dialog.spec.tsx.snap
@@ -16,7 +16,7 @@ exports[`async action dialog matches snapshot 1`] = `
tabindex="0"
>
<div
- class="bp3-dialog bp3-alert async-alert-dialog"
+ class="bp3-dialog bp3-alert async-action-dialog"
>
<div
class="bp3-alert-body"
diff --git
a/web-console/src/dialogs/async-action-dialog/async-action-dialog.scss
b/web-console/src/dialogs/async-action-dialog/async-action-dialog.scss
new file mode 100644
index 0000000..3e53727
--- /dev/null
+++ b/web-console/src/dialogs/async-action-dialog/async-action-dialog.scss
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.async-action-dialog {
+ .progress-group {
+ width: 100%;
+ }
+}
diff --git
a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
index e5a26c3..9891d1c 100644
--- a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
+++ b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
@@ -16,16 +16,28 @@
* limitations under the License.
*/
-import { Button, Classes, Dialog, Icon, Intent, ProgressBar } from
'@blueprintjs/core';
-import { IconName } from '@blueprintjs/icons';
+import {
+ Button,
+ Classes,
+ Dialog,
+ FormGroup,
+ Icon,
+ IconName,
+ Intent,
+ ProgressBar,
+} from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import React from 'react';
import { AppToaster } from '../../singletons/toaster';
-export interface AsyncAlertDialogProps {
- action: null | (() => Promise<void>);
- onClose: (success: boolean) => void;
+import './async-action-dialog.scss';
+
+export interface AsyncActionDialogProps {
+ action: () => Promise<void>;
+ onClose: () => void;
+ onSuccess?: () => void;
confirmButtonText: string;
confirmButtonDisabled?: boolean;
cancelButtonText?: string;
@@ -36,24 +48,33 @@ export interface AsyncAlertDialogProps {
failText: string;
}
-export interface AsyncAlertDialogState {
+export interface AsyncActionDialogState {
working: boolean;
}
export class AsyncActionDialog extends React.PureComponent<
- AsyncAlertDialogProps,
- AsyncAlertDialogState
+ AsyncActionDialogProps,
+ AsyncActionDialogState
> {
- constructor(props: AsyncAlertDialogProps) {
+ private mounted = false;
+
+ constructor(props: AsyncActionDialogProps) {
super(props);
this.state = {
working: false,
};
}
+ componentDidMount(): void {
+ this.mounted = true;
+ }
+
+ componentWillUnmount(): void {
+ this.mounted = false;
+ }
+
private handleConfirm = async () => {
- const { action, onClose, successText, failText } = this.props;
- if (!action) throw new Error('should never get here');
+ const { action, onClose, onSuccess, successText, failText } = this.props;
this.setState({ working: true });
try {
@@ -63,22 +84,30 @@ export class AsyncActionDialog extends React.PureComponent<
message: `${failText}: ${e.message}`,
intent: Intent.DANGER,
});
- this.setState({ working: false });
- onClose(false);
+ if (this.mounted) {
+ this.setState({ working: false });
+ onClose();
+ }
return;
}
AppToaster.show({
message: successText,
intent: Intent.SUCCESS,
});
- this.setState({ working: false });
- onClose(true);
+ if (this.mounted) {
+ this.setState({ working: false });
+ }
+ if (onSuccess) onSuccess();
+ onClose();
+ };
+
+ private handleClose = () => {
+ const { onClose } = this.props;
+ onClose();
};
render() {
const {
- action,
- onClose,
className,
icon,
intent,
@@ -88,34 +117,41 @@ export class AsyncActionDialog extends React.PureComponent<
children,
} = this.props;
const { working } = this.state;
- if (!action) return null;
-
- const handleClose = () => onClose(false);
return (
<Dialog
isOpen
- className={classNames(Classes.ALERT, 'async-alert-dialog', className)}
+ className={classNames(Classes.ALERT, 'async-action-dialog', className)}
canEscapeKeyClose={!working}
- onClose={handleClose}
+ onClose={this.handleClose}
>
<div className={Classes.ALERT_BODY}>
- {icon && <Icon icon={icon} />}
- {!working && <div
className={Classes.ALERT_CONTENTS}>{children}</div>}
+ {working ? (
+ <FormGroup className="progress-group" label="Processing action...">
+ <ProgressBar intent={intent || Intent.PRIMARY} />
+ </FormGroup>
+ ) : (
+ <>
+ {icon && <Icon icon={icon} />}
+ <div className={Classes.ALERT_CONTENTS}>{children}</div>
+ </>
+ )}
+ </div>
+ <div className={Classes.ALERT_FOOTER}>
+ {working ? (
+ <Button icon={IconNames.EYE_OFF} text="Run in background"
onClick={this.handleClose} />
+ ) : (
+ <>
+ <Button
+ intent={intent}
+ text={confirmButtonText}
+ onClick={this.handleConfirm}
+ disabled={confirmButtonDisabled}
+ />
+ <Button text={cancelButtonText || 'Cancel'}
onClick={this.handleClose} />
+ </>
+ )}
</div>
- {working ? (
- <ProgressBar />
- ) : (
- <div className={Classes.ALERT_FOOTER}>
- <Button
- intent={intent}
- text={confirmButtonText}
- onClick={this.handleConfirm}
- disabled={confirmButtonDisabled}
- />
- <Button text={cancelButtonText || 'Cancel'} onClick={handleClose}
/>
- </div>
- )}
</Dialog>
);
}
diff --git a/web-console/src/utils/general.tsx
b/web-console/src/utils/general.tsx
index 7f13b04..6f834e8 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -24,6 +24,12 @@ import numeral from 'numeral';
import React from 'react';
import { Filter, FilterRender } from 'react-table';
+export function wait(ms: number): Promise<void> {
+ return new Promise(resolve => {
+ setTimeout(resolve, ms);
+ });
+}
+
export function addFilter(filters: Filter[], id: string, value: string):
Filter[] {
value = `"${value}"`;
const currentFilter = filters.find(f => f.id === id);
diff --git a/web-console/src/utils/query-manager.tsx
b/web-console/src/utils/query-manager.tsx
index 8938067..1317c7b 100644
--- a/web-console/src/utils/query-manager.tsx
+++ b/web-console/src/utils/query-manager.tsx
@@ -117,18 +117,15 @@ export class QueryManager<Q, R> {
}
public runQuery(query: Q): void {
+ if (this.terminated) return;
this.nextQuery = query;
this.trigger();
}
- public rerunLastQuery(): void {
+ public rerunLastQuery(runInBackground = false): void {
+ if (this.terminated) return;
this.nextQuery = this.lastQuery;
- this.trigger();
- }
-
- public rerunLastQueryInBackground(auto: boolean): void {
- this.nextQuery = this.lastQuery;
- if (auto) {
+ if (runInBackground) {
this.runWhenIdle();
} else {
this.trigger();
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 dac1512..c2f33f2 100755
---
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
@@ -246,64 +246,5 @@ exports[`data source view matches snapshot 1`] = `
style={Object {}}
subRowsKey="_subRows"
/>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Drop data"
- failText="Could not drop data"
- intent="danger"
- onClose={[Function]}
- successText="Data drop request acknowledged, next time the coordinator
runs data will be dropped"
- >
- <p>
- Are you sure you want to drop all the data for datasource 'null'?
- </p>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Enable datasource"
- failText="Could not enable datasource"
- intent="primary"
- onClose={[Function]}
- successText="Datasource has been enabled"
- >
- <p>
- Are you sure you want to enable datasource 'null'?
- </p>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonDisabled={true}
- confirmButtonText="Drop selected data"
- failText="Could not drop data"
- intent="primary"
- onClose={[Function]}
- successText="Drop request submitted"
- >
- <p>
- Please select the interval that you want to drop?
- </p>
- <Blueprint3.FormGroup>
- <Blueprint3.InputGroup
- onChange={[Function]}
- placeholder="2018-01-01T00:00:00/2018-01-03T00:00:00"
- value=""
- />
- </Blueprint3.FormGroup>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Permanently delete data"
- failText="Could not submit kill task"
- intent="danger"
- onClose={[Function]}
- successText="Kill task was issued. Datasource will be deleted"
- >
- <p>
- Are you sure you want to permanently delete the data in datasource
'null'?
- </p>
- <p>
- This action can not be undone.
- </p>
- </AsyncActionDialog>
</div>
`;
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx
b/web-console/src/views/datasource-view/datasource-view.tsx
index ab6f1b4..9a4ae18 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -266,27 +266,26 @@ GROUP BY 1`;
renderDropDataAction() {
const { dropDataDatasource } = this.state;
+ if (!dropDataDatasource) return;
return (
<AsyncActionDialog
- action={
- dropDataDatasource
- ? async () => {
- const resp = await axios.delete(
- `/druid/coordinator/v1/datasources/${dropDataDatasource}`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.delete(
+ `/druid/coordinator/v1/datasources/${dropDataDatasource}`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Drop data"
successText="Data drop request acknowledged, next time the coordinator
runs data will be dropped"
failText="Could not drop data"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ dropDataDatasource: null });
- if (success) this.datasourceQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.datasourceQueryManager.rerunLastQuery();
}}
>
<p>
@@ -298,27 +297,26 @@ GROUP BY 1`;
renderEnableAction() {
const { enableDatasource } = this.state;
+ if (!enableDatasource) return;
return (
<AsyncActionDialog
- action={
- enableDatasource
- ? async () => {
- const resp = await axios.post(
- `/druid/coordinator/v1/datasources/${enableDatasource}`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+ `/druid/coordinator/v1/datasources/${enableDatasource}`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Enable datasource"
successText="Datasource has been enabled"
failText="Could not enable datasource"
intent={Intent.PRIMARY}
- onClose={success => {
+ onClose={() => {
this.setState({ enableDatasource: null });
- if (success) this.datasourceQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.datasourceQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to enable datasource
'${enableDatasource}'?`}</p>
@@ -328,34 +326,33 @@ GROUP BY 1`;
renderDropReloadAction() {
const { dropReloadDatasource, dropReloadAction, dropReloadInterval } =
this.state;
+ if (!dropReloadDatasource) return;
const isDrop = dropReloadAction === 'drop';
return (
<AsyncActionDialog
- action={
- dropReloadDatasource
- ? async () => {
- if (!dropReloadInterval) return;
- const resp = await axios.post(
- `/druid/coordinator/v1/datasources/${dropReloadDatasource}/${
- isDrop ? 'markUnused' : 'markUsed'
- }`,
- {
- interval: dropReloadInterval,
- },
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ if (!dropReloadInterval) return;
+ const resp = await axios.post(
+ `/druid/coordinator/v1/datasources/${dropReloadDatasource}/${
+ isDrop ? 'markUnused' : 'markUsed'
+ }`,
+ {
+ interval: dropReloadInterval,
+ },
+ );
+ return resp.data;
+ }}
confirmButtonText={`${isDrop ? 'Drop' : 'Reload'} selected data`}
confirmButtonDisabled={!/.\/./.test(dropReloadInterval)}
successText={`${isDrop ? 'Drop' : 'Reload'} request submitted`}
failText={`Could not ${isDrop ? 'drop' : 'reload'} data`}
intent={Intent.PRIMARY}
- onClose={success => {
+ onClose={() => {
this.setState({ dropReloadDatasource: null });
- if (success) this.datasourceQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.datasourceQueryManager.rerunLastQuery();
}}
>
<p>{`Please select the interval that you want to ${isDrop ? 'drop' :
'reload'}?`}</p>
@@ -375,27 +372,26 @@ GROUP BY 1`;
renderKillAction() {
const { killDatasource } = this.state;
+ if (!killDatasource) return;
return (
<AsyncActionDialog
- action={
- killDatasource
- ? async () => {
- const resp = await axios.delete(
-
`/druid/coordinator/v1/datasources/${killDatasource}?kill=true&interval=1000/3000`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.delete(
+
`/druid/coordinator/v1/datasources/${killDatasource}?kill=true&interval=1000/3000`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Permanently delete data"
successText="Kill task was issued. Datasource will be deleted"
failText="Could not submit kill task"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ killDatasource: null });
- if (success) this.datasourceQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.datasourceQueryManager.rerunLastQuery();
}}
>
<p>
@@ -808,7 +804,7 @@ GROUP BY 1`;
<div className="data-sources-view app-view">
<ViewControlBar label="Datasources">
<RefreshButton
- onRefresh={auto =>
this.datasourceQueryManager.rerunLastQueryInBackground(auto)}
+ onRefresh={auto =>
this.datasourceQueryManager.rerunLastQuery(auto)}
localStorageKey={LocalStorageKeys.DATASOURCES_REFRESH_RATE}
/>
{!noSqlMode && (
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 228fed1..a57aea4 100755
---
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
@@ -220,17 +220,5 @@ exports[`lookups view matches snapshot 1`] = `
onClose={[Function]}
onSubmit={[Function]}
/>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Delete lookup"
- failText="Could not delete lookup"
- intent="danger"
- onClose={[Function]}
- successText="Lookup was deleted"
- >
- <p>
- Are you sure you want to delete the lookup 'null'?
- </p>
- </AsyncActionDialog>
</div>
`;
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx
b/web-console/src/views/lookups-view/lookups-view.tsx
index a792cbb..2b6bb19 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -235,25 +235,24 @@ export class LookupsView extends
React.PureComponent<LookupsViewProps, LookupsVi
renderDeleteLookupAction() {
const { deleteLookupTier, deleteLookupName } = this.state;
+ if (!deleteLookupTier) return;
return (
<AsyncActionDialog
- action={
- deleteLookupTier
- ? async () => {
- await axios.delete(
-
`/druid/coordinator/v1/lookups/config/${deleteLookupTier}/${deleteLookupName}`,
- );
- }
- : null
- }
+ action={async () => {
+ await axios.delete(
+
`/druid/coordinator/v1/lookups/config/${deleteLookupTier}/${deleteLookupName}`,
+ );
+ }}
confirmButtonText="Delete lookup"
successText="Lookup was deleted"
failText="Could not delete lookup"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ deleteLookupTier: null, deleteLookupName: null });
- if (success) this.lookupsQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.lookupsQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to delete the lookup
'${deleteLookupName}'?`}</p>
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 efd5139..5179ad4 100755
---
a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
+++
b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
@@ -305,20 +305,5 @@ exports[`segments-view matches snapshot 1`] = `
subRowsKey="_subRows"
/>
</div>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Drop Segment"
- failText="Could not drop segment"
- intent="danger"
- onClose={[Function]}
- successText="Segment drop request acknowledged, next time the coordinator
runs segment will be dropped"
- >
- <p>
- Are you sure you want to drop segment 'null'?
- </p>
- <p>
- This action is not reversible.
- </p>
- </AsyncActionDialog>
</Fragment>
`;
diff --git a/web-console/src/views/segments-view/segments-view.tsx
b/web-console/src/views/segments-view/segments-view.tsx
index 8be5c05..2a65636 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -479,30 +479,27 @@ export class SegmentsView extends
React.PureComponent<SegmentsViewProps, Segment
renderTerminateSegmentAction() {
const { terminateSegmentId, terminateDatasourceId } = this.state;
+ if (!terminateDatasourceId || !terminateSegmentId) return;
return (
<AsyncActionDialog
- action={
- terminateSegmentId
- ? async () => {
- const resp = await axios.delete(
-
`/druid/coordinator/v1/datasources/${terminateDatasourceId}/segments/${terminateSegmentId}`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.delete(
+
`/druid/coordinator/v1/datasources/${terminateDatasourceId}/segments/${terminateSegmentId}`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Drop Segment"
successText="Segment drop request acknowledged, next time the
coordinator runs segment will be dropped"
failText="Could not drop segment"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ terminateSegmentId: null });
- if (success) {
- this.segmentsNoSqlQueryManager.rerunLastQuery();
- this.segmentsSqlQueryManager.rerunLastQuery();
- }
+ }}
+ onSuccess={() => {
+ this.segmentsNoSqlQueryManager.rerunLastQuery();
+ this.segmentsSqlQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to drop segment
'${terminateSegmentId}'?`}</p>
@@ -528,8 +525,8 @@ export class SegmentsView extends
React.PureComponent<SegmentsViewProps, Segment
<RefreshButton
onRefresh={auto =>
noSqlMode
- ?
this.segmentsNoSqlQueryManager.rerunLastQueryInBackground(auto)
- :
this.segmentsSqlQueryManager.rerunLastQueryInBackground(auto)
+ ? this.segmentsNoSqlQueryManager.rerunLastQuery(auto)
+ : this.segmentsSqlQueryManager.rerunLastQuery(auto)
}
localStorageKey={LocalStorageKeys.SEGMENTS_REFRESH_RATE}
/>
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 520323d..315424a 100755
---
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
@@ -284,29 +284,5 @@ exports[`servers view action servers view 1`] = `
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.tsx
b/web-console/src/views/servers-view/servers-view.tsx
index 01c8319..142faa0 100644
--- a/web-console/src/views/servers-view/servers-view.tsx
+++ b/web-console/src/views/servers-view/servers-view.tsx
@@ -560,27 +560,26 @@ ORDER BY "rank" DESC, "server" DESC`;
renderDisableWorkerAction() {
const { middleManagerDisableWorkerHost } = this.state;
+ if (!middleManagerDisableWorkerHost) return;
return (
<AsyncActionDialog
- action={
- middleManagerDisableWorkerHost
- ? async () => {
- const resp = await axios.post(
-
`/druid/indexer/v1/worker/${middleManagerDisableWorkerHost}/disable`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+
`/druid/indexer/v1/worker/${middleManagerDisableWorkerHost}/disable`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Disable worker"
successText="Worker has been disabled"
failText="Could not disable worker"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ middleManagerDisableWorkerHost: null });
- if (success) this.serverQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.serverQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to disable worker
'${middleManagerDisableWorkerHost}'?`}</p>
@@ -590,27 +589,26 @@ ORDER BY "rank" DESC, "server" DESC`;
renderEnableWorkerAction() {
const { middleManagerEnableWorkerHost } = this.state;
+ if (!middleManagerEnableWorkerHost) return;
return (
<AsyncActionDialog
- action={
- middleManagerEnableWorkerHost
- ? async () => {
- const resp = await axios.post(
-
`/druid/indexer/v1/worker/${middleManagerEnableWorkerHost}/enable`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+ `/druid/indexer/v1/worker/${middleManagerEnableWorkerHost}/enable`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Enable worker"
successText="Worker has been enabled"
failText="Could not enable worker"
intent={Intent.PRIMARY}
- onClose={success => {
+ onClose={() => {
this.setState({ middleManagerEnableWorkerHost: null });
- if (success) this.serverQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.serverQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to enable worker
'${middleManagerEnableWorkerHost}'?`}</p>
@@ -647,7 +645,7 @@ ORDER BY "rank" DESC, "server" DESC`;
</Button>
</ButtonGroup>
<RefreshButton
- onRefresh={auto =>
this.serverQueryManager.rerunLastQueryInBackground(auto)}
+ onRefresh={auto => this.serverQueryManager.rerunLastQuery(auto)}
localStorageKey={LocalStorageKeys.SERVERS_REFRESH_RATE}
/>
{!noSqlMode && (
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 9ced81f..33ddf2f 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
@@ -70,6 +70,61 @@ exports[`tasks view matches snapshot 1`] = `
text="Submit supervisor"
/>
</Blueprint3.Popover>
+ <Blueprint3.Popover
+ boundary="scrollParent"
+ captureDismiss={false}
+ content={
+ <Blueprint3.Menu>
+ <Blueprint3.MenuItem
+ disabled={false}
+ icon="play"
+ multiline={false}
+ onClick={[Function]}
+ popoverProps={Object {}}
+ shouldDismissPopover={true}
+ text="Resume all supervisors"
+ />
+ <Blueprint3.MenuItem
+ disabled={false}
+ icon="pause"
+ multiline={false}
+ onClick={[Function]}
+ popoverProps={Object {}}
+ shouldDismissPopover={true}
+ text="Suspend all supervisors"
+ />
+ <Blueprint3.MenuItem
+ disabled={false}
+ icon="cross"
+ intent="danger"
+ multiline={false}
+ onClick={[Function]}
+ popoverProps={Object {}}
+ shouldDismissPopover={true}
+ text="Terminate all supervisors"
+ />
+ </Blueprint3.Menu>
+ }
+ defaultIsOpen={false}
+ disabled={false}
+ hasBackdrop={false}
+ hoverCloseDelay={300}
+ hoverOpenDelay={150}
+ inheritDarkTheme={true}
+ interactionKind="click"
+ minimal={false}
+ modifiers={Object {}}
+ openOnTargetFocus={true}
+ position="bottom-left"
+ targetTagName="span"
+ transitionDuration={300}
+ usePortal={true}
+ wrapperTagName="span"
+ >
+ <Blueprint3.Button
+ icon="more"
+ />
+ </Blueprint3.Popover>
<TableColumnSelector
columns={
Array [
@@ -261,57 +316,6 @@ exports[`tasks view matches snapshot 1`] = `
style={Object {}}
subRowsKey="_subRows"
/>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Resume supervisor"
- failText="Could not resume supervisor"
- intent="primary"
- onClose={[Function]}
- successText="Supervisor has been resumed"
- >
- <p>
- Are you sure you want to resume supervisor 'null'?
- </p>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Suspend supervisor"
- failText="Could not suspend supervisor"
- intent="danger"
- onClose={[Function]}
- successText="Supervisor has been suspended"
- >
- <p>
- Are you sure you want to suspend supervisor 'null'?
- </p>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Reset supervisor"
- failText="Could not reset supervisor"
- intent="danger"
- onClose={[Function]}
- successText="Supervisor has been reset"
- >
- <p>
- Are you sure you want to reset supervisor 'null'?
- </p>
- </AsyncActionDialog>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Terminate supervisor"
- failText="Could not terminate supervisor"
- intent="danger"
- onClose={[Function]}
- successText="Supervisor has been terminated"
- >
- <p>
- Are you sure you want to terminate supervisor 'null'?
- </p>
- <p>
- This action is not reversible.
- </p>
- </AsyncActionDialog>
</div>
<div
className="bottom-pane"
@@ -639,18 +643,6 @@ exports[`tasks view matches snapshot 1`] = `
style={Object {}}
subRowsKey="_subRows"
/>
- <AsyncActionDialog
- action={null}
- confirmButtonText="Kill task"
- failText="Could not kill task"
- intent="danger"
- onClose={[Function]}
- successText="Task was killed"
- >
- <p>
- Are you sure you want to kill task 'null'?
- </p>
- </AsyncActionDialog>
</div>
</t>
<Blueprint3.Alert
diff --git a/web-console/src/views/task-view/tasks-view.tsx
b/web-console/src/views/task-view/tasks-view.tsx
index 39bb7f9..28c0541 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -95,6 +95,10 @@ export interface TasksViewState {
resetSupervisorId: string | null;
terminateSupervisorId: string | null;
+ showResumeAllSupervisors: boolean;
+ showSuspendAllSupervisors: boolean;
+ showTerminateAllSupervisors: boolean;
+
tasksLoading: boolean;
tasks: any[] | null;
tasksError: string | null;
@@ -206,6 +210,10 @@ ORDER BY "rank" DESC, "created_time" DESC`;
supervisorTableActionDialogId: null,
terminateSupervisorId: null,
+ showResumeAllSupervisors: false,
+ showSuspendAllSupervisors: false,
+ showTerminateAllSupervisors: false,
+
tasksLoading: true,
tasks: null,
tasksError: null,
@@ -394,27 +402,26 @@ ORDER BY "rank" DESC, "created_time" DESC`;
renderResumeSupervisorAction() {
const { resumeSupervisorId } = this.state;
+ if (!resumeSupervisorId) return;
return (
<AsyncActionDialog
- action={
- resumeSupervisorId
- ? async () => {
- const resp = await axios.post(
- `/druid/indexer/v1/supervisor/${resumeSupervisorId}/resume`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+ `/druid/indexer/v1/supervisor/${resumeSupervisorId}/resume`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Resume supervisor"
successText="Supervisor has been resumed"
failText="Could not resume supervisor"
intent={Intent.PRIMARY}
- onClose={success => {
+ onClose={() => {
this.setState({ resumeSupervisorId: null });
- if (success) this.supervisorQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to resume supervisor
'${resumeSupervisorId}'?`}</p>
@@ -424,27 +431,26 @@ ORDER BY "rank" DESC, "created_time" DESC`;
renderSuspendSupervisorAction() {
const { suspendSupervisorId } = this.state;
+ if (!suspendSupervisorId) return;
return (
<AsyncActionDialog
- action={
- suspendSupervisorId
- ? async () => {
- const resp = await axios.post(
-
`/druid/indexer/v1/supervisor/${suspendSupervisorId}/suspend`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+ `/druid/indexer/v1/supervisor/${suspendSupervisorId}/suspend`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Suspend supervisor"
successText="Supervisor has been suspended"
failText="Could not suspend supervisor"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ suspendSupervisorId: null });
- if (success) this.supervisorQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to suspend supervisor
'${suspendSupervisorId}'?`}</p>
@@ -454,27 +460,26 @@ ORDER BY "rank" DESC, "created_time" DESC`;
renderResetSupervisorAction() {
const { resetSupervisorId } = this.state;
+ if (!resetSupervisorId) return;
return (
<AsyncActionDialog
- action={
- resetSupervisorId
- ? async () => {
- const resp = await axios.post(
- `/druid/indexer/v1/supervisor/${resetSupervisorId}/reset`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+ `/druid/indexer/v1/supervisor/${resetSupervisorId}/reset`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Reset supervisor"
successText="Supervisor has been reset"
failText="Could not reset supervisor"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ resetSupervisorId: null });
- if (success) this.supervisorQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to reset supervisor
'${resetSupervisorId}'?`}</p>
@@ -484,27 +489,26 @@ ORDER BY "rank" DESC, "created_time" DESC`;
renderTerminateSupervisorAction() {
const { terminateSupervisorId } = this.state;
+ if (!terminateSupervisorId) return;
return (
<AsyncActionDialog
- action={
- terminateSupervisorId
- ? async () => {
- const resp = await axios.post(
-
`/druid/indexer/v1/supervisor/${terminateSupervisorId}/terminate`,
- {},
- );
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await axios.post(
+ `/druid/indexer/v1/supervisor/${terminateSupervisorId}/terminate`,
+ {},
+ );
+ return resp.data;
+ }}
confirmButtonText="Terminate supervisor"
successText="Supervisor has been terminated"
failText="Could not terminate supervisor"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ terminateSupervisorId: null });
- if (success) this.supervisorQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to terminate supervisor
'${terminateSupervisorId}'?`}</p>
@@ -638,24 +642,23 @@ ORDER BY "rank" DESC, "created_time" DESC`;
renderKillTaskAction() {
const { killTaskId } = this.state;
+ if (!killTaskId) return;
return (
<AsyncActionDialog
- action={
- killTaskId
- ? async () => {
- const resp = await
axios.post(`/druid/indexer/v1/task/${killTaskId}/shutdown`, {});
- return resp.data;
- }
- : null
- }
+ action={async () => {
+ const resp = await
axios.post(`/druid/indexer/v1/task/${killTaskId}/shutdown`, {});
+ return resp.data;
+ }}
confirmButtonText="Kill task"
successText="Task was killed"
failText="Could not kill task"
intent={Intent.DANGER}
- onClose={success => {
+ onClose={() => {
this.setState({ killTaskId: null });
- if (success) this.taskQueryManager.rerunLastQuery();
+ }}
+ onSuccess={() => {
+ this.taskQueryManager.rerunLastQuery();
}}
>
<p>{`Are you sure you want to kill task '${killTaskId}'?`}</p>
@@ -849,6 +852,118 @@ ORDER BY "rank" DESC, "created_time" DESC`;
);
}
+ renderBulkSupervisorActions() {
+ const bulkSupervisorActionsMenu = (
+ <Menu>
+ <MenuItem
+ icon={IconNames.PLAY}
+ text="Resume all supervisors"
+ onClick={() => this.setState({ showResumeAllSupervisors: true })}
+ />
+ <MenuItem
+ icon={IconNames.PAUSE}
+ text="Suspend all supervisors"
+ onClick={() => this.setState({ showSuspendAllSupervisors: true })}
+ />
+ <MenuItem
+ icon={IconNames.CROSS}
+ text="Terminate all supervisors"
+ intent={Intent.DANGER}
+ onClick={() => this.setState({ showTerminateAllSupervisors: true })}
+ />
+ </Menu>
+ );
+
+ return (
+ <>
+ <Popover content={bulkSupervisorActionsMenu}
position={Position.BOTTOM_LEFT}>
+ <Button icon={IconNames.MORE} />
+ </Popover>
+ {this.renderResumeAllSupervisorAction()}
+ {this.renderSuspendAllSupervisorAction()}
+ {this.renderTerminateAllSupervisorAction()}
+ </>
+ );
+ }
+
+ renderResumeAllSupervisorAction() {
+ const { showResumeAllSupervisors } = this.state;
+ if (!showResumeAllSupervisors) return;
+
+ return (
+ <AsyncActionDialog
+ action={async () => {
+ const resp = await
axios.post(`/druid/indexer/v1/supervisor/resumeAll`, {});
+ return resp.data;
+ }}
+ confirmButtonText="Resume all supervisors"
+ successText="All supervisors have been resumed"
+ failText="Could not resume all supervisors"
+ intent={Intent.PRIMARY}
+ onClose={() => {
+ this.setState({ showResumeAllSupervisors: false });
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
+ }}
+ >
+ <p>Are you sure you want to resume all the supervisors?</p>
+ </AsyncActionDialog>
+ );
+ }
+
+ renderSuspendAllSupervisorAction() {
+ const { showSuspendAllSupervisors } = this.state;
+ if (!showSuspendAllSupervisors) return;
+
+ return (
+ <AsyncActionDialog
+ action={async () => {
+ const resp = await
axios.post(`/druid/indexer/v1/supervisor/suspendAll`, {});
+ return resp.data;
+ }}
+ confirmButtonText="Suspend all supervisors"
+ successText="All supervisors have been suspended"
+ failText="Could not suspend all supervisors"
+ intent={Intent.DANGER}
+ onClose={() => {
+ this.setState({ showSuspendAllSupervisors: false });
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
+ }}
+ >
+ <p>Are you sure you want to suspend all the supervisors?</p>
+ </AsyncActionDialog>
+ );
+ }
+
+ renderTerminateAllSupervisorAction() {
+ const { showTerminateAllSupervisors } = this.state;
+ if (!showTerminateAllSupervisors) return;
+
+ return (
+ <AsyncActionDialog
+ action={async () => {
+ const resp = await
axios.post(`/druid/indexer/v1/supervisor/terminateAll`, {});
+ return resp.data;
+ }}
+ confirmButtonText="Terminate all supervisors"
+ successText="All supervisors have been terminated"
+ failText="Could not terminate all supervisors"
+ intent={Intent.DANGER}
+ onClose={() => {
+ this.setState({ showTerminateAllSupervisors: false });
+ }}
+ onSuccess={() => {
+ this.supervisorQueryManager.rerunLastQuery();
+ }}
+ >
+ <p>Are you sure you want to terminate all the supervisors?</p>
+ </AsyncActionDialog>
+ );
+ }
+
render() {
const { goToQuery, goToLoadDataView, noSqlMode } = this.props;
const {
@@ -912,11 +1027,12 @@ ORDER BY "rank" DESC, "created_time" DESC`;
<ViewControlBar label="Supervisors">
<RefreshButton
localStorageKey={LocalStorageKeys.SUPERVISORS_REFRESH_RATE}
- onRefresh={auto =>
this.supervisorQueryManager.rerunLastQueryInBackground(auto)}
+ onRefresh={auto =>
this.supervisorQueryManager.rerunLastQuery(auto)}
/>
<Popover content={submitSupervisorMenu}
position={Position.BOTTOM_LEFT}>
<Button icon={IconNames.PLUS} text="Submit supervisor" />
</Popover>
+ {this.renderBulkSupervisorActions()}
<TableColumnSelector
columns={supervisorTableColumns}
onChange={column =>
@@ -958,7 +1074,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
</ButtonGroup>
<RefreshButton
localStorageKey={LocalStorageKeys.TASKS_REFRESH_RATE}
- onRefresh={auto =>
this.taskQueryManager.rerunLastQueryInBackground(auto)}
+ onRefresh={auto => this.taskQueryManager.rerunLastQuery(auto)}
/>
{!noSqlMode && (
<Button
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]