Browse Source

HUE-9005 [editor] Extract log handling to a separate log component

Johan Ahlen 6 years ago
parent
commit
24053ea24f

+ 72 - 23
desktop/core/src/desktop/js/api/apiHelper.js

@@ -2076,32 +2076,34 @@ class ApiHelper {
   executeStatement(options) {
   executeStatement(options) {
     const executable = options.executable;
     const executable = options.executable;
     const url = EXECUTE_API_PREFIX + executable.sourceType;
     const url = EXECUTE_API_PREFIX + executable.sourceType;
-    const deferred = $.Deferred();
-
-    this.simplePost(url, ApiHelper.adaptExecutableToNotebook(executable, options.session), options)
-      .done(response => {
-        if (response.handle) {
-          deferred.resolve(response.handle);
-        } else {
-          deferred.reject('No handle in execute response');
-        }
-      })
-      .fail(deferred.reject);
 
 
-    const promise = deferred.promise();
+    const promise = new Promise((resolve, reject) => {
+      this.simplePost(
+        url,
+        ApiHelper.adaptExecutableToNotebook(executable, options.session),
+        options
+      )
+        .done(response => {
+          if (response.handle) {
+            resolve(response.handle);
+          } else {
+            reject('No handle in execute response');
+          }
+        })
+        .fail(reject);
+    });
 
 
-    promise.cancel = () => {
-      const cancelDeferred = $.Deferred();
-      deferred
-        .done(handle => {
+    executable.addCancellable({
+      cancel: async () => {
+        try {
+          const handle = await promise;
           if (options.executable.handle !== handle) {
           if (options.executable.handle !== handle) {
             options.executable.handle = handle;
             options.executable.handle = handle;
           }
           }
-          this.cancelStatement(options).always(cancelDeferred.resolve);
-        })
-        .fail(cancelDeferred.resolve);
-      return cancelDeferred;
-    };
+          this.cancelStatement(options);
+        } catch (err) {}
+      }
+    });
 
 
     return promise;
     return promise;
   }
   }
@@ -2130,6 +2132,41 @@ class ApiHelper {
     return new CancellablePromise(deferred, request);
     return new CancellablePromise(deferred, request);
   }
   }
 
 
+  /**
+   *
+   * @param {Object} options
+   * @param {boolean} [options.silenceErrors]
+   * @param {Executable} options.executable
+   * @param {number} [options.from]
+   * @param {Object[]} [options.jobs]
+   * @param {string} options.fullLog
+   *
+   * @return {Promise<?>}
+   */
+  fetchLogs(options) {
+    return new Promise((resolve, reject) => {
+      const data = ApiHelper.adaptExecutableToNotebook(options.executable);
+      data.full_log = options.fullLog;
+      data.jobs = options.jobs && JSON.stringify(options.jobs);
+      data.from = options.from || 0;
+      const request = this.simplePost('/notebook/api/get_logs', data, options)
+        .done(response => {
+          resolve({
+            logs: (response.status === 1 && response.message) || response.logs || '',
+            jobs: response.jobs || [],
+            isFullLogs: response.isFullLogs
+          });
+        })
+        .fail(reject);
+
+      options.executable.addCancellable({
+        cancel: () => {
+          this.cancelActiveRequest(request);
+        }
+      });
+    });
+  }
+
   /**
   /**
    *
    *
    * @param {Object} options
    * @param {Object} options
@@ -2178,7 +2215,7 @@ class ApiHelper {
       data.rows = options.rows;
       data.rows = options.rows;
       data.startOver = !!options.startOver;
       data.startOver = !!options.startOver;
 
 
-      this.simplePost(
+      const request = this.simplePost(
         '/notebook/api/fetch_result_data',
         '/notebook/api/fetch_result_data',
         data,
         data,
         {
         {
@@ -2192,6 +2229,12 @@ class ApiHelper {
           resolve(data.result);
           resolve(data.result);
         })
         })
         .fail(reject);
         .fail(reject);
+
+      options.executable.addCancellable({
+        cancel: () => {
+          this.cancelActiveRequest(request);
+        }
+      });
     });
     });
   }
   }
 
 
@@ -2205,7 +2248,7 @@ class ApiHelper {
    */
    */
   async fetchResultSize2(options) {
   async fetchResultSize2(options) {
     return new Promise((resolve, reject) => {
     return new Promise((resolve, reject) => {
-      this.simplePost(
+      const request = this.simplePost(
         '/notebook/api/fetch_result_size',
         '/notebook/api/fetch_result_size',
         ApiHelper.adaptExecutableToNotebook(options.executable),
         ApiHelper.adaptExecutableToNotebook(options.executable),
         options
         options
@@ -2214,6 +2257,12 @@ class ApiHelper {
           resolve(response.result);
           resolve(response.result);
         })
         })
         .fail(reject);
         .fail(reject);
+
+      options.executable.addCancellable({
+        cancel: () => {
+          this.cancelActiveRequest(request);
+        }
+      });
     });
     });
   }
   }
 
 

+ 151 - 0
desktop/core/src/desktop/js/apps/notebook2/components/ko.executableLogs.js

