Răsfoiți Sursa

HUE-3281 [core] Use ko components for editor and metastore

Johan Ahlen 9 ani în urmă
părinte
comite
da5ba55

+ 390 - 0
desktop/core/src/desktop/static/desktop/ext/js/text.js

@@ -0,0 +1,390 @@
+/**
+ * @license text 2.0.14 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, http://github.com/requirejs/text/LICENSE
+ */
+/*jslint regexp: true */
+/*global require, XMLHttpRequest, ActiveXObject,
+ define, window, process, Packages,
+ java, location, Components, FileUtils */
+
+define(['module'], function (module) {
+  'use strict';
+
+  var text, fs, Cc, Ci, xpcIsWindows,
+      progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
+      xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
+      bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
+      hasLocation = typeof location !== 'undefined' && location.href,
+      defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
+      defaultHostName = hasLocation && location.hostname,
+      defaultPort = hasLocation && (location.port || undefined),
+      buildMap = {},
+      masterConfig = (module.config && module.config()) || {};
+
+  text = {
+    version: '2.0.14',
+
+    strip: function (content) {
+      //Strips <?xml ...?> declarations so that external SVG and XML
+      //documents can be added to a document without worry. Also, if the string
+      //is an HTML document, only the part inside the body tag is returned.
+      if (content) {
+        content = content.replace(xmlRegExp, "");
+        var matches = content.match(bodyRegExp);
+        if (matches) {
+          content = matches[1];
+        }
+      } else {
+        content = "";
+      }
+      return content;
+    },
+
+    jsEscape: function (content) {
+      return content.replace(/(['\\])/g, '\\$1')
+          .replace(/[\f]/g, "\\f")
+          .replace(/[\b]/g, "\\b")
+          .replace(/[\n]/g, "\\n")
+          .replace(/[\t]/g, "\\t")
+          .replace(/[\r]/g, "\\r")
+          .replace(/[\u2028]/g, "\\u2028")
+          .replace(/[\u2029]/g, "\\u2029");
+    },
+
+    createXhr: masterConfig.createXhr || function () {
+      //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
+      var xhr, i, progId;
+      if (typeof XMLHttpRequest !== "undefined") {
+        return new XMLHttpRequest();
+      } else if (typeof ActiveXObject !== "undefined") {
+        for (i = 0; i < 3; i += 1) {
+          progId = progIds[i];
+          try {
+            xhr = new ActiveXObject(progId);
+          } catch (e) {}
+
+          if (xhr) {
+            progIds = [progId];  // so faster next time
+            break;
+          }
+        }
+      }
+
+      return xhr;
+    },
+
+    /**
+     * Parses a resource name into its component parts. Resource names
+     * look like: module/name.ext!strip, where the !strip part is
+     * optional.
+     * @param {String} name the resource name
+     * @returns {Object} with properties "moduleName", "ext" and "strip"
+     * where strip is a boolean.
+     */
+    parseName: function (name) {
+      var modName, ext, temp,
+          strip = false,
+          index = name.lastIndexOf("."),
+          isRelative = name.indexOf('./') === 0 ||
+              name.indexOf('../') === 0;
+
+      if (index !== -1 && (!isRelative || index > 1)) {
+        modName = name.substring(0, index);
+        ext = name.substring(index + 1);
+      } else {
+        modName = name;
+      }
+
+      temp = ext || modName;
+      index = temp.indexOf("!");
+      if (index !== -1) {
+        //Pull off the strip arg.
+        strip = temp.substring(index + 1) === "strip";
+        temp = temp.substring(0, index);
+        if (ext) {
+          ext = temp;
+        } else {
+          modName = temp;
+        }
+      }
+
+      return {
+        moduleName: modName,
+        ext: ext,
+        strip: strip
+      };
+    },
+
+    xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
+
+    /**
+     * Is an URL on another domain. Only works for browser use, returns
+     * false in non-browser environments. Only used to know if an
+     * optimized .js version of a text resource should be loaded
+     * instead.
+     * @param {String} url
+     * @returns Boolean
+     */
+    useXhr: function (url, protocol, hostname, port) {
+      var uProtocol, uHostName, uPort,
+          match = text.xdRegExp.exec(url);
+      if (!match) {
+        return true;
+      }
+      uProtocol = match[2];
+      uHostName = match[3];
+
+      uHostName = uHostName.split(':');
+      uPort = uHostName[1];
+      uHostName = uHostName[0];
+
+      return (!uProtocol || uProtocol === protocol) &&
+          (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
+          ((!uPort && !uHostName) || uPort === port);
+    },
+
+    finishLoad: function (name, strip, content, onLoad) {
+      content = strip ? text.strip(content) : content;
+      if (masterConfig.isBuild) {
+        buildMap[name] = content;
+      }
+      onLoad(content);
+    },
+
+    load: function (name, req, onLoad, config) {
+      //Name has format: some.module.filext!strip
+      //The strip part is optional.
+      //if strip is present, then that means only get the string contents
+      //inside a body tag in an HTML string. For XML/SVG content it means
+      //removing the <?xml ...?> declarations so the content can be inserted
+      //into the current doc without problems.
+
+      // Do not bother with the work if a build and text will
+      // not be inlined.
+      if (config && config.isBuild && !config.inlineText) {
+        onLoad();
+        return;
+      }
+
+      masterConfig.isBuild = config && config.isBuild;
+
+      var parsed = text.parseName(name),
+          nonStripName = parsed.moduleName +
+              (parsed.ext ? '.' + parsed.ext : ''),
+          url = req.toUrl(nonStripName),
+          useXhr = (masterConfig.useXhr) ||
+              text.useXhr;
+
+      // Do not load if it is an empty: url
+      if (url.indexOf('empty:') === 0) {
+        onLoad();
+        return;
+      }
+
+      //Load the text. Use XHR if possible and in a browser.
+      if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
+        text.get(url, function (content) {
+          text.finishLoad(name, parsed.strip, content, onLoad);
+        }, function (err) {
+          if (onLoad.error) {
+            onLoad.error(err);
+          }
+        });
+      } else {
+        //Need to fetch the resource across domains. Assume
+        //the resource has been optimized into a JS module. Fetch
+        //by the module name + extension, but do not include the
+        //!strip part to avoid file system issues.
+        req([nonStripName], function (content) {
+          text.finishLoad(parsed.moduleName + '.' + parsed.ext,
+              parsed.strip, content, onLoad);
+        });
+      }
+    },
+
+    write: function (pluginName, moduleName, write, config) {
+      if (buildMap.hasOwnProperty(moduleName)) {
+        var content = text.jsEscape(buildMap[moduleName]);
+        write.asModule(pluginName + "!" + moduleName,
+            "define(function () { return '" +
+            content +
+            "';});\n");
+      }
+    },
+
+    writeFile: function (pluginName, moduleName, req, write, config) {
+      var parsed = text.parseName(moduleName),
+          extPart = parsed.ext ? '.' + parsed.ext : '',
+          nonStripName = parsed.moduleName + extPart,
+      //Use a '.js' file name so that it indicates it is a
+      //script that can be loaded across domains.
+          fileName = req.toUrl(parsed.moduleName + extPart) + '.js';
+
+      //Leverage own load() method to load plugin value, but only
+      //write out values that do not have the strip argument,
+      //to avoid any potential issues with ! in file names.
+      text.load(nonStripName, req, function (value) {
+        //Use own write() method to construct full module value.
+        //But need to create shell that translates writeFile's
+        //write() to the right interface.
+        var textWrite = function (contents) {
+          return write(fileName, contents);
+        };
+        textWrite.asModule = function (moduleName, contents) {
+          return write.asModule(moduleName, fileName, contents);
+        };
+
+        text.write(pluginName, nonStripName, textWrite, config);
+      }, config);
+    }
+  };
+
+  if (masterConfig.env === 'node' || (!masterConfig.env &&
+      typeof process !== "undefined" &&
+      process.versions &&
+      !!process.versions.node &&
+      !process.versions['node-webkit'] &&
+      !process.versions['atom-shell'])) {
+    //Using special require.nodeRequire, something added by r.js.
+    fs = require.nodeRequire('fs');
+
+    text.get = function (url, callback, errback) {
+      try {
+        var file = fs.readFileSync(url, 'utf8');
+        //Remove BOM (Byte Mark Order) from utf8 files if it is there.
+        if (file[0] === '\uFEFF') {
+          file = file.substring(1);
+        }
+        callback(file);
+      } catch (e) {
+        if (errback) {
+          errback(e);
+        }
+      }
+    };
+  } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
+      text.createXhr())) {
+    text.get = function (url, callback, errback, headers) {
+      var xhr = text.createXhr(), header;
+      xhr.open('GET', url, true);
+
+      //Allow plugins direct access to xhr headers
+      if (headers) {
+        for (header in headers) {
+          if (headers.hasOwnProperty(header)) {
+            xhr.setRequestHeader(header.toLowerCase(), headers[header]);
+          }
+        }
+      }
+
+      //Allow overrides specified in config
+      if (masterConfig.onXhr) {
+        masterConfig.onXhr(xhr, url);
+      }
+
+      xhr.onreadystatechange = function (evt) {
+        var status, err;
+        //Do not explicitly handle errors, those should be
+        //visible via console output in the browser.
+        if (xhr.readyState === 4) {
+          status = xhr.status || 0;
+          if (status > 399 && status < 600) {
+            //An http 4xx or 5xx error. Signal an error.
+            err = new Error(url + ' HTTP status: ' + status);
+            err.xhr = xhr;
+            if (errback) {
+              errback(err);
+            }
+          } else {
+            callback(xhr.responseText);
+          }
+
+          if (masterConfig.onXhrComplete) {
+            masterConfig.onXhrComplete(xhr, url);
+          }
+        }
+      };
+      xhr.send(null);
+    };
+  } else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
+      typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
+    //Why Java, why is this so awkward?
+    text.get = function (url, callback) {
+      var stringBuffer, line,
+          encoding = "utf-8",
+          file = new java.io.File(url),
+          lineSeparator = java.lang.System.getProperty("line.separator"),
+          input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
+          content = '';
+      try {
+        stringBuffer = new java.lang.StringBuffer();
+        line = input.readLine();
+
+        // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
+        // http://www.unicode.org/faq/utf_bom.html
+
+        // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
+        // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
+        if (line && line.length() && line.charAt(0) === 0xfeff) {
+          // Eat the BOM, since we've already found the encoding on this file,
+          // and we plan to concatenating this buffer with others; the BOM should
+          // only appear at the top of a file.
+          line = line.substring(1);
+        }
+
+        if (line !== null) {
+          stringBuffer.append(line);
+        }
+
+        while ((line = input.readLine()) !== null) {
+          stringBuffer.append(lineSeparator);
+          stringBuffer.append(line);
+        }
+        //Make sure we return a JavaScript string and not a Java string.
+        content = String(stringBuffer.toString()); //String
+      } finally {
+        input.close();
+      }
+      callback(content);
+    };
+  } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
+      typeof Components !== 'undefined' && Components.classes &&
+      Components.interfaces)) {
+    //Avert your gaze!
+    Cc = Components.classes;
+    Ci = Components.interfaces;
+    Components.utils['import']('resource://gre/modules/FileUtils.jsm');
+    xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);
+
+    text.get = function (url, callback) {
+      var inStream, convertStream, fileObj,
+          readData = {};
+
+      if (xpcIsWindows) {
+        url = url.replace(/\//g, '\\');
+      }
+
+      fileObj = new FileUtils.File(url);
+
+      //XPCOM, you so crazy
+      try {
+        inStream = Cc['@mozilla.org/network/file-input-stream;1']
+            .createInstance(Ci.nsIFileInputStream);
+        inStream.init(fileObj, 1, 0, false);
+
+        convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
+            .createInstance(Ci.nsIConverterInputStream);
+        convertStream.init(inStream, "utf-8", inStream.available(),
+            Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+        convertStream.readString(inStream.available(), readData);
+        convertStream.close();
+        inStream.close();
+        callback(readData.value);
+      } catch (e) {
+        throw new Error((fileObj && fileObj.path || '') + ': ' + e);
+      }
+    };
+  }
+  return text;
+});

