Răsfoiți Sursa

[editor] Split the results grid and charts across two tabs

Johan Ahlen 4 ani în urmă
părinte
comite
ce25259169

+ 0 - 282
desktop/core/src/desktop/js/apps/editor/components/ko.snippetResults.js

@@ -1,282 +0,0 @@
-// 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 * as ko from 'knockout';
-
-import componentUtils from 'ko/components/componentUtils';
-import DisposableComponent from 'ko/components/DisposableComponent';
-import huePubSub from 'utils/huePubSub';
-import I18n from 'utils/i18n';
-
-import { RESULT_CHART_COMPONENT } from 'apps/editor/components/resultChart/ko.resultChart';
-import { RESULT_GRID_COMPONENT } from 'apps/editor/components/resultGrid/ko.resultGrid';
-import { REDRAW_FIXED_HEADERS_EVENT } from 'apps/editor/events';
-import { REDRAW_CHART_EVENT } from 'apps/editor/events';
-import { EXECUTABLE_UPDATED_EVENT, ExecutionStatus } from 'apps/editor/execution/executable';
-import { RESULT_TYPE, RESULT_UPDATED_EVENT } from 'apps/editor/execution/executionResult';
-import { attachTracker } from 'apps/editor/components/executableStateHandler';
-import { defer } from 'utils/hueUtils';
-import { CURRENT_QUERY_TAB_SWITCHED_EVENT } from 'apps/editor/snippet';
-
-export const NAME = 'snippet-results';
-
-// prettier-ignore
-const TEMPLATE = `
-<div class="snippet-tab-actions">
-  <div class="btn-group">
-    <button class="btn btn-editor btn-mini disable-feedback" data-bind="toggle: showGrid, css: { 'active': showGrid }"><i class="fa fa-fw fa-th"></i> ${ I18n('Grid') }</button>
-    <button class="btn btn-editor btn-mini disable-feedback" data-bind="toggle: showChart, css: { 'active': showChart }"><i class="hcha fa-fw hcha-bar-chart"></i> ${ I18n('Chart') }</button>
-  </div>
-  <div class="btn-group pull-right">
-    <button class="btn btn-editor btn-mini disable-feedback" data-bind="toggle: isResultFullScreenMode">
-      <!-- ko if: isResultFullScreenMode -->
-      <i class="fa fa-compress"></i> ${ I18n('Collapse') }
-      <!-- /ko -->
-      <!-- ko ifnot: isResultFullScreenMode -->
-      <i class="fa fa-expand"></i> ${ I18n('Expand') }
-      <!-- /ko -->
-    </button>
-  </div>
-</div>
-
-<div class="result-tab-body">
-  <div data-bind="visible: type() !== 'table'" style="display:none; margin: 10px 0; overflow-y: auto">
-    <!-- ko if: data().length && data()[0][1] != "" -->
-    <pre data-bind="text: data()[0][1]" class="no-margin-bottom"></pre>
-    <!-- /ko -->
-    <!-- ko ifnot: data().length && data()[0][1] != "" -->
-    <pre class="no-margin-bottom"><i class="fa fa-check muted"></i> ${ I18n('Done.') }</pre>
-    <!-- /ko -->
-    <!-- ko if: images().length -->
-    <ul class="unstyled results-images" data-bind="foreach: images">
-      <li>
-        <img data-bind="attr: {'src': 'data:image/png;base64,' + $data}" class="margin-bottom-10"  alt="${ I18n('Result image') }"/>
-      </li>
-    </ul>
-    <!-- /ko -->
-  </div>
-  <div class="table-results" data-bind="visible: type() === 'table', css: { 'table-results-notebook': notebookMode }" style="display: none;">
-    <div class="table-results-inner" data-bind="visible: !executing() && hasData() && showGrid()" style="display: none; position: relative;">
-      <execution-results-ko-bridge class="table-results-bridge" data-bind="vueKoProps: {
-        executableObservable: activeExecutable
-      }"></execution-results-ko-bridge>
-    </div>
-    <div data-bind="visible: !executing() && hasData() && showChart()" style="display: none; position: relative;">
-      <!-- ko component: {
-        name: '${ RESULT_CHART_COMPONENT }',
-        params: {
-          activeExecutable: activeExecutable,
-          cleanedMeta: cleanedMeta,
-          cleanedDateTimeMeta: cleanedDateTimeMeta,
-          cleanedNumericMeta: cleanedNumericMeta,
-          cleanedStringMeta: cleanedStringMeta,
-          data: data,
-          id: id,
-          meta: meta,
-          showChart: showChart
-        }
-      } --><!-- /ko -->
-    </div>
-    <div data-bind="visible: !executing() && !hasData() && streaming()" style="display: none;">
-      <h1 class="empty">${ I18n('Waiting for streaming data...') }</h1>
-    </div>
-    <div data-bind="visible: !executing() && !hasData() && !hasResultSet() && status() === 'available' && fetchedOnce()" style="display: none;">
-      <h1 class="empty">${ I18n('Success.') }</h1>
-    </div>
-    <div data-bind="visible: !executing() && !hasData() && hasResultSet() && status() === 'available' && fetchedOnce()" style="display: none;">
-      <h1 class="empty">${ I18n('Empty result.') }</h1>
-    </div>
-    <div data-bind="visible: !executing() && !hasData() && status() === 'expired'" style="display: none;">
-      <h1 class="empty">${ I18n('Results have expired, rerun the query if needed.') }</h1>
-    </div>
-    <div data-bind="visible: executing" style="display: none;">
-      <h1 class="empty"><i class="fa fa-spinner fa-spin"></i> ${ I18n('Executing...') }</h1>
-    </div>
-    <ul id="wsResult">
-    </ul>
-  </div>
-</div>
-`;
-
-class SnippetResults extends DisposableComponent {
-  constructor(params, element) {
-    super();
-    this.element = element;
-
-    this.activeExecutable = params.activeExecutable;
-
-    this.editorMode = params.editorMode;
-    this.isPresentationMode = params.isPresentationMode;
-    this.isResultFullScreenMode = params.isResultFullScreenMode;
-    this.resultsKlass = params.resultsKlass;
-    this.id = params.id; // TODO: Get rid of
-
-    this.status = ko.observable();
-    this.type = ko.observable(RESULT_TYPE.TABLE);
-    this.meta = ko.observableArray();
-    this.streaming = ko.observable();
-    this.data = ko.observableArray();
-    this.lastFetchedRows = ko.observableArray();
-    this.images = ko.observableArray();
-    this.hasMore = ko.observable();
-    this.hasResultSet = ko.observable();
-    this.fetchedOnce = ko.observable(false);
-
-    this.subscribe(CURRENT_QUERY_TAB_SWITCHED_EVENT, queryTab => {
-      if (queryTab === 'queryResults') {
-        defer(() => {
-          huePubSub.publish(REDRAW_FIXED_HEADERS_EVENT);
-        });
-      }
-    });
-
-    this.executing = ko.pureComputed(() => this.status() === ExecutionStatus.running);
-
-    this.hasData = ko.pureComputed(() => this.data().length);
-
-    this.notebookMode = ko.pureComputed(() => !this.editorMode() || this.isPresentationMode());
-
-    this.visible = ko.pureComputed(
-      () => !this.notebookMode() || this.executing() || this.hasResultSet()
-    );
-
-    const trackedObservables = {
-      showGrid: true,
-      showChart: false
-    };
-
-    this.showGrid = ko.observable(trackedObservables.showGrid);
-    this.showChart = ko.observable(trackedObservables.showChart);
-
-    attachTracker(this.activeExecutable, NAME, this, trackedObservables);
-
-    this.cleanedMeta = ko.observableArray();
-    this.cleanedDateTimeMeta = ko.observableArray();
-    this.cleanedStringMeta = ko.observableArray();
-    this.cleanedNumericMeta = ko.observableArray();
-
-    this.subscribe(this.showChart, val => {
-      if (val) {
-        this.showGrid(false);
-        huePubSub.publish(REDRAW_CHART_EVENT);
-        huePubSub.publish('editor.chart.shown', this);
-      }
-    });
-
-    this.subscribe(this.showGrid, val => {
-      if (val) {
-        this.showChart(false);
-        huePubSub.publish('editor.grid.shown', this);
-        defer(() => huePubSub.publish(REDRAW_FIXED_HEADERS_EVENT));
-        huePubSub.publish('table.extender.redraw');
-      }
-    });
-
-    this.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
-      if (this.activeExecutable() === executable) {
-        this.updateFromExecutable(executable);
-      }
-    });
-
-    let lastRenderedResult = undefined;
-    const handleResultChange = () => {
-      if (this.activeExecutable() && this.activeExecutable().result) {
-        const refresh = lastRenderedResult !== this.activeExecutable().result;
-        this.updateFromExecutionResult(this.activeExecutable().result, refresh);
-        lastRenderedResult = this.activeExecutable().result;
-      } else {
-        this.resetResultData();
-      }
-    };
-
-    this.subscribe(RESULT_UPDATED_EVENT, executionResult => {
-      if (this.activeExecutable() === executionResult.executable) {
-        handleResultChange();
-      }
-    });
-
-    this.subscribe(this.activeExecutable, handleResultChange);
-  }
-
-  resetResultData() {
-    this.images([]);
-    this.lastFetchedRows([]);
-    this.data([]);
-    this.meta([]);
-    this.streaming(false);
-    this.cleanedMeta([]);
-    this.cleanedDateTimeMeta([]);
-    this.cleanedNumericMeta([]);
-    this.cleanedStringMeta([]);
-    this.hasMore(false);
-    this.type(RESULT_TYPE.TABLE);
-    // eslint-disable-next-line no-undef
-    $('#wsResult').empty();
-  }
-
-  updateFromExecutionResult(executionResult, refresh) {
-    if (refresh) {
-      this.resetResultData();
-    }
-
-    if (executionResult) {
-      this.fetchedOnce(executionResult.fetchedOnce);
-      this.hasMore(executionResult.hasMore);
-      this.type(executionResult.type);
-      this.streaming(executionResult.streaming);
-
-      if (!this.meta().length && executionResult.meta.length) {
-        this.meta(executionResult.koEnrichedMeta);
-        this.cleanedMeta(executionResult.cleanedMeta);
-        this.cleanedDateTimeMeta(executionResult.cleanedDateTimeMeta);
-        this.cleanedStringMeta(executionResult.cleanedStringMeta);
-        this.cleanedNumericMeta(executionResult.cleanedNumericMeta);
-      }
-
-      if (refresh) {
-        this.data(executionResult.rows.concat());
-      } else if (
-        executionResult.lastRows.length &&
-        this.data().length !== executionResult.rows.length
-      ) {
-        this.data.push(...executionResult.lastRows);
-      }
-      this.lastFetchedRows(executionResult.lastRows);
-    }
-  }
-
-  updateFromExecutable(executable) {
-    this.status(executable.status);
-    this.hasResultSet(executable.handle && executable.handle.has_result_set);
-    if (!this.hasResultSet) {
-      this.resetResultData();
-    }
-  }
-
-  fetchResult() {
-    if (this.activeExecutable() && this.activeExecutable().result) {
-      this.activeExecutable().result.fetchRows({ rows: 100 });
-    }
-  }
-}
-
-componentUtils.registerComponent(
-  NAME,
-  {
-    createViewModel: (params, componentInfo) => new SnippetResults(params, componentInfo.element)
-  },
-  TEMPLATE
-);