@@ -0,0 +1,151 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import ko from 'knockout';
+
+import 'ko/bindings/ko.publish';
+
+import componentUtils from 'ko/components/componentUtils';
+import { EXECUTABLE_UPDATED_EVENT, EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
+import DisposableComponent from 'ko/components/DisposableComponent';
+import I18n from 'utils/i18n';
+import { RESULT_UPDATED_EVENT } from 'apps/notebook2/execution/executionResult';
+import { LOGS_UPDATED_EVENT } from 'apps/notebook2/execution/executionLogs';
+
+export const NAME = 'executable-logs';
+
+// prettier-ignore
+const TEMPLATE = `
+<div class="snippet-log-container margin-bottom-10" data-bind="visible: showLogs" style="display: none;">
+  <div data-bind="delayedOverflow: 'slow', css: resultsKlass" style="margin-top: 5px; position: relative;">
+    <a href="javascript: void(0)" class="inactive-action close-logs-overlay" data-bind="toggle: showLogs">&times;</a>
+    <ul data-bind="visible: jobs().length, foreach: jobs" class="unstyled jobs-overlay">
+      <li data-bind="attr: { 'id': name.substr(4) }">
+        <a class="pointer" data-bind="
+            text: $.trim(name),
+            click: function() {
+              huePubSub.publish('show.jobs.panel', {
+                id: name,
+                interface: $parent.sourceType() == 'impala' ? 'queries' : 'jobs',
+                compute: $parent.compute
+              });
+            },
+            clickBubble: false
+          "></a>
+        <!-- ko if: percentJob >= 0 -->
+        <div class="progress-job progress pull-left" style="background-color: #FFF; width: 100%" data-bind="
+            css: {
+              'progress-warning': percentJob < 100,
+              'progress-success': percentJob === 100
+            }">
+          <div class="bar" data-bind="style: { 'width': percentJob + '%' }"></div>
+        </div>
+        <!-- /ko -->
+        <div class="clearfix"></div>
+      </li>
+    </ul>
+    <span data-bind="visible: !isPresentationMode() || !isHidingCode()">
+    <pre data-bind="visible: !logs() && jobs().length" class="logs logs-bigger">${ I18n('No logs available at this moment.') }</pre>
+    <pre data-bind="visible: logs, text: logs, logScroller: logs, logScrollerVisibilityEvent: showLogs" class="logs logs-bigger logs-populated"></pre>
+  </span>
+  </div>
+  <div class="snippet-log-resizer" data-bind="
+      visible: logs,
+      logResizer: {
+        parent: '.snippet-log-container',
+        target: '.logs-populated',
+        mainScrollable: window.MAIN_SCROLLABLE,
+        onStart: function () { huePubSub.publish('result.grid.hide.fixed.headers') },
+        onResize: function() {
+          huePubSub.publish('result.grid.hide.fixed.headers');
+          window.setTimeout(function () {
+            huePubSub.publish('result.grid.redraw.fixed.headers');
+          }, 500);
+        },
+        minHeight: 50
+      }
+    ">
+    <i class="fa fa-ellipsis-h"></i>
+  </div>
+</div>
+<div class="snippet-log-container margin-bottom-10">
+  <div data-bind="visible: !hasResultset() && status() == 'available' && fetchedOnce(), css: resultsKlass" style="display:none;">
+    <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-check muted"></i> ${ I18n('Success.') }</pre>
+  </div>
+
+  <div data-bind="visible: hasResultset() && status() === 'available' && hasEmptyResult() && fetchedOnce() && !explanation().length, css: resultsKlass" style="display:none;">
+    <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-check muted"></i> ${ I18n("Done. 0 results.") }</pre>
+  </div>
+
+  <div data-bind="visible: status() === 'expired', css: resultsKlass" style="display:none;">
+    <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-check muted"></i> ${ I18n("Results have expired, rerun the query if needed.") }</pre>
+  </div>
+
+  <div data-bind="visible: status() === 'available' && !fetchedOnce(), css: resultsKlass" style="display:none;">
+    <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-spin fa-spinner"></i> ${ I18n('Loading...') }</pre>
+  </div>
+</div>
+`;
+
+class ExecutableLogs extends DisposableComponent {
+  constructor(params) {
+    super();
+
+    this.activeExecutable = params.activeExecutable;
+    this.showLogs = params.showLogs;
+    this.resultsKlass = params.resultsKlass;
+    this.isPresentationMode = params.isPresentationMode;
+    this.isHidingCode = params.isHidingCode;
+    this.explanation = params.explanation;
+
+    this.status = ko.observable();
+    this.fetchedOnce = ko.observable();
+    this.hasResultset = ko.observable();
+    this.hasEmptyResult = ko.observable();
+    this.sourceType = ko.observable();
+    this.compute = undefined;
+
+    this.jobs = ko.observableArray();
+    this.logs = ko.observable();
+
+    this.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
+      if (this.activeExecutable() === executable) {
+        this.hasResultset(executable.handle.has_result_set);
+        this.status(executable.status);
+        this.sourceType(executable.sourceType);
+        if (!this.compute) {
+          this.compute = executable.compute;
+        }
+      }
+    });
+
+    this.subscribe(RESULT_UPDATED_EVENT, executionResult => {
+      if (this.activeExecutable() === executionResult.executable) {
+        this.fetchedOnce(executionResult.fetchedOnce);
+        this.hasEmptyResult(executionResult.rows.length === 0);
+      }
+    });
+
+    this.subscribe(LOGS_UPDATED_EVENT, executionLogs => {
+      if (this.activeExecutable() === executionLogs.executable) {
+        this.logs(executionLogs.fullLog);
+        this.jobs(executionLogs.jobs);
+      }
+    });
+  }
+}
+
+componentUtils.registerComponent(NAME, ExecutableLogs, TEMPLATE);

+ 26 - 32
desktop/core/src/desktop/js/apps/notebook2/components/ko.snippetResults.js

@@ -187,35 +187,31 @@ class SnippetResults extends DisposableComponent {
       this.meta().filter(item => item.name !== '' && isNumericColumn(item.type))
       this.meta().filter(item => item.name !== '' && isNumericColumn(item.type))
     );
     );
 
 
-    this.trackKoSub(
-      this.showChart.subscribe(val => {
-        if (val) {
-          this.showGrid(false);
-          huePubSub.publish(REDRAW_CHART_EVENT);
-          huePubSub.publish('editor.chart.shown', this);
-        }
-      })
-    );
+    this.subscribe(this.showChart, val => {
+      if (val) {
+        this.showGrid(false);
+        huePubSub.publish(REDRAW_CHART_EVENT);
+        huePubSub.publish('editor.chart.shown', this);
+      }
+    });
 
 
-    this.trackKoSub(
-      this.showGrid.subscribe(val => {
-        if (val) {
-          this.showChart(false);
-          huePubSub.publish('editor.grid.shown', this);
-          huePubSub.publish(REDRAW_FIXED_HEADERS_EVENT);
-          huePubSub.publish('table.extender.redraw');
-        }
-      })
-    );
+    this.subscribe(this.showGrid, val => {
+      if (val) {
+        this.showChart(false);
+        huePubSub.publish('editor.grid.shown', this);
+        huePubSub.publish(REDRAW_FIXED_HEADERS_EVENT);
+        huePubSub.publish('table.extender.redraw');
+      }
+    });
 
 
-    huePubSub.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
+    this.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
       if (this.selectedExecutable() === executable) {
       if (this.selectedExecutable() === executable) {
         this.updateFromExecutable(executable);
         this.updateFromExecutable(executable);
       }
       }
     });
     });
 
 
     let lastRenderedResult = undefined;
     let lastRenderedResult = undefined;
-    huePubSub.subscribe(RESULT_UPDATED_EVENT, executionResult => {
+    this.subscribe(RESULT_UPDATED_EVENT, executionResult => {
       if (this.selectedExecutable() === executionResult.executable) {
       if (this.selectedExecutable() === executionResult.executable) {
         const refresh = lastRenderedResult !== executionResult;
         const refresh = lastRenderedResult !== executionResult;
         this.updateFromExecutionResult(executionResult, refresh);
         this.updateFromExecutionResult(executionResult, refresh);
@@ -223,18 +219,16 @@ class SnippetResults extends DisposableComponent {
       }
       }
     });
     });
 
 
-    this.trackKoSub(
-      this.selectedExecutable.subscribe(executable => {
-        if (executable && executable.result) {
-          if (executable !== lastRenderedResult) {
-            this.updateFromExecutionResult(executable.result, true);
-            lastRenderedResult = executable;
-          }
-        } else {
-          this.reset();
+    this.subscribe(this.selectedExecutable, executable => {
+      if (executable && executable.result) {
+        if (executable !== lastRenderedResult) {
+          this.updateFromExecutionResult(executable.result, true);
+          lastRenderedResult = executable;
         }
         }
-      })
-    );
+      } else {
+        this.reset();
+      }
+    });
   }
   }
 
 
   reset() {
   reset() {

+ 26 - 31
desktop/core/src/desktop/js/apps/notebook2/components/resultChart/ko.resultChart.js

@@ -19,7 +19,6 @@ import ko from 'knockout';
 import componentUtils from 'ko/components/componentUtils';
 import componentUtils from 'ko/components/componentUtils';
 import DisposableComponent from 'ko/components/DisposableComponent';
 import DisposableComponent from 'ko/components/DisposableComponent';
 import hueAnalytics from 'utils/hueAnalytics';
 import hueAnalytics from 'utils/hueAnalytics';
-import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 import I18n from 'utils/i18n';
 import {
 import {
   leafletMapChartTransformer,
   leafletMapChartTransformer,
@@ -546,38 +545,34 @@ class ResultChart extends DisposableComponent {
       );
       );
     });
     });
 
 