+ 29 - 0
desktop/core/src/desktop/static/desktop/js/ko.editor.js

@@ -0,0 +1,29 @@
+// 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.
+
+define([
+  'knockout'
+], function (ko) {
+
+  var EditorViewModel = function () {
+    console.log('ko editor model loaded');
+  };
+
+  ko.components.register('editor', {
+    viewModel: EditorViewModel,
+    template: { require: 'text!/ko_editor' }
+  });
+});

+ 29 - 0
desktop/core/src/desktop/static/desktop/js/ko.metastore.js

@@ -0,0 +1,29 @@
+// 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.
+
+define([
+  'knockout'
+], function (ko) {
+
+  var EditorViewModel = function () {
+    console.log('ko metastore model loaded');
+  };
+
+  ko.components.register('metastore', {
+    viewModel: EditorViewModel,
+    template: { require: 'text!/ko_metastore' }
+  });
+});

+ 24 - 0
desktop/core/src/desktop/templates/ko_editor.mako

@@ -0,0 +1,24 @@
+## 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.
+
+<%!
+from desktop import conf
+from desktop.lib.i18n import smart_unicode
+from django.utils.translation import ugettext as _
+from desktop.views import _ko
+%>
+
+<div>${_('I\'m an editor')}</div>

+ 24 - 0
desktop/core/src/desktop/templates/ko_metastore.mako

