You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by cw...@apache.org on 2019/05/25 05:52:35 UTC

[incubator-druid] branch master updated: Web-Console: add go to editor button to tasks and supervisors view (#7705)

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 5f50f35  Web-Console: add go to editor button to tasks and supervisors view (#7705)
5f50f35 is described below

commit 5f50f357a47f4564481f4321105f14f971952174
Author: mcbrewster <37...@users.noreply.github.com>
AuthorDate: Fri May 24 22:52:26 2019 -0700

    Web-Console: add go to editor button to tasks and supervisors view (#7705)
    
    * add go to editor button to tasks and supervisors
    
    * fix package.json
    
    * remove intent
    
    * quuick fixes
    
    * fixes
    
    * add getsupervisorjson and gettaskjson
    
    * remove space
    
    * remove gotoloaddata
    
    * fixes
    
    * add error handling
    
    * save
    
    * add loader
    
    * remove initspec
    
    * fixup! add loader
    
    * update snapshots
    
    * remove gotoloaddataview form headerbar.spec
---
 .../src/components/header-bar/header-bar.spec.tsx  |   1 -
 .../src/components/header-bar/header-bar.tsx       |   1 -
 web-console/src/console-application.tsx            |  14 +-
 .../__snapshots__/load-data-view.spec.tsx.snap     | 222 +++------------------
 .../views/load-data-view/load-data-view.spec.tsx   |   1 -
 .../src/views/load-data-view/load-data-view.tsx    |  60 ++++--
 web-console/src/views/task-view/tasks-view.tsx     |  53 +++--
 7 files changed, 116 insertions(+), 236 deletions(-)

diff --git a/web-console/src/components/header-bar/header-bar.spec.tsx b/web-console/src/components/header-bar/header-bar.spec.tsx
index 7299103..54b842b 100644
--- a/web-console/src/components/header-bar/header-bar.spec.tsx
+++ b/web-console/src/components/header-bar/header-bar.spec.tsx
@@ -27,7 +27,6 @@ describe('describe header bar', () => {
       <HeaderBar
         active={'load-data'}
         hideLegacy={false}
-        goToLoadDataView={() => {}}
       />);
     expect(headerBar).toMatchSnapshot();
   });
diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx
index acc722c..be8bbfe 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -52,7 +52,6 @@ export type HeaderActiveTab = null | 'load-data' | 'query' | 'datasources' | 'se
 export interface HeaderBarProps extends React.Props<any> {
   active: HeaderActiveTab;
   hideLegacy: boolean;
-  goToLoadDataView: () => void;
 }
 
 export interface HeaderBarState {
diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx
index 0ddb5cc..582dc8e 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -100,7 +100,7 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
     });
   }
 
-  private initSpec: any | null;
+  private supervisorId: string | null;
   private taskId: string | null;
   private openDialog: string | null;
   private datasource: string | null;
@@ -151,8 +151,8 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
 
   private resetInitialsWithDelay() {
     setTimeout(() => {
-      this.initSpec = null;
       this.taskId = null;
+      this.supervisorId = null;
       this.openDialog = null;
       this.datasource = null;
       this.onlyUnavailable = null;
@@ -161,8 +161,9 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
     }, 50);
   }
 
-  private goToLoadDataView = (initSpec?: any) => {
-    if (initSpec) this.initSpec = initSpec;
+  private goToLoadDataView = (supervisorId?: string, taskId?: string ) => {
+    if (taskId) this.taskId = taskId;
+    if (supervisorId) this.supervisorId = supervisorId;
     window.location.hash = 'load-data';
     this.resetInitialsWithDelay();
   }
@@ -197,7 +198,7 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
     const { hideLegacy } = this.props;
 
     return <>
-      <HeaderBar active={active} hideLegacy={hideLegacy} goToLoadDataView={this.goToLoadDataView}/>
+      <HeaderBar active={active} hideLegacy={hideLegacy}/>
       <div className={classNames('view-container', classType)}>{el}</div>
     </>;
   }