-    this.trackKoSub(this.chartType.subscribe(this.redrawChart.bind(this)));
+    this.subscribe(this.chartType, this.redrawChart.bind(this));
 
 
-    this.trackKoSub(
-      this.meta.subscribe(() => {
-        if (this.chartX()) {
-          this.chartX(this.guessMetaField(this.chartX()));
-        }
-        if (this.chartXPivot()) {
-          this.chartXPivot(this.guessMetaField(this.chartXPivot()));
-        }
-        if (this.chartYSingle()) {
-          this.chartYSingle(this.guessMetaField(this.chartYSingle()));
-        }
-        if (this.chartMapLabel()) {
-          this.chartMapLabel(this.guessMetaField(this.chartMapLabel()));
-        }
-        if (this.chartYMulti()) {
-          this.chartYMulti(this.guessMetaFields(this.chartYMulti()));
-        }
-      })
-    );
+    this.subscribe(this.meta, () => {
+      if (this.chartX()) {
+        this.chartX(this.guessMetaField(this.chartX()));
+      }
+      if (this.chartXPivot()) {
+        this.chartXPivot(this.guessMetaField(this.chartXPivot()));
+      }
+      if (this.chartYSingle()) {
+        this.chartYSingle(this.guessMetaField(this.chartYSingle()));
+      }
+      if (this.chartMapLabel()) {
+        this.chartMapLabel(this.guessMetaField(this.chartMapLabel()));
+      }
+      if (this.chartYMulti()) {
+        this.chartYMulti(this.guessMetaFields(this.chartYMulti()));
+      }
+    });
 
 
-    this.trackKoSub(this.showChart.subscribe(this.prepopulateChart.bind(this)));
-    this.trackKoSub(this.chartType.subscribe(this.prepopulateChart.bind(this)));
-    this.trackKoSub(this.chartXPivot.subscribe(this.prepopulateChart.bind(this)));
+    this.subscribe(this.showChart, this.prepopulateChart.bind(this));
+    this.subscribe(this.chartType, this.prepopulateChart.bind(this));
+    this.subscribe(this.chartXPivot, this.prepopulateChart.bind(this));
 
 
-    this.trackKoSub(
-      this.hasDataForChart.subscribe(() => {
-        this.chartX.notifySubscribers();
-        this.chartX.valueHasMutated();
-      })
-    );
+    this.subscribe(this.hasDataForChart, () => {
+      this.chartX.notifySubscribers();
+      this.chartX.valueHasMutated();
+    });
 
 
     this.hideStacked = ko.pureComputed(() => !this.chartYMulti().length);
     this.hideStacked = ko.pureComputed(() => !this.chartYMulti().length);
 
 
@@ -658,7 +653,7 @@ class ResultChart extends DisposableComponent {
       group: this.chartScatterGroup()
       group: this.chartScatterGroup()
     }));
     }));
 
 
-    this.trackPubSub(huePubSub.subscribe(REDRAW_CHART_EVENT, this.redrawChart.bind(this)));
+    this.subscribe(REDRAW_CHART_EVENT, this.redrawChart.bind(this));
 
 
     const resizeId = UUID();
     const resizeId = UUID();
     let resizeTimeout = -1;
     let resizeTimeout = -1;

+ 32 - 48
desktop/core/src/desktop/js/apps/notebook2/components/resultGrid/ko.resultGrid.js