@@ -0,0 +1,24 @@
+## 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.
+
+<%!
+from desktop import conf
+from desktop.lib.i18n import smart_unicode
+from django.utils.translation import ugettext as _
+from desktop.views import _ko
+%>
+
+<div>${_('I\'m a metastore')}</div>

+ 22 - 4
desktop/core/src/desktop/templates/responsive.mako

@@ -103,7 +103,7 @@ ${ hueIcons.symbols() }
 
   <div class="content-wrapper">
     <div class="left-nav">
-      Query
+      <a href="javascript:void(0);" data-bind="click: function () { huePubSub.publish('switch.app', 'editor'); }">Query</a><br/>
       <br/>
       [Hive, Impala, Pig, PySpark, Solr, MapReduce...]<br/>
       [Query 1, Query 2, Query 3, Query 4]<br/>
@@ -116,7 +116,7 @@ ${ hueIcons.symbols() }
 
       <br/>
 
-      Browse<br/>
+      <a href="javascript:void(0);" data-bind="click: function () { huePubSub.publish('switch.app', 'metastore'); }">Browse</a><br/>
       [Tables]<br/>
       [Files]<br/>
       [Indexes]<br/>
@@ -170,7 +170,7 @@ ${ hueIcons.symbols() }
       <a href="javascript: void(0);" title="${_('Toggle Assist')}" class="pointer show-assist" style="display:none;">
         <i class="fa fa-chevron-right"></i>
       </a>