@@ -208,7 +209,8 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
   }
 
   private wrappedLoadDataView = () => {
-    return this.wrapInViewContainer('load-data', <LoadDataView initSpec={this.initSpec} goToTask={this.goToTask}/>, 'narrow-pad');
+
+    return this.wrapInViewContainer('load-data', <LoadDataView initSupervisorId={this.supervisorId} initTaskId={this.taskId} goToTask={this.goToTask}/>, 'narrow-pad');
   }
 
   private wrappedSqlView = () => {
diff --git a/web-console/src/views/load-data-view/__snapshots__/load-data-view.spec.tsx.snap b/web-console/src/views/load-data-view/__snapshots__/load-data-view.spec.tsx.snap
index 78b7fb7..dc90d04 100644
--- a/web-console/src/views/load-data-view/__snapshots__/load-data-view.spec.tsx.snap
+++ b/web-console/src/views/load-data-view/__snapshots__/load-data-view.spec.tsx.snap
@@ -2,211 +2,41 @@
 
 exports[`describe load data view load data view snapshot 1`] = `
 <div
-  className="load-data-view app-view connect"
+  className="load-data-view app-view init"
 >
   <div
-    className="bp3-tabs stage-nav"
+    className="intro"
   >
-    <div
-      className="stage-section"
-      key="Connect and parse raw data"
-    >
-      <div
-        className="stage-nav-l1"
-      >
-        Connect and parse raw data
-      </div>
-      <Blueprint3.ButtonGroup
-        className="stage-nav-l2"
-      >
-        <Blueprint3.Button
-          active={true}
-          className="connect"
-          icon={false}
-          key="connect"
-          onClick={[Function]}
-          text="Connect"
-        />
-        <Blueprint3.Button
-          active={false}
-          className="parser"
-          icon={false}
-          key="parser"
-          onClick={[Function]}
-          text="Parse data"
-        />
-        <Blueprint3.Button
-          active={false}
-          className="timestamp"
-          icon={false}
-          key="timestamp"
-          onClick={[Function]}
-          text="Parse time"
-        />
-      </Blueprint3.ButtonGroup>
-    </div>
-    <div
-      className="stage-section"
-      key="Transform and configure schema"
-    >
-      <div
-        className="stage-nav-l1"
-      >
-        Transform and configure schema
-      </div>
-      <Blueprint3.ButtonGroup
-        className="stage-nav-l2"
-      >
-        <Blueprint3.Button
-          active={false}
-          className="transform"
-          icon={false}
-          key="transform"
-          onClick={[Function]}
-          text="Transform"
-        />
-        <Blueprint3.Button
-          active={false}
-          className="filter"
-          icon={false}
-          key="filter"
-          onClick={[Function]}
-          text="Filter"
-        />
-        <Blueprint3.Button
-          active={false}
-          className="schema"
-          icon={false}
-          key="schema"
-          onClick={[Function]}
-          text="Configure schema"
-        />
-      </Blueprint3.ButtonGroup>
-    </div>
-    <div
-      className="stage-section"
-      key="Tune parameters"
-    >
-      <div
-        className="stage-nav-l1"
-      >
-        Tune parameters
-      </div>
-      <Blueprint3.ButtonGroup
-        className="stage-nav-l2"
-      >
-        <Blueprint3.Button
-          active={false}
-          className="partition"
-          icon={false}
-          key="partition"
-          onClick={[Function]}
-          text="Partition"
-        />
-        <Blueprint3.Button
-          active={false}
-          className="tuning"
-          icon={false}
-          key="tuning"
-          onClick={[Function]}
-          text="Tune"
-        />
-        <Blueprint3.Button
-          active={false}
-          className="publish"
-          icon={false}
-          key="publish"
-          onClick={[Function]}
-          text="Publish"
-        />
-      </Blueprint3.ButtonGroup>
-    </div>
-    <div
-      className="stage-section"
-      key="Verify and submit"
-    >
-      <div
-        className="stage-nav-l1"
-      >
-        Verify and submit
-      </div>
-      <Blueprint3.ButtonGroup
-        className="stage-nav-l2"
-      >
-        <Blueprint3.Button
-          active={false}
-          className="json-spec"
-          icon="eye-open"
-          key="json-spec"
-          onClick={[Function]}
-          text="Edit JSON spec"
-        />
-      </Blueprint3.ButtonGroup>
-    </div>
-  </div>
-  <div
-    className="main"
-  >
-    <Loader
-      loading={true}
-    />
+    Please specify where your raw data is located
   </div>
   <div
-    className="control"
+    className="cards"
   >
-    <Blueprint3.Callout
-      className="intro"
-    >
-      <p>
-        Druid ingests raw data and converts it into a custom, 
-        <ExternalLink
-          href="http://druid.io/docs/latest/design/segments.html"
-        >
-          indexed
-        </ExternalLink>
-         format that is optimized for analytic queries.
-      </p>
-      <p>
-        To get started, please specify where your raw data is stored and what data you want to ingest.
-      </p>
-      <p>
-        Click "Preview" to look at the sampled raw data.
-      </p>
-    </Blueprint3.Callout>
-    <Blueprint3.FormGroup
-      label="IO Config"
+    <Blueprint3.Card
+      elevation={0}
+      interactive={true}
+      onClick={[Function]}
     >
-      <JSONInput
-        height="300px"
-        onChange={[Function]}
-        value={
-          Object {
-            "type": "test",
-          }
-        }
-      />
-    </Blueprint3.FormGroup>
-    <Blueprint3.Button
-      disabled={false}
+      Other (streaming)
+    </Blueprint3.Card>
+    <Blueprint3.Card
+      elevation={0}
+      interactive={true}
       onClick={[Function]}
-      text="Preview"
-    />
+    >
+      Other (batch)
+    </Blueprint3.Card>
   </div>
-  <div
-    className="next-bar"
+  <Blueprint3.Alert
+    canEscapeKeyCancel={false}
+    canOutsideClickCancel={false}
+    confirmButtonText="Close"
+    icon="warning-sign"
+    intent="warning"
+    isOpen={false}
+    onConfirm={[Function]}
   >
-    <Blueprint3.Button
-      className="prev"
-      icon="arrow-left"
-      onClick={[Function]}
-      text="Restart"
-    />
-    <Blueprint3.Button
-      disabled={true}
-      intent="primary"
-      onClick={[Function]}
-      text="Next: Parse data"
-    />
-  </div>
+    <p />
+  </Blueprint3.Alert>
 </div>
 `;
diff --git a/web-console/src/views/load-data-view/load-data-view.spec.tsx b/web-console/src/views/load-data-view/load-data-view.spec.tsx
index 28e5382..3a33d6a 100644
--- a/web-console/src/views/load-data-view/load-data-view.spec.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.spec.tsx
@@ -29,7 +29,6 @@ describe('describe load data view', () => {
   it('load data view snapshot', () => {
     const loadDataView = shallow(
       <LoadDataView
-        initSpec={{dataSchema: {dataSource: 'test', parser:{parseSpec: {format: 'test',  dimensionsSpec: {}, timestampSpec: {column: 'test', format: 'test', missingValue: 'test'}, }}}, ioConfig: { type: 'test'}}}
         goToTask={(taskId: string | null) => {}}
       />);
     expect(loadDataView).toMatchSnapshot();
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 773b6fd..fdba39c 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
@@ -143,8 +143,8 @@ function getTimestampSpec(headerAndRows: HeaderAndRows | null): TimestampSpec {
   return timestampSpecs[0] || getEmptyTimestampSpec();
 }
 
-type Stage = 'connect' | 'parser' | 'timestamp' | 'transform' | 'filter' | 'schema' | 'partition' | 'tuning' | 'publish' | 'json-spec';
-const STAGES: Stage[] = ['connect', 'parser', 'timestamp', 'transform', 'filter', 'schema', 'partition', 'tuning', 'publish', 'json-spec'];
+type Stage = 'connect' | 'parser' | 'timestamp' | 'transform' | 'filter' | 'schema' | 'partition' | 'tuning' | 'publish' | 'json-spec' | 'loading';
+const STAGES: Stage[] = ['connect', 'parser', 'timestamp', 'transform', 'filter', 'schema', 'partition', 'tuning', 'publish', 'json-spec', 'loading'];
 
 const SECTIONS: { name: string, stages: Stage[] }[] = [
   { name: 'Connect and parse raw data', stages: ['connect', 'parser', 'timestamp'] },
@@ -163,19 +163,20 @@ const VIEW_TITLE: Record<Stage, string> = {
   'partition': 'Partition',
   'tuning': 'Tune',
   'publish': 'Publish',
-  'json-spec': 'Edit JSON spec'
+  'json-spec': 'Edit JSON spec',
+  'loading': 'Loading'
 };
 
 export interface LoadDataViewProps extends React.Props<any> {
-  initSpec: IngestionSpec | null;
-  goToTask: (taskId: string | null, openDialog?: string) => void;
+  initSupervisorId?: string | null;
+  initTaskId?: string | null;
+  goToTask: (taskId: string | null, supervisor?: string) => void;
 }
 
 export interface LoadDataViewState {
   stage: Stage;
   spec: IngestionSpec;
   cacheKey: string | undefined;
-
   // dialogs / modals
   showResetConfirm: boolean;
   newRollup: boolean | null;
@@ -225,9 +226,8 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
   constructor(props: LoadDataViewProps) {
     super(props);
 
-    let spec = props.initSpec || parseJson(String(localStorageGet(LocalStorageKeys.INGESTION_SPEC)));
+    let spec = parseJson(String(localStorageGet(LocalStorageKeys.INGESTION_SPEC)));
     if (!spec || typeof spec !== 'object') spec = {};
-
     this.state = {
       stage: 'connect',
       spec,
@@ -281,9 +281,15 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
 
   componentDidMount(): void {
     this.getOverlordModules();
-    this.updateStage('connect');
+    if (this.props.initTaskId) {
+      this.updateStage('loading');
+      this.getTaskJson();
+    } else if (this.props.initSupervisorId) {
+      this.updateStage('loading');
+      this.getSupervisorJson(); } else this.updateStage('connect');
   }
 
+
   async getOverlordModules() {
     let overlordModules: string[];
     try {
@@ -327,8 +333,7 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
 
   render() {
     const { stage, spec } = this.state;
-
-    if (!Object.keys(spec).length) {
+    if (!Object.keys(spec).length && !this.props.initSupervisorId && !this.props.initTaskId) {
       return <div className={classNames('load-data-view', 'app-view', 'init')}>
         {this.renderInitStage()}
       </div>;
@@ -350,11 +355,11 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
       {stage === 'publish' && this.renderPublishStage()}
 
       {stage === 'json-spec' && this.renderJsonSpecStage()}
+      {stage === 'loading' && this.renderLoading()}
 
       {this.renderResetConfirm()}
     </div>;
   }
-
   renderStepNav() {
     const { stage } = this.state;
 
@@ -1052,7 +1057,7 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
               },
               minWidth: timestamp ? 200 : 100,
               resizable: !timestamp
-          };
+            };
           })}
           defaultPageSize={50}
           showPagination={false}
@@ -2348,6 +2353,35 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
   }
 
   // ==================================================================
+  private getSupervisorJson = async (): Promise<void> =>  {
+    try {
+      const resp = await axios.get(`/druid/indexer/v1/supervisor/${this.props.initSupervisorId}`);
+      this.updateSpec(resp.data);
+      this.updateStage('json-spec');
+    } catch (e) {
+      AppToaster.show({
+        message: `Failed to get supervisor spec: ${getDruidErrorMessage(e)}`,
+        intent: Intent.DANGER
+      });
+    }
+  }
+
+  private getTaskJson = async (): Promise<void> =>  {
+    try {
+      const resp = await axios.get(`/druid/indexer/v1/task/${this.props.initTaskId}`);
+      this.updateSpec(resp.data.payload.spec);
+      this.updateStage('json-spec');
+    } catch (e) {
+      AppToaster.show({
+        message: `Failed to get task spec: ${getDruidErrorMessage(e)}`,
+        intent: Intent.DANGER
+      });
+    }
+  }
+
+  renderLoading() {
+    return <Loader loading/>;
+  }
 
   renderJsonSpecStage() {
     const { goToTask } = this.props;
diff --git a/web-console/src/views/task-view/tasks-view.tsx b/web-console/src/views/task-view/tasks-view.tsx
index 4d65dd8..da4f3ae 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -48,7 +48,7 @@ export interface TasksViewProps extends React.Props<any> {
   openDialog: string | null;
   goToSql: (initSql: string) => void;
   goToMiddleManager: (middleManager: string) => void;
-  goToLoadDataView: () => void;
+  goToLoadDataView: (supervisorId?: string, taskId?: string) => void;
   noSqlMode: boolean;
 }
 
@@ -286,9 +286,17 @@ ORDER BY "rank" DESC, "created_time" DESC`);
     this.taskQueryManager.rerunLastQuery();
   }
 
-  private getSupervisorActions(id: string, supervisorSuspended: boolean): BasicAction[] {
-    return [
-      {
+  private getSupervisorActions(id: string, supervisorSuspended: boolean, type: string): BasicAction[] {
+    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.STEP_BACKWARD,
         title: 'Reset',
         onAction: () => this.setState({ resetSupervisorId: id })
@@ -304,7 +312,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
         intent: Intent.DANGER,
         onAction: () => this.setState({ terminateSupervisorId: id })
       }
-    ];
+      );
+    // @ts-ignore
+    return actions;
   }
 
   renderResumeSupervisorAction() {
@@ -413,7 +423,6 @@ ORDER BY "rank" DESC, "created_time" DESC`);
   renderSupervisorTable() {
     const { supervisors, supervisorsLoading, supervisorsError } = this.state;
     const { supervisorTableColumnSelectionHandler } = this;
-
     return <>
       <ReactTable
         data={supervisors || []}
@@ -477,8 +486,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             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);
+              const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type);
               const supervisorMenu = basicActionsToMenu(supervisorActions);
 
               return <ActionCell>
@@ -510,18 +520,24 @@ ORDER BY "rank" DESC, "created_time" DESC`);
     </>;
   }
 
-  // --------------------------------------
-
-  private getTaskActions(id: string, status: string): BasicAction[] {
-    if (status !== 'RUNNING' && status !== 'WAITING' && status !== 'PENDING') return [];
-    return [
-      {
+  private getTaskActions(id: string, status: string, type: string): BasicAction[] {
+    const actions: BasicAction[] = [];
+    if (type === 'index' || type === 'index_parallel') {
+      actions.push({
+        icon: IconNames.CLOUD_UPLOAD,
+        title: 'Open in data loader',
+        onAction: () => this.props.goToLoadDataView(undefined, id)
+      });
+    }
+    if (status === 'RUNNING' || status === 'WAITING' || status === 'PENDING') {
+      actions.push({
         icon: IconNames.CROSS,
         title: 'Kill',
         intent: Intent.DANGER,
-        onAction: () => this.setState({ killTaskId: id })
-      }
-    ];
+        onAction: () => this.setState({killTaskId: id})
+      });
+    }
+    return actions;
   }
 
   renderKillTaskAction() {
@@ -657,8 +673,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             Cell: row => {
               if (row.aggregated) return '';
               const id = row.value;
+              const type = row.row.type;
               const { status } = row.original;
-              const taskActions = this.getTaskActions(id, status);
+              const taskActions = this.getTaskActions(id, status, type);
               const taskMenu = basicActionsToMenu(taskActions);
 
               return <ActionCell>
@@ -688,11 +705,11 @@ ORDER BY "rank" DESC, "created_time" DESC`);
     </>;
   }
 
+
   render() {
     const { goToSql, goToLoadDataView, noSqlMode } = this.props;
     const { groupTasksBy, supervisorSpecDialogOpen, taskSpecDialogOpen, alertErrorMsg, taskTableActionDialogId, taskTableActionDialogActions, supervisorTableActionDialogId, supervisorTableActionDialogActions } = this.state;
     const { supervisorTableColumnSelectionHandler, taskTableColumnSelectionHandler } = this;
-
     const submitTaskMenu = <Menu>
       <MenuItem
         text="Raw JSON task"


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