+ 508 - 356
desktop/core/src/desktop/js/apps/editor/components/resultChart/ko.resultChart.js

@@ -15,11 +15,8 @@
 // limitations under the License.
 
 import * as ko from 'knockout';
+import $ from 'jquery';
 
-import componentUtils from 'ko/components/componentUtils';
-import DisposableComponent from 'ko/components/DisposableComponent';
-import hueAnalytics from 'utils/hueAnalytics';
-import I18n from 'utils/i18n';
 import {
   leafletMapChartTransformer,
   mapChartTransformer,
@@ -28,12 +25,18 @@ import {
   scatterChartTransformer,
   timelineChartTransformer
 } from './chartTransformers';
-import $ from 'jquery';
-import { UUID } from 'utils/hueUtils';
-import { REDRAW_CHART_EVENT } from 'apps/editor/events';
 import { attachTracker } from 'apps/editor/components/executableStateHandler';
+import { REDRAW_CHART_EVENT } from 'apps/editor/events';
+import { EXECUTABLE_UPDATED_EVENT, ExecutionStatus } from 'apps/editor/execution/executable';
+import { RESULT_TYPE, RESULT_UPDATED_EVENT } from 'apps/editor/execution/executionResult';
+import { CURRENT_QUERY_TAB_SWITCHED_EVENT } from 'apps/editor/snippet';
+import componentUtils from 'ko/components/componentUtils';
+import DisposableComponent from 'ko/components/DisposableComponent';
+import hueAnalytics from 'utils/hueAnalytics';
+import { defer, UUID } from 'utils/hueUtils';
+import I18n from 'utils/i18n';
 
-export const RESULT_CHART_COMPONENT = 'result-chart';
+export const RESULT_CHART_COMPONENT = 'snippet-result-chart';
 
 const TYPES = window.HUE_CHARTS.TYPES;
 
@@ -70,7 +73,7 @@ export const CHART_TIMELINE_TYPE = {
 
 // prettier-ignore
 const TEMPLATE = `
-<div class="snippet-tab-actions-append">
+<div class="snippet-tab-actions">
   <div class="btn-group">
     <button class="btn btn-mini btn-editor dropdown-toggle" data-toggle="dropdown">
       <!-- ko if: isBarChart -->
@@ -131,371 +134,460 @@ const TEMPLATE = `
       <i class="fa fa-cog"></i> ${ I18n('Settings') }
     </button>
   </div>
-</div>
-
-<div class="split-result-container">
-  <div class="result-settings-panel" style="display: none;" data-bind="visible: chartSettingsVisible">
-    <div>
-      <!-- ko if: chartType -->
-      <!-- ko if: isTimelineChart() || isBarChart() -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('type') }</li>
-      </ul>
-      <div>
-        <select data-bind="
-            selectedOptions: chartTimelineType,
-            optionsCaption: '${ I18n('Choose a type...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n("Choose a type...") }',
-              update: chartTimelineType,
-              dropdownAutoWidth: true
-            }
-          ">
-          <option value="${ CHART_TIMELINE_TYPE.BAR }">${ I18n("Bars") }</option>
-          <option value="${ CHART_TIMELINE_TYPE.LINE }">${ I18n("Lines") }</option>
-        </select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko if: isPieChart -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('value') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: cleanedNumericMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n("Choose a column...") }',
-              update: chartYSingle,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('legend') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: cleanedMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n("Choose a column...") }',
-              update: chartX,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko ifnot: isPieChart -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li data-bind="visible: !isMapChart() && !isGradientMapChart()" class="nav-header">${ I18n('x-axis') }</li>
-        <li data-bind="visible: isGradientMapChart" class="nav-header">${ I18n('region') }</li>
-        <li data-bind="visible: isMapChart" class="nav-header">${ I18n('latitude') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: chartMetaOptions,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n("Choose a column...") }',
-              update: chartX,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li data-bind="visible: !isMapChart() && !isGradientMapChart()" class="nav-header">${ I18n('y-axis') }</li>
-        <li data-bind="visible: isGradientMapChart" class="nav-header">${ I18n('value') }</li>
-        <li data-bind="visible: isMapChart" class="nav-header">${ I18n('longitude') }</li>
-      </ul>
-
-      <div style="max-height: 220px" data-bind="
-          delayedOverflow,
-          visible: ((isBarChart() && !chartXPivot()) || isLineChart() || isTimelineChart())
-        ">
-        <ul class="unstyled" data-bind="foreach: cleanedNumericMeta" style="margin-bottom: 0">
-          <li><label class="checkbox"><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartYMulti" /> <span data-bind="text: $data.name"></span></label></li>
-        </ul>
-      </div>
-      <div class="input-medium" data-bind="visible: (isBarChart() && chartXPivot()) || isMapChart() || isGradientMapChart() || isScatterChart()">
-        <select data-bind="
-            options: isGradientMapChart() ? cleanedMeta : cleanedNumericMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n("Choose a column...") }',
-              update: chartYSingle,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-      <!-- /ko -->
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: isBarChart">
-        <li class="nav-header">${ I18n('group') }</li>
-      </ul>
-      <div data-bind="visible: isBarChart">
-        <select class="input-medium" data-bind="
-            options: cleanedMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column to pivot...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n("Choose a column to pivot...") }',
-              update: chartXPivot,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('limit') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-          options: chartLimits,
-          optionsCaption: '${ I18n('Limit the number of results to...') }',
-          select2: {
-            width: '100%',
-            placeholder: '${ I18n('Limit the number of results to...') }',
-            update: chartLimit,
-            dropdownAutoWidth: true
-          }
-        "></select>
-      </div>
-
-      <!-- ko if: isMapChart -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('type') }</li>
-      </ul>
-      <div>
-        <select data-bind="
-            selectedOptions: chartMapType,
-            optionsCaption: '${ I18n('Choose a type...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n('Choose a type...') }',
-              update: chartMapType,
-              dropdownAutoWidth: true
-            }
-          ">
-          <option value="${ CHART_MAP_TYPE.MARKER }">${ I18n("Markers") }</option>
-          <option value="${ CHART_MAP_TYPE.HEAT }">${ I18n("Heatmap") }</option>
-        </select>
-      </div>
-
-      <!-- ko if: chartMapType() === '${ CHART_MAP_TYPE.MARKER }' -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('label') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: cleanedMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n('Choose a column...') }',
-              update: chartMapLabel,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko if: chartMapType() === '${ CHART_MAP_TYPE.HEAT }' -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('intensity') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: cleanedNumericMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n('Choose a column...') }',
-              update: chartMapHeat,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-      <!-- /ko -->
+  <div class="btn-group pull-right">
+    <button class="btn btn-editor btn-mini disable-feedback" data-bind="toggle: isResultFullScreenMode">
+      <!-- ko if: isResultFullScreenMode -->
+      <i class="fa fa-compress"></i> ${ I18n('Collapse') }
       <!-- /ko -->
