Эх сурвалжийг харах

HUE-9000 [editor] Add CSV and XLS download actions in editor v2

Johan Ahlen 6 жил өмнө
parent
commit
1700377748

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

@@ -20,7 +20,9 @@ import ko from 'knockout';
 import componentUtils from 'ko/components/componentUtils';
 import DisposableComponent from 'ko/components/DisposableComponent';
 import I18n from 'utils/i18n';
+import huePubSub from 'utils/huePubSub';
 import hueUtils from 'utils/hueUtils';
+import { SHOW_DOWNLOAD_RESULT_MODAL_EVENT } from 'apps/notebook2/components/resultGrid/ko.resultDownloadModal';
 
 export const NAME = 'result-download-actions';
 
@@ -38,7 +40,7 @@ const TEMPLATE = `
     <span class="caret"></span>
   </button>
   <ul class="dropdown-menu">
-    <li style="display: none;">
+    <li>
       <a href="javascript:void(0)" data-bind="
           click: downloadCsv,
           event: { mouseover: mouseOver, mouseout: mouseOut },
@@ -91,6 +93,7 @@ class ResultDownloadActions extends DisposableComponent {
   constructor(params) {
     super();
 
+    this.activeExecutable = params.activeExecutable;
     this.data = params.data;
     this.meta = params.meta;
 
@@ -158,9 +161,19 @@ class ResultDownloadActions extends DisposableComponent {
     $('.clipboard-content').empty();
   }
 
-  downloadCsv() {}
+  downloadCsv() {
+    huePubSub.publish(SHOW_DOWNLOAD_RESULT_MODAL_EVENT, {
+      format: 'csv',
+      executable: this.activeExecutable()
+    });
+  }
 
-  downloadXls() {}
+  downloadXls() {
+    huePubSub.publish(SHOW_DOWNLOAD_RESULT_MODAL_EVENT, {
+      format: 'xls',
+      executable: this.activeExecutable()
+    });
+  }
 
   exportToDashboard() {
     // saveTarget('dashboard');

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

@@ -0,0 +1,170 @@
+// 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 $ from 'jquery';
+import ko from 'knockout';
+
+import { ApiHelper } from 'api/apiHelper';
+import componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import { UUID } from 'utils/hueUtils';
+
+export const NAME = 'download-result-modal';
+
+export const SHOW_DOWNLOAD_RESULT_MODAL_EVENT = 'show.download.result.modal';
+
+// prettier-ignore
+const TEMPLATE = `
+  <div class="modal-header">
+    <!-- ko if: isDownloading -->
+    <h2 class="modal-title">${ I18n('Your download is being prepared') }</h2>
+    <!-- /ko -->
+    <!-- ko if: downloadTruncated -->
+    <h2 class="modal-title">${ I18n('Your downloaded results have been truncated') }</h2>
+    <!-- /ko -->
+  </div>
+  <div class="modal-body">
+    <!-- ko if: isDownloading -->
+    ${ I18n('Please wait, it might take a while...') } <i class="fa fa-spinner fa-spin"></i>
+    <!-- /ko -->
+    <!-- ko if: downloadTruncated -->
+    ${ I18n('The number of resulting rows was too big to be downloaded and the resulting file has been truncated to') }
+    <strong data-bind="text: downloadCounter"></strong>
+    ${ I18n('rows.') }
+    <!-- /ko -->
+  </div>
+  <div class="modal-footer">
+    <!-- ko if: isDownloading -->
+    <button data-bind="click: cancelDownload" class="btn btn-danger disable-feedback">${ I18n('Cancel Download') }</button>
+    <!-- /ko -->
+    <!-- ko if: downloadTruncated -->
+    <button class="btn disable-feedback" data-dismiss="modal">${ I18n('Close') }</button>
+    <!-- /ko -->
+  </div>
+`;
+
+class DownloadResultModal {
+  constructor(params, $downloadProgressModal) {
+    if (typeof trackOnGA == 'function') {
+      trackOnGA('notebook/download/' + params.format);
+    }
+
+    const executable = params.executable;
+    this.$downloadProgressModal = $downloadProgressModal;
+
+    this.isDownloading = ko.observable(true);
+    this.downloadTruncated = ko.observable(false);
+    this.downloadCounter = ko.observable(0);
+
+    const id = executable.executor.snippet ? executable.executor.snippet.id : UUID();
+
+    params.executable.toContext(id).then(jsonContext => {
+      this.cookieId = 'download-' + id;
+      $.cookie(this.cookieId, null, { expires: -1, path: '/' });
+
+      this.addAndSubmitDownloadForm(jsonContext, params.format);
+
+      this.checkDownloadInterval = -1;
+      this.trackCookie();
+    });
+  }
+
+  trackCookie() {
+    let timesChecked = 0;
+    this.checkDownloadInterval = window.setInterval(() => {
+      if (!$.cookie(this.cookieId)) {
+        if (timesChecked > 9) {
+          this.$downloadProgressModal.show();
+        }
+      } else {
+        window.clearInterval(this.checkDownloadInterval);
+        try {
+          const cookieContent = $.cookie(this.cookieId);
+          const result = JSON.parse(
+            cookieContent
+              .substr(1, cookieContent.length - 2)
+              .replace(/\\"/g, '"')
+              .replace(/\\054/g, ',')
+          );
+          this.downloadTruncated(result.truncated);
+          this.downloadCounter(result.row_counter);
+          this.isDownloading(false);
+          if (this.downloadTruncated()) {
+            this.$downloadProgressModal.show();
+          } else {
+            this.$downloadProgressModal.hide();
+          }
+        } catch (e) {
+          this.isDownloading(false);
+          this.$downloadProgressModal.hide();
+        }
+      }
+      timesChecked++;
+    }, 500);
+  }
+
+  addAndSubmitDownloadForm(jsonContext, format) {
+    const $downloadForm = $(
+      '<form method="POST" class="download-form" style="display: inline" action="' +
+        window.HUE_BASE_URL +
+        '/notebook/download"></form>'
+    );
+
+    $('<input type="hidden" name="csrfmiddlewaretoken" />')
+      .val(window.CSRF_TOKEN)
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="notebook" />')
+      .val(jsonContext.notebook)
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="snippet" />')
+      .val(jsonContext.snippet)
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="format" />')
+      .val(format)
+      .appendTo($downloadForm);
+    this.$downloadProgressModal.append($downloadForm);
+    $downloadForm.submit();
+  }
+
+  cancelDownload() {
+    window.clearInterval(this.checkDownloadInterval);
+    this.$downloadProgressModal.hide();
+  }
+}
+
+const cleanAndRemoveKoElement = selector => {
+  const $element = $(selector);
+  if ($element.length) {
+    ko.cleanNode($element[0]);
+    $element.remove();
+  }
+};
+
+componentUtils.registerComponent(NAME, undefined, TEMPLATE).then(() => {
+  huePubSub.subscribe(SHOW_DOWNLOAD_RESULT_MODAL_EVENT, params => {
+    cleanAndRemoveKoElement('#downloadProgressModal');
+    const $downloadProgressModal = $(
+      '<div id="downloadProgressModal" data-bind="component: { name: \'' +
+        NAME +
+        '\', params: $data }" data-keyboard="true" class="modal hide fade downloadProgressModal" tabindex="-1"/>'
+    );
+    $('body').append($downloadProgressModal);
+
+    const model = new DownloadResultModal(params, $downloadProgressModal);
+    ko.applyBindings(model, $downloadProgressModal[0]);
+  });
+});

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

@@ -53,6 +53,7 @@ const TEMPLATE = `
   <!-- ko component: {
     name: 'result-download-actions',
     params: {
+      activeExecutable: activeExecutable,
       meta: meta,
       data: data
     }