Преглед изворни кода

HUE-9013 [editor] Switch to event based result handling for the result grid and chart components

This also introduces active and latest executable in the snippet to support multiple result handling
Johan Ahlen пре 6 година
родитељ
комит
2207d910bb

+ 89 - 12
desktop/core/src/desktop/js/apps/notebook2/components/ko.snippetResults.js

@@ -25,9 +25,24 @@ import 'apps/notebook2/components/resultChart/ko.resultChart';
 import 'apps/notebook2/components/resultGrid/ko.resultGrid';
 import 'apps/notebook2/components/resultGrid/ko.resultGrid';
 import { REDRAW_FIXED_HEADERS_EVENT } from 'apps/notebook2/components/resultGrid/ko.resultGrid';
 import { REDRAW_FIXED_HEADERS_EVENT } from 'apps/notebook2/components/resultGrid/ko.resultGrid';
 import { REDRAW_CHART_EVENT } from 'apps/notebook2/components/resultChart/ko.resultChart';
 import { REDRAW_CHART_EVENT } from 'apps/notebook2/components/resultChart/ko.resultChart';
+import { EXECUTABLE_UPDATED_EVENT } from 'apps/notebook2/execution/executable';
+import { RESULT_TYPE, RESULT_UPDATED_EVENT } from 'apps/notebook2/execution/executionResult';
 
 
 export const NAME = 'snippet-results';
 export const NAME = 'snippet-results';
 
 
+const META_TYPE_TO_CSS = {
+  TINYINT_TYPE: 'sort-numeric',
+  SMALLINT_TYPE: 'sort-numeric',
+  INT_TYPE: 'sort-numeric',
+  BIGINT_TYPE: 'sort-numeric',
+  FLOAT_TYPE: 'sort-numeric',
+  DOUBLE_TYPE: 'sort-numeric',
+  DECIMAL_TYPE: 'sort-numeric',
+  TIMESTAMP_TYPE: 'sort-date',
+  DATE_TYPE: 'sort-date',
+  DATETIME_TYPE: 'sort-date'
+};
+
 const isNumericColumn = type =>
 const isNumericColumn = type =>
   ['tinyint', 'smallint', 'int', 'bigint', 'float', 'double', 'decimal', 'real'].indexOf(type) !==
   ['tinyint', 'smallint', 'int', 'bigint', 'float', 'double', 'decimal', 'real'].indexOf(type) !==
   -1;
   -1;