-      <h1>responsive</h1>
+      <div data-bind='component: currentApp'></div>
     </div>
   </div>
 </div>
@@ -181,6 +181,7 @@ ${ hueIcons.symbols() }
 <script src="${ static('desktop/ext/js/moment-with-locales.min.js') }"></script>
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.total-storage.min.js') }"></script>
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.nicescroll.min.js') }"></script>
+<script src="${ static('desktop/ext/js/jquery/plugins/jquery.cookie.js') }"></script>
 
 ${ require.config() }
 ${ tableStats.tableStats() }
@@ -191,11 +192,28 @@ ${ assist.assistPanel() }
   require([
     'knockout',
     'assistPanel',
+    'text',
     'ko.hue-bindings',
     'knockout-sortable',
-    'ko.switch-case'
+    'ko.switch-case',
+    'desktop/js/ko.editor',
+    'desktop/js/ko.metastore'
   ], function (ko) {
 
+    var OnePageViewModel = function () {
+      var self = this;
+
+      self.currentApp = ko.observable('editor');
+
+      huePubSub.subscribe('switch.app', function (name) {
+        console.log(name);
+        self.currentApp(name);
+      })
+    }
+
+    ko.applyBindings(new OnePageViewModel(), $('.page-content')[0]);
+
+    ko.applyBindings({}, $('.left-nav')[0])
 
     var isAssistVisible = ko.observable(true);
     isAssistVisible.subscribe(function (newValue) {

+ 4 - 0
desktop/core/src/desktop/urls.py

@@ -95,6 +95,10 @@ dynamic_patterns += patterns('desktop.views',
   # Responsive
   (r'^responsive', 'responsive'),
 
+  # KO components, change to doc?name=ko_editor or similar
+  (r'^ko_editor', 'ko_editor'),
+  (r'^ko_metastore', 'ko_metastore'),
+
   # Jasmine
   (r'^jasmine', 'jasmine'),
 

+ 16 - 0
desktop/core/src/desktop/views.py

@@ -73,6 +73,22 @@ def responsive(request):
     'tours_and_tutorials': Settings.get_settings().tours_and_tutorials
   })
 
+def ko_editor(request):
+  apps = appmanager.get_apps_dict(request.user)
+
+  return render('ko_editor.mako', request, {
+    'apps': apps,
+    'tours_and_tutorials': Settings.get_settings().tours_and_tutorials
+  })
+
+def ko_metastore(request):
+  apps = appmanager.get_apps_dict(request.user)
+
+  return render('ko_metastore.mako', request, {
+    'apps': apps,
+    'tours_and_tutorials': Settings.get_settings().tours_and_tutorials
+  })
+
 def home(request):
   docs = _get_docs(request.user)