-
-      <!-- ko if: isScatterChart -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('scatter size') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: cleanedNumericMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n('Choose a column...') }',
-              update: chartScatterSize,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('scatter group') }</li>
-      </ul>
-      <div>
-        <select class="input-medium" data-bind="
-            options: cleanedMeta,
-            optionsText: 'name',
-            optionsValue: 'name',
-            optionsCaption: '${ I18n('Choose a column...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n('Choose a column...') }',
-              update: chartScatterGroup,
-              dropdownAutoWidth: true
-            }
-          "></select>
-      </div>
+      <!-- ko ifnot: isResultFullScreenMode -->
+      <i class="fa fa-expand"></i> ${ I18n('Expand') }
       <!-- /ko -->
+    </button>
+  </div>
+</div>
 
-      <!-- ko if: isGradientMapChart -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('scope') }</li>
-      </ul>
-      <div data-bind="visible: chartType() != ''">
-        <select data-bind="
-            selectedOptions: chartScope,
-            optionsCaption: '${ I18n('Choose a scope...') }',
-            select2: {
-              width: '100%',
-              placeholder: '${ I18n('Choose a scope...') }',
-              update: chartScope,
-              dropdownAutoWidth: true
+<div class="result-tab-body">
+  <div class="table-results" data-bind="visible: type() === 'table', css: { 'table-results-notebook': notebookMode }" style="display: none;">
+    <div class="chart-container" data-bind="visible: !executing() && hasData()" style="display: none; position: relative;">
+      <div class="split-result-container">
+        <div class="result-settings-panel" style="display: none;" data-bind="visible: chartSettingsVisible">
+          <div>
+            <!-- ko if: chartType -->
+            <!-- ko if: isTimelineChart() || isBarChart() -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('type') }</li>
+            </ul>
+            <div>
+              <select data-bind="
+                  selectedOptions: chartTimelineType,
+                  optionsCaption: '${ I18n('Choose a type...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n("Choose a type...") }',
+                    update: chartTimelineType,
+                    dropdownAutoWidth: true
+                  }
+                ">
+                <option value="${ CHART_TIMELINE_TYPE.BAR }">${ I18n("Bars") }</option>
+                <option value="${ CHART_TIMELINE_TYPE.LINE }">${ I18n("Lines") }</option>
+              </select>
+            </div>
+            <!-- /ko -->
+      
+            <!-- ko if: isPieChart -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('value') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: cleanedNumericMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n("Choose a column...") }',
+                    update: chartYSingle,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('legend') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: cleanedMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n("Choose a column...") }',
+                    update: chartX,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+            <!-- /ko -->
+      
+            <!-- ko ifnot: isPieChart -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li data-bind="visible: !isMapChart() && !isGradientMapChart()" class="nav-header">${ I18n('x-axis') }</li>
+              <li data-bind="visible: isGradientMapChart" class="nav-header">${ I18n('region') }</li>
+              <li data-bind="visible: isMapChart" class="nav-header">${ I18n('latitude') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: chartMetaOptions,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n("Choose a column...") }',
+                    update: chartX,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+      
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li data-bind="visible: !isMapChart() && !isGradientMapChart()" class="nav-header">${ I18n('y-axis') }</li>
+              <li data-bind="visible: isGradientMapChart" class="nav-header">${ I18n('value') }</li>
+              <li data-bind="visible: isMapChart" class="nav-header">${ I18n('longitude') }</li>
+            </ul>
+      
+            <div style="max-height: 220px" data-bind="
+                delayedOverflow,
+                visible: ((isBarChart() && !chartXPivot()) || isLineChart() || isTimelineChart())
+              ">
+              <ul class="unstyled" data-bind="foreach: cleanedNumericMeta" style="margin-bottom: 0">
+                <li><label class="checkbox"><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartYMulti" /> <span data-bind="text: $data.name"></span></label></li>
+              </ul>
+            </div>
+            <div class="input-medium" data-bind="visible: (isBarChart() && chartXPivot()) || isMapChart() || isGradientMapChart() || isScatterChart()">
+              <select data-bind="
+                  options: isGradientMapChart() ? cleanedMeta : cleanedNumericMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n("Choose a column...") }',
+                    update: chartYSingle,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+            <!-- /ko -->
+      
+            <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: isBarChart">
+              <li class="nav-header">${ I18n('group') }</li>
+            </ul>
+            <div data-bind="visible: isBarChart">
+              <select class="input-medium" data-bind="
+                  options: cleanedMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column to pivot...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n("Choose a column to pivot...") }',
+                    update: chartXPivot,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+      
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('limit') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                options: chartLimits,
+                optionsCaption: '${ I18n('Limit the number of results to...') }',
+                select2: {
+                  width: '100%',
+                  placeholder: '${ I18n('Limit the number of results to...') }',
+                  update: chartLimit,
+                  dropdownAutoWidth: true
+                }
+              "></select>
+            </div>
+      
+            <!-- ko if: isMapChart -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('type') }</li>
+            </ul>
+            <div>
+              <select data-bind="
+                  selectedOptions: chartMapType,
+                  optionsCaption: '${ I18n('Choose a type...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n('Choose a type...') }',
+                    update: chartMapType,
+                    dropdownAutoWidth: true
+                  }
+                ">
+                <option value="${ CHART_MAP_TYPE.MARKER }">${ I18n("Markers") }</option>
+                <option value="${ CHART_MAP_TYPE.HEAT }">${ I18n("Heatmap") }</option>
+              </select>
+            </div>
+      
+            <!-- ko if: chartMapType() === '${ CHART_MAP_TYPE.MARKER }' -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('label') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: cleanedMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n('Choose a column...') }',
+                    update: chartMapLabel,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+            <!-- /ko -->
+      
+            <!-- ko if: chartMapType() === '${ CHART_MAP_TYPE.HEAT }' -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('intensity') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: cleanedNumericMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n('Choose a column...') }',
+                    update: chartMapHeat,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+            <!-- /ko -->
+            <!-- /ko -->
+      
+            <!-- ko if: isScatterChart -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('scatter size') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: cleanedNumericMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n('Choose a column...') }',
+                    update: chartScatterSize,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+      
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('scatter group') }</li>
+            </ul>
+            <div>
+              <select class="input-medium" data-bind="
+                  options: cleanedMeta,
+                  optionsText: 'name',
+                  optionsValue: 'name',
+                  optionsCaption: '${ I18n('Choose a column...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n('Choose a column...') }',
+                    update: chartScatterGroup,
+                    dropdownAutoWidth: true
+                  }
+                "></select>
+            </div>
+            <!-- /ko -->
+      
+            <!-- ko if: isGradientMapChart -->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('scope') }</li>
+            </ul>
+            <div data-bind="visible: chartType() != ''">
+              <select data-bind="
+                  selectedOptions: chartScope,
+                  optionsCaption: '${ I18n('Choose a scope...') }',
+                  select2: {
+                    width: '100%',
+                    placeholder: '${ I18n('Choose a scope...') }',
+                    update: chartScope,
+                    dropdownAutoWidth: true
+                  }
+                ">
+                <option value="${ CHART_SCOPE.WORLD }">${ I18n("World") }</option>
+                <option value="${ CHART_SCOPE.EUROPE }">${ I18n("Europe") }</option>
+                <option value="${ CHART_SCOPE.AUS }">${ I18n("Australia") }</option>
+                <option value="${ CHART_SCOPE.BRA }">${ I18n("Brazil") }</option>
+                <option value="${ CHART_SCOPE.CAN }">${ I18n("Canada") }</option>
+                <option value="${ CHART_SCOPE.CHN }">${ I18n("China") }</option>
+                <option value="${ CHART_SCOPE.FRA }">${ I18n("France") }</option>
+                <option value="${ CHART_SCOPE.DEU }">${ I18n("Germany") }</option>
+                <option value="${ CHART_SCOPE.ITA }">${ I18n("Italy") }</option>
+                <option value="${ CHART_SCOPE.JPN }">${ I18n("Japan") }</option>
+                <option value="${ CHART_SCOPE.GBR }">${ I18n("UK") }</option>
+                <option value="${ CHART_SCOPE.USA }">${ I18n("USA") }</option>
+              </select>
+            </div>
+            <!-- /ko -->
+      
+            <!-- ko ifnot: isMapChart() || isGradientMapChart() || isScatterChart()-->
+            <ul class="nav nav-list" style="border: none; background-color: #FFF">
+              <li class="nav-header">${ I18n('sorting') }</li>
+            </ul>
+            <div class="btn-group" data-toggle="buttons-radio">
+              <a rel="tooltip" data-placement="top" title="${ I18n('No sorting') }" href="javascript:void(0)" class="btn" data-bind="
+                  css: { 'active': chartSorting() === '${ CHART_SORTING.NONE }' },
+                  click: function() { chartSorting('${ CHART_SORTING.NONE }'); }
+                "><i class="fa fa-align-left fa-rotate-270"></i></a>
+              <a rel="tooltip" data-placement="top" title="${ I18n('Sort ascending') }" href="javascript:void(0)" class="btn" data-bind="
+                  css: { 'active': chartSorting() == '${ CHART_SORTING.ASC }' },
+                  click: function() { chartSorting('${ CHART_SORTING.ASC }'); }
+                "><i class="fa fa-sort-amount-asc fa-rotate-270"></i></a>
+              <a rel="tooltip" data-placement="top" title="${ I18n('Sort descending') }" href="javascript:void(0)" class="btn" data-bind="
+                  css: { 'active': chartSorting() == '${ CHART_SORTING.DESC }' },
+                  click: function(){ chartSorting('${ CHART_SORTING.DESC }'); }
+                "><i class="fa fa-sort-amount-desc fa-rotate-270"></i></a>
+            </div>
+            <!-- /ko -->
+            <!-- /ko -->
+          </div>
+        </div>
+      
+        <div class="split-result-resizer" style="display: none;" data-bind="
+            visible: chartSettingsVisible,
+            splitFlexDraggable : {
+              containerSelector: '.split-result-container',
+              sidePanelSelector: '.result-settings-panel',
+              sidePanelVisible: chartSettingsVisible,
+              orientation: 'left',
+              appName: 'result_chart',
+              onPosition: function() {  }
             }
