You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by fj...@apache.org on 2019/07/31 14:13:37 UTC

[incubator-druid] branch master updated: Web console: better action linking between views (#8203)

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 15dd3d7  Web console: better action linking between views (#8203)
15dd3d7 is described below

commit 15dd3d7256f924341b21630a024a9124be581057
Author: Vadim Ogievetsky <va...@gmail.com>
AuthorDate: Wed Jul 31 07:13:25 2019 -0700

    Web console: better action linking between views (#8203)
    
    * better actions
    
    * better action linking
---
 .../components/center-message/center-message.tsx   |   4 +-
 .../src/components/show-history/show-history.tsx   |   8 +-
 web-console/src/components/show-log/show-log.tsx   |   4 +-
 web-console/src/console-application.tsx            |  36 ++++---
 .../compaction-dialog/compaction-dialog.spec.tsx   |   2 +-
 .../compaction-dialog/compaction-dialog.tsx        |  23 +++--
 .../segment-table-action-dialog.spec.tsx.snap      |  58 +++++++++--
 .../segment-table-action-dialog.spec.tsx           |   2 +-
 .../segment-table-action-dialog.tsx                |  10 +-
 .../supervisor-table-action-dialog.spec.tsx.snap   |  88 +++++++++-------
 .../supervisor-table-action-dialog.tsx             |   4 +-
 .../table-action-dialog.spec.tsx.snap              |   3 -
 .../table-action-dialog/table-action-dialog.tsx    |  29 +++++-
 .../task-table-action-dialog.spec.tsx.snap         |  58 +++++++++--
 .../task-table-action-dialog.tsx                   |   4 +-
 web-console/src/utils/basic-action.tsx             |  23 +----
 .../src/views/datasource-view/datasource-view.tsx  | 112 ++++++++++++++++-----
 .../src/views/load-data-view/load-data-view.tsx    |   5 +-
 .../__snapshots__/segments-view.spec.tsx.snap      |   2 +-
 .../src/views/segments-view/segments-view.tsx      |   7 +-
 .../src/views/task-view/tasks-view.spec.tsx        |   3 +-
 web-console/src/views/task-view/tasks-view.tsx     |  50 ++++++---
 22 files changed, 374 insertions(+), 161 deletions(-)

diff --git a/web-console/src/components/center-message/center-message.tsx b/web-console/src/components/center-message/center-message.tsx
index 6e88141..c9f4b8c 100644
--- a/web-console/src/components/center-message/center-message.tsx
+++ b/web-console/src/components/center-message/center-message.tsx
@@ -24,9 +24,11 @@ export interface CenterMessageProps {}
 
 export class CenterMessage extends React.PureComponent<CenterMessageProps> {
   render(): JSX.Element {
+    const { children } = this.props;
+
     return (
       <div className="center-message bp3-input">
-        <div className="center-message-inner">{this.props.children}</div>
+        <div className="center-message-inner">{children}</div>
       </div>
     );
   }
diff --git a/web-console/src/components/show-history/show-history.tsx b/web-console/src/components/show-history/show-history.tsx
index 9fd2637..3dc20c3 100644
--- a/web-console/src/components/show-history/show-history.tsx
+++ b/web-console/src/components/show-history/show-history.tsx
@@ -42,7 +42,7 @@ export interface ShowHistoryState {
 }
 
 export class ShowHistory extends React.PureComponent<ShowHistoryProps, ShowHistoryState> {
-  private showHistoryQueryManager: QueryManager<null, string>;
+  private showHistoryQueryManager: QueryManager<string, PastSupervisor[]>;
   constructor(props: ShowHistoryProps, context: any) {
     super(props, context);
     this.state = {
@@ -51,8 +51,8 @@ export class ShowHistory extends React.PureComponent<ShowHistoryProps, ShowHisto
     };
 
     this.showHistoryQueryManager = new QueryManager({
-      processQuery: async () => {
-        const resp = await axios.get(this.props.endpoint);
+      processQuery: async (endpoint: string) => {
+        const resp = await axios.get(endpoint);
         return resp.data;
       },
       onStateChange: ({ result, loading, error }) => {
@@ -66,7 +66,7 @@ export class ShowHistory extends React.PureComponent<ShowHistoryProps, ShowHisto
   }
 
   componentDidMount(): void {
-    this.showHistoryQueryManager.runQuery(null);
+    this.showHistoryQueryManager.runQuery(this.props.endpoint);
   }
 
   render(): JSX.Element | null {
diff --git a/web-console/src/components/show-log/show-log.tsx b/web-console/src/components/show-log/show-log.tsx
index 801de24..9a6dddc 100644
--- a/web-console/src/components/show-log/show-log.tsx
+++ b/web-console/src/components/show-log/show-log.tsx
@@ -60,7 +60,9 @@ export class ShowLog extends React.PureComponent<ShowLogProps, ShowLogState> {
   }
 
   componentDidMount(): void {
-    if (this.props.status === 'RUNNING') {
+    const { status } = this.props;
+
+    if (status === 'RUNNING') {
       this.tail();
     }
   }
diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx
index 08f253a..1898f39 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -105,13 +105,13 @@ export class ConsoleApplication extends React.PureComponent<
     });
   }
 
-  private supervisorId: string | undefined;
-  private taskId: string | undefined;
-  private openDialog: string | undefined;
-  private datasource: string | undefined;
-  private onlyUnavailable: boolean | undefined;
-  private initQuery: string | undefined;
-  private middleManager: string | undefined;
+  private supervisorId?: string;
+  private taskId?: string;
+  private openDialog?: string;
+  private datasource?: string;
+  private onlyUnavailable?: boolean;
+  private initQuery?: string;
+  private middleManager?: string;
 
   constructor(props: ConsoleApplicationProps, context: any) {
     super(props, context);
@@ -158,27 +158,33 @@ export class ConsoleApplication extends React.PureComponent<
     }, 50);
   }
 
-  private goToLoadDataView = (supervisorId?: string, taskId?: string) => {
+  private goToLoadData = (supervisorId?: string, taskId?: string) => {
     if (taskId) this.taskId = taskId;
     if (supervisorId) this.supervisorId = supervisorId;
     window.location.hash = 'load-data';
     this.resetInitialsWithDelay();
   };
 
-  private goToTask = (taskId: string | undefined, openDialog?: string) => {
-    this.taskId = taskId;
-    if (openDialog) this.openDialog = openDialog;
-    window.location.hash = 'tasks';
+  private goToDatasources = (datasource: string) => {
+    this.datasource = datasource;
+    window.location.hash = 'datasources';
     this.resetInitialsWithDelay();
   };
 
   private goToSegments = (datasource: string, onlyUnavailable = false) => {
-    this.datasource = `"${datasource}"`;
+    this.datasource = datasource;
     this.onlyUnavailable = onlyUnavailable;
     window.location.hash = 'segments';
     this.resetInitialsWithDelay();
   };
 
+  private goToTask = (taskId: string | undefined, openDialog?: string) => {
+    this.taskId = taskId;
+    if (openDialog) this.openDialog = openDialog;
+    window.location.hash = 'tasks';
+    this.resetInitialsWithDelay();
+  };
+
   private goToMiddleManager = (middleManager: string) => {
     this.middleManager = middleManager;
     window.location.hash = 'servers';
@@ -232,6 +238,7 @@ export class ConsoleApplication extends React.PureComponent<
     return this.wrapInViewContainer(
       'datasources',
       <DatasourcesView
+        initDatasource={this.datasource}
         goToQuery={this.goToQuery}
         goToSegments={this.goToSegments}
         noSqlMode={noSqlMode}
@@ -259,9 +266,10 @@ export class ConsoleApplication extends React.PureComponent<
       <TasksView
         taskId={this.taskId}
         openDialog={this.openDialog}
+        goToDatasource={this.goToDatasources}
         goToQuery={this.goToQuery}
         goToMiddleManager={this.goToMiddleManager}
-        goToLoadDataView={this.goToLoadDataView}
+        goToLoadData={this.goToLoadData}
         noSqlMode={noSqlMode}
       />,
     );
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx
index 44ae80b..c944313 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx
@@ -29,7 +29,7 @@ describe('compaction dialog', () => {
         onSave={() => {}}
         onDelete={() => {}}
         datasource={'test'}
-        configData={'test'}
+        compactionConfig={{}}
       />
     );
     render(compactionDialog);
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
index 46976a5..f195145 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
@@ -25,10 +25,10 @@ import './compaction-dialog.scss';
 
 export interface CompactionDialogProps {
   onClose: () => void;
-  onSave: (config: any) => void;
+  onSave: (config: Record<string, any>) => void;
   onDelete: () => void;
   datasource: string;
-  configData: any;
+  compactionConfig?: Record<string, any>;
 }
 
 export interface CompactionDialogState {
@@ -48,7 +48,7 @@ export class CompactionDialog extends React.PureComponent<
   }
 
   componentDidMount(): void {
-    const { datasource, configData } = this.props;
+    const { datasource, compactionConfig } = this.props;
     let config: Record<string, any> = {
       dataSource: datasource,
       inputSegmentSizeBytes: 419430400,
@@ -59,16 +59,23 @@ export class CompactionDialog extends React.PureComponent<
       taskPriority: 25,
       tuningConfig: null,
     };
-    if (configData !== undefined) {
-      config = configData;
+    if (compactionConfig !== undefined) {
+      config = compactionConfig;
     }
     this.setState({
       currentConfig: config,
     });
   }
 
+  private handleSubmit = () => {
+    const { onSave } = this.props;
+    const { currentConfig } = this.state;
+    if (!currentConfig) return;
+    onSave(currentConfig);
+  };
+
   render(): JSX.Element {
-    const { onClose, onSave, onDelete, datasource, configData } = this.props;
+    const { onClose, onDelete, datasource, compactionConfig } = this.props;
     const { currentConfig, allJSONValid } = this.state;
     return (
       <Dialog
@@ -119,13 +126,13 @@ export class CompactionDialog extends React.PureComponent<
               text="Delete"
               intent={Intent.DANGER}
               onClick={onDelete}
-              disabled={configData === undefined}
+              disabled={!compactionConfig}
             />
             <Button text="Close" onClick={onClose} />
             <Button
               text="Submit"
               intent={Intent.PRIMARY}
-              onClick={() => onSave(currentConfig)}
+              onClick={this.handleSubmit}
               disabled={!currentConfig || !allJSONValid}
             />
           </div>
diff --git a/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap
index 139e218..ac1bc4d 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/segments-table-action-dialog/__snapshots__/segment-table-action-dialog.spec.tsx.snap
@@ -150,16 +150,62 @@ exports[`task table action dialog matches snapshot 1`] = `
           <div
             class="footer-actions-left"
           >
-            <button
-              class="bp3-button"
-              type="button"
+            <span
+              class="bp3-popover-wrapper"
             >
               <span
-                class="bp3-button-text"
+                class="bp3-popover-target"
               >
-                test
+                <button
+                  class="bp3-button"
+                  type="button"
+                >
+                  <span
+                    class="bp3-icon bp3-icon-wrench"
+                    icon="wrench"
+                  >
+                    <svg
+                      data-icon="wrench"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      width="16"
+                    >
+                      <desc>
+                        wrench
+                      </desc>
+                      <path
+                        d="M15.83 3.7l-3.06 3.05-2.84-.7-.7-2.83L12.29.17a5.004 5.004 0 00-4.83 1.29 4.967 4.967 0 00-1.12 5.36L.58 12.58c-.36.36-.58.86-.58 1.41 0 1.1.9 2 2 2 .55 0 1.05-.22 1.41-.59l5.77-5.77c1.79.69 3.91.33 5.35-1.12 1.32-1.3 1.74-3.15 1.3-4.81z"
+                        fill-rule="evenodd"
+                      />
+                    </svg>
+                  </span>
+                  <span
+                    class="bp3-button-text"
+                  >
+                    Actions
+                  </span>
+                  <span
+                    class="bp3-icon bp3-icon-caret-down"
+                    icon="caret-down"
+                  >
+                    <svg
+                      data-icon="caret-down"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      width="16"
+                    >
+                      <desc>
+                        caret-down
+                      </desc>
+                      <path
+                        d="M12 6.5c0-.28-.22-.5-.5-.5h-7a.495.495 0 00-.37.83l3.5 4c.09.1.22.17.37.17s.28-.07.37-.17l3.5-4c.08-.09.13-.2.13-.33z"
+                        fill-rule="evenodd"
+                      />
+                    </svg>
+                  </span>
+                </button>
               </span>
-            </button>
+            </span>
           </div>
           <div
             class="bp3-dialog-footer-actions"
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
index e51a732..46e4c57 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
@@ -25,7 +25,7 @@ describe('task table action dialog', () => {
   it('matches snapshot', () => {
     const taskTableActionDialog = (
       <SegmentTableActionDialog
-        dataSourceId="test"
+        datasourceId="test"
         segmentId="test"
         actions={[{ title: 'test', onAction: () => null }]}
         onClose={() => {}}
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
index 37f4702..8a952e5 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
@@ -20,12 +20,12 @@ import { IDialogProps } from '@blueprintjs/core';
 import React from 'react';
 
 import { ShowJson } from '../../components';
-import { BasicAction, basicActionsToButtons } from '../../utils/basic-action';
+import { BasicAction } from '../../utils/basic-action';
 import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
 
 interface SegmentTableActionDialogProps extends IDialogProps {
   segmentId?: string;
-  dataSourceId?: string;
+  datasourceId?: string;
   actions: BasicAction[];
   onClose: () => void;
 }
@@ -46,7 +46,7 @@ export class SegmentTableActionDialog extends React.PureComponent<
   }
 
   render(): React.ReactNode {
-    const { segmentId, onClose, dataSourceId, actions } = this.props;
+    const { segmentId, onClose, datasourceId, actions } = this.props;
     const { activeTab } = this.state;
 
     const taskTableSideButtonMetadata: SideButtonMetaData[] = [
@@ -64,11 +64,11 @@ export class SegmentTableActionDialog extends React.PureComponent<
         sideButtonMetadata={taskTableSideButtonMetadata}
         onClose={onClose}
         title={`Segment: ${segmentId}`}
-        bottomButtons={basicActionsToButtons(actions)}
+        actions={actions}
       >
         {activeTab === 'metadata' && (
           <ShowJson
-            endpoint={`/druid/coordinator/v1/metadata/datasources/${dataSourceId}/segments/${segmentId}`}
+            endpoint={`/druid/coordinator/v1/metadata/datasources/${datasourceId}/segments/${segmentId}`}
             downloadFilename={`Segment-metadata-${segmentId}.json`}
           />
         )}
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap
index f3aee74..2673cae 100755
--- a/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap
@@ -237,46 +237,62 @@ exports[`supervisor table action dialog matches snapshot 1`] = `
           <div
             class="footer-actions-left"
           >
-            <button
-              class="bp3-button"
-              type="button"
-            >
-              <span
-                class="bp3-button-text"
-              >
-                test
-              </span>
-            </button>
-            <button
-              class="bp3-button"
-              type="button"
-            >
-              <span
-                class="bp3-button-text"
-              >
-                test
-              </span>
-            </button>
-            <button
-              class="bp3-button"
-              type="button"
-            >
-              <span
-                class="bp3-button-text"
-              >
-                test
-              </span>
-            </button>
-            <button
-              class="bp3-button"
-              type="button"
+            <span
+              class="bp3-popover-wrapper"
             >
               <span
-                class="bp3-button-text"
+                class="bp3-popover-target"
               >
-                test
+                <button
+                  class="bp3-button"
+                  type="button"
+                >
+                  <span
+                    class="bp3-icon bp3-icon-wrench"
+                    icon="wrench"
+                  >
+                    <svg
+                      data-icon="wrench"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      width="16"
+                    >
+                      <desc>
+                        wrench
+                      </desc>
+                      <path
+                        d="M15.83 3.7l-3.06 3.05-2.84-.7-.7-2.83L12.29.17a5.004 5.004 0 00-4.83 1.29 4.967 4.967 0 00-1.12 5.36L.58 12.58c-.36.36-.58.86-.58 1.41 0 1.1.9 2 2 2 .55 0 1.05-.22 1.41-.59l5.77-5.77c1.79.69 3.91.33 5.35-1.12 1.32-1.3 1.74-3.15 1.3-4.81z"
+                        fill-rule="evenodd"
+                      />
+                    </svg>
+                  </span>
+                  <span
+                    class="bp3-button-text"
+                  >
+                    Actions
+                  </span>
+                  <span
+                    class="bp3-icon bp3-icon-caret-down"
+                    icon="caret-down"
+                  >
+                    <svg
+                      data-icon="caret-down"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      width="16"
+                    >
+                      <desc>
+                        caret-down
+                      </desc>
+                      <path
+                        d="M12 6.5c0-.28-.22-.5-.5-.5h-7a.495.495 0 00-.37.83l3.5 4c.09.1.22.17.37.17s.28-.07.37-.17l3.5-4c.08-.09.13-.2.13-.33z"
+                        fill-rule="evenodd"
+                      />
+                    </svg>
+                  </span>
+                </button>
               </span>
-            </button>
+            </span>
           </div>
           <div
             class="bp3-dialog-footer-actions"
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
index 94acb90..8720b78 100644
--- a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
@@ -22,7 +22,7 @@ import React from 'react';
 import { ShowJson } from '../../components';
 import { ShowHistory } from '../../components/show-history/show-history';
 import { SupervisorStatisticsTable } from '../../components/supervisor-statistics-table/supervisor-statistics-table';
-import { BasicAction, basicActionsToButtons } from '../../utils/basic-action';
+import { BasicAction } from '../../utils/basic-action';
 import { deepGet } from '../../utils/object-change';
 import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
 
@@ -83,7 +83,7 @@ export class SupervisorTableActionDialog extends React.PureComponent<
         sideButtonMetadata={supervisorTableSideButtonMetadata}
         onClose={onClose}
         title={`Supervisor: ${supervisorId}`}
-        bottomButtons={basicActionsToButtons(actions)}
+        actions={actions}
       >
         {activeTab === 'status' && (
           <ShowJson
diff --git a/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap
index 00ff48a..ec333f3 100644
--- a/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap
@@ -62,9 +62,6 @@ exports[`table action dialog matches snapshot 1`] = `
           class="bp3-dialog-footer"
         >
           <div
-            class="footer-actions-left"
-          />
-          <div
             class="bp3-dialog-footer-actions"
           >
             <button
diff --git a/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx b/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
index 1e9209e..5182e34 100644
--- a/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
+++ b/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
@@ -16,9 +16,21 @@
  * limitations under the License.
  */
 
-import { Button, Classes, Dialog, Icon, IconName, IDialogProps, Intent } from '@blueprintjs/core';
+import {
+  Button,
+  Classes,
+  Dialog,
+  Icon,
+  IconName,
+  IDialogProps,
+  Intent,
+  Popover,
+} from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
 import React from 'react';
 
+import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
+
 import './table-action-dialog.scss';
 
 export interface SideButtonMetaData {
@@ -31,7 +43,7 @@ export interface SideButtonMetaData {
 interface TableActionDialogProps extends IDialogProps {
   sideButtonMetadata: SideButtonMetaData[];
   onClose: () => void;
-  bottomButtons?: React.ReactNode;
+  actions?: BasicAction[];
 }
 
 export class TableActionDialog extends React.PureComponent<TableActionDialogProps> {
@@ -41,7 +53,8 @@ export class TableActionDialog extends React.PureComponent<TableActionDialogProp
   }
 
   render(): JSX.Element {
-    const { sideButtonMetadata, isOpen, onClose, title, bottomButtons } = this.props;
+    const { sideButtonMetadata, isOpen, onClose, title, actions, children } = this.props;
+    const actionsMenu = actions ? basicActionsToMenu(actions) : undefined;
 
     return (
       <Dialog className="table-action-dialog" isOpen={isOpen} onClose={onClose} title={title}>
@@ -59,10 +72,16 @@ export class TableActionDialog extends React.PureComponent<TableActionDialogProp
               />
             ))}
           </div>
-          <div className="main-section">{this.props.children}</div>
+          <div className="main-section">{children}</div>
         </div>
         <div className={Classes.DIALOG_FOOTER}>
-          <div className="footer-actions-left">{bottomButtons}</div>
+          {actionsMenu && (
+            <div className="footer-actions-left">
+              <Popover content={actionsMenu}>
+                <Button icon={IconNames.WRENCH} text="Actions" rightIcon={IconNames.CARET_DOWN} />
+              </Popover>
+            </div>
+          )}
           <div className={Classes.DIALOG_FOOTER_ACTIONS}>
             <Button text="Close" intent={Intent.PRIMARY} onClick={onClose} />
           </div>
diff --git a/web-console/src/dialogs/task-table-action-dialog/__snapshots__/task-table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/task-table-action-dialog/__snapshots__/task-table-action-dialog.spec.tsx.snap
index ac5c9c4..927f496 100644
--- a/web-console/src/dialogs/task-table-action-dialog/__snapshots__/task-table-action-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/task-table-action-dialog/__snapshots__/task-table-action-dialog.spec.tsx.snap
@@ -237,16 +237,62 @@ exports[`task table action dialog matches snapshot 1`] = `
           <div
             class="footer-actions-left"
           >
-            <button
-              class="bp3-button"
-              type="button"
+            <span
+              class="bp3-popover-wrapper"
             >
               <span
-                class="bp3-button-text"
+                class="bp3-popover-target"
               >
-                test
+                <button
+                  class="bp3-button"
+                  type="button"
+                >
+                  <span
+                    class="bp3-icon bp3-icon-wrench"
+                    icon="wrench"
+                  >
+                    <svg
+                      data-icon="wrench"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      width="16"
+                    >
+                      <desc>
+                        wrench
+                      </desc>
+                      <path
+                        d="M15.83 3.7l-3.06 3.05-2.84-.7-.7-2.83L12.29.17a5.004 5.004 0 00-4.83 1.29 4.967 4.967 0 00-1.12 5.36L.58 12.58c-.36.36-.58.86-.58 1.41 0 1.1.9 2 2 2 .55 0 1.05-.22 1.41-.59l5.77-5.77c1.79.69 3.91.33 5.35-1.12 1.32-1.3 1.74-3.15 1.3-4.81z"
+                        fill-rule="evenodd"
+                      />
+                    </svg>
+                  </span>
+                  <span
+                    class="bp3-button-text"
+                  >
+                    Actions
+                  </span>
+                  <span
+                    class="bp3-icon bp3-icon-caret-down"
+                    icon="caret-down"
+                  >
+                    <svg
+                      data-icon="caret-down"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      width="16"
+                    >
+                      <desc>
+                        caret-down
+                      </desc>
+                      <path
+                        d="M12 6.5c0-.28-.22-.5-.5-.5h-7a.495.495 0 00-.37.83l3.5 4c.09.1.22.17.37.17s.28-.07.37-.17l3.5-4c.08-.09.13-.2.13-.33z"
+                        fill-rule="evenodd"
+                      />
+                    </svg>
+                  </span>
+                </button>
               </span>
-            </button>
+            </span>
           </div>
           <div
             class="bp3-dialog-footer-actions"
diff --git a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
index be2faae..24caa78 100644
--- a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
+++ b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
@@ -20,7 +20,7 @@ import { IDialogProps } from '@blueprintjs/core';
 import React from 'react';
 
 import { ShowJson, ShowLog } from '../../components';
-import { BasicAction, basicActionsToButtons } from '../../utils/basic-action';
+import { BasicAction } from '../../utils/basic-action';
 import { deepGet } from '../../utils/object-change';
 import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
 
@@ -83,7 +83,7 @@ export class TaskTableActionDialog extends React.PureComponent<
         sideButtonMetadata={taskTableSideButtonMetadata}
         onClose={onClose}
         title={`Task: ${taskId}`}
-        bottomButtons={basicActionsToButtons(actions)}
+        actions={actions}
       >
         {activeTab === 'status' && (
           <ShowJson
diff --git a/web-console/src/utils/basic-action.tsx b/web-console/src/utils/basic-action.tsx
index 5f4cf23..2d0e4f8 100644
--- a/web-console/src/utils/basic-action.tsx
+++ b/web-console/src/utils/basic-action.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Button, IconName, Intent, Menu, MenuItem } from '@blueprintjs/core';
+import { IconName, Intent, Menu, MenuItem } from '@blueprintjs/core';
 import React from 'react';
 
 export interface BasicAction {
@@ -26,8 +26,8 @@ export interface BasicAction {
   onAction: () => void;
 }
 
-export function basicActionsToMenu(basicActions: BasicAction[]) {
-  if (!basicActions.length) return null;
+export function basicActionsToMenu(basicActions: BasicAction[]): JSX.Element | undefined {
+  if (!basicActions.length) return;
   return (
     <Menu>
       {basicActions.map((action, i) => (
@@ -42,20 +42,3 @@ export function basicActionsToMenu(basicActions: BasicAction[]) {
     </Menu>
   );
 }
-
-export function basicActionsToButtons(basicActions: BasicAction[]) {
-  if (!basicActions.length) return null;
-  return (
-    <>
-      {basicActions.map((action, i) => (
-        <Button
-          key={i}
-          icon={action.icon}
-          text={action.title}
-          intent={action.intent}
-          onClick={action.onAction}
-        />
-      ))}
-    </>
-  );
-}
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 9390984..d9dfae1 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -84,12 +84,6 @@ function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string
   return loadDrop.join(', ') || 'No segments to load/drop';
 }
 
-export interface DatasourcesViewProps {
-  goToQuery: (initSql: string) => void;
-  goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
-  noSqlMode: boolean;
-}
-
 interface Datasource {
   datasource: string;
   rules: any[];
@@ -107,17 +101,34 @@ interface DatasourceQueryResultRow {
   num_rows: number;
 }
 
+interface RetentionDialogOpenOn {
+  datasource: string;
+  rules: any[];
+}
+
+interface CompactionDialogOpenOn {
+  datasource: string;
+  compactionConfig: Record<string, any>;
+}
+
+export interface DatasourcesViewProps {
+  goToQuery: (initSql: string) => void;
+  goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
+  noSqlMode: boolean;
+  initDatasource?: string;
+}
+
 export interface DatasourcesViewState {
   datasourcesLoading: boolean;
   datasources: Datasource[] | null;
   tiers: string[];
   defaultRules: any[];
   datasourcesError?: string;
-  datasourcesFilter: Filter[];
+  datasourceFilter: Filter[];
 
   showDisabled: boolean;
-  retentionDialogOpenOn?: { datasource: string; rules: any[] };
-  compactionDialogOpenOn?: { datasource: string; configData: any };
+  retentionDialogOpenOn?: RetentionDialogOpenOn;
+  compactionDialogOpenOn?: CompactionDialogOpenOn;
   dropDataDatasource?: string;
   enableDatasource?: string;
   killDatasource?: string;
@@ -167,12 +178,18 @@ GROUP BY 1`;
 
   constructor(props: DatasourcesViewProps, context: any) {
     super(props, context);
+
+    const datasourceFilter: Filter[] = [];
+    if (props.initDatasource) {
+      datasourceFilter.push({ id: 'datasource', value: `"${props.initDatasource}"` });
+    }
+
     this.state = {
       datasourcesLoading: true,
       datasources: null,
       tiers: [],
       defaultRules: [],
-      datasourcesFilter: [],
+      datasourceFilter,
 
       showDisabled: false,
       dropReloadAction: 'drop',
@@ -422,9 +439,9 @@ GROUP BY 1`;
         }}
       >
         <p>
-          {`Are you sure you want to permanently delete the data in datasource '${killDatasource}'?`}
+          {`Are you sure you want to permanently delete the deep storage data for datasource '${killDatasource}'?`}
         </p>
-        <p>This action can not be undone.</p>
+        <p>This action is not reversible and the data deleted will be lost.</p>
       </AsyncActionDialog>
     );
   }
@@ -514,7 +531,12 @@ GROUP BY 1`;
     this.setState({ showDisabled: !showDisabled });
   }
 
-  getDatasourceActions(datasource: string, disabled: boolean): BasicAction[] {
+  getDatasourceActions(
+    datasource: string,
+    disabled: boolean,
+    rules: any[],
+    compactionConfig: Record<string, any>,
+  ): BasicAction[] {
     const { goToQuery } = this.props;
 
     if (disabled) {
@@ -526,7 +548,7 @@ GROUP BY 1`;
         },
         {
           icon: IconNames.TRASH,
-          title: 'Permanently delete (kill task)',
+          title: 'Delete segments (issue kill task)',
           intent: Intent.DANGER,
           onAction: () => this.setState({ killDatasource: datasource }),
         },
@@ -539,6 +561,30 @@ GROUP BY 1`;
           onAction: () => goToQuery(`SELECT * FROM "${datasource}"`),
         },
         {
+          icon: IconNames.AUTOMATIC_UPDATES,
+          title: 'Edit retention rules',
+          onAction: () => {
+            this.setState({
+              retentionDialogOpenOn: {
+                datasource,
+                rules,
+              },
+            });
+          },
+        },
+        {
+          icon: IconNames.COMPRESSED,
+          title: 'Edit compaction configuration',
+          onAction: () => {
+            this.setState({
+              compactionDialogOpenOn: {
+                datasource,
+                compactionConfig,
+              },
+            });
+          },
+        },
+        {
           icon: IconNames.EXPORT,
           title: 'Reload data by interval',
           onAction: () =>
@@ -556,6 +602,12 @@ GROUP BY 1`;
           intent: Intent.DANGER,
           onAction: () => this.setState({ dropDataDatasource: datasource }),
         },
+        {
+          icon: IconNames.TRASH,
+          title: 'Delete unused segments (issue kill task)',
+          intent: Intent.DANGER,
+          onAction: () => this.setState({ killDatasource: datasource }),
+        },
       ];
     }
   }
@@ -584,7 +636,7 @@ GROUP BY 1`;
     return (
       <CompactionDialog
         datasource={compactionDialogOpenOn.datasource}
-        configData={compactionDialogOpenOn.configData}
+        compactionConfig={compactionDialogOpenOn.compactionConfig}
         onClose={() => this.setState({ compactionDialogOpenOn: undefined })}
         onSave={this.saveCompaction}
         onDelete={this.deleteCompaction}
@@ -599,7 +651,7 @@ GROUP BY 1`;
       defaultRules,
       datasourcesLoading,
       datasourcesError,
-      datasourcesFilter,
+      datasourceFilter,
       showDisabled,
       hiddenColumns,
       showChart,
@@ -619,9 +671,9 @@ GROUP BY 1`;
               : datasourcesError || ''
           }
           filterable
-          filtered={datasourcesFilter}
+          filtered={datasourceFilter}
           onFilteredChange={filtered => {
-            this.setState({ datasourcesFilter: filtered });
+            this.setState({ datasourceFilter: filtered });
           }}
           columns={[
             {
@@ -634,7 +686,7 @@ GROUP BY 1`;
                   <a
                     onClick={() => {
                       this.setState({
-                        datasourcesFilter: addFilter(datasourcesFilter, 'datasource', value),
+                        datasourceFilter: addFilter(datasourceFilter, 'datasource', value),
                       });
                     }}
                   >
@@ -759,10 +811,6 @@ GROUP BY 1`;
               filterable: false,
               Cell: row => {
                 const { compaction } = row.original;
-                const compactionOpenOn: { datasource: string; configData: any } | null = {
-                  datasource: row.original.datasource,
-                  configData: compaction,
-                };
                 let text: string;
                 if (compaction) {
                   text = `Target: ${formatBytes(compaction.targetCompactionSizeBytes)}`;
@@ -772,7 +820,14 @@ GROUP BY 1`;
                 return (
                   <span
                     className="clickable-cell"
-                    onClick={() => this.setState({ compactionDialogOpenOn: compactionOpenOn })}
+                    onClick={() =>
+                      this.setState({
+                        compactionDialogOpenOn: {
+                          datasource: row.original.datasource,
+                          compactionConfig: compaction,
+                        },
+                      })
+                    }
                   >
                     {text}&nbsp;
                     <ActionIcon icon={IconNames.EDIT} />
@@ -813,8 +868,13 @@ GROUP BY 1`;
               filterable: false,
               Cell: row => {
                 const datasource = row.value;
-                const { disabled } = row.original;
-                const datasourceActions = this.getDatasourceActions(datasource, disabled);
+                const { disabled, rules, compaction } = row.original;
+                const datasourceActions = this.getDatasourceActions(
+                  datasource,
+                  disabled,
+                  rules,
+                  compaction,
+                );
                 return <ActionCell actions={datasourceActions} />;
               },
               show: hiddenColumns.exists(ActionCell.COLUMN_LABEL),
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 59bfc5d..acfe43d 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
@@ -348,13 +348,14 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
   }
 
   componentDidMount(): void {
+    const { initTaskId, initSupervisorId } = this.props;
     const { spec } = this.state;
 
     this.getOverlordModules();
-    if (this.props.initTaskId) {
+    if (initTaskId) {
       this.updateStep('loading');
       this.getTaskJson();
-    } else if (this.props.initSupervisorId) {
+    } else if (initSupervisorId) {
       this.updateStep('loading');
       this.getSupervisorJson();
     } else if (isEmptyIngestionSpec(spec)) {
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 70e4003..189a921 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
@@ -259,7 +259,7 @@ exports[`segments-view matches snapshot 1`] = `
         Array [
           Object {
             "id": "datasource",
-            "value": "test",
+            "value": "\\"test\\"",
           },
         ]
       }
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index 8ec0cc8..18583ff 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -137,7 +137,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
     super(props, context);
 
     const segmentFilter: Filter[] = [];
-    if (props.datasource) segmentFilter.push({ id: 'datasource', value: props.datasource });
+    if (props.datasource) segmentFilter.push({ id: 'datasource', value: `"${props.datasource}"` });
     if (props.onlyUnavailable) segmentFilter.push({ id: 'is_available', value: 'false' });
 
     this.state = {
@@ -291,7 +291,8 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
   }
 
   componentDidMount(): void {
-    if (this.props.noSqlMode) {
+    const { noSqlMode } = this.props;
+    if (noSqlMode) {
       this.segmentsNoSqlQueryManager.runQuery(null);
     }
   }
@@ -673,7 +674,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
         {segmentTableActionDialogId && (
           <SegmentTableActionDialog
             segmentId={segmentTableActionDialogId}
-            dataSourceId={datasourceTableActionDialogId}
+            datasourceId={datasourceTableActionDialogId}
             actions={actions}
             onClose={() => this.setState({ segmentTableActionDialogId: undefined })}
             isOpen
diff --git a/web-console/src/views/task-view/tasks-view.spec.tsx b/web-console/src/views/task-view/tasks-view.spec.tsx
index 93bc323..3616695 100644
--- a/web-console/src/views/task-view/tasks-view.spec.tsx
+++ b/web-console/src/views/task-view/tasks-view.spec.tsx
@@ -27,9 +27,10 @@ describe('tasks view', () => {
       <TasksView
         openDialog={'test'}
         taskId={'test'}
+        goToDatasource={() => {}}
         goToQuery={() => {}}
         goToMiddleManager={() => {}}
-        goToLoadDataView={() => {}}
+        goToLoadData={() => {}}
         noSqlMode={false}
       />,
     );
diff --git a/web-console/src/views/task-view/tasks-view.tsx b/web-console/src/views/task-view/tasks-view.tsx
index 9480d3c..d94265a 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -79,9 +79,10 @@ const taskTableColumns: string[] = [
 export interface TasksViewProps {
   taskId: string | undefined;
   openDialog: string | undefined;
+  goToDatasource: (datasource: string) => void;
   goToQuery: (initSql: string) => void;
   goToMiddleManager: (middleManager: string) => void;
-  goToLoadDataView: (supervisorId?: string, taskId?: string) => void;
+  goToLoadData: (supervisorId?: string, taskId?: string) => void;
   noSqlMode: boolean;
 }
 
@@ -350,13 +351,22 @@ ORDER BY "rank" DESC, "created_time" DESC`;
     supervisorSuspended: boolean,
     type: string,
   ): BasicAction[] {
+    const { goToDatasource, goToLoadData } = this.props;
+
     const actions: BasicAction[] = [];
     if (type === 'kafka' || type === 'kinesis') {
-      actions.push({
-        icon: IconNames.CLOUD_UPLOAD,
-        title: 'Open in data loader',
-        onAction: () => this.props.goToLoadDataView(id),
-      });
+      actions.push(
+        {
+          icon: IconNames.MULTI_SELECT,
+          title: 'Go to datasource',
+          onAction: () => goToDatasource(id),
+        },
+        {
+          icon: IconNames.CLOUD_UPLOAD,
+          title: 'Open in data loader',
+          onAction: () => goToLoadData(id),
+        },
+      );
     }
     actions.push(
       {
@@ -604,13 +614,27 @@ ORDER BY "rank" DESC, "created_time" DESC`;
     );
   }
 
-  private getTaskActions(id: string, status: string, type: string): BasicAction[] {
+  private getTaskActions(
+    id: string,
+    datasource: string,
+    status: string,
+    type: string,
+  ): BasicAction[] {
+    const { goToDatasource, goToLoadData } = this.props;
+
     const actions: BasicAction[] = [];
+    if (datasource && status === 'SUCCESS') {
+      actions.push({
+        icon: IconNames.MULTI_SELECT,
+        title: 'Go to datasource',
+        onAction: () => goToDatasource(datasource),
+      });
+    }
     if (type === 'index' || type === 'index_parallel') {
       actions.push({
         icon: IconNames.CLOUD_UPLOAD,
         title: 'Open in data loader',
-        onAction: () => this.props.goToLoadDataView(undefined, id),
+        onAction: () => goToLoadData(undefined, id),
       });
     }
     if (status === 'RUNNING' || status === 'WAITING' || status === 'PENDING') {
@@ -811,8 +835,8 @@ ORDER BY "rank" DESC, "created_time" DESC`;
                 if (row.aggregated) return '';
                 const id = row.value;
                 const type = row.row.type;
-                const { status } = row.original;
-                const taskActions = this.getTaskActions(id, status, type);
+                const { datasource, status } = row.original;
+                const taskActions = this.getTaskActions(id, datasource, status, type);
                 return (
                   <ActionCell
                     onDetail={() =>
@@ -949,7 +973,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
   }
 
   render(): JSX.Element {
-    const { goToQuery, goToLoadDataView, noSqlMode } = this.props;
+    const { goToQuery, goToLoadData, noSqlMode } = this.props;
     const {
       groupTasksBy,
       supervisorSpecDialogOpen,
@@ -969,7 +993,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         <MenuItem
           icon={IconNames.CLOUD_UPLOAD}
           text="Go to data loader"
-          onClick={() => goToLoadDataView()}
+          onClick={() => goToLoadData()}
         />
         <MenuItem
           icon={IconNames.MANUALLY_ENTERED_DATA}
@@ -984,7 +1008,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         <MenuItem
           icon={IconNames.CLOUD_UPLOAD}
           text="Go to data loader"
-          onClick={() => goToLoadDataView()}
+          onClick={() => goToLoadData()}
         />
         <MenuItem
           icon={IconNames.MANUALLY_ENTERED_DATA}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org