@@ -187,11 +187,9 @@ class ResultGrid extends DisposableComponent {
 
 
     this.hueDatatable = undefined;
     this.hueDatatable = undefined;
 
 
-    this.trackKoSub(
-      this.columnsVisible.subscribe(() => {
-        defer(this.redrawFixedHeaders.bind(this));
-      })
-    );
+    this.subscribe(this.columnsVisible, () => {
+      defer(this.redrawFixedHeaders.bind(this));
+    });
 
 
     this.filteredMeta = ko.pureComputed(() => {
     this.filteredMeta = ko.pureComputed(() => {
       if (!this.metaFilter() || this.metaFilter().query === '') {
       if (!this.metaFilter() || this.metaFilter().query === '') {
@@ -228,35 +226,29 @@ class ResultGrid extends DisposableComponent {
       return this.filteredMeta().length;
       return this.filteredMeta().length;
     });
     });
 
 
-    this.trackKoSub(
-      this.data.subscribe(data => {
-        this.render();
-      })
-    );
+    this.subscribe(this.data, this.render.bind(this));
 
 
-    this.trackKoSub(
-      this.meta.subscribe(meta => {
-        if (meta) {
-          if (this.hueDatatable) {
-            this.hueDatatable.fnDestroy();
-            this.hueDatatable = undefined;
-          }
+    this.subscribe(this.meta, meta => {
+      if (meta) {
+        if (this.hueDatatable) {
+          this.hueDatatable.fnDestroy();
+          this.hueDatatable = undefined;
+        }
 
 
-          const $resultTable = this.getResultTableElement();
-          if ($resultTable.data('plugin_jHueTableExtender2')) {
-            $resultTable.data('plugin_jHueTableExtender2').destroy();
-          }
-          $resultTable.data('scrollToCol', null);
-          $resultTable.data('scrollToRow', null);
-          $resultTable.data('rendered', false);
-          if ($resultTable.hasClass('dt')) {
-            $resultTable.removeClass('dt');
-            $resultTable.find('thead tr').empty();
-            $resultTable.data('lockedRows', {});
-          }
+        const $resultTable = this.getResultTableElement();
+        if ($resultTable.data('plugin_jHueTableExtender2')) {
+          $resultTable.data('plugin_jHueTableExtender2').destroy();
         }
         }
-      })
-    );
+        $resultTable.data('scrollToCol', null);
+        $resultTable.data('scrollToRow', null);
+        $resultTable.data('rendered', false);
+        if ($resultTable.hasClass('dt')) {
+          $resultTable.removeClass('dt');
+          $resultTable.find('thead tr').empty();
+          $resultTable.data('lockedRows', {});
+        }
+      }
+    });
 
 
     this.disposals.push(() => {
     this.disposals.push(() => {
       if (this.hueDatatable) {
       if (this.hueDatatable) {
@@ -449,27 +441,19 @@ class ResultGrid extends DisposableComponent {
       $scrollElement.off('scroll.resultGrid');
       $scrollElement.off('scroll.resultGrid');
     });
     });
 
 
-    this.trackKoSub(
-      this.columnsVisible.subscribe(newValue => {
-        if (newValue) {
-          dataScroll();
-        }
-      })
-    );
+    this.subscribe(this.columnsVisible, newValue => {
+      if (newValue) {
+        dataScroll();
+      }
+    });
 
 
-    this.trackPubSub(
-      huePubSub.subscribe(REDRAW_FIXED_HEADERS_EVENT, this.redrawFixedHeaders.bind(this))
-    );
+    this.subscribe(REDRAW_FIXED_HEADERS_EVENT, this.redrawFixedHeaders.bind(this));
 
 
-    this.trackPubSub(huePubSub.subscribe(SHOW_GRID_SEARCH_EVENT, this.showSearch.bind(this)));
+    this.subscribe(SHOW_GRID_SEARCH_EVENT, this.showSearch.bind(this));
 
 
-    this.trackPubSub(
-      huePubSub.subscribe(HIDE_FIXED_HEADERS_EVENT, this.hideFixedHeaders.bind(this))
-    );
+    this.subscribe(HIDE_FIXED_HEADERS_EVENT, this.hideFixedHeaders.bind(this));
 
 
-    this.trackPubSub(
-      huePubSub.subscribe(SHOW_NORMAL_RESULT_EVENT, this.showNormalResult.bind(this))
-    );
+    this.subscribe(SHOW_NORMAL_RESULT_EVENT, this.showNormalResult.bind(this));
 
 
     return this.hueDatatable;
     return this.hueDatatable;
   }
   }

+ 45 - 31
desktop/core/src/desktop/js/apps/notebook2/execution/executable.js

@@ -19,6 +19,7 @@ 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';
+import ExecutionLogs from 'apps/notebook2/execution/executionLogs';
 
 
 /**
 /**
  *
  *
@@ -64,9 +65,13 @@ export default class Executable {
     this.status = EXECUTION_STATUS.ready;
     this.status = EXECUTION_STATUS.ready;
     this.progress = 0;
     this.progress = 0;
     this.result = undefined;
     this.result = undefined;
+    this.logs = new ExecutionLogs(this);
 
 
-    this.lastCancellable = undefined;
+    this.cancellables = [];
     this.notifyThrottle = -1;
     this.notifyThrottle = -1;
+
+    this.executeStarted = 0;
+    this.executeEnded = 0;
   }
   }
 
 
   setStatus(status) {
   setStatus(status) {
@@ -79,6 +84,10 @@ export default class Executable {
     this.notify();
     this.notify();
   }
   }
 
 
+  getExecutionTime() {
+    return (this.executeEnded || Date.now()) - this.executeStarted;
+  }
+
   notify() {
   notify() {
     window.clearTimeout(this.notifyThrottle);
     window.clearTimeout(this.notifyThrottle);
     this.notifyThrottle = window.setTimeout(() => {
     this.notifyThrottle = window.setTimeout(() => {
@@ -90,6 +99,7 @@ export default class Executable {
     if (this.status !== EXECUTION_STATUS.ready) {
     if (this.status !== EXECUTION_STATUS.ready) {
       return;
       return;
     }
     }
+    this.executeStarted = Date.now();
 
 
     this.setStatus(EXECUTION_STATUS.running);
     this.setStatus(EXECUTION_STATUS.running);
     this.setProgress(0);
     this.setProgress(0);
@@ -107,6 +117,7 @@ export default class Executable {
       }
       }
 
 
       this.checkStatus();
       this.checkStatus();
+      this.logs.fetchLogs();
     } catch (err) {
     } catch (err) {
       this.setStatus(EXECUTION_STATUS.failed);
       this.setStatus(EXECUTION_STATUS.failed);
       throw err;
       throw err;
@@ -114,20 +125,28 @@ export default class Executable {
   }
   }
 
 
   checkStatus(statusCheckCount) {
   checkStatus(statusCheckCount) {
+    let checkStatusTimeout = -1;
+
     if (!statusCheckCount) {
     if (!statusCheckCount) {
       statusCheckCount = 0;
       statusCheckCount = 0;
+      this.cancellables.push({
+        cancel: () => {
+          window.clearTimeout(checkStatusTimeout);
+        }
+      });
     }
     }
     statusCheckCount++;
     statusCheckCount++;
-    let checkStatusTimeout = -1;
-    this.lastCancellable = apiHelper
-      .checkExecutionStatus({ executable: this })
-      .done(queryStatus => {
+
+    this.cancellables.push(
+      apiHelper.checkExecutionStatus({ executable: this }).done(queryStatus => {
         switch (this.status) {
         switch (this.status) {
           case EXECUTION_STATUS.success:
           case EXECUTION_STATUS.success:
+            this.executeEnded = Date.now();
             this.setStatus(queryStatus);
             this.setStatus(queryStatus);
             this.setProgress(99); // TODO: why 99 here (from old code)?
             this.setProgress(99); // TODO: why 99 here (from old code)?
             break;
             break;
           case EXECUTION_STATUS.available:
           case EXECUTION_STATUS.available:
+            this.executeEnded = Date.now();
             this.setStatus(queryStatus);
             this.setStatus(queryStatus);
             this.setProgress(100);
             this.setProgress(100);
             if (!this.result && this.handle.has_result_set) {
             if (!this.result && this.handle.has_result_set) {
@@ -136,6 +155,7 @@ export default class Executable {
             }
             }
             break;
             break;
           case EXECUTION_STATUS.expired:
           case EXECUTION_STATUS.expired:
+            this.executeEnded = Date.now();
             this.setStatus(queryStatus);
             this.setStatus(queryStatus);
             break;
             break;
           case EXECUTION_STATUS.running:
           case EXECUTION_STATUS.running:
@@ -150,17 +170,15 @@ export default class Executable {
             );
             );
             break;
             break;
           default:
           default:
+            this.executeEnded = Date.now();
             console.warn('Got unknown status ' + queryStatus);
             console.warn('Got unknown status ' + queryStatus);
         }
         }
-      });
-
-    this.lastCancellable.onCancel(() => {
-      window.clearTimeout(checkStatusTimeout);
-    });
+      })
+    );
   }
   }
 
 
-  setLastCancellable(lastCancellable) {
-    this.lastCancellable = lastCancellable;
+  addCancellable(cancellable) {
+    this.cancellables.push(cancellable);
   }
   }
 
 
   async internalExecute(session) {
   async internalExecute(session) {
@@ -172,30 +190,26 @@ export default class Executable {
   }
   }
 
 
   async cancel() {
   async cancel() {
-    return new Promise(resolve => {
-      if (this.lastCancellable && this.status === EXECUTION_STATUS.running) {
-        hueAnalytics.log('notebook', 'cancel/' + this.sourceType);
-        this.setStatus(EXECUTION_STATUS.canceling);
-        this.lastCancellable.cancel().always(() => {
-          this.setStatus(EXECUTION_STATUS.canceled);
-          resolve();
-        });
-        this.lastCancellable = undefined;
-      } else {
-        resolve();
+    if (this.cancellables.length && this.status === EXECUTION_STATUS.running) {
+      hueAnalytics.log('notebook', 'cancel/' + this.sourceType);
+      this.setStatus(EXECUTION_STATUS.canceling);
+      while (this.cancellables.length) {
+        await this.cancellables.pop().cancel();
       }
       }
-    });
+      this.setStatus(EXECUTION_STATUS.canceled);
+    }
   }
   }
 
 
   async close() {
   async close() {
+    while (this.cancellables.length) {
+      await this.cancellables.pop().cancel();
+    }
+
     return new Promise(resolve => {
     return new Promise(resolve => {
-      if (this.status === EXECUTION_STATUS.running) {
-        this.cancel().finally(resolve);
-      } else if (this.status !== EXECUTION_STATUS.closed) {
-        apiHelper.closeStatement({ executable: this }).finally(resolve);
-      }
-    }).finally(() => {
-      this.setStatus(EXECUTION_STATUS.closed);
+      apiHelper.closeStatement({ executable: this }).finally(() => {
+        this.setStatus(EXECUTION_STATUS.closed);
+        resolve();
+      });
     });
     });
   }
   }
 }
 }

+ 75 - 0
desktop/core/src/desktop/js/apps/notebook2/execution/executionLogs.js

@@ -0,0 +1,75 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import apiHelper from 'api/apiHelper';
+import huePubSub from 'utils/huePubSub';
+import { EXECUTION_STATUS } from './executable';
+
+export const LOGS_UPDATED_EVENT = 'hue.executable.logs.updated';
+
+export default class ExecutionLogs {
+  /**
+   *
+   * @param {Executable} executable
+   */
+  constructor(executable) {
+    this.executable = executable;
+    this.fullLog = '';
+    this.logLines = 0;
+    this.jobs = [];
+  }
+
+  async fetchLogs(finalFetch) {
+    const logDetails = await apiHelper.fetchLogs(this);
+
+    if (logDetails.logs.indexOf('Unable to locate') === -1 || logDetails.isFullLogs) {
+      this.fullLog = logDetails.logs;
+    } else {
+      this.fullLog += '\n' + logDetails.logs;
+    }
+    this.logLines = this.fullLog.split(/\r\n|\r|\n/).length;
+
+    if (logDetails.jobs) {
+      logDetails.jobs.forEach(job => {
+        if (typeof job.percentJob === 'undefined') {
+          job.percentJob = -1;
+        }
+      });
+      this.jobs = logDetails.jobs;
+    } else {
+      this.jobs = [];
+    }
+
+    huePubSub.publish(LOGS_UPDATED_EVENT, this);
+
+    if (!finalFetch) {
+      const delay = this.executable.getExecutionTime() > 45000 ? 5000 : 1000;
+      const fetchLogsTimeout = window.setTimeout(async () => {
+        await this.fetchLogs(
+          this.executable.status !== EXECUTION_STATUS.running &&
+            this.executable.status !== EXECUTION_STATUS.starting &&
+            this.executable.status !== EXECUTION_STATUS.waiting
+        );
+      }, delay);
+
+      this.executable.addCancellable({
+        cancel: () => {
+          window.clearTimeout(fetchLogsTimeout);
+        }
+      });
+    }
+  }
+}

+ 2 - 1
desktop/core/src/desktop/js/apps/notebook2/execution/executionResult.js

@@ -42,6 +42,7 @@ export default class ExecutionResult {
     this.type = undefined;
     this.type = undefined;
     this.hasMore = true;
     this.hasMore = true;
     this.isEscaped = false;
     this.isEscaped = false;
+    this.fetchedOnce = false;
   }
   }
 
 
   async fetchResultSize() {
   async fetchResultSize() {
@@ -106,7 +107,7 @@ export default class ExecutionResult {
     this.hasMore = resultResponse.has_more;
     this.hasMore = resultResponse.has_more;
     this.isEscaped = resultResponse.isEscaped;
     this.isEscaped = resultResponse.isEscaped;
     this.type = resultResponse.type;
     this.type = resultResponse.type;
-
+    this.fetchedOnce = true;
     huePubSub.publish(RESULT_UPDATED_EVENT, this);
     huePubSub.publish(RESULT_UPDATED_EVENT, this);
   }
   }
 }
 }

+ 3 - 10
desktop/core/src/desktop/js/apps/notebook2/execution/sqlExecutable.js

@@ -44,16 +44,9 @@ export default class SqlExecutable extends Executable {
   }
   }
 
 
   async internalExecute(session) {
   async internalExecute(session) {
-    return new Promise((resolve, reject) => {
-      this.setLastCancellable(
-        apiHelper
-          .executeStatement({
-            executable: this,
-            session: session
-          })
-          .done(resolve)
-          .fail(reject)
-      );
+    return await apiHelper.executeStatement({
+      executable: this,
+      session: session
     });
     });
   }
   }
 
 

+ 1 - 3
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -19,6 +19,7 @@ import ko from 'knockout';
 import komapping from 'knockout.mapping';
 import komapping from 'knockout.mapping';
 import { markdown } from 'markdown';
 import { markdown } from 'markdown';
 
 
+import 'apps/notebook2/components/ko.executableLogs';
 import 'apps/notebook2/components/ko.executableProgressBar';
 import 'apps/notebook2/components/ko.executableProgressBar';
 import 'apps/notebook2/components/ko.snippetEditorActions';
 import 'apps/notebook2/components/ko.snippetEditorActions';
 import 'apps/notebook2/components/ko.snippetExecuteActions';
 import 'apps/notebook2/components/ko.snippetExecuteActions';
@@ -772,9 +773,6 @@ export default class Snippet {
 
 
     this.showLogs.subscribe(val => {
     this.showLogs.subscribe(val => {
       huePubSub.publish(REDRAW_FIXED_HEADERS_EVENT);
       huePubSub.publish(REDRAW_FIXED_HEADERS_EVENT);
-      if (val) {
-        this.getLogs();
-      }
       if (this.parentVm.editorMode()) {
       if (this.parentVm.editorMode()) {
         $.totalStorage('hue.editor.showLogs', val);
         $.totalStorage('hue.editor.showLogs', val);
       }
       }

+ 14 - 10
desktop/core/src/desktop/js/ko/components/DisposableComponent.js

@@ -14,21 +14,25 @@
 // See the License for the specific language governing permissions and
 // See the License for the specific language governing permissions and
 // limitations under the License.
 // limitations under the License.
 
 
+import huePubSub from 'utils/huePubSub';
+
 export default class DisposableComponent {
 export default class DisposableComponent {
   constructor() {
   constructor() {
     this.disposals = [];
     this.disposals = [];
   }
   }
 
 
-  trackKoSub(koSub) {
-    this.disposals.push(() => {
-      koSub.dispose();
-    });
-  }
-
-  trackPubSub(pubSub) {
-    this.disposals.push(() => {
-      pubSub.remove();
-    });
+  subscribe(subscribable, callback) {
+    if (typeof subscribable === 'string') {
+      const pubSub = huePubSub.subscribe(subscribable, callback);
+      this.disposals.push(() => {
+        pubSub.remove();
+      });
+    } else if (subscribable.subscribe) {
+      const sub = subscribable.subscribe(callback);
+      this.disposals.push(() => {
+        sub.dispose();
+      });
+    }
   }
   }
 
 
   addDisposalCallback(callback) {
   addDisposalCallback(callback) {

+ 69 - 112
desktop/libs/notebook/src/notebook/templates/editor_components2.mako

@@ -64,11 +64,11 @@
         $("a[href='#queryBuilderTab']").click();
         $("a[href='#queryBuilderTab']").click();
       });
       });
       function generateQuery() {
       function generateQuery() {
-        var result = QueryBuilder.buildHiveQuery();
-        if (result.status == "fail") {
+        var hiveQuery = QueryBuilder.buildHiveQuery();
+        if (hiveQuery.status == "fail") {
           $("#invalidQueryBuilder${ suffix }").modal("show");
           $("#invalidQueryBuilder${ suffix }").modal("show");
         } else {
         } else {
-          replaceAce(result.query);
+          replaceAce(hiveQuery.query);
         }
         }
       }
       }
 
 
@@ -484,70 +484,6 @@
     <!-- /ko -->
     <!-- /ko -->
   </script>
   </script>
 
 
-  <script type="text/html" id="snippet-log${ suffix }">
-    <div class="snippet-log-container margin-bottom-10" data-bind="visible: showLogs">
-      <div data-bind="delayedOverflow: 'slow', css: resultsKlass" style="margin-top: 5px; position: relative;">
-        <a href="javascript: void(0)" class="inactive-action close-logs-overlay" data-bind="click: function(){ showLogs(false) }">&times;</a>
-        <ul data-bind="visible: jobs().length > 0, foreach: jobs" class="unstyled jobs-overlay">
-          <li data-bind="attr: {'id': $data.name.substr(4)}">
-            % if is_embeddable:
-              <a class="pointer" data-bind="text: $.trim($data.name), click: function() { huePubSub.publish('show.jobs.panel', {id: $data.name, interface: $parent.type() == 'impala' ? 'queries' : 'jobs', compute: $parent.compute}); }, clickBubble: false"></a>
-            % else:
-              <a data-bind="text: $.trim($data.name), hueLink: $data.url"></a>
-            % endif
-            <!-- ko if: typeof percentJob === 'function' && percentJob() > -1 -->
-            <div class="progress-job progress pull-left" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': percentJob() < 100, 'progress-success': percentJob() === 100}">
-              <div class="bar" data-bind="style: {'width': percentJob() + '%'}"></div>
-            </div>
-            <!-- /ko -->
-            <div class="clearfix"></div>
-          </li>
-        </ul>
-        <span data-bind="visible: !$root.isPresentationMode() || !$root.isHidingCode()">
-        <pre data-bind="visible: (!result.logs() || result.logs().length == 0) && jobs().length > 0" class="logs logs-bigger">${ _('No logs available at this moment.') }</pre>
-        <pre data-bind="visible: result.logs() && result.logs().length > 0, text: result.logs, logScroller: result.logs, logScrollerVisibilityEvent: showLogs" class="logs logs-bigger logs-populated"></pre>
-      </span>
-      </div>
-      <div class="snippet-log-resizer" data-bind="
-          visible: result.logs().length > 0,
-          logResizer: {
-            parent: '.snippet-log-container',
-            target: '.logs-populated',
-            mainScrollable: MAIN_SCROLLABLE,
-            onStart: function () { huePubSub.publish('result.grid.hide.fixed.headers') },
-            onResize: function() {
-              huePubSub.publish('result.grid.hide.fixed.headers');
-              window.setTimeout(function () {
-                huePubSub.publish('result.grid.redraw.fixed.headers');
-              }, 500);
-            },
-            minHeight: 50
-          }
-        ">
-        <i class="fa fa-ellipsis-h"></i>
-      </div>
-    </div>
-    <div class="snippet-log-container margin-bottom-10">
-      <div data-bind="visible: ! result.hasResultset() && status() == 'available' && result.fetchedOnce(), css: resultsKlass, click: function(){  }" style="display:none;">
-        <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-check muted"></i> ${ _('Success.') }</pre>
-      </div>
-
-      <div data-bind="visible: result.hasResultset() && status() == 'available' && result.data().length == 0 && result.fetchedOnce() && result.explanation().length <= 0, css: resultsKlass" style="display:none;">
-        <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-check muted"></i> ${ _("Done. 0 results.") }</pre>
-      </div>
-
-      <div data-bind="visible: status() == 'expired', css: resultsKlass" style="display:none;">
-        <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-check muted"></i> ${ _("Results have expired, rerun the query if needed.") }</pre>
-      </div>
-
-      <!-- ko if: status() == 'available' && ! result.fetchedOnce() -->
-      <div data-bind="css: resultsKlass">
-        <pre class="margin-top-10 no-margin-bottom"><i class="fa fa-spin fa-spinner"></i> ${ _('Loading...') }</pre>
-      </div>
-      <!-- /ko -->
-    </div>
-  </script>
-
   <script type="text/html" id="query-tabs${ suffix }">
   <script type="text/html" id="query-tabs${ suffix }">
     <div class="query-history-container" data-bind="onComplete: function(){ redrawFixedHeaders(200); }">
     <div class="query-history-container" data-bind="onComplete: function(){ redrawFixedHeaders(200); }">
       <div data-bind="delayedOverflow: 'slow', css: resultsKlass" style="margin-top: 5px; position: relative;">
       <div data-bind="delayedOverflow: 'slow', css: resultsKlass" style="margin-top: 5px; position: relative;">
@@ -586,14 +522,14 @@
           % endif
           % endif
           <li data-bind="click: function(){ currentQueryTab('queryResults'); }, css: {'active': currentQueryTab() == 'queryResults'}">
           <li data-bind="click: function(){ currentQueryTab('queryResults'); }, css: {'active': currentQueryTab() == 'queryResults'}">
             <a class="inactive-action" style="display:inline-block" href="#queryResults" data-toggle="tab">${_('Results')}
             <a class="inactive-action" style="display:inline-block" href="#queryResults" data-toggle="tab">${_('Results')}
-              <!-- ko if: result.rows() != null  -->
-              (<span data-bind="text: result.rows().toLocaleString() + (type() == 'impala' && result.rows() == 1024 ? '+' : '')" title="${ _('Number of rows') }"></span>)
-              <!-- /ko -->
+##               <!-- ko if: result.rows() != null  -->
+##               (<span data-bind="text: result.rows().toLocaleString() + (type() == 'impala' && result.rows() == 1024 ? '+' : '')" title="${ _('Number of rows') }"></span>)
+##               <!-- /ko -->
             </a>
             </a>
           </li>
           </li>
-          <!-- ko if: result.explanation().length > 0 -->
-          <li data-bind="click: function(){ currentQueryTab('queryExplain'); }, css: {'active': currentQueryTab() == 'queryExplain'}"><a class="inactive-action" href="#queryExplain" data-toggle="tab">${_('Explain')}</a></li>
-          <!-- /ko -->
+##           <!-- ko if: result.explanation().length > 0 -->
+##           <li data-bind="click: function(){ currentQueryTab('queryExplain'); }, css: {'active': currentQueryTab() == 'queryExplain'}"><a class="inactive-action" href="#queryExplain" data-toggle="tab">${_('Explain')}</a></li>
+##           <!-- /ko -->
           <!-- ko foreach: pinnedContextTabs -->
           <!-- ko foreach: pinnedContextTabs -->
           <li data-bind="click: function() { $parent.currentQueryTab(tabId) }, css: { 'active': $parent.currentQueryTab() === tabId }">
           <li data-bind="click: function() { $parent.currentQueryTab(tabId) }, css: { 'active': $parent.currentQueryTab() === tabId }">
             <a class="inactive-action" data-toggle="tab" data-bind="attr: { 'href': '#' + tabId }">
             <a class="inactive-action" data-toggle="tab" data-bind="attr: { 'href': '#' + tabId }">
@@ -746,11 +682,11 @@
             <!-- /ko -->
             <!-- /ko -->
           </div>
           </div>
 
 
-          <!-- ko if: result.explanation().length > 0 -->
-          <div class="tab-pane" id="queryExplain" data-bind="css: {'active': currentQueryTab() == 'queryExplain'}">
-            <!-- ko template: { name: 'snippet-explain${ suffix }' } --><!-- /ko -->
-          </div>
-          <!-- /ko -->
+##           <!-- ko if: result.explanation().length > 0 -->
+##           <div class="tab-pane" id="queryExplain" data-bind="css: {'active': currentQueryTab() == 'queryExplain'}">
+##             <!-- ko template: { name: 'snippet-explain${ suffix }' } --><!-- /ko -->
+##           </div>
+##           <!-- /ko -->
 
 
           <!-- ko if: HAS_WORKLOAD_ANALYTICS && type() === 'impala' -->
           <!-- ko if: HAS_WORKLOAD_ANALYTICS && type() === 'impala' -->
           <div class="tab-pane" id="executionAnalysis" data-bind="css: {'active': currentQueryTab() == 'executionAnalysis'}" style="padding: 10px;">
           <div class="tab-pane" id="executionAnalysis" data-bind="css: {'active': currentQueryTab() == 'executionAnalysis'}" style="padding: 10px;">
@@ -808,7 +744,7 @@
     <div class="hover-actions inline pull-right" style="font-size: 15px;">
     <div class="hover-actions inline pull-right" style="font-size: 15px;">
       <!-- ko template: { name: 'query-redacted${ suffix }' } --><!-- /ko -->
       <!-- ko template: { name: 'query-redacted${ suffix }' } --><!-- /ko -->
       <!-- ko template: { name: 'longer-operation${ suffix }' } --><!-- /ko -->
       <!-- ko template: { name: 'longer-operation${ suffix }' } --><!-- /ko -->
-      <span class="execution-timer" data-bind="visible: type() != 'text' && status() != 'ready' && status() != 'loading', text: result.executionTime().toHHMMSS()" title="${ _('Execution time') }"></span>
+##       <span class="execution-timer" data-bind="visible: type() != 'text' && status() != 'ready' && status() != 'loading', text: result.executionTime().toHHMMSS()" title="${ _('Execution time') }"></span>
 
 
       <!-- ko template: { name: 'snippet-header-database-selection' } --><!-- /ko -->
       <!-- ko template: { name: 'snippet-header-database-selection' } --><!-- /ko -->
 
 
@@ -823,7 +759,7 @@
     <div class="hover-actions inline pull-right" style="font-size: 15px; position: relative;" data-bind="style: { 'marginRight': $root.isPresentationMode() || $root.isResultFullScreenMode() ? '40px' : '0' }">
     <div class="hover-actions inline pull-right" style="font-size: 15px; position: relative;" data-bind="style: { 'marginRight': $root.isPresentationMode() || $root.isResultFullScreenMode() ? '40px' : '0' }">
       <!-- ko template: { name: 'query-redacted${ suffix }' } --><!-- /ko -->
       <!-- ko template: { name: 'query-redacted${ suffix }' } --><!-- /ko -->
       <!-- ko template: { name: 'longer-operation${ suffix }' } --><!-- /ko -->
       <!-- ko template: { name: 'longer-operation${ suffix }' } --><!-- /ko -->
-      <span class="execution-timer" data-bind="visible: type() != 'text' && status() != 'ready' && status() != 'loading', text: result.executionTime().toHHMMSS()" title="${ _('Execution time') }"></span>
+##       <span class="execution-timer" data-bind="visible: type() != 'text' && status() != 'ready' && status() != 'loading', text: result.executionTime().toHHMMSS()" title="${ _('Execution time') }"></span>
 
 
       <!-- ko template: { name: 'snippet-header-database-selection' } --><!-- /ko -->
       <!-- ko template: { name: 'snippet-header-database-selection' } --><!-- /ko -->
 
 
@@ -876,7 +812,13 @@
               <!-- ko template: { if: ['text', 'markdown'].indexOf(type()) == -1 && ! $root.isResultFullScreenMode(), name: 'snippet-execution-controls${ suffix }' } --><!-- /ko -->
               <!-- ko template: { if: ['text', 'markdown'].indexOf(type()) == -1 && ! $root.isResultFullScreenMode(), name: 'snippet-execution-controls${ suffix }' } --><!-- /ko -->
             </div>
             </div>
             <!-- ko if: !$root.isResultFullScreenMode() -->
             <!-- ko if: !$root.isResultFullScreenMode() -->
-            <!-- ko template: 'snippet-log${ suffix }' --><!-- /ko -->
+            <!-- ko component: { name: 'executable-logs', params: {
+              activeExecutable: activeExecutable,
+              showLogs: showLogs,
+              resultsKlass: resultsKlass,
+              isPresentationMode: parentNotebook.isPresentationMode,
+              isHidingCode: parentNotebook.isHidingCode
+            }} --><!-- /ko -->
             <!-- /ko -->
             <!-- /ko -->
             <!-- ko if: $root.editorMode() -->
             <!-- ko if: $root.editorMode() -->
             <!-- ko template: 'query-tabs${ suffix }' --><!-- /ko -->
             <!-- ko template: 'query-tabs${ suffix }' --><!-- /ko -->
@@ -1057,19 +999,35 @@
         <div class="clearfix margin-bottom-20"></div>
         <div class="clearfix margin-bottom-20"></div>
         <!-- /ko -->
         <!-- /ko -->
 
 
-        <div class="ace-editor" data-bind="visible: statementType() === 'text' || statementType() !== 'text' && externalStatementLoaded(), css: {'single-snippet-editor ace-editor-resizable' : $root.editorMode(), 'active-editor': inFocus }, attr: { id: id() }, delayedOverflow: 'slow', aceEditor: {
-        snippet: $data,
-        contextTooltip: '${ _ko("Right-click for details") }',
-        expandStar: '${ _ko("Right-click to expand with columns") }',
-        highlightedRange: result.statement_range,
-        readOnly: $root.isPresentationMode(),
-        aceOptions: {
-          showLineNumbers: $root.editorMode(),
-          showGutter: $root.editorMode(),
-          maxLines: $root.editorMode() ? null : 25,
-          minLines: $root.editorMode() ? null : 3
-        }
-      }, style: {opacity: statementType() !== 'text' || $root.isPresentationMode() ? '0.75' : '1', 'min-height': $root.editorMode() ? '0' : '48px', 'top': $root.editorMode() && statementType() !== 'text' ? '60px' : '0'}"></div>
+        <div class="ace-editor" data-bind="
+            visible: statementType() === 'text' || statementType() !== 'text' && externalStatementLoaded(),
+            css: {
+              'single-snippet-editor ace-editor-resizable' : $root.editorMode(),
+              'active-editor': inFocus
+            },
+            attr: {
+              id: id()
+            },
+            delayedOverflow: 'slow',
+            aceEditor: {
+              snippet: $data,
+              contextTooltip: '${ _ko("Right-click for details") }',
+              expandStar: '${ _ko("Right-click to expand with columns") }',
+##               highlightedRange: result.statement_range,
+              readOnly: $root.isPresentationMode(),
+              aceOptions: {
+                showLineNumbers: $root.editorMode(),
+                showGutter: $root.editorMode(),
+                maxLines: $root.editorMode() ? null : 25,
+                minLines: $root.editorMode() ? null : 3
+              }
+            },
+            style: {
+              'opacity': statementType() !== 'text' || $root.isPresentationMode() ? '0.75' : '1',
+              'min-height': $root.editorMode() ? '0' : '48px',
+              'top': $root.editorMode() && statementType() !== 'text' ? '60px' : '0'
+            }
+          "></div>
         <!-- ko component: { name: 'hueAceAutocompleter', params: { editor: ace.bind($data), snippet: $data } } --><!-- /ko -->
         <!-- ko component: { name: 'hueAceAutocompleter', params: { editor: ace.bind($data), snippet: $data } } --><!-- /ko -->
 
 
         <ul class="table-drop-menu hue-context-menu">
         <ul class="table-drop-menu hue-context-menu">
@@ -1120,11 +1078,11 @@
             <span data-bind="visible: ! $root.isHidingCode()">${ _('Hide the code') }</span>
             <span data-bind="visible: ! $root.isHidingCode()">${ _('Hide the code') }</span>
           </a>
           </a>
         </li>
         </li>
-        <li>
-          <a class="pointer" data-bind="click: function() { $root.selectedNotebook().clearResults() }">
-            <i class="fa fa-fw fa-eraser"></i> ${ _('Clear results') }
-          </a>
-        </li>
+##         <li>
+##           <a class="pointer" data-bind="click: function() { $root.selectedNotebook().clearResults() }">
+##             <i class="fa fa-fw fa-eraser"></i> ${ _('Clear results') }
+##           </a>
+##         </li>
         <li>
         <li>
           <a href="javascript:void(0)" data-bind="click: displayCombinedContent, visible: ! $root.isPresentationMode() ">
           <a href="javascript:void(0)" data-bind="click: displayCombinedContent, visible: ! $root.isPresentationMode() ">
             <i class="fa fa-fw fa-file-text-o"></i> ${ _('Show all content') }
             <i class="fa fa-fw fa-file-text-o"></i> ${ _('Show all content') }
@@ -1175,9 +1133,9 @@
     </div>
     </div>
   </script>
   </script>
 
 
-  <script type="text/html" id="snippet-explain${ suffix }">
-    <pre class="no-margin-bottom" data-bind="text: result.explanation"></pre>
-  </script>
+##   <script type="text/html" id="snippet-explain${ suffix }">
+##     <pre class="no-margin-bottom" data-bind="text: result.explanation"></pre>
+##   </script>
 
 
   <script type="text/html" id="text-snippet-body${ suffix }">
   <script type="text/html" id="text-snippet-body${ suffix }">
     <div data-bind="attr: {'id': 'editor_' + id()}, html: statement_raw, value: statement_raw, medium: {}" data-placeHolder="${ _('Type your text here, select some text to format it') }" class="text-snippet"></div>
     <div data-bind="attr: {'id': 'editor_' + id()}, html: statement_raw, value: statement_raw, medium: {}" data-placeHolder="${ _('Type your text here, select some text to format it') }" class="text-snippet"></div>
@@ -1387,16 +1345,15 @@
         <!-- ko if: status() === 'loading' -->
         <!-- ko if: status() === 'loading' -->
         <i class="fa fa-fw fa-spinner fa-spin"></i> ${ _('Creating session') }
         <i class="fa fa-fw fa-spinner fa-spin"></i> ${ _('Creating session') }
         <!-- /ko -->
         <!-- /ko -->
-        <!-- ko if: status() !== 'loading' && $root.editorMode() && result.statements_count() > 1 -->
-        ${ _('Statement ')} <span data-bind="text: (result.statement_id() + 1) + '/' + result.statements_count()"></span>
-        <div style="display: inline-block"
-             class="label label-info"
-             data-bind="attr: {
-             'title':'${ _ko('Showing results of the statement #')}' + (result.statement_id() + 1)}">
-          <div class="pull-left" data-bind="text: (result.statement_id() + 1)"></div><div class="pull-left">/</div><div class="pull-left" data-bind="text: result.statements_count()"></div>
-        </div>
-
-        <!-- /ko -->
+##         <!-- ko if: status() !== 'loading' && $root.editorMode() && result.statements_count() > 1 -->
+##         ${ _('Statement ')} <span data-bind="text: (result.statement_id() + 1) + '/' + result.statements_count()"></span>
+##         <div style="display: inline-block"
+##              class="label label-info"
+##              data-bind="attr: {
+##              'title':'${ _ko('Showing results of the statement #')}' + (result.statement_id() + 1)}">
+##           <div class="pull-left" data-bind="text: (result.statement_id() + 1)"></div><div class="pull-left">/</div><div class="pull-left" data-bind="text: result.statements_count()"></div>
+##         </div>
+##         <!-- /ko -->
       </div>
       </div>
 
 
 ##       TODO: Move to snippet execution-actions
 ##       TODO: Move to snippet execution-actions