-          ">
-          <option value="${ CHART_SCOPE.WORLD }">${ I18n("World") }</option>
-          <option value="${ CHART_SCOPE.EUROPE }">${ I18n("Europe") }</option>
-          <option value="${ CHART_SCOPE.AUS }">${ I18n("Australia") }</option>
-          <option value="${ CHART_SCOPE.BRA }">${ I18n("Brazil") }</option>
-          <option value="${ CHART_SCOPE.CAN }">${ I18n("Canada") }</option>
-          <option value="${ CHART_SCOPE.CHN }">${ I18n("China") }</option>
-          <option value="${ CHART_SCOPE.FRA }">${ I18n("France") }</option>
-          <option value="${ CHART_SCOPE.DEU }">${ I18n("Germany") }</option>
-          <option value="${ CHART_SCOPE.ITA }">${ I18n("Italy") }</option>
-          <option value="${ CHART_SCOPE.JPN }">${ I18n("Japan") }</option>
-          <option value="${ CHART_SCOPE.GBR }">${ I18n("UK") }</option>
-          <option value="${ CHART_SCOPE.USA }">${ I18n("USA") }</option>
-        </select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko ifnot: isMapChart() || isGradientMapChart() || isScatterChart()-->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${ I18n('sorting') }</li>
-      </ul>
-      <div class="btn-group" data-toggle="buttons-radio">
-        <a rel="tooltip" data-placement="top" title="${ I18n('No sorting') }" href="javascript:void(0)" class="btn" data-bind="
-            css: { 'active': chartSorting() === '${ CHART_SORTING.NONE }' },
-            click: function() { chartSorting('${ CHART_SORTING.NONE }'); }
-          "><i class="fa fa-align-left fa-rotate-270"></i></a>
-        <a rel="tooltip" data-placement="top" title="${ I18n('Sort ascending') }" href="javascript:void(0)" class="btn" data-bind="
-            css: { 'active': chartSorting() == '${ CHART_SORTING.ASC }' },
-            click: function() { chartSorting('${ CHART_SORTING.ASC }'); }
-          "><i class="fa fa-sort-amount-asc fa-rotate-270"></i></a>
-        <a rel="tooltip" data-placement="top" title="${ I18n('Sort descending') }" href="javascript:void(0)" class="btn" data-bind="
-            css: { 'active': chartSorting() == '${ CHART_SORTING.DESC }' },
-            click: function(){ chartSorting('${ CHART_SORTING.DESC }'); }
-          "><i class="fa fa-sort-amount-desc fa-rotate-270"></i></a>
+          "><div class="resize-bar"></div>
+        </div>
+      
+        <div class="split-result-content chart-container">
+          <h1 class="empty" data-bind="visible: !hasDataForChart()" style="display:none">${ I18n('Select the chart parameters on the left') }</h1>
+      
+          <div data-bind="visible: hasDataForChart" style="display:none">
+            <!-- ko if: isPieChart -->
+            <div class="chart" data-bind="attr: { 'id': chartId }, pieChart: pieChartParams()"></div>
+            <!-- /ko -->
+      
+            <!-- ko if: isBarChart -->
+            <div class="chart" data-bind="attr: { 'id': chartId }, barChart: barChartParams()"></div>
+            <!-- /ko -->
+      
+            <!-- ko if: isLineChart -->
+            <div class="chart" data-bind="attr: { 'id': chartId }, lineChart: lineChartParams()"></div>
+            <!-- /ko -->
+      
+            <!-- ko if: isTimelineChart -->
+            <div class="chart" data-bind="attr:{ 'id': chartId }, timelineChart: timeLineChartParams()"></div>
+            <!-- /ko -->
+      
+            <!-- ko if: isMapChart -->
+            <div class="chart" data-bind="attr:{ 'id': chartId }, leafletMapChart: leafletMapChartParams()"></div>
+            <!-- /ko -->
+      
+            <!-- ko if: isGradientMapChart -->
+            <div class="chart" data-bind="attr:{ 'id': chartId }, mapChart: mapChartParams()"></div>
+            <!-- /ko -->
+      
+            <!-- ko if: isScatterChart -->
+            <div class="chart" data-bind="attr:{ 'id': chartId }, scatterChart: scatterChartParams()"></div>
+            <!-- /ko -->
+          </div>
+        </div>
       </div>