@@ -82,7 +97,7 @@ const TEMPLATE = `
           params: {
           params: {
             data: data,
             data: data,
             editorMode: editorMode,
             editorMode: editorMode,
-            fetchResult: fetchResult,
+            fetchResult: fetchResult.bind($data),
             hasMore: hasMore,
             hasMore: hasMore,
             isPresentationMode: isPresentationMode,
             isPresentationMode: isPresentationMode,
             isResultFullScreenMode: isResultFullScreenMode,
             isResultFullScreenMode: isResultFullScreenMode,
@@ -117,23 +132,39 @@ class SnippetResults extends DisposableComponent {
     super();
     super();
     this.element = element;
     this.element = element;
 
 
-    this.type = params.type; // result
-    this.hasSomeResults = params.hasSomeResults; // result
-    this.images = params.images; // result
-    this.data = params.data; // result
-    this.meta = params.meta; // result
+    this.activeExecutable = params.activeExecutable;
+    this.latestExecutable = params.latestExecutable;
 
 
-    // Grid specific
     this.editorMode = params.editorMode;
     this.editorMode = params.editorMode;
-    this.fetchResult = params.fetchResult;
-    this.hasMore = params.hasMore;
     this.isPresentationMode = params.isPresentationMode;
     this.isPresentationMode = params.isPresentationMode;
     this.isResultFullScreenMode = params.isResultFullScreenMode;
     this.isResultFullScreenMode = params.isResultFullScreenMode;
     this.resultsKlass = params.resultsKlass;
     this.resultsKlass = params.resultsKlass;
-    this.status = params.status;
+    this.id = params.id; // TODO: Get rid of
+
+    huePubSub.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
+      if (this.activeExecutable() === executable) {
+        this.updateFromExecutable(executable);
+      }
+    });
+
+    const lastRenderedResult = undefined;
+    huePubSub.subscribe(RESULT_UPDATED_EVENT, executionResult => {
+      if (this.activeExecutable() === executionResult.executable) {
+        const refresh = lastRenderedResult === executionResult;
+        this.updateFromExecutionResult(executionResult, refresh);
+        this.lastRenderedResult = executionResult;
+      }
+    });
+
+    this.status = ko.observable();
+    this.type = ko.observable(RESULT_TYPE.TABLE);
+    this.meta = ko.observableArray();
+    this.data = ko.observableArray();
+    this.images = ko.observableArray();
+    this.hasMore = ko.observable();
+    this.hasResultSet = ko.observable();
 
 
-    // Chart specific
-    this.id = params.id;
+    this.hasSomeResult = ko.pureComputed(() => this.data().length);
 
 
     this.showGrid = ko.observable(true); // TODO: Should be persisted
     this.showGrid = ko.observable(true); // TODO: Should be persisted
     this.showChart = ko.observable(false); // TODO: Should be persisted
     this.showChart = ko.observable(false); // TODO: Should be persisted
@@ -173,6 +204,52 @@ class SnippetResults extends DisposableComponent {
       this.meta().filter(item => item.name !== '' && isNumericColumn(item.type))
       this.meta().filter(item => item.name !== '' && isNumericColumn(item.type))
     );
     );
   }
   }
+
+  reset() {
+    this.images([]);
+    this.data([]);
+    this.meta([]);
+    this.hasMore(false);
+    this.type(RESULT_TYPE.TABLE);
+    this.status(undefined);
+  }
+
+  updateFromExecutionResult(executionResult, refresh) {
+    if (refresh) {
+      this.reset();
+    }
+
+    if (executionResult) {
+      this.hasMore(executionResult.hasMore);
+      this.type(executionResult.type);
+
+      if (!this.meta().length && executionResult.meta.length) {
+        this.meta(
+          executionResult.meta.map((item, index) => ({
+            name: item.name,
+            type: item.type.replace(/_type/i, '').toLowerCase(),
+            comment: item.comment,
+            cssClass: META_TYPE_TO_CSS[item.type] || 'sort-string',
+            checked: ko.observable(true),
+            originalIndex: index
+          }))
+        );
+      }
+
+      if (executionResult.lastRows.length) {
+        this.data.push(...executionResult.lastRows);
+      }
+    }
+  }
+
+  updateFromExecutable(executable) {
+    this.hasResultSet(executable.handle.has_result_set);
+    if (!this.hasResultSet) {
+      this.reset();
+    }
+  }
+
+  fetchResult() {}
 }
 }
 
 
 componentUtils.registerComponent(
 componentUtils.registerComponent(

+ 0 - 16
desktop/core/src/desktop/js/apps/notebook2/components/resultGrid/ko.resultGrid.js

@@ -187,28 +187,12 @@ class ResultGrid extends DisposableComponent {
 
 
     this.hueDatatable = undefined;
     this.hueDatatable = undefined;
 
 
-    const adaptMeta = () => {
-      this.meta().forEach((item, index) => {
-        if (typeof item.checked === 'undefined') {
-          item.checked = ko.observable(true);
-          item.type = item.type.replace(/_type/i, '').toLowerCase();
-          item.originalIndex = index;
-        }
-      });
-    };
-
     this.trackKoSub(
     this.trackKoSub(
       this.columnsVisible.subscribe(() => {
       this.columnsVisible.subscribe(() => {
         defer(this.redrawFixedHeaders.bind(this));
         defer(this.redrawFixedHeaders.bind(this));
       })
       })
     );
     );
 
 
-    this.trackKoSub(
-      this.meta.subscribe(() => {
-        adaptMeta();
-      })
-    );
-
     this.filteredMeta = ko.pureComputed(() => {
     this.filteredMeta = ko.pureComputed(() => {
       if (!this.metaFilter() || this.metaFilter().query === '') {
       if (!this.metaFilter() || this.metaFilter().query === '') {
         return this.meta();
         return this.meta();

+ 60 - 54
desktop/core/src/desktop/js/apps/notebook2/execution/executable.js

@@ -15,10 +15,11 @@
 // limitations under the License.
 // limitations under the License.
 
 
 import apiHelper from 'api/apiHelper';
 import apiHelper from 'api/apiHelper';
-import { ExecutionResult } from 'apps/notebook2/execution/executionResult';
+import ExecutionResult from 'apps/notebook2/execution/executionResult';
 import hueAnalytics from 'utils/hueAnalytics';
 import hueAnalytics from 'utils/hueAnalytics';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
+
 /**
 /**
  *
  *
  * @type {{running: string, canceling: string, canceled: string, expired: string, waiting: string, success: string, ready: string, available: string, closed: string, starting: string}}
  * @type {{running: string, canceling: string, canceled: string, expired: string, waiting: string, success: string, ready: string, available: string, closed: string, starting: string}}
@@ -37,7 +38,7 @@ export const EXECUTION_STATUS = {
   closed: 'closed'
   closed: 'closed'
 };
 };
 
 
-const EXECUTABLE_UPDATED_EVENT = 'hue.executable.updated';
+export const EXECUTABLE_UPDATED_EVENT = 'hue.executable.updated';
 
 
 export default class Executable {
 export default class Executable {
   /**
   /**
@@ -48,18 +49,21 @@ export default class Executable {
    * @param {string} [options.statement] - Either supply a statement or a parsedStatement
    * @param {string} [options.statement] - Either supply a statement or a parsedStatement
    * @param {SqlStatementsParserResult} [options.parsedStatement] - Either supply a statement or a parsedStatement
    * @param {SqlStatementsParserResult} [options.parsedStatement] - Either supply a statement or a parsedStatement
    * @param {string} [options.database]
    * @param {string} [options.database]
+   * @param {Executor} options.executor
    * @param {Session[]} [options.sessions]
    * @param {Session[]} [options.sessions]
    */
    */
   constructor(options) {
   constructor(options) {
     this.compute = options.compute;
     this.compute = options.compute;
     this.namespace = options.namespace;
     this.namespace = options.namespace;
     this.sourceType = options.sourceType;
     this.sourceType = options.sourceType;
+    this.executor = options.executor;
 
 
     this.handle = {
     this.handle = {
       statement_id: 0 // TODO: Get rid of need for initial handle in the backend
       statement_id: 0 // TODO: Get rid of need for initial handle in the backend
     };
     };
     this.status = EXECUTION_STATUS.ready;
     this.status = EXECUTION_STATUS.ready;
     this.progress = 0;
     this.progress = 0;
+    this.result = undefined;
 
 
     this.lastCancellable = undefined;
     this.lastCancellable = undefined;
     this.notifyThrottle = -1;
     this.notifyThrottle = -1;
@@ -87,55 +91,6 @@ export default class Executable {
       return;
       return;
     }
     }
 
 
-    let statusCheckCount = 0;
-    let checkStatusTimeout = -1;
-
-    const checkStatus = () =>
-      new Promise((statusResolve, statusReject) => {
-        statusCheckCount++;
-        this.lastCancellable = apiHelper
-          .checkExecutionStatus({ executable: this })
-          .done(queryStatus => {
-            switch (this.status) {
-              case EXECUTION_STATUS.success:
-                this.setStatus(queryStatus);
-                this.setProgress(99); // TODO: why 99 here (from old code)?
-                statusResolve();
-                break;
-              case EXECUTION_STATUS.available:
-                this.setStatus(queryStatus);
-                this.setProgress(100);
-                statusResolve();
-                break;
-              case EXECUTION_STATUS.expired:
-                this.setStatus(queryStatus);
-                statusReject();
-                break;
-              case EXECUTION_STATUS.running:
-              case EXECUTION_STATUS.starting:
-              case EXECUTION_STATUS.waiting:
-                this.setStatus(queryStatus);
-                checkStatusTimeout = window.setTimeout(
-                  () => {
-                    checkStatus()
-                      .then(statusResolve)
-                      .catch(statusReject);
-                  },
-                  statusCheckCount > 45 ? 5000 : 1000
-                );
-                break;
-              default:
-                console.warn('Got unknown status ' + queryStatus);
-                statusReject();
-            }
-          })
-          .fail(statusReject);
-
-        this.lastCancellable.onCancel(() => {
-          window.clearTimeout(checkStatusTimeout);
-        });
-      });
-
     this.setStatus(EXECUTION_STATUS.running);
     this.setStatus(EXECUTION_STATUS.running);
     this.setProgress(0);
     this.setProgress(0);
 
 
@@ -143,14 +98,65 @@ export default class Executable {
       const session = await sessionManager.getSession({ type: this.sourceType });
       const session = await sessionManager.getSession({ type: this.sourceType });
       hueAnalytics.log('notebook', 'execute/' + this.sourceType);
       hueAnalytics.log('notebook', 'execute/' + this.sourceType);
       this.handle = await this.internalExecute(session);
       this.handle = await this.internalExecute(session);
-      await checkStatus();
-      this.result = new ExecutionResult(this);
+
+      if (this.handle.has_result_set && this.handle.sync) {
+        this.result = new ExecutionResult(this);
+        if (this.handle.sync) {
+          this.result.fetchRows();
+        }
+      }
+
+      this.checkStatus();
     } catch (err) {
     } catch (err) {
       this.setStatus(EXECUTION_STATUS.failed);
       this.setStatus(EXECUTION_STATUS.failed);
       throw err;
       throw err;
     }
     }
+  }
 
 
-    return this.result;
+  checkStatus(statusCheckCount) {
+    if (!statusCheckCount) {
+      statusCheckCount = 0;
+    }
+    statusCheckCount++;
+    let checkStatusTimeout = -1;
+    this.lastCancellable = apiHelper
+      .checkExecutionStatus({ executable: this })
+      .done(queryStatus => {
+        switch (this.status) {
+          case EXECUTION_STATUS.success:
+            this.setStatus(queryStatus);
+            this.setProgress(99); // TODO: why 99 here (from old code)?
+            break;
+          case EXECUTION_STATUS.available:
+            this.setStatus(queryStatus);
+            this.setProgress(100);
+            if (!this.result && this.handle.has_result_set) {
+              this.result = new ExecutionResult(this);
+              this.result.fetchRows();
+            }
+            break;
+          case EXECUTION_STATUS.expired:
+            this.setStatus(queryStatus);
+            break;
+          case EXECUTION_STATUS.running:
+          case EXECUTION_STATUS.starting:
+          case EXECUTION_STATUS.waiting:
+            this.setStatus(queryStatus);
+            checkStatusTimeout = window.setTimeout(
+              () => {
+                this.checkStatus(statusCheckCount);
+              },
+              statusCheckCount > 45 ? 5000 : 1000
+            );
+            break;
+          default:
+            console.warn('Got unknown status ' + queryStatus);
+        }
+      });
+
+    this.lastCancellable.onCancel(() => {
+      window.clearTimeout(checkStatusTimeout);
+    });
   }
   }
 
 
   setLastCancellable(lastCancellable) {
   setLastCancellable(lastCancellable) {

+ 40 - 43
desktop/core/src/desktop/js/apps/notebook2/execution/executionResult.js

@@ -15,40 +15,37 @@
 // limitations under the License.
 // limitations under the License.
 
 
 import apiHelper from 'api/apiHelper';
 import apiHelper from 'api/apiHelper';
+import huePubSub from 'utils/huePubSub';
 import { sleep } from 'utils/hueUtils';
 import { sleep } from 'utils/hueUtils';
+import { EXECUTION_STATUS } from './executable';
 
 
-/**
- *  available +----> fetching +----> done
- *      ^                     |
- *      |                     +----> fail
- *      |                     |
- *      +---------------------+
- *
- * If the handle indidcates that there's no result set available the ExecutionResult will have initial status set to
- * RESULT_STATUS.done
- *
- * @type { { canceling: string, canceled: string, fail: string, ready: string, executing: string, done: string } }
- */
-const RESULT_STATUS = {
-  ready: 'ready',
-  available: 'available',
-  fetching: 'fetching',
-  done: 'done',
-  fail: 'fail'
+export const RESULT_UPDATED_EVENT = 'hue.executable.result.updated';
+
+export const RESULT_TYPE = {
+  TABLE: 'table'
 };
 };
 
 
-class ExecutionResult {
+export default class ExecutionResult {
   /**
   /**
    *
    *
    * @param {Executable} executable
    * @param {Executable} executable
    */
    */
   constructor(executable) {
   constructor(executable) {
     this.executable = executable;
     this.executable = executable;
-    this.status = executable.handle.has_result_set ? RESULT_STATUS.available : RESULT_STATUS.done;
+
+    this.type = RESULT_TYPE.TABLE;
+    this.state = {};
+    this.rows = [];
+    this.meta = [];
+    this.lastRows = [];
+    this.images = [];
+    this.type = undefined;
+    this.hasMore = true;
+    this.isEscaped = false;
   }
   }
 
 
   async fetchResultSize() {
   async fetchResultSize() {
-    if (this.status === RESULT_STATUS.fail) {
+    if (this.executable.status === EXECUTION_STATUS.failed) {
       return;
       return;
     }
     }
 
 
@@ -78,38 +75,38 @@ class ExecutionResult {
   /**
   /**
    * Fetches additional rows
    * Fetches additional rows
    *
    *
-   * @param {Object} options
-   * @param {number} options.rows
+   * @param {Object} [options]
+   * @param {number} [options.rows]
    * @param {boolean} [options.startOver]
    * @param {boolean} [options.startOver]
    *
    *
    * @return {Promise}
    * @return {Promise}
    */
    */
   async fetchRows(options) {
   async fetchRows(options) {
-    if (this.status !== RESULT_STATUS.available) {
+    if (this.executable.status !== EXECUTION_STATUS.available) {
       return Promise.reject();
       return Promise.reject();
     }
     }
 
 
-    this.status = RESULT_STATUS.fetching;
+    const resultResponse = await apiHelper.fetchResults({
+      executable: this.executable,
+      rows: (options && options.rows) || 100,
+      startOver: options && options.startOver
+    });
 
 
-    try {
-      const resultResponse = await apiHelper.fetchResults({
-        executable: this.executable,
-        rows: options.rows,
-        startOver: !!options.startOver
-      });
+    const initialIndex = this.rows.length;
+    resultResponse.data.forEach((row, index) => {
+      row.unshift(initialIndex + index + 1);
+    });
 
 
-      if (resultResponse.has_more) {
-        this.status = RESULT_STATUS.available;
-      } else {
-        this.status = RESULT_STATUS.done;
-      }
-
-      return resultResponse;
-    } catch (err) {
-      this.status = RESULT_STATUS.fail;
-      return Promise.reject(err);
+    this.rows.push(...resultResponse.data);
+    this.lastRows = resultResponse.data;
+    if (!this.meta.length) {
+      this.meta = resultResponse.meta;
+      this.meta.unshift({ type: 'INT_TYPE', name: '', comment: null });
     }
     }
+    this.hasMore = resultResponse.has_more;
+    this.isEscaped = resultResponse.isEscaped;
+    this.type = resultResponse.type;
+
+    huePubSub.publish(RESULT_UPDATED_EVENT, this);
   }
   }
 }
 }
-
-export { RESULT_STATUS, ExecutionResult };

+ 16 - 28
desktop/core/src/desktop/js/apps/notebook2/execution/executor.js

@@ -16,6 +16,7 @@
 
 
 import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
 import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
 import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
+import { EXECUTABLE_UPDATED_EVENT } from 'apps/notebook2/execution/executable';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 
 
@@ -56,12 +57,12 @@ class Executor {
     this.executed = [];
     this.executed = [];
 
 
     if (this.isSqlEngine) {
     if (this.isSqlEngine) {
-      this.toExecute = SqlExecutable.fromStatement(options);
+      this.toExecute = SqlExecutable.fromStatement({ executor: this, ...options });
     } else {
     } else {
       throw new Error('Not implemented yet');
       throw new Error('Not implemented yet');
     }
     }
 
 
-    huePubSub.subscribe('hue.executable.updated', executable => {
+    huePubSub.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
       if (
       if (
         executable === this.currentExecutable ||
         executable === this.currentExecutable ||
         this.executed.some(executed => executed === executable)
         this.executed.some(executed => executed === executable)
@@ -85,32 +86,19 @@ class Executor {
   }
   }
 
 
   async executeNext() {
   async executeNext() {
-    return new Promise((resolve, reject) => {
-      const executeBatch = () => {
-        if (this.toExecute.length === 0) {
-          reject();
-        } else {
-          this.currentExecutable = this.toExecute.shift();
-          this.currentExecutable
-            .execute()
-            .then(executionResult => {
-              this.executed.push(this.currentExecutable);
-              this.currentExecutable = undefined;
-
-              if (this.canExecuteNextInBatch()) {
-                this.executeNext()
-                  .then(executeBatch)
-                  .catch(reject);
-              } else {
-                resolve(executionResult);
-              }
-            })
-            .catch(reject);
-        }
-      };
-
-      executeBatch();
-    });
+    if (this.toExecute.length === 0) {
+      return;
+    }
+
+    this.currentExecutable = this.toExecute.shift();
+
+    await this.currentExecutable.execute();
+
+    this.executed.push(this.currentExecutable);
+
+    if (this.canExecuteNextInBatch()) {
+      await this.executeNext();
+    }
   }
   }
 
 
   canExecuteNextInBatch() {
   canExecuteNextInBatch() {

+ 13 - 0
desktop/core/src/desktop/js/apps/notebook2/execution/sqlExecutable.js

@@ -29,6 +29,7 @@ export default class SqlExecutable extends Executable {
    * @param {string} [options.statement] - Either supply a statement or a parsedStatement
    * @param {string} [options.statement] - Either supply a statement or a parsedStatement
    * @param {SqlStatementsParserResult} [options.parsedStatement] - Either supply a statement or a parsedStatement
    * @param {SqlStatementsParserResult} [options.parsedStatement] - Either supply a statement or a parsedStatement
    * @param {string} [options.database]
    * @param {string} [options.database]
+   * @param {Executor} options.executor
    * @param {Session[]} [options.sessions]
    * @param {Session[]} [options.sessions]
    */
    */
   constructor(options) {
   constructor(options) {
@@ -56,6 +57,17 @@ export default class SqlExecutable extends Executable {
     });
     });
   }
   }
 
 
+  /**
+   *
+   * @param options
+   * @param options.database
+   * @param options.statement
+   * @param options.executor
+   * @param options.sourceType
+   * @param options.compute
+   * @param options.namespace
+   * @return {Array}
+   */
   static fromStatement(options) {
   static fromStatement(options) {
     const result = [];
     const result = [];
     let database = options.database;
     let database = options.database;
@@ -74,6 +86,7 @@ export default class SqlExecutable extends Executable {
         if (!skip) {
         if (!skip) {
           result.push(
           result.push(
             new SqlExecutable({
             new SqlExecutable({
+              executor: options.executor,
               sourceType: options.sourceType,
               sourceType: options.sourceType,
               compute: options.compute,
               compute: options.compute,
               namespace: options.namespace,
               namespace: options.namespace,

+ 7 - 2
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -27,7 +27,7 @@ import 'apps/notebook2/components/ko.snippetResults';
 import AceAutocompleteWrapper from 'apps/notebook/aceAutocompleteWrapper';
 import AceAutocompleteWrapper from 'apps/notebook/aceAutocompleteWrapper';
 import apiHelper from 'api/apiHelper';
 import apiHelper from 'api/apiHelper';
 import dataCatalog from 'catalog/dataCatalog';
 import dataCatalog from 'catalog/dataCatalog';
-import Executor from 'apps/notebook2/execution/executor';
+import Executor, { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 import hueAnalytics from 'utils/hueAnalytics';
 import hueAnalytics from 'utils/hueAnalytics';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import hueUtils from 'utils/hueUtils';
 import hueUtils from 'utils/hueUtils';
@@ -1008,10 +1008,15 @@ export default class Snippet {
       timeout: this.parentVm.autocompleteTimeout
       timeout: this.parentVm.autocompleteTimeout
     });
     });
 
 
-    huePubSub.subscribe('hue.executor.updated', details => {
+    this.latestExecutable = ko.observable();
+    this.activeExecutable = ko.observable();
+
+    huePubSub.subscribe(EXECUTOR_UPDATED_EVENT, details => {
       const executable = details.executable;
       const executable = details.executable;
 
 
       if (details.executor === this.executor()) {
       if (details.executor === this.executor()) {
+        this.latestExecutable(executable);
+        this.activeExecutable(executable); // TODO: Move to pureComputed (based on cursor)
         this.status(executable.status);
         this.status(executable.status);
         this.progress(executable.progress);
         this.progress(executable.progress);
       }
       }

+ 6 - 18
desktop/libs/notebook/src/notebook/templates/editor_components2.mako

@@ -735,19 +735,13 @@
           <div class="tab-pane" id="queryResults" data-bind="css: {'active': currentQueryTab() == 'queryResults'}">
           <div class="tab-pane" id="queryResults" data-bind="css: {'active': currentQueryTab() == 'queryResults'}">
             <!-- ko if: ['text', 'jar', 'py', 'markdown'].indexOf(type()) === -1 -->
             <!-- ko if: ['text', 'jar', 'py', 'markdown'].indexOf(type()) === -1 -->
               <!-- ko component: { name: 'snippet-results', params: {
               <!-- ko component: { name: 'snippet-results', params: {
-                data: result.data,
+                latestExecutable: latestExecutable,
+                activeExecutable: activeExecutable,
                 editorMode: parentVm.editorMode,
                 editorMode: parentVm.editorMode,
-                fetchResult: result.fetchMoreRows,
-                hasMore: result.hasMore,
-                hasSomeResults: result.hasSomeResults,
                 id: id,
                 id: id,
-                images: result.images,
                 isPresentationMode: parentNotebook.isPresentationMode,
                 isPresentationMode: parentNotebook.isPresentationMode,
                 isResultFullScreenMode: parentVm.isResultFullScreenMode,
                 isResultFullScreenMode: parentVm.isResultFullScreenMode,
-                meta: result.meta,
-                resultsKlass: resultsKlass,
-                status: status,
-                type: result.type,
+                resultsKlass: resultsKlass
               }} --><!-- /ko -->
               }} --><!-- /ko -->
             <!-- /ko -->
             <!-- /ko -->
           </div>
           </div>
@@ -889,19 +883,13 @@
             <!-- /ko -->
             <!-- /ko -->
             <!-- ko if: !$root.editorMode() && ['text', 'jar', 'java', 'distcp', 'shell', 'mapreduce', 'py', 'markdown'].indexOf(type()) === -1 -->
             <!-- ko if: !$root.editorMode() && ['text', 'jar', 'java', 'distcp', 'shell', 'mapreduce', 'py', 'markdown'].indexOf(type()) === -1 -->
               <!-- ko component: { name: 'snippet-results', params: {
               <!-- ko component: { name: 'snippet-results', params: {
-                data: result.data,
+                latestExecutable: latestExecutable,
+                activeExecutable: activeExecutable,
                 editorMode: parentVm.editorMode,
                 editorMode: parentVm.editorMode,
-                fetchResult: result.fetchMoreRows,
-                hasMore: result.hasMore,
-                hasSomeResults: result.hasSomeResults,
                 id: id,
                 id: id,
-                images: result.images,
                 isPresentationMode: parentNotebook.isPresentationMode,
                 isPresentationMode: parentNotebook.isPresentationMode,
                 isResultFullScreenMode: parentVm.isResultFullScreenMode,
                 isResultFullScreenMode: parentVm.isResultFullScreenMode,
-                meta: result.meta,
-                resultsKlass: resultsKlass,
-                status: status,
-                type: result.type,
+                resultsKlass: resultsKlass
               }} --><!-- /ko -->
               }} --><!-- /ko -->
             <!-- /ko -->
             <!-- /ko -->
             <div class="clearfix"></div>
             <div class="clearfix"></div>