-      <!-- /ko -->
-      <!-- /ko -->
     </div>
+    <div data-bind="visible: !executing() && !hasData() && streaming()" style="display: none;">
+      <h1 class="empty">${ I18n('Waiting for streaming data...') }</h1>
+    </div>
+    <div data-bind="visible: !executing() && !hasData() && !hasResultSet() && status() === 'available' && fetchedOnce()" style="display: none;">
+      <h1 class="empty">${ I18n('Success.') }</h1>
+    </div>
+    <div data-bind="visible: !executing() && !hasData() && hasResultSet() && status() === 'available' && fetchedOnce()" style="display: none;">
+      <h1 class="empty">${ I18n('Empty result.') }</h1>
+    </div>
+    <div data-bind="visible: !executing() && !hasData() && status() === 'expired'" style="display: none;">
+      <h1 class="empty">${ I18n('Results have expired, rerun the query if needed.') }</h1>
+    </div>
+    <div data-bind="visible: executing" style="display: none;">
+      <h1 class="empty"><i class="fa fa-spinner fa-spin"></i> ${ I18n('Executing...') }</h1>
+    </div>
+    <ul id="wsResult">
+    </ul>
   </div>
+</div>
+`;
 
-  <div class="split-result-resizer" style="display: none;" data-bind="
-      visible: chartSettingsVisible,
-      splitFlexDraggable : {
-        containerSelector: '.split-result-container',
-        sidePanelSelector: '.result-settings-panel',
-        sidePanelVisible: chartSettingsVisible,
-        orientation: 'left',
-        appName: 'result_chart',
-        onPosition: function() {  }
-      }
-    "><div class="resize-bar"></div>
-  </div>
+class ResultChart extends DisposableComponent {
+  constructor(params) {
+    super();
 
-  <div class="split-result-content chart-container">
-    <h1 class="empty" data-bind="visible: !hasDataForChart()" style="display:none">${ I18n('Select the chart parameters on the left') }</h1>
+    this.activeExecutable = params.activeExecutable;
 
-    <div data-bind="visible: hasDataForChart" style="display:none">
-      <!-- ko if: isPieChart -->
-      <div class="chart" data-bind="attr: { 'id': chartId }, pieChart: pieChartParams()"></div>
-      <!-- /ko -->
+    this.editorMode = params.editorMode;
+    this.isPresentationMode = params.isPresentationMode;
+    this.isResultFullScreenMode = params.isResultFullScreenMode;
+    this.resultsKlass = params.resultsKlass;
+    this.id = params.id; // TODO: Get rid of
+
+    this.status = ko.observable();
+    this.type = ko.observable(RESULT_TYPE.TABLE);
+    this.meta = ko.observableArray();
+    this.streaming = ko.observable();
+    this.data = ko.observableArray();
+    this.lastFetchedRows = ko.observableArray();
+    this.images = ko.observableArray();
+    this.hasMore = ko.observable();
+    this.hasResultSet = ko.observable();
+    this.fetchedOnce = ko.observable(false);
+
+    this.subscribe(CURRENT_QUERY_TAB_SWITCHED_EVENT, queryTab => {
+      if (queryTab === 'queryChart') {
+        defer(() => {
+          this.redrawChart();
+        });
+      }
+    });
 
-      <!-- ko if: isBarChart -->
-      <div class="chart" data-bind="attr: { 'id': chartId }, barChart: barChartParams()"></div>
-      <!-- /ko -->
+    this.executing = ko.pureComputed(() => this.status() === ExecutionStatus.running);
 
-      <!-- ko if: isLineChart -->
-      <div class="chart" data-bind="attr: { 'id': chartId }, lineChart: lineChartParams()"></div>
-      <!-- /ko -->
+    this.hasData = ko.pureComputed(() => this.data().length);
 
-      <!-- ko if: isTimelineChart -->
-      <div class="chart" data-bind="attr:{ 'id': chartId }, timelineChart: timeLineChartParams()"></div>
-      <!-- /ko -->
+    this.notebookMode = ko.pureComputed(() => !this.editorMode() || this.isPresentationMode());
 
-      <!-- ko if: isMapChart -->
-      <div class="chart" data-bind="attr:{ 'id': chartId }, leafletMapChart: leafletMapChartParams()"></div>
-      <!-- /ko -->
+    this.visible = ko.pureComputed(
+      () => !this.notebookMode() || this.executing() || this.hasResultSet()
+    );
 
-      <!-- ko if: isGradientMapChart -->
-      <div class="chart" data-bind="attr:{ 'id': chartId }, mapChart: mapChartParams()"></div>
-      <!-- /ko -->
+    this.cleanedMeta = ko.observableArray();
+    this.cleanedDateTimeMeta = ko.observableArray();
+    this.cleanedStringMeta = ko.observableArray();
+    this.cleanedNumericMeta = ko.observableArray();
 
-      <!-- ko if: isScatterChart -->
-      <div class="chart" data-bind="attr:{ 'id': chartId }, scatterChart: scatterChartParams()"></div>
-      <!-- /ko -->
-    </div>
-  </div>
-</div>
-`;
+    this.subscribe(EXECUTABLE_UPDATED_EVENT, executable => {
+      if (this.activeExecutable() === executable) {
+        this.updateFromExecutable(executable);
+      }
+    });
 
-class ResultChart extends DisposableComponent {
-  constructor(params) {
-    super();
+    let lastRenderedResult = undefined;
+    const handleResultChange = () => {
+      if (this.activeExecutable() && this.activeExecutable().result) {
+        const refresh = lastRenderedResult !== this.activeExecutable().result;
+        this.updateFromExecutionResult(this.activeExecutable().result, refresh);
+        lastRenderedResult = this.activeExecutable().result;
+      } else {
+        this.resetResultData();
+      }
+    };
 
-    this.data = params.data;
-    this.id = params.id;
-    this.activeExecutable = params.activeExecutable;
+    this.subscribe(RESULT_UPDATED_EVENT, executionResult => {
+      if (this.activeExecutable() === executionResult.executable) {
+        handleResultChange();
+      }
+    });
 
-    this.meta = params.meta;
-    this.cleanedMeta = params.cleanedMeta;
-    this.cleanedDateTimeMeta = params.cleanedDateTimeMeta;
-    this.cleanedNumericMeta = params.cleanedNumericMeta;
-    this.cleanedStringMeta = params.cleanedNumericMeta;
-    this.showChart = params.showChart;
+    this.subscribe(this.activeExecutable, handleResultChange);
 
     const trackedObservables = {
       chartLimit: undefined,
@@ -579,7 +671,6 @@ class ResultChart extends DisposableComponent {
       }
     });
 
-    this.subscribe(this.showChart, this.prepopulateChart.bind(this));
     this.subscribe(this.chartType, this.prepopulateChart.bind(this));
     this.subscribe(this.chartXPivot, this.prepopulateChart.bind(this));
 
@@ -683,6 +774,67 @@ class ResultChart extends DisposableComponent {
     });
   }
 
+  resetResultData() {
+    this.images([]);
+    this.lastFetchedRows([]);
+    this.data([]);
+    this.meta([]);
+    this.streaming(false);
+    this.cleanedMeta([]);
+    this.cleanedDateTimeMeta([]);
+    this.cleanedNumericMeta([]);
+    this.cleanedStringMeta([]);
+    this.hasMore(false);
+    this.type(RESULT_TYPE.TABLE);
+    // eslint-disable-next-line no-undef
+    $('#wsResult').empty();
+  }
+
+  updateFromExecutionResult(executionResult, refresh) {
+    if (refresh) {
+      this.resetResultData();
+    }
+
+    if (executionResult) {
+      this.fetchedOnce(executionResult.fetchedOnce);
+      this.hasMore(executionResult.hasMore);
+      this.type(executionResult.type);
+      this.streaming(executionResult.streaming);
+
+      if (!this.meta().length && executionResult.meta.length) {
+        this.meta(executionResult.koEnrichedMeta);
+        this.cleanedMeta(executionResult.cleanedMeta);
+        this.cleanedDateTimeMeta(executionResult.cleanedDateTimeMeta);
+        this.cleanedStringMeta(executionResult.cleanedStringMeta);
+        this.cleanedNumericMeta(executionResult.cleanedNumericMeta);
+      }
+
+      if (refresh) {
+        this.data(executionResult.rows.concat());
+      } else if (
+        executionResult.lastRows.length &&
+        this.data().length !== executionResult.rows.length
+      ) {
+        this.data.push(...executionResult.lastRows);
+      }
+      this.lastFetchedRows(executionResult.lastRows);
+    }
+  }
+
+  updateFromExecutable(executable) {
+    this.status(executable.status);
+    this.hasResultSet(executable.handle && executable.handle.has_result_set);
+    if (!this.hasResultSet) {
+      this.resetResultData();
+    }
+  }
+
+  fetchResult() {
+    if (this.activeExecutable() && this.activeExecutable().result) {
+      this.activeExecutable().result.fetchRows({ rows: 100 });
+    }
+  }
+
   guessMetaField(originalField) {
     let newField = undefined;
     this.cleanedMeta().some(fld => {

+ 1 - 1
desktop/core/src/desktop/js/apps/editor/snippet.js

@@ -22,7 +22,7 @@ import { markdown } from 'markdown';
 import 'apps/editor/components/ko.executableLogs';
 import 'apps/editor/components/ko.executableProgressBar';
 import 'apps/editor/components/ko.snippetEditorActions';
-import 'apps/editor/components/ko.snippetResults';
+import 'apps/editor/components/resultChart/ko.resultChart';
 import 'apps/editor/components/ko.queryHistory';
 
 import './components/ExecutableActionsKoBridge.vue';

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
desktop/libs/notebook/src/notebook/static/notebook/css/editor2.css


+ 79 - 80
desktop/libs/notebook/src/notebook/static/notebook/less/editor2.less

@@ -115,36 +115,104 @@
 
               overflow: auto;
             }
+          }
 
+          .editor-chart-panel {
             .result-tab-body {
-              .flexRowLayoutRow(0 1 100%);
+              .flex(0 1 100%);
 
               overflow: hidden;
+            }
 
-              .table-results {
-                .fillAbsolute();
+            .table-results,
+            .chart-container {
+              height: 100%;
+              width: 100%;
+              position: relative;;
+            }
 
-                .table-results-inner,
-                .table-results-inner > .table-results-bridge {
-                  position: relative;
-                  height: 100%;
-                  width: 100%;
+            .result-settings {
+              .nav-list .nav-header {
+                margin-bottom: -3px;
+                padding-left: 3px;
+              }
+
+              .checkbox {
+                padding-top: 0;
+              }
+            }
+
+            .split-result-container {
+              .display-flex();
+
+              position: relative;
+              width: 100%;
+              height: 100%;
+
+              .result-settings-panel {
+                .flex(0 0 200px);
+
+                min-width: 200px;
+                overflow-y: auto;
+                padding-bottom: 15px;
+              }
+
+              .split-result-resizer {
+                .flex(0 0 3px);
+
+                cursor: ew-resize;
+              }
+
+              .split-result-content {
+                .flex(0 1 100%);
+
+                overflow-y: auto;
+
+                .resultTable th {
+                  color: @cui-white;
+                }
+
+                .dataTables_wrapper .resultTable th {
+                  color: @cui-gray-800;
                 }
 
-                .table-results-inner .hue-table-container {
-                  height: 100%;
+                .results-images {
+                  border-left: 5px solid @cui-default-border-color;
+                  background-color: @cui-gray-050;
+                  padding: 5px;
+                }
+
+                .table-results {
+                  position: relative;
                   width: 100%;
-                  overflow: auto;
+                  min-height: 250px;
                 }
               }
             }
           }
+          .execution-results-tab-panel {
+            .fillAbsolute();
+
+            .table-results-bridge,
+            .hue-table-container {
+              position: relative;
+              height: 100%;
+              width: 100%;
+            }
+
+            .hue-table-container {
+              overflow: auto;
+            }
+          }
         }
       }
     }
   }
 
 
+
+
+
   .snippet-move-placeholder {
     margin: 10px;
     height: 100px;
@@ -414,75 +482,6 @@
     }
   }
 
-  .snippet-tab-body {
-    padding: 0 5px;
-
-    .snippet-tab-actions-append {
-      position: absolute;
-      left: 150px;
-      top: -35px;
-    }
-
-    .result-settings {
-      .nav-list .nav-header {
-        margin-bottom: -3px;
-        padding-left: 3px;
-      }
-
-      .checkbox {
-        padding-top: 0;
-      }
-    }
-
-    .split-result-container {
-      .display-flex();
-
-      position: relative;
-      width: 100%;
-
-      .result-settings-panel {
-        .flex(0 0 200px);
-
-        min-width: 200px;
-      }
-
-      .split-result-resizer {
-        .flex(0 0 3px);
-
-        cursor: ew-resize;
-      }
-
-      .split-result-content {
-        .flex(1);
-
-        .resultTable th {
-          color: @cui-white;
-        }
-
-        .dataTables_wrapper .resultTable th {
-          color: @cui-gray-800;
-        }
-
-        .results-images {
-          border-left: 5px solid @cui-default-border-color;
-          background-color: @cui-gray-050;
-          padding: 5px;
-        }
-
-        .table-results {
-          position: relative;
-          width: 100%;
-          min-height: 290px;
-
-          table > thead > tr > td:first-child,
-          table > tbody > tr > td:first-child {
-            background-color: @cui-gray-050;
-          }
-        }
-      }
-    }
-  }
-
   .nav-list {
     padding-left: 0;
   }

+ 14 - 3
desktop/libs/notebook/src/notebook/templates/editor2.mako

@@ -962,7 +962,7 @@
         <li data-bind="click: function() { currentQueryTab('queryHistory'); }, css: { 'active': currentQueryTab() == 'queryHistory' }">
           <a class="inactive-action" style="display:inline-block" href="#queryHistory" data-toggle="tab">${_('Query History')}</a>
         </li>
-        <li class="margin-right-20" data-bind="click: function(){ currentQueryTab('savedQueries'); }, css: { 'active': currentQueryTab() == 'savedQueries' }">
+        <li data-bind="click: function(){ currentQueryTab('savedQueries'); }, css: { 'active': currentQueryTab() == 'savedQueries' }">
           <a class="inactive-action" style="display:inline-block" href="#savedQueries" data-toggle="tab">${_('Saved Queries')}</a>
         </li>
         <li data-bind="click: function() { currentQueryTab('queryResults'); }, css: {'active': currentQueryTab() == 'queryResults'}">
@@ -972,6 +972,9 @@
 ##          <!-- /ko -->
           </a>
         </li>
+        <li data-bind="click: function() { currentQueryTab('queryChart'); }, css: {'active': currentQueryTab() == 'queryChart'}">
+          <a class="inactive-action" style="display:inline-block" href="#queryChart" data-toggle="tab">${_('Chart')}</a>
+        </li>
         <!-- ko if: explanation -->
         <li data-bind="click: function() { currentQueryTab('queryExplain'); }, css: {'active': currentQueryTab() == 'queryExplain'}"><a class="inactive-action" href="#queryExplain" data-toggle="tab">${_('Explain')}</a></li>
         <!-- /ko -->
@@ -1019,8 +1022,16 @@
         </div>
 
         <div class="tab-pane" id="queryResults" data-bind="css: {'active': currentQueryTab() == 'queryResults'}">
-          <div class="editor-bottom-tab-panel">
-            <!-- ko component: { name: 'snippet-results', params: {
+          <div class="execution-results-tab-panel">
+            <execution-results-ko-bridge class="table-results-bridge" data-bind="vueKoProps: {
+                executableObservable: activeExecutable
+              }"></execution-results-ko-bridge>
+          </div>
+        </div>
+
+        <div class="tab-pane" id="queryChart" data-bind="css: {'active': currentQueryTab() == 'queryChart'}">
+          <div class="editor-bottom-tab-panel editor-chart-panel">
+            <!-- ko component: { name: 'snippet-result-chart', params: {
               activeExecutable: activeExecutable,
               editorMode: parentVm.editorMode,
               id: id,

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff