You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2017/01/14 18:27:24 UTC

[1/2] zeppelin git commit: [ZEPPELIN-1619] Load js package as a plugin visualization

Repository: zeppelin
Updated Branches:
  refs/heads/master 00742ffdb -> 300f75323


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/visualization/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/package.json b/zeppelin-web/src/app/visualization/package.json
new file mode 100644
index 0000000..c0fb9d3
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "zeppelin-vis",
+  "description": "Visualization API",
+  "version": "0.7.0-SNAPSHOT",
+  "main": "visualization",
+  "dependencies": {
+    "json3": "~3.3.1",
+    "nvd3": "~1.7.1",
+    "lodash": "~3.9.3"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache/zeppelin"
+  },
+  "license": "Apache-2.0"
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/components/helium/helium.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/helium/helium.service.js b/zeppelin-web/src/components/helium/helium.service.js
new file mode 100644
index 0000000..ae44425
--- /dev/null
+++ b/zeppelin-web/src/components/helium/helium.service.js
@@ -0,0 +1,62 @@
+/*
+ * Licensed 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.
+ */
+
+(function() {
+
+  angular.module('zeppelinWebApp').service('heliumService', heliumService);
+
+  heliumService.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
+
+  function heliumService($http, baseUrlSrv, ngToast) {
+
+    var url = baseUrlSrv.getRestApiBase() + '/helium/visualizations/load';
+    if (process.env.HELIUM_VIS_DEV) {
+      url = url + '?refresh=true';
+    }
+    var visualizations = [];
+
+    // load should be promise
+    this.load = $http.get(url).success(function(response) {
+      if (response.substring(0, 'ERROR:'.length) !== 'ERROR:') {
+        eval(response);
+      } else {
+        console.error(response);
+      }
+    });
+
+    this.get = function() {
+      return visualizations;
+    };
+
+    this.getVisualizationOrder = function() {
+      return $http.get(baseUrlSrv.getRestApiBase() + '/helium/visualizationOrder');
+    };
+
+    this.setVisualizationOrder = function(list) {
+      return $http.post(baseUrlSrv.getRestApiBase() + '/helium/visualizationOrder', list);
+    };
+
+    this.getAllPackageInfo = function() {
+      return $http.get(baseUrlSrv.getRestApiBase() + '/helium/all');
+    };
+
+    this.enable = function(name, artifact) {
+      return $http.post(baseUrlSrv.getRestApiBase() + '/helium/enable/' + name, artifact);
+    };
+
+    this.disable = function(name) {
+      return $http.post(baseUrlSrv.getRestApiBase() + '/helium/disable/' + name);
+    };
+  };
+})();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/components/navbar/navbar.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html
index a477055..cf8b5b6 100644
--- a/zeppelin-web/src/components/navbar/navbar.html
+++ b/zeppelin-web/src/components/navbar/navbar.html
@@ -94,6 +94,7 @@ limitations under the License.
               <li><a href="#/interpreter">Interpreter</a></li>
               <li><a href="#/notebookRepos">Notebook Repos</a></li>
               <li><a href="#/credential">Credential</a></li>
+              <li><a href="#/helium">Helium</a></li>
               <li><a href="#/configuration">Configuration</a></li>
               <li ng-if="ticket.principal && ticket.principal !== 'anonymous'" role="separator" style="margin: 5px 0;" class="divider"></li>
               <li ng-if="ticket.principal && ticket.principal !== 'anonymous'"><a ng-click="navbar.logout()">Logout</a></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 3769d65..0bc5129 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -61,6 +61,7 @@ limitations under the License.
     <link rel="stylesheet" href="app/jobmanager/jobmanager.css" />
     <link rel="stylesheet" href="app/jobmanager/jobs/job.css" />
     <link rel="stylesheet" href="app/interpreter/interpreter.css" />
+    <link rel="stylesheet" href="app/helium/helium.css" />
     <link rel="stylesheet" href="app/credential/credential.css" />
     <link rel="stylesheet" href="app/configuration/configuration.css" />
     <link rel="stylesheet" href="components/expandCollapse/expandCollapse.css" />

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.js b/zeppelin-web/src/index.js
index 95d799d..191d2e3 100644
--- a/zeppelin-web/src/index.js
+++ b/zeppelin-web/src/index.js
@@ -45,6 +45,7 @@ import './app/notebook/paragraph/paragraph.controller.js';
 import './app/notebook/paragraph/result/result.controller.js';
 import './app/search/result-list.controller.js';
 import './app/notebookRepos/notebookRepos.controller.js';
+import './app/helium/helium.controller.js';
 import './components/arrayOrderingSrv/arrayOrdering.service.js';
 import './components/clipboard/clipboard.controller.js';
 import './components/navbar/navbar.controller.js';
@@ -73,3 +74,4 @@ import './components/noteAction/noteAction.service.js';
 import './components/notevarshareService/notevarshare.service.js';
 import './components/rename/rename.controller.js';
 import './components/rename/rename.service.js';
+import './components/helium/helium.service.js';

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/webpack.config.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/webpack.config.js b/zeppelin-web/webpack.config.js
index 0efc721..d3a2681 100644
--- a/zeppelin-web/webpack.config.js
+++ b/zeppelin-web/webpack.config.js
@@ -205,7 +205,14 @@ module.exports = function makeWebpackConfig () {
       // Reference: https://github.com/webpack/extract-text-webpack-plugin
       // Extract css files
       // Disabled when in test mode or not in build mode
-      new ExtractTextPlugin('[name].[hash].css', {disable: !isProd})
+      new ExtractTextPlugin('[name].[hash].css', {disable: !isProd}),
+
+      // Reference: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
+      new webpack.DefinePlugin({
+        'process.env': {
+          HELIUM_VIS_DEV: process.env.HELIUM_VIS_DEV
+        }
+      })
     )
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml
index 07fa2df..d620512 100644
--- a/zeppelin-zengine/pom.xml
+++ b/zeppelin-zengine/pom.xml
@@ -45,6 +45,7 @@
     <org.reflections.version>0.9.8</org.reflections.version>
     <xml.apis.version>1.4.01</xml.apis.version>
     <eclipse.jgit.version>4.1.1.201511131810-r</eclipse.jgit.version>
+    <frontend.maven.plugin.version>1.3</frontend.maven.plugin.version>
 
     <!--test library versions-->
     <google.truth.version>0.27</google.truth.version>
@@ -218,6 +219,26 @@
     </dependency>
 
     <dependency>
+      <groupId>com.github.eirslett</groupId>
+      <artifactId>frontend-maven-plugin</artifactId>
+      <version>${frontend.maven.plugin.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-utils</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.maven</groupId>
+          <artifactId>maven-plugin-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.maven</groupId>
+          <artifactId>maven-artifact</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>
@@ -256,7 +277,6 @@
       <version>${jetty.version}</version>
       <scope>test</scope>
     </dependency>
-
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
index 79eb3a6..8ef30c8 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
@@ -16,6 +16,7 @@
  */
 package org.apache.zeppelin.helium;
 
+import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import org.apache.commons.io.FileUtils;
@@ -32,10 +33,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * Manages helium packages
@@ -48,10 +46,19 @@ public class Helium {
   private final String heliumConfPath;
   private final String defaultLocalRegistryPath;
   private final Gson gson;
+  private final HeliumVisualizationFactory visualizationFactory;
+  private final HeliumApplicationFactory applicationFactory;
 
-  public Helium(String heliumConfPath, String defaultLocalRegistryPath) throws IOException {
+  public Helium(
+      String heliumConfPath,
+      String defaultLocalRegistryPath,
+      HeliumVisualizationFactory visualizationFactory,
+      HeliumApplicationFactory applicationFactory)
+      throws IOException, TaskRunnerException {
     this.heliumConfPath = heliumConfPath;
     this.defaultLocalRegistryPath = defaultLocalRegistryPath;
+    this.visualizationFactory = visualizationFactory;
+    this.applicationFactory = applicationFactory;
 
     GsonBuilder builder = new GsonBuilder();
     builder.setPrettyPrinting();
@@ -83,6 +90,14 @@ public class Helium {
     }
   }
 
+  public HeliumApplicationFactory getApplicationFactory() {
+    return applicationFactory;
+  }
+
+  public HeliumVisualizationFactory getVisualizationFactory() {
+    return visualizationFactory;
+  }
+
   private synchronized HeliumConf loadConf(String path) throws IOException {
     File heliumConfFile = new File(path);
     if (!heliumConfFile.isFile()) {
@@ -104,6 +119,7 @@ public class Helium {
   public synchronized void save() throws IOException {
     String jsonString;
     synchronized (registry) {
+      clearNotExistsPackages();
       heliumConf.setRegistry(registry);
       jsonString = gson.toJson(heliumConf);
     }
@@ -116,20 +132,118 @@ public class Helium {
     FileUtils.writeStringToFile(heliumConfFile, jsonString);
   }
 
-  public List<HeliumPackageSearchResult> getAllPackageInfo() {
-    List<HeliumPackageSearchResult> list = new LinkedList<>();
+  private void clearNotExistsPackages() {
+    Map<String, List<HeliumPackageSearchResult>> all = getAllPackageInfo();
+
+    // clear visualization display order
+    List<String> packageOrder = heliumConf.getVisualizationDisplayOrder();
+    List<String> clearedOrder = new LinkedList<>();
+    for (String pkgName : packageOrder) {
+      if (all.containsKey(pkgName)) {
+        clearedOrder.add(pkgName);
+      }
+    }
+    heliumConf.setVisualizationDisplayOrder(clearedOrder);
+
+    // clear enabled package
+    Map<String, String> enabledPackages = heliumConf.getEnabledPackages();
+    for (String pkgName : enabledPackages.keySet()) {
+      if (!all.containsKey(pkgName)) {
+        heliumConf.disablePackage(pkgName);
+      }
+    }
+  }
+
+  public Map<String, List<HeliumPackageSearchResult>> getAllPackageInfo() {
+    Map<String, String> enabledPackageInfo = heliumConf.getEnabledPackages();
+
+    Map<String, List<HeliumPackageSearchResult>> map = new HashMap<>();
     synchronized (registry) {
       for (HeliumRegistry r : registry) {
         try {
           for (HeliumPackage pkg : r.getAll()) {
-            list.add(new HeliumPackageSearchResult(r.name(), pkg));
+            String name = pkg.getName();
+            String artifact = enabledPackageInfo.get(name);
+            boolean enabled = (artifact != null && artifact.equals(pkg.getArtifact()));
+
+            if (!map.containsKey(name)) {
+              map.put(name, new LinkedList<HeliumPackageSearchResult>());
+            }
+            map.get(name).add(new HeliumPackageSearchResult(r.name(), pkg, enabled));
           }
         } catch (IOException e) {
           logger.error(e.getMessage(), e);
         }
       }
     }
-    return list;
+
+    // sort version (artifact)
+    for (String name : map.keySet()) {
+      List<HeliumPackageSearchResult> packages = map.get(name);
+      Collections.sort(packages, new Comparator<HeliumPackageSearchResult>() {
+        @Override
+        public int compare(HeliumPackageSearchResult o1, HeliumPackageSearchResult o2) {
+          return o2.getPkg().getArtifact().compareTo(o1.getPkg().getArtifact());
+        }
+      });
+    }
+    return map;
+  }
+
+  public HeliumPackageSearchResult getPackageInfo(String name, String artifact) {
+    Map<String, List<HeliumPackageSearchResult>> infos = getAllPackageInfo();
+    List<HeliumPackageSearchResult> packages = infos.get(name);
+    if (artifact == null) {
+      return packages.get(0);
+    } else {
+      for (HeliumPackageSearchResult pkg : packages) {
+        if (pkg.getPkg().getArtifact().equals(artifact)) {
+          return pkg;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public File recreateVisualizationBundle() throws IOException, TaskRunnerException {
+    return visualizationFactory.bundle(getVisualizationPackagesToBundle(), true);
+  }
+
+  public void enable(String name, String artifact) throws IOException, TaskRunnerException {
+    HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact);
+
+    // no package found.
+    if (pkgInfo == null) {
+      return;
+    }
+
+    // enable package
+    heliumConf.enablePackage(name, artifact);
+
+    // if package is visualization, rebuild bundle
+    if (pkgInfo.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) {
+      visualizationFactory.bundle(getVisualizationPackagesToBundle());
+    }
+
+    save();
+  }
+
+  public void disable(String name) throws IOException, TaskRunnerException {
+    String artifact = heliumConf.getEnabledPackages().get(name);
+
+    if (artifact == null) {
+      return;
+    }
+
+    heliumConf.disablePackage(name);
+
+    HeliumPackageSearchResult pkg = getPackageInfo(name, artifact);
+    if (pkg == null || pkg.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) {
+      visualizationFactory.bundle(getVisualizationPackagesToBundle());
+    }
+
+    save();
   }
 
   public HeliumPackageSuggestion suggestApp(Paragraph paragraph) {
@@ -153,20 +267,89 @@ public class Helium {
       allResources = ResourcePoolUtils.getAllResources();
     }
 
-    for (HeliumPackageSearchResult pkg : getAllPackageInfo()) {
-      ResourceSet resources = ApplicationLoader.findRequiredResourceSet(
-          pkg.getPkg().getResources(),
-          paragraph.getNote().getId(),
-          paragraph.getId(),
-          allResources);
-      if (resources == null) {
-        continue;
-      } else {
-        suggestion.addAvailablePackage(pkg);
+    for (List<HeliumPackageSearchResult> pkgs : getAllPackageInfo().values()) {
+      for (HeliumPackageSearchResult pkg : pkgs) {
+        if (pkg.getPkg().getType() == HeliumPackage.Type.APPLICATION && pkg.isEnabled()) {
+          ResourceSet resources = ApplicationLoader.findRequiredResourceSet(
+              pkg.getPkg().getResources(),
+              paragraph.getNote().getId(),
+              paragraph.getId(),
+              allResources);
+          if (resources == null) {
+            continue;
+          } else {
+            suggestion.addAvailablePackage(pkg);
+          }
+          break;
+        }
       }
     }
 
     suggestion.sort();
     return suggestion;
   }
+
+  /**
+   * Get enabled visualization packages
+   *
+   * @return ordered list of enabled visualization package
+   */
+  public List<HeliumPackage> getVisualizationPackagesToBundle() {
+    Map<String, List<HeliumPackageSearchResult>> allPackages = getAllPackageInfo();
+    List<String> visOrder = heliumConf.getVisualizationDisplayOrder();
+
+    List<HeliumPackage> orderedVisualizationPackages = new LinkedList<>();
+
+    // add enabled packages in visOrder
+    for (String name : visOrder) {
+      List<HeliumPackageSearchResult> versions = allPackages.get(name);
+      if (versions == null) {
+        continue;
+      }
+      for (HeliumPackageSearchResult pkgInfo : versions) {
+        if (pkgInfo.getPkg().getType() == HeliumPackage.Type.VISUALIZATION && pkgInfo.isEnabled()) {
+          orderedVisualizationPackages.add(pkgInfo.getPkg());
+          allPackages.remove(name);
+          break;
+        }
+      }
+    }
+
+    // add enabled packages not in visOrder
+    for (List<HeliumPackageSearchResult> pkgs : allPackages.values()) {
+      for (HeliumPackageSearchResult pkg : pkgs) {
+        if (pkg.getPkg().getType() == HeliumPackage.Type.VISUALIZATION && pkg.isEnabled()) {
+          orderedVisualizationPackages.add(pkg.getPkg());
+          break;
+        }
+      }
+    }
+
+    return orderedVisualizationPackages;
+  }
+
+  /**
+   * Get enabled package list in order
+   * @return
+   */
+  public List<String> getVisualizationPackageOrder() {
+    List orderedPackageList = new LinkedList<>();
+    List<HeliumPackage> packages = getVisualizationPackagesToBundle();
+
+    for (HeliumPackage pkg : packages) {
+      orderedPackageList.add(pkg.getName());
+    }
+
+    return orderedPackageList;
+  }
+
+  public void setVisualizationPackageOrder(List<String> orderedPackageList)
+      throws IOException, TaskRunnerException {
+    heliumConf.setVisualizationDisplayOrder(orderedPackageList);
+
+    // if package is visualization, rebuild bundle
+    visualizationFactory.bundle(getVisualizationPackagesToBundle());
+
+    save();
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java
index 2c535d4..d3b0fb4 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java
@@ -16,8 +16,7 @@
  */
 package org.apache.zeppelin.helium;
 
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
 
 /**
  * Helium config. This object will be persisted to conf/heliumc.conf
@@ -25,6 +24,13 @@ import java.util.List;
 public class HeliumConf {
   List<HeliumRegistry> registry = new LinkedList<>();
 
+  // enabled packages {name, version}
+  Map<String, String> enabled = Collections.synchronizedMap(new HashMap<String, String>());
+
+  // enabled visualization package display order
+  List<String> visualizationDisplayOrder = new LinkedList<>();
+
+
   public List<HeliumRegistry> getRegistry() {
     return registry;
   }
@@ -32,4 +38,36 @@ public class HeliumConf {
   public void setRegistry(List<HeliumRegistry> registry) {
     this.registry = registry;
   }
+
+  public Map<String, String> getEnabledPackages() {
+    return new HashMap<>(enabled);
+  }
+
+  public void enablePackage(HeliumPackage pkg) {
+    enablePackage(pkg.getName(), pkg.getArtifact());
+  }
+
+  public void enablePackage(String name, String artifact) {
+    enabled.put(name, artifact);
+  }
+
+  public void disablePackage(HeliumPackage pkg) {
+    disablePackage(pkg.getName());
+  }
+
+  public void disablePackage(String name) {
+    enabled.remove(name);
+  }
+
+  public List<String> getVisualizationDisplayOrder() {
+    if (visualizationDisplayOrder == null) {
+      return new LinkedList<String>();
+    } else {
+      return visualizationDisplayOrder;
+    }
+  }
+
+  public void setVisualizationDisplayOrder(List<String> orderedPackageList) {
+    visualizationDisplayOrder = orderedPackageList;
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java
index 57a9d45..36b6115 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java
@@ -22,15 +22,17 @@ package org.apache.zeppelin.helium;
 public class HeliumPackageSearchResult {
   private final String registry;
   private final HeliumPackage pkg;
+  private final boolean enabled;
 
   /**
    * Create search result item
    * @param registry registry name
    * @param pkg package information
    */
-  public HeliumPackageSearchResult(String registry, HeliumPackage pkg) {
+  public HeliumPackageSearchResult(String registry, HeliumPackage pkg, boolean enabled) {
     this.registry = registry;
     this.pkg = pkg;
+    this.enabled = enabled;
   }
 
   public String getRegistry() {
@@ -40,4 +42,8 @@ public class HeliumPackageSearchResult {
   public HeliumPackage getPkg() {
     return pkg;
   }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java
new file mode 100644
index 0000000..a06c18b
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java
@@ -0,0 +1,351 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+package org.apache.zeppelin.helium;
+
+import com.github.eirslett.maven.plugins.frontend.lib.*;
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import com.google.gson.Gson;
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Appender;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.WriterAppender;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * Load helium visualization
+ */
+public class HeliumVisualizationFactory {
+  Logger logger = LoggerFactory.getLogger(HeliumVisualizationFactory.class);
+  private final String NODE_VERSION = "v6.9.1";
+  private final String NPM_VERSION = "3.10.8";
+  private final String DEFAULT_NPM_REGISTRY_URL = "http://registry.npmjs.org/";
+
+  private final FrontendPluginFactory frontEndPluginFactory;
+  private final File workingDirectory;
+  private File tabledataModulePath;
+  private File visualizationModulePath;
+  private Gson gson;
+
+  String bundleCacheKey = "";
+  File currentBundle;
+
+  ByteArrayOutputStream out  = new ByteArrayOutputStream();
+
+  public HeliumVisualizationFactory(
+      File moduleDownloadPath,
+      File tabledataModulePath,
+      File visualizationModulePath) throws TaskRunnerException {
+    this(moduleDownloadPath);
+    this.tabledataModulePath = tabledataModulePath;
+    this.visualizationModulePath = visualizationModulePath;
+  }
+
+  public HeliumVisualizationFactory(File moduleDownloadPath) throws TaskRunnerException {
+    this.workingDirectory = new File(moduleDownloadPath, "vis");
+    File installDirectory = workingDirectory;
+
+    frontEndPluginFactory = new FrontendPluginFactory(
+        workingDirectory, installDirectory);
+
+    currentBundle = new File(workingDirectory, "vis.bundle.cache.js");
+    gson = new Gson();
+    installNodeAndNpm();
+    configureLogger();
+  }
+
+  private void installNodeAndNpm() {
+    try {
+      NPMInstaller npmInstaller = frontEndPluginFactory.getNPMInstaller(getProxyConfig());
+      npmInstaller.setNpmVersion(NPM_VERSION);
+      npmInstaller.install();
+
+      NodeInstaller nodeInstaller = frontEndPluginFactory.getNodeInstaller(getProxyConfig());
+      nodeInstaller.setNodeVersion(NODE_VERSION);
+      nodeInstaller.install();
+    } catch (InstallationException e) {
+      logger.error(e.getMessage(), e);
+    }
+  }
+
+  private ProxyConfig getProxyConfig() {
+    List<ProxyConfig.Proxy> proxy = new LinkedList<>();
+    return new ProxyConfig(proxy);
+  }
+
+  public File bundle(List<HeliumPackage> pkgs) throws IOException, TaskRunnerException {
+    return bundle(pkgs, false);
+  }
+
+  public synchronized File bundle(List<HeliumPackage> pkgs, boolean forceRefresh)
+      throws IOException, TaskRunnerException {
+    // package.json
+    URL pkgUrl = Resources.getResource("helium/package.json");
+    String pkgJson = Resources.toString(pkgUrl, Charsets.UTF_8);
+    StringBuilder dependencies = new StringBuilder();
+
+    FileFilter npmPackageCopyFilter = new FileFilter() {
+      @Override
+      public boolean accept(File pathname) {
+        String fileName = pathname.getName();
+        if (fileName.startsWith(".") || fileName.startsWith("#") || fileName.startsWith("~")) {
+          return false;
+        } else {
+          return true;
+        }
+      }
+    };
+
+    for (HeliumPackage pkg : pkgs) {
+      String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
+      if (moduleNameVersion == null) {
+        logger.error("Can't get module name and version of package " + pkg.getName());
+        continue;
+      }
+      if (dependencies.length() > 0) {
+        dependencies.append(",\n");
+      }
+      dependencies.append("\"" + moduleNameVersion[0] + "\": \"" + moduleNameVersion[1] + "\"");
+
+      if (isLocalPackage(pkg)) {
+        FileUtils.copyDirectory(
+            new File(pkg.getArtifact()),
+            new File(workingDirectory, "node_modules/" + pkg.getName()),
+            npmPackageCopyFilter);
+      }
+    }
+    pkgJson = pkgJson.replaceFirst("DEPENDENCIES", dependencies.toString());
+
+    // check if we can use previous bundle or not
+    if (dependencies.toString().equals(bundleCacheKey) && currentBundle.isFile() && !forceRefresh) {
+      return currentBundle;
+    }
+
+    // webpack.config.js
+    URL webpackConfigUrl = Resources.getResource("helium/webpack.config.js");
+    String webpackConfig = Resources.toString(webpackConfigUrl, Charsets.UTF_8);
+
+    // generate load.js
+    StringBuilder loadJsImport = new StringBuilder();
+    StringBuilder loadJsRegister = new StringBuilder();
+
+    long idx = 0;
+    for (HeliumPackage pkg : pkgs) {
+      String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
+      if (moduleNameVersion == null) {
+        continue;
+      }
+
+      String className = "vis" + idx++;
+      loadJsImport.append(
+          "import " + className + " from \"" + moduleNameVersion[0] + "\"\n");
+
+      loadJsRegister.append("visualizations.push({\n");
+      loadJsRegister.append("id: \"" + moduleNameVersion[0] + "\",\n");
+      loadJsRegister.append("name: \"" + pkg.getName() + "\",\n");
+      loadJsRegister.append("icon: " + gson.toJson(pkg.getIcon()) + ",\n");
+      loadJsRegister.append("class: " + className + "\n");
+      loadJsRegister.append("})\n");
+    }
+
+    FileUtils.write(new File(workingDirectory, "package.json"), pkgJson);
+    FileUtils.write(new File(workingDirectory, "webpack.config.js"), webpackConfig);
+    FileUtils.write(new File(workingDirectory, "load.js"),
+        loadJsImport.append(loadJsRegister).toString());
+
+    // install tabledata module
+    File tabledataModuleInstallPath = new File(workingDirectory,
+        "node_modules/zeppelin-tabledata");
+    if (tabledataModulePath != null && !tabledataModuleInstallPath.exists()) {
+      FileUtils.copyDirectory(
+          tabledataModulePath,
+          tabledataModuleInstallPath,
+          npmPackageCopyFilter);
+    }
+
+    // install visualization module
+    File visModuleInstallPath = new File(workingDirectory,
+        "node_modules/zeppelin-vis");
+    if (visualizationModulePath != null && !visModuleInstallPath.exists()) {
+      FileUtils.copyDirectory(visualizationModulePath, visModuleInstallPath, npmPackageCopyFilter);
+    }
+
+    out.reset();
+    npmCommand("install");
+    npmCommand("run bundle");
+
+    File visBundleJs = new File(workingDirectory, "vis.bundle.js");
+    if (!visBundleJs.isFile()) {
+      throw new IOException(
+          "Can't create visualization bundle : \n" + new String(out.toByteArray()));
+    }
+
+    WebpackResult result = getWebpackResultFromOutput(new String(out.toByteArray()));
+    if (result.errors.length > 0) {
+      visBundleJs.delete();
+      throw new IOException(result.errors[0]);
+    }
+
+    synchronized (this) {
+      currentBundle.delete();
+      FileUtils.moveFile(visBundleJs, currentBundle);
+      bundleCacheKey = dependencies.toString();
+    }
+    return currentBundle;
+  }
+
+  private WebpackResult getWebpackResultFromOutput(String output) {
+    BufferedReader reader = new BufferedReader(new StringReader(output));
+
+    String line;
+    boolean webpackRunDetected = false;
+    boolean resultJsonDetected = false;
+    StringBuffer sb = new StringBuffer();
+    try {
+      while ((line = reader.readLine()) != null) {
+        if (!webpackRunDetected) {
+          if (line.contains("webpack.js") && line.endsWith("--json")) {
+            webpackRunDetected = true;
+          }
+          continue;
+        }
+
+        if (!resultJsonDetected) {
+          if (line.equals("{")) {
+            sb.append(line);
+            resultJsonDetected = true;
+          }
+          continue;
+        }
+
+        if (resultJsonDetected && webpackRunDetected) {
+          sb.append(line);
+        }
+      }
+
+      Gson gson = new Gson();
+      return gson.fromJson(sb.toString(), WebpackResult.class);
+    } catch (IOException e) {
+      logger.error(e.getMessage(), e);
+      return new WebpackResult();
+    }
+  }
+
+  public File getCurrentBundle() {
+    synchronized (this) {
+      if (currentBundle.isFile()) {
+        return currentBundle;
+      } else {
+        return null;
+      }
+    }
+  }
+
+  private boolean isLocalPackage(HeliumPackage pkg) {
+    return (pkg.getArtifact().startsWith(".") || pkg.getArtifact().startsWith("/"));
+  }
+
+  private String[] getNpmModuleNameAndVersion(HeliumPackage pkg) {
+    String artifact = pkg.getArtifact();
+
+    if (isLocalPackage(pkg)) {
+      File packageJson = new File(artifact, "package.json");
+      if (!packageJson.isFile()) {
+        return null;
+      }
+      Gson gson = new Gson();
+      try {
+        NpmPackage npmPackage = gson.fromJson(
+            FileUtils.readFileToString(packageJson),
+            NpmPackage.class);
+
+        String[] nameVersion = new String[2];
+        nameVersion[0] = npmPackage.name;
+        nameVersion[1] = npmPackage.version;
+        return nameVersion;
+      } catch (IOException e) {
+        logger.error(e.getMessage(), e);
+        return null;
+      }
+    } else {
+      String[] nameVersion = new String[2];
+
+      int pos;
+      if ((pos = artifact.indexOf('@')) > 0) {
+        nameVersion[0] = artifact.substring(0, pos);
+        nameVersion[1] = artifact.substring(pos + 1);
+      } else if (
+          (pos = artifact.indexOf('^')) > 0 ||
+              (pos = artifact.indexOf('~')) > 0) {
+        nameVersion[0] = artifact.substring(0, pos);
+        nameVersion[1] = artifact.substring(pos);
+      } else {
+        nameVersion[0] = artifact;
+        nameVersion[1] = "";
+      }
+      return nameVersion;
+    }
+  }
+
+  public synchronized void install(HeliumPackage pkg) throws TaskRunnerException {
+    npmCommand("install " + pkg.getArtifact());
+  }
+
+  private void npmCommand(String args) throws TaskRunnerException {
+    npmCommand(args, new HashMap<String, String>());
+  }
+
+  private void npmCommand(String args, Map<String, String> env) throws TaskRunnerException {
+    NpmRunner npm = frontEndPluginFactory.getNpmRunner(getProxyConfig(), DEFAULT_NPM_REGISTRY_URL);
+
+    npm.execute(args, env);
+  }
+
+  private void configureLogger() {
+    org.apache.log4j.Logger npmLogger = org.apache.log4j.Logger.getLogger(
+        "com.github.eirslett.maven.plugins.frontend.lib.DefaultNpmRunner");
+    Enumeration appenders = org.apache.log4j.Logger.getRootLogger().getAllAppenders();
+
+    if (appenders != null) {
+      while (appenders.hasMoreElements()) {
+        Appender appender = (Appender) appenders.nextElement();
+        appender.addFilter(new Filter() {
+
+          @Override
+          public int decide(LoggingEvent loggingEvent) {
+            if (loggingEvent.getLoggerName().contains("DefaultNpmRunner")) {
+              return DENY;
+            } else {
+              return NEUTRAL;
+            }
+          }
+        });
+      }
+    }
+    npmLogger.addAppender(new WriterAppender(
+        new PatternLayout("%m%n"),
+        out
+    ));
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java
new file mode 100644
index 0000000..271f73f
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+package org.apache.zeppelin.helium;
+
+import java.util.Map;
+
+/**
+ * To read package.json
+ */
+public class NpmPackage {
+  public String name;
+  public String version;
+  public Map<String, String> dependencies;
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java
new file mode 100644
index 0000000..3414209
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+package org.apache.zeppelin.helium;
+
+/**
+ * Represetns webpack json format result
+ */
+public class WebpackResult {
+  public final String [] errors = new String[0];
+  public final String [] warnings = new String[0];
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/resources/helium/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/resources/helium/package.json b/zeppelin-zengine/src/main/resources/helium/package.json
new file mode 100644
index 0000000..e6ec612
--- /dev/null
+++ b/zeppelin-zengine/src/main/resources/helium/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "zeppelin-vis-bundle",
+  "main": "load",
+  "scripts": {
+    "bundle": "node/node node_modules/webpack/bin/webpack.js --display-error-details --json"
+  },
+  "dependencies": {
+    DEPENDENCIES
+  },
+  "devDependencies": {
+    "webpack": "^1.12.2",
+    "babel": "^5.8.23",
+    "babel-loader": "^5.3.2"
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/main/resources/helium/webpack.config.js
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/resources/helium/webpack.config.js b/zeppelin-zengine/src/main/resources/helium/webpack.config.js
new file mode 100644
index 0000000..80b8c6a
--- /dev/null
+++ b/zeppelin-zengine/src/main/resources/helium/webpack.config.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+module.exports = {
+    entry: ['./'],
+    output: {
+        path: './',
+        filename: 'vis.bundle.js',
+    },
+    resolve: {
+        root: __dirname + "/node_modules",
+        extensions: [".js"]
+    },
+    module: {
+        loaders: [{
+            test: /\.js$/,
+            //exclude: /node_modules/,
+            loader: 'babel-loader'
+        }]
+    }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
index 73c78d3..2588c4c 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
@@ -134,7 +134,8 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
         "desc1",
         "",
         HeliumTestApplication.class.getName(),
-        new String[][]{});
+        new String[][]{},
+        "", "");
 
     Note note1 = notebook.createNote(anonymous);
     factory.setInterpreters("user", note1.getId(),factory.getDefaultInterpreterSettingList());
@@ -179,7 +180,8 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
         "desc1",
         "",
         HeliumTestApplication.class.getName(),
-        new String[][]{});
+        new String[][]{},
+        "", "");
 
     Note note1 = notebook.createNote(anonymous);
     factory.setInterpreters("user", note1.getId(), factory.getDefaultInterpreterSettingList());
@@ -218,7 +220,8 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
         "desc1",
         "",
         HeliumTestApplication.class.getName(),
-        new String[][]{});
+        new String[][]{},
+        "", "");
 
     Note note1 = notebook.createNote(anonymous);
     notebook.bindInterpretersToNote("user", note1.getId(), factory.getDefaultInterpreterSettingList());
@@ -278,7 +281,8 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
         "desc1",
         "",
         HeliumTestApplication.class.getName(),
-        new String[][]{});
+        new String[][]{},
+        "", "");
 
     Note note1 = notebook.createNote(anonymous);
     notebook.bindInterpretersToNote("user", note1.getId(), factory.getDefaultInterpreterSettingList());

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java
index e90cbc3..03d77b7 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java
@@ -55,7 +55,9 @@ public class HeliumLocalRegistryTest {
         "desc1",
         "artifact1",
         "classname1",
-        new String[][]{});
+        new String[][]{},
+        "license",
+        "");
     FileUtils.writeStringToFile(new File(r1Path, "pkg1.json"), gson.toJson(pkg1));
 
     // then

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java
index 1ed31c5..7c168d0 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.zeppelin.helium;
 
+import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException;
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
 import org.junit.Before;
@@ -48,10 +49,11 @@ public class HeliumTest {
   }
 
   @Test
-  public void testSaveLoadConf() throws IOException, URISyntaxException {
+  public void testSaveLoadConf() throws IOException, URISyntaxException, TaskRunnerException {
     // given
     File heliumConf = new File(tmpDir, "helium.conf");
-    Helium helium = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath());
+    Helium helium = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath(),
+        null, null);
     assertFalse(heliumConf.exists());
     HeliumTestRegistry registry1 = new HeliumTestRegistry("r1", "r1");
     helium.addRegistry(registry1);
@@ -65,14 +67,16 @@ public class HeliumTest {
     assertTrue(heliumConf.exists());
 
     // then
-    Helium heliumRestored = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath());
+    Helium heliumRestored = new Helium(
+        heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath(), null, null);
     assertEquals(2, heliumRestored.getAllRegistry().size());
   }
 
   @Test
-  public void testRestoreRegistryInstances() throws IOException, URISyntaxException {
+  public void testRestoreRegistryInstances() throws IOException, URISyntaxException, TaskRunnerException {
     File heliumConf = new File(tmpDir, "helium.conf");
-    Helium helium = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath());
+    Helium helium = new Helium(
+        heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath(), null, null);
     HeliumTestRegistry registry1 = new HeliumTestRegistry("r1", "r1");
     HeliumTestRegistry registry2 = new HeliumTestRegistry("r2", "r2");
     helium.addRegistry(registry1);
@@ -85,7 +89,9 @@ public class HeliumTest {
         "desc1",
         "artifact1",
         "className1",
-        new String[][]{}));
+        new String[][]{},
+        "",
+        ""));
 
     registry2.add(new HeliumPackage(
         HeliumPackage.Type.APPLICATION,
@@ -93,7 +99,9 @@ public class HeliumTest {
         "desc2",
         "artifact2",
         "className2",
-        new String[][]{}));
+        new String[][]{},
+        "",
+        ""));
 
     // then
     assertEquals(2, helium.getAllPackageInfo().size());

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java
new file mode 100644
index 0000000..47af409
--- /dev/null
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+package org.apache.zeppelin.helium;
+
+import com.github.eirslett.maven.plugins.frontend.lib.InstallationException;
+import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException;
+import com.google.common.io.Resources;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class HeliumVisualizationFactoryTest {
+  private File tmpDir;
+  private HeliumVisualizationFactory hvf;
+
+  @Before
+  public void setUp() throws InstallationException, TaskRunnerException {
+    tmpDir = new File(System.getProperty("java.io.tmpdir") + "/ZeppelinLTest_" + System.currentTimeMillis());
+    tmpDir.mkdirs();
+
+    // get module dir
+    URL res = Resources.getResource("helium/webpack.config.js");
+    String resDir = new File(res.getFile()).getParent();
+    File moduleDir = new File(resDir + "/../../../../zeppelin-web/src/app/");
+
+    hvf = new HeliumVisualizationFactory(tmpDir,
+        new File(moduleDir, "tabledata"),
+        new File(moduleDir, "visualization"));
+  }
+
+  @After
+  public void tearDown() throws IOException {
+    FileUtils.deleteDirectory(tmpDir);
+  }
+
+  @Test
+  public void testInstallNpm() throws InstallationException {
+    assertTrue(new File(tmpDir, "vis/node/npm").isFile());
+    assertTrue(new File(tmpDir, "vis/node/node").isFile());
+  }
+
+  @Test
+  public void downloadPackage() throws TaskRunnerException {
+    HeliumPackage pkg = new HeliumPackage(
+        HeliumPackage.Type.VISUALIZATION,
+        "lodash",
+        "lodash",
+        "lodash@3.9.3",
+        "",
+        null,
+        "license",
+        "icon"
+    );
+    hvf.install(pkg);
+    assertTrue(new File(tmpDir, "vis/node_modules/lodash").isDirectory());
+  }
+
+  @Test
+  public void bundlePackage() throws IOException, TaskRunnerException {
+    HeliumPackage pkg = new HeliumPackage(
+        HeliumPackage.Type.VISUALIZATION,
+        "zeppelin-bubblechart",
+        "zeppelin-bubblechart",
+        "zeppelin-bubblechart@0.0.3",
+        "",
+        null,
+        "license",
+        "icon"
+    );
+    List<HeliumPackage> pkgs = new LinkedList<>();
+    pkgs.add(pkg);
+    File bundle = hvf.bundle(pkgs);
+    assertTrue(bundle.isFile());
+    long lastModified = bundle.lastModified();
+
+    // bundle again and check if it served from cache
+    bundle = hvf.bundle(pkgs);
+    assertEquals(lastModified, bundle.lastModified());
+  }
+
+
+  @Test
+  public void bundleLocalPackage() throws IOException, TaskRunnerException {
+    URL res = Resources.getResource("helium/webpack.config.js");
+    String resDir = new File(res.getFile()).getParent();
+    String localPkg = resDir + "/../../../src/test/resources/helium/vis1";
+
+    HeliumPackage pkg = new HeliumPackage(
+        HeliumPackage.Type.VISUALIZATION,
+        "vis1",
+        "vis1",
+        localPkg,
+        "",
+        null,
+        "license",
+        "fa fa-coffee"
+    );
+    List<HeliumPackage> pkgs = new LinkedList<>();
+    pkgs.add(pkg);
+    File bundle = hvf.bundle(pkgs);
+    assertTrue(bundle.isFile());
+  }
+
+  @Test
+  public void bundleErrorPropagation() throws IOException, TaskRunnerException {
+    URL res = Resources.getResource("helium/webpack.config.js");
+    String resDir = new File(res.getFile()).getParent();
+    String localPkg = resDir + "/../../../src/test/resources/helium/vis2";
+
+    HeliumPackage pkg = new HeliumPackage(
+        HeliumPackage.Type.VISUALIZATION,
+        "vis2",
+        "vis2",
+        localPkg,
+        "",
+        null,
+        "license",
+        "fa fa-coffee"
+    );
+    List<HeliumPackage> pkgs = new LinkedList<>();
+    pkgs.add(pkg);
+    File bundle = null;
+    try {
+      bundle = hvf.bundle(pkgs);
+      // should throw exception
+      assertTrue(false);
+    } catch (IOException e) {
+      assertTrue(e.getMessage().contains("error in the package"));
+    }
+    assertNull(bundle);
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/resources/helium/vis1/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/resources/helium/vis1/package.json b/zeppelin-zengine/src/test/resources/helium/vis1/package.json
new file mode 100644
index 0000000..481ad58
--- /dev/null
+++ b/zeppelin-zengine/src/test/resources/helium/vis1/package.json
@@ -0,0 +1,12 @@
+{
+  "name": "vis1",
+  "version": "1.0.0",
+  "description": "",
+  "main": "vis1",
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "zeppelin-tabledata": "*",
+    "zeppelin-vis": "*"
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js b/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js
new file mode 100644
index 0000000..98bd552
--- /dev/null
+++ b/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 Visualization from 'zeppelin-vis'
+import PassthroughTransformation from 'zeppelin-tabledata/passthrough'
+
+/**
+ * Base class for visualization
+ */
+export default class vis1 extends Visualization {
+  constructor(targetEl, config) {
+    super(targetEl, config)
+    this.passthrough = new PassthroughTransformation(config);
+    console.log('passthrough %o', this.passthrough);
+  }
+
+  render(tableData) {
+    this.targetEl.html('Vis1')
+  }
+
+  getTransformation() {
+    return this.passthrough
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/resources/helium/vis2/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/resources/helium/vis2/package.json b/zeppelin-zengine/src/test/resources/helium/vis2/package.json
new file mode 100644
index 0000000..b45ee6b
--- /dev/null
+++ b/zeppelin-zengine/src/test/resources/helium/vis2/package.json
@@ -0,0 +1,12 @@
+{
+  "name": "vis2",
+  "version": "1.0.0",
+  "description": "",
+  "main": "vis2",
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "zeppelin-tabledata": "*",
+    "zeppelin-vis": "*"
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js b/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js
new file mode 100644
index 0000000..e9cd324
--- /dev/null
+++ b/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 Visualization from 'zeppelin-vis'
+import PassthroughTransformation from 'zeppelin-tabledata/passthrough'
+
+/**
+ * Base class for visualization
+ */
+export default class vis2 extends Visualization {
+  constructor(targetEl, config) {
+    super(targetEl, config)
+    this.passthrough = new PassthroughTransformation(config);
+
+  }
+
+  render(tableData) {
+    this.targetEl.html('Vis2')
+    error in the package
+  }
+
+  getTransformation() {
+    return this.passthrough
+  }
+}


[2/2] zeppelin git commit: [ZEPPELIN-1619] Load js package as a plugin visualization

Posted by mo...@apache.org.
[ZEPPELIN-1619] Load js package as a plugin visualization

### What is this PR for?
Current helium plugin application api (experimental) requires create library in java class, and need to create both backend / frontend code in the package. Which is good if your plugin requires both frontend and backend code running.

However, when user just want to make new visualization which completely runs on front-end side in javascript, creating helium application in java project and taking care of backend code can be bit of overhead and barrier for javascript developers.

This PR adds capability to load pure javascript package as a visualization.

### how it works

1. create (copy, download) 'helium package json' file into `ZEPPELIN_HOME/helium` directory.
  The json file point visualization js package in npm repository or local file system in `artifact` field.
  `type` field in the json file need to be `VISUALIZATION`

Here's an example (zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json)
```
{
  "type" : "VISUALIZATION",
  "name" : "zeppelin_horizontalbar",
  "description" : "Horizontal Bar chart (example)",
  "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar",
  "icon" : "<i class='fa fa-bar-chart rotate90flipX'></i>"
}
```

2. Go to helium GUI menu. (e.g. http://localhost:8080/#/helium).
  The menu will list all available packages.
<img width="796" alt="writing_visualization_helium_menu" src="https://cloud.githubusercontent.com/assets/1540981/21749660/0f401c10-d558-11e6-9961-b6d0a9c023d8.png">

3. click 'enable' in any package want to use.
Once a visualization package is enabled, `HeliumVisualizationFactory` will collect all enabled visualizations and create js bundle on the fly.

4. js bundle will be loaded on notebook and additional visualization becomes available
![image](https://cloud.githubusercontent.com/assets/1540981/21749729/709b2b3e-d559-11e6-8318-7f2871e7c39a.png)

### Programming API to create new plugin visualization.

Simply extends [visualization.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js) and overrides some methods, such as

```
  /**
   * get transformation
   */
  getTransformation() {
    // override this
  };

  /**
   * Method will be invoked when data or configuration changed
   */
  render(tableData) {
    // override this
  };

  /**
   * Refresh visualization.
   */
  refresh() {
    // override this
  };

  /**
   * method will be invoked when visualization need to be destroyed.
   * Don't need to destroy this.targetEl.
   */
  destroy() {
    // override this
  };

  /**
   * return {
   *   template : angular template string or url (url should end with .html),
   *   scope : an object to bind to template scope
   * }
   */
  getSetting() {
    // override this
  };
```

This is exactly the same api that built-in visualization uses.

an example implementation included `zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js`.
Actually [all built-in visualizations](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization/builtins) are example

### Packaging and publishing visualization

Each visualization will need `package.json` file (e.g. `zeppelin-examples/zeppelin-example-horizontalbar/package.json`) to be packaged.
Package can be published in npm repository or package can be deployed to the local filesystem.

`zeppelin-examples/zeppelin-example-horizontalbar/` is an example package that is deployed in the local filesystem

### Development mode

First, locally install and enable your development package by setting `artifact` field to the development directory.
Then run zeppelin-web in visualization development mode with following command
```
cd zeppelin-web
npm run visdev
```
When you have change in your local development package, just reload your notebook. Then Zeppelin will automatically rebuild / reload the package.

Any feedback would be appreciated!

### What type of PR is it?
Feature

### Todos
* [x] - Load plugin visualization js package on runtime
* [x] - Make the feature works in zeppelin Binary package
* [x] - Show loading indicator while 'enable' / 'disable' package
* [x] - Add document
* [x] - Add license of new dependency
* [x] - Development mode
* [x] - Propagate error to front-end
* [x] - Display multiple versions of a package.

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-1619

### How should this be tested?
Build Zeppelin with `-Pexamples` flag. That'll install example visualization package `horizontalbar`.
You'll able to select `horizontalbar` along with other built-in visualizations
![image](https://cloud.githubusercontent.com/assets/1540981/21655057/27d61740-d26d-11e6-88f2-02c653e102c6.png)

To test npm online package install capability,  Place [zeppelin-bubble.json](https://github.com/Leemoonsoo/zeppelin-bubble/blob/master/zeppelin-bubble.json) in hour local registry (`ZEPPELIN_HOME/helium`) and enable it in Helium gui menu.
And then zeppelin will download package from npm repository and load.
![bubblechart](https://cloud.githubusercontent.com/assets/1540981/21749717/280aa430-d559-11e6-9209-889a4f86d7e2.gif)

### Questions:
* Does the licenses files need update? yes
* Is there breaking changes for older versions? no
* Does this needs documentation? yes

Author: Lee moon soo <mo...@apache.org>

Closes #1842 from Leemoonsoo/ZEPPELIN-1619-rebased and squashes the following commits:

7c49bbb [Lee moon soo] Let Zeppelin continue to bootstrap on offline
816bdec [Lee moon soo] Display license of package when enabling
28fb37d [Lee moon soo] beautifulize helium menu
295768e [Lee moon soo] fix drag and drop visualization reorder
bb304db [Lee moon soo] Sort version in decreasing order
e7f18f1 [Lee moon soo] fix english in docs and labels
c7b187f [Lee moon soo] Merge branch 'master' into ZEPPELIN-1619-rebased
4c87983 [Lee moon soo] Merge remote-tracking branch 'apache-github/master' into ZEPPELIN-1619-rebased
ecd925b [Lee moon soo] Merge remote-tracking branch 'apache-github/master' into ZEPPELIN-1619-rebased
a92cadd [Lee moon soo] Use minifiable syntax
cec534c [Lee moon soo] Reduce log message
f373f1d [Lee moon soo] Ignore removed package
e18d9a4 [Lee moon soo] Ability to customize order of visualization package display
cd74396 [Lee moon soo] Add rest api doc
9de5d6d [Lee moon soo] exclude .npmignore and package.json from zeppelin-web rat check
08abded [Lee moon soo] exclude package.json from rat check
661c26b [Lee moon soo] update screenshot and keep experimental tag only in docs
4515805 [Lee moon soo] Display multiple versions of a package
408c512 [Lee moon soo] Make unittest test bundling with proper vis package on npm registry
fb7a147 [Lee moon soo] display svg icon
47de6d9 [Lee moon soo] Propagate bundle error to the front-end
0fe5e00 [Lee moon soo] visualization development mode
022e8f6 [Lee moon soo] exclude zeppelin-examples/zeppelin-example-horizontalbar/package.json file from rat check
2ef3b69 [Lee moon soo] Add new dependency license
f943d33 [Lee moon soo] Add doc
f494dbd [Lee moon soo] package npm dependency module in binary package
b655fa6 [Lee moon soo] use any version of dependency in example. so zeppelin version bumpup doesn't need to take care of them
2aec52d [Lee moon soo] show loading indicator while enable/disable package
6c380f6 [Lee moon soo] refactor code to fix HeliumTest
e142336 [Lee moon soo] update unittest
7d5e0ae [Lee moon soo] Resolve dependency conflict
c50a524 [Lee moon soo] Add conf/helium.json in .gitignore
1c7b73a [Lee moon soo] add result.css
d2823ad [Lee moon soo] load visualization and tabledata module from source instead npm if accessible
4e1b061 [Lee moon soo] Convert horizontalbar to VISUALIZATION example
a5a935b [Lee moon soo] connect visualization factory with restapi
4b21252 [Lee moon soo] initial implementation of helium menu
0c4da2e [Lee moon soo] pass bundled visualization to result.controller.js
f5ce99e [Lee moon soo] import helium service js
1663582 [Lee moon soo] initial implementation of helium menu
74d52d4 [Lee moon soo] bundle visualization package from npm repository on the fly


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/300f7532
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/300f7532
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/300f7532

Branch: refs/heads/master
Commit: 300f7532342d1ea47b85d3b777a8797a3e2248d4
Parents: 00742ff
Author: Lee moon soo <mo...@apache.org>
Authored: Thu Jan 12 10:58:06 2017 -0800
Committer: Lee moon soo <mo...@apache.org>
Committed: Sat Jan 14 10:27:17 2017 -0800

----------------------------------------------------------------------
 .gitignore                                      |   1 +
 .travis.yml                                     |   3 +-
 docs/_includes/themes/zeppelin/_navigation.html |   4 +-
 .../docs-img/writing_visualization_example.png  | Bin 0 -> 80870 bytes
 .../writing_visualization_helium_menu.png       | Bin 0 -> 177591 bytes
 .../development/writingzeppelinvisualization.md | 212 +++++++++++
 docs/rest-api/rest-helium.md                    | 378 +++++++++++++++++++
 pom.xml                                         |   3 +-
 .../src/assemble/distribution.xml               |   8 +
 zeppelin-distribution/src/bin_license/LICENSE   |   2 +
 .../zeppelin-example-clock.json                 |   1 +
 .../horizontalbar.js                            |  77 ++++
 .../zeppelin-example-horizontalbar/package.json |  12 +
 .../app/horizontalbar/HorizontalBar.java        |  85 -----
 .../example/app/horizontalbar/horizontalbar.js  |  56 ---
 .../horizontalbar/horizontalbar_mockdata.txt    |  10 -
 .../zeppelin-example-horizontalbar.json         |  11 +-
 .../apache/zeppelin/helium/HeliumPackage.java   |  14 +-
 .../zeppelin/helium/ApplicationLoaderTest.java  |   4 +-
 zeppelin-server/pom.xml                         |   5 +-
 .../org/apache/zeppelin/rest/HeliumRestApi.java |  95 ++++-
 .../apache/zeppelin/server/ZeppelinServer.java  |  47 ++-
 zeppelin-web/.eslintrc                          |   3 +-
 zeppelin-web/package.json                       |   2 +
 zeppelin-web/pom.xml                            |   3 +-
 zeppelin-web/src/app/app.js                     |  22 +-
 .../src/app/helium/helium.controller.js         | 219 +++++++++++
 zeppelin-web/src/app/helium/helium.css          | 107 ++++++
 zeppelin-web/src/app/helium/helium.html         |  86 +++++
 .../paragraph/result/result-chart-selector.html |   5 +-
 .../paragraph/result/result.controller.js       |  37 +-
 .../app/notebook/paragraph/result/result.css    |  24 +-
 zeppelin-web/src/app/tabledata/.npmignore       |   1 +
 zeppelin-web/src/app/tabledata/package.json     |  15 +
 zeppelin-web/src/app/visualization/.npmignore   |   1 +
 zeppelin-web/src/app/visualization/package.json |  16 +
 .../src/components/helium/helium.service.js     |  62 +++
 zeppelin-web/src/components/navbar/navbar.html  |   1 +
 zeppelin-web/src/index.html                     |   1 +
 zeppelin-web/src/index.js                       |   2 +
 zeppelin-web/webpack.config.js                  |   9 +-
 zeppelin-zengine/pom.xml                        |  22 +-
 .../java/org/apache/zeppelin/helium/Helium.java | 221 ++++++++++-
 .../org/apache/zeppelin/helium/HeliumConf.java  |  42 ++-
 .../helium/HeliumPackageSearchResult.java       |   8 +-
 .../helium/HeliumVisualizationFactory.java      | 351 +++++++++++++++++
 .../org/apache/zeppelin/helium/NpmPackage.java  |  28 ++
 .../apache/zeppelin/helium/WebpackResult.java   |  25 ++
 .../src/main/resources/helium/package.json      |  15 +
 .../src/main/resources/helium/webpack.config.js |  34 ++
 .../helium/HeliumApplicationFactoryTest.java    |  12 +-
 .../helium/HeliumLocalRegistryTest.java         |   4 +-
 .../org/apache/zeppelin/helium/HeliumTest.java  |  22 +-
 .../helium/HeliumVisualizationFactoryTest.java  | 157 ++++++++
 .../src/test/resources/helium/vis1/package.json |  12 +
 .../src/test/resources/helium/vis1/vis1.js      |  37 ++
 .../src/test/resources/helium/vis2/package.json |  12 +
 .../src/test/resources/helium/vis2/vis2.js      |  38 ++
 58 files changed, 2453 insertions(+), 231 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 2947228..5b638fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ conf/interpreter.json
 conf/notebook-authorization.json
 conf/shiro.ini
 conf/credentials.json
+conf/helium.json
 
 # other generated files
 spark/dependency-reduced-pom.xml

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 1e046c4..48e8aa7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -106,8 +106,7 @@ after_success:
 
 after_failure:
   - echo "Travis exited with ${TRAVIS_TEST_RESULT}"
-  - cat target/rat.txt
-  - cat zeppelin-server/target/rat.txt
+  - find . -name rat.txt | xargs cat
   - cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.log
   - cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.out
   - cat zeppelin-web/npm-debug.log

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/docs/_includes/themes/zeppelin/_navigation.html
----------------------------------------------------------------------
diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html
index 42b38f4..b13ef68 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -103,6 +103,7 @@
                 <li><a href="{{BASE_PATH}}/rest-api/rest-notebook.html">Notebook API</a></li>
                 <li><a href="{{BASE_PATH}}/rest-api/rest-configuration.html">Configuration API</a></li>
                 <li><a href="{{BASE_PATH}}/rest-api/rest-credential.html">Credential API</a></li>
+                <li><a href="{{BASE_PATH}}/rest-api/rest-helium.html">Helium API</a></li>
                 <li role="separator" class="divider"></li>
                 <li class="title"><span><b>Security</b><span></li>
                 <li><a href="{{BASE_PATH}}/security/shiroauthentication.html">Shiro Authentication</a></li>                
@@ -118,7 +119,8 @@
                 <li role="separator" class="divider"></li>
                 <li class="title"><span><b>Contibute</b><span></li>
                 <li><a href="{{BASE_PATH}}/development/writingzeppelininterpreter.html">Writing Zeppelin Interpreter</a></li>
-                <li><a href="{{BASE_PATH}}/development/writingzeppelinapplication.html">Writing Zeppelin Application (Experimental)</a></li>                
+                <li><a href="{{BASE_PATH}}/development/writingzeppelinvisualization.html">Writing Zeppelin Visualization (Experimental)</a></li>
+                <li><a href="{{BASE_PATH}}/development/writingzeppelinapplication.html">Writing Zeppelin Application (Experimental)</a></li>
                 <li><a href="{{BASE_PATH}}/development/howtocontribute.html">How to contribute (code)</a></li>
                 <li><a href="{{BASE_PATH}}/development/howtocontributewebsite.html">How to contribute (website)</a></li>
               </ul>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png b/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png
new file mode 100644
index 0000000..219d7c8
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_helium_menu.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_helium_menu.png b/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_helium_menu.png
new file mode 100644
index 0000000..7e1ce20
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_helium_menu.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/docs/development/writingzeppelinvisualization.md
----------------------------------------------------------------------
diff --git a/docs/development/writingzeppelinvisualization.md b/docs/development/writingzeppelinvisualization.md
new file mode 100644
index 0000000..d7c2268
--- /dev/null
+++ b/docs/development/writingzeppelinvisualization.md
@@ -0,0 +1,212 @@
+---
+layout: page
+title: "Writing a new Visualization(Experimental)"
+description: "Apache Zeppelin Application is a package that runs on Interpreter process and displays it's output inside of the notebook. Make your own Application in Apache Zeppelin is quite easy."
+group: development
+---
+<!--
+Licensed 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.
+-->
+{% include JB/setup %}
+
+# Writing a new Visualization (Experimental)
+
+<div id="toc"></div>
+
+## What is Apache Zeppelin Visualization
+
+Apache Zeppelin Visualization is a pluggable package that can be loaded/unloaded on runtime through Helium framework in Zeppelin. A Visualization is a javascript npm package and user can use them just like any other built-in visualization in notebook.
+
+
+## How it works
+
+
+#### 1. Load Helium package files from registry
+Zeppelin needs to know what Visualization packages are available. Zeppelin searches _Helium package file_ from local registry (by default helium/ directory) by default.
+_Helium package file_ provides informations like name, artifact, and so on. It's similar to _package.json_ in npm package.
+
+Here's an example `helium/zeppelin-example-horizontalbar.json`
+
+```
+{
+  "type" : "VISUALIZATION",
+  "name" : "zeppelin_horizontalbar",
+  "description" : "Horizontal Bar chart (example)",
+  "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar",
+  "license" : "Apache-2.0",
+  "icon" : "<i class='fa fa-bar-chart rotate90flipX'></i>"
+}
+```
+
+Check [Create helium package file](#3-create-helium-package-file) section to learn about it.
+
+
+#### 2. Enable packages
+Once Zeppelin loads _Helium package files_ from local registry, available packages are displayed in Helium menu.
+
+Click 'enable' button.
+
+<img class="img-responsive" style="width:70%" src="/assets/themes/zeppelin/img/docs-img/writing_visualization_helium_menu.png" />
+
+
+#### 3. Create and load visualization bundle on the fly
+
+Once a Visualization package is enabled, [HeliumVisualizationFactory](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationPackage.java) creates a js bundle. The js bundle is served by `helium/visualization/load` rest api endpoint.
+
+
+#### 4. Run visualization
+
+Zeppelin shows additional button for loaded Visualizations.
+User can use just like any other built-in visualizations.
+
+<img class="img-responsive" style="width:70%" src="/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png" />
+
+
+
+## Write new Visualization
+
+#### 1. Create a npm package
+
+Create a [package.json](https://docs.npmjs.com/files/package.json) in your new Visualization directory. Normally, you can add any dependencies in package.json however Zeppelin Visualization package only allows two dependencies: [zeppelin-vis](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization) and [zeppelin-tabledata](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/tabledata).
+
+Here's an example
+
+```
+{
+  "name": "zeppelin_horizontalbar",
+  "description" : "Horizontal Bar chart",
+  "version": "1.0.0",
+  "main": "horizontalbar",
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "zeppelin-tabledata": "*",
+    "zeppelin-vis": "*"
+  }
+}
+```
+
+#### 2. Create your own visualization
+
+To create your own visualization, you need to create a js file and import [Visualization](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js) class from [zeppelin-vis](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization) package and extend the class. [zeppelin-tabledata](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/tabledata) package provides some useful transformations, like pivot, you can use in your visualization. (you can create your own transformation, too).
+
+[Visualization](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js) class, there're several methods that you need to override and implement. Here's simple visualization that just prints `Hello world`.
+
+```
+import Visualization from 'zeppelin-vis'
+import PassthroughTransformation from 'zeppelin-tabledata/passthrough'
+
+export default class helloworld extends Visualization {
+  constructor(targetEl, config) {
+    super(targetEl, config)
+    this.passthrough = new PassthroughTransformation(config);
+  }
+
+  render(tableData) {
+    this.targetEl.html('Hello world!')
+  }
+
+  getTransformation() {
+    return this.passthrough
+  }
+}
+```
+
+To learn more about `Visualization` class, check [visualization.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js).
+
+You can check complete visualization package example [here](https://github.com/apache/zeppelin/tree/master/zeppelin-examples/zeppelin-example-horizontalbar).
+
+Zeppelin's built-in visualization uses the same API, so you can check [built-in visualizations](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization/builtins) as additional examples.
+
+
+#### 3. Create __Helium package file__
+
+__Helium Package file__ is a json file that provides information about the application.
+Json file contains the following information
+
+```
+{
+  "type" : "VISUALIZATION",
+  "name" : "zeppelin_horizontalbar",
+  "description" : "Horizontal Bar chart (example)",
+  "license" : "Apache-2.0",
+  "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar",
+  "icon" : "<i class='fa fa-bar-chart rotate90flipX'></i>"
+}
+```
+
+##### type
+
+When you're creating a visualization, 'type' should be 'VISUALIZATION'.
+Check [application](./writingzeppelinapplication.html) type if you're interested in the other types of package.
+
+##### name
+
+Name of visualization. Should be unique. Allows `[A-Za-z90-9_]`.
+
+
+##### description
+
+A short description about visualization.
+
+##### artifact
+
+Location of the visualization npm package. Support npm package with version or local filesystem path.
+
+e.g.
+
+When artifact exists in npm repository
+
+```
+artifact: "my-visualiztion@1.0.0"
+```
+
+
+When artifact exists in local file system
+
+```
+artifact: "/path/to/my/visualization"
+```
+
+##### license
+
+License information.
+
+e.g.
+
+```
+license: "Apache-2.0"
+```
+
+##### icon
+
+Icon to be used in visualization select button. String in this field will be rendered as a HTML tag.
+
+e.g.
+
+```
+icon: "<i class='fa fa-coffee'></i>"
+```
+
+
+#### 4. Run in dev mode
+
+Place your __Helium package file__ in local registry (ZEPPELIN_HOME/helium).
+Run Zeppelin. And then run zeppelin-web in visualization dev mode.
+
+```
+cd zeppelin-web
+yarn run visdev
+```
+
+You can browse localhost:9000. Everytime refresh your browser, Zeppelin will rebuild your visualization and reload changes.

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/docs/rest-api/rest-helium.md
----------------------------------------------------------------------
diff --git a/docs/rest-api/rest-helium.md b/docs/rest-api/rest-helium.md
new file mode 100644
index 0000000..8d2ff4d
--- /dev/null
+++ b/docs/rest-api/rest-helium.md
@@ -0,0 +1,378 @@
+---
+layout: page
+title: "Apache Zeppelin Helium REST API"
+description: "This page contains Apache Zeppelin Helium REST API information."
+group: rest-api
+---
+<!--
+Licensed 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.
+-->
+{% include JB/setup %}
+
+# Apache Zeppelin Helium REST API
+
+<div id="toc"></div>
+
+## Overview
+Apache Zeppelin provides several REST APIs for interaction and remote activation of zeppelin functionality.
+All REST APIs are available starting with the following endpoint `http://[zeppelin-server]:[zeppelin-port]/api`. 
+Note that Apache Zeppelin REST APIs receive or return JSON objects, it is recommended for you to install some JSON viewers such as [JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc).
+
+If you work with Apache Zeppelin and find a need for an additional REST API, please [file an issue or send us an email](http://zeppelin.apache.org/community.html).
+
+## Helium REST API List
+
+### List of all available helium packages
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method returns all the available helium packages in configured registries.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/all```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <pre>
+{
+  "status": "OK",
+  "message": "",
+  "body": {
+    "zeppelin.clock": [
+      {
+        "registry": "local",
+        "pkg": {
+          "type": "APPLICATION",
+          "name": "zeppelin.clock",
+          "description": "Clock (example)",
+          "artifact": "zeppelin-examples\/zeppelin-example-clock\/target\/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
+          "className": "org.apache.zeppelin.example.app.clock.Clock",
+          "resources": [
+            [
+              ":java.util.Date"
+            ]
+          ],
+          "icon": "icon"
+        },
+        "enabled": false
+      }
+    ],
+    "zeppelin-bubblechart": [
+      {
+        "registry": "local",
+        "pkg": {
+          "type": "VISUALIZATION",
+          "name": "zeppelin-bubblechart",
+          "description": "Animated bubble chart",
+          "artifact": ".\/..\/helium\/zeppelin-bubble",
+          "icon": "icon"
+        },
+        "enabled": true
+      },
+      {
+        "registry": "local",
+        "pkg": {
+          "type": "VISUALIZATION",
+          "name": "zeppelin-bubblechart",
+          "description": "Animated bubble chart",
+          "artifact": "zeppelin-bubblechart@0.0.2",
+          "icon": "icon"
+        },
+        "enabled": false
+      }
+    ],
+    "zeppelin_horizontalbar": [
+      {
+        "registry": "local",
+        "pkg": {
+          "type": "VISUALIZATION",
+          "name": "zeppelin_horizontalbar",
+          "description": "Horizontal Bar chart (example)",
+          "artifact": ".\/zeppelin-examples\/zeppelin-example-horizontalbar",
+          "icon": "icon"
+        },
+        "enabled": true
+      }
+    ]
+  }
+}
+        </pre>
+      </td>
+    </tr>
+  </table>
+
+<br/>
+### Suggest Helium application
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method returns suggested helium application for the paragraph.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/suggest/[Note ID]/[Paragraph ID]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td>
+        404 on note or paragraph not exists <br />
+        500
+      </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <pre>
+{
+  "status": "OK",
+  "message": "",
+  "body": {
+    "available": [
+      {
+        "registry": "local",
+        "pkg": {
+          "type": "APPLICATION",
+          "name": "zeppelin.clock",
+          "description": "Clock (example)",
+          "artifact": "zeppelin-examples\/zeppelin-example-clock\/target\/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
+          "className": "org.apache.zeppelin.example.app.clock.Clock",
+          "resources": [
+            [
+              ":java.util.Date"
+            ]
+          ],
+          "icon": "icon"
+        },
+        "enabled": true
+      }
+    ]
+  }
+}
+        </pre>
+      </td>
+    </tr>
+  </table>
+  
+<br/>
+### Load helium Application on a paragraph
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method returns a helium Application id on success.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/load/[Note ID]/[Paragraph ID]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td>
+          404 on note or paragraph not exists <br/>
+          500 for any other errors
+      </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <pre>
+{
+  "status": "OK",
+  "message": "",
+  "body": "app_2C5FYRZ1E-20170108-040449_2068241472zeppelin_clock"
+}
+        </pre>
+      </td>
+    </tr>
+  </table>
+
+<br/>
+### Load bundled visualization script
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method returns bundled helium visualization javascript. When refresh=true (optional) is provided, Zeppelin rebuild bundle. otherwise, provided from cache</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizations/load[?refresh=true]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200 reponse body is executable javascript</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td>
+          200 reponse body is error message string starts with ERROR:<br/>
+      </td>
+    </tr>
+  </table>
+
+<br/>
+### Enable package
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```POST``` method enables a helium package. Needs artifact name in input payload</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/enable/[Package Name]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample input</td>
+      <td>
+        <pre>
+zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNAPSHOT.jar
+        </pre>
+      </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <pre>
+{"status":"OK"}
+        </pre>
+      </td>
+    </tr>
+  </table>
+
+<br/>
+### Disable package
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```POST``` method disables a helium package.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/disable/[Package Name]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <code>{"status":"OK"}</code>
+      </td>
+    </tr>
+  </table>
+<br />
+
+### Get visualization display order
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method returns display order of enabled visualization packages.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizationOrder```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <code>{"status":"OK","body":["zeppelin_horizontalbar","zeppelin-bubblechart"]}</code>
+      </td>
+    </tr>
+  </table>
+
+
+<br />
+
+### Set visualization display order
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```POST``` method sets visualization packages display order.</td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizationOrder```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td>Sample JSON input</td>
+      <td>
+        <code>["zeppelin-bubblechart", "zeppelin_horizontalbar"]</code>
+      </td>
+    </tr>
+    <tr>
+      <td>Sample JSON response</td>
+      <td>
+        <code>{"status":"OK"}</code>
+      </td>
+    </tr>
+  </table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index dcd359b..481015c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -940,7 +940,8 @@
               <exclude>docs/_site/**</exclude>
               <exclude>docs/Gemfile.lock</exclude>
 
-              <exclude>**/horizontalbar_mockdata.txt</exclude>
+              <!-- package.json -->
+              <exclude>**/package.json</exclude>
 
               <!-- compiled R packages (binaries) -->
               <exclude>R/lib/**</exclude>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-distribution/src/assemble/distribution.xml
----------------------------------------------------------------------
diff --git a/zeppelin-distribution/src/assemble/distribution.xml b/zeppelin-distribution/src/assemble/distribution.xml
index 371aed3..e8188e8 100644
--- a/zeppelin-distribution/src/assemble/distribution.xml
+++ b/zeppelin-distribution/src/assemble/distribution.xml
@@ -95,5 +95,13 @@
       <outputDirectory>/lib/interpreter</outputDirectory>
       <directory>../zeppelin-interpreter/target/lib</directory>
     </fileSet>
+    <fileSet>
+      <outputDirectory>/lib/node_modules/zeppelin-vis</outputDirectory>
+      <directory>../zeppelin-web/src/app/visualization</directory>
+    </fileSet>
+    <fileSet>
+      <outputDirectory>/lib/node_modules/zeppelin-tabledata</outputDirectory>
+      <directory>../zeppelin-web/src/app/tabledata</directory>
+    </fileSet>
   </fileSets>
 </assembly>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-distribution/src/bin_license/LICENSE
----------------------------------------------------------------------
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index 7cce062..1197ea7 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -214,6 +214,8 @@ The following components are provided under Apache License.
     (Apache 2.0) Maven Wagon HTTP Shared 1.0 (org.apache.maven.wagon:wagon-http-shared:1.0 - https://mvnrepository.com/artifact/org.apache.maven.wagon/wagon-http-shared/1.0)
     (Apache 2.0) Commons HTTP Client 3.1 (commons-httpclient:commons-httpclient:3.1 - https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient/3.1)
     (Apache 2.0) Scalatest 2.2.4 (org.scalatest:scalatest_2.10:2.2.4 - https://github.com/scalatest/scalatest)
+    (Apache 2.0) frontend-maven-plugin 1.3 (com.github.eirslett:frontend-maven-plugin:1.3 - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE
+    (Apache 2.0) frontend-plugin-core 1.3 (com.github.eirslett:frontend-plugin-core) - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE
 
 ========================================================================
 MIT licenses

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json
index a7526f5..3e48697 100644
--- a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json
+++ b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json
@@ -21,5 +21,6 @@
   "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
   "className" : "org.apache.zeppelin.example.app.clock.Clock",
   "resources" : [[":java.util.Date"]],
+  "license" : "Apache-2.0",
   "icon" : '<i class="fa fa-clock-o"></i>'
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js b/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js
new file mode 100644
index 0000000..d574a89
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 Nvd3ChartVisualization from 'zeppelin-vis/builtins/visualization-nvd3chart';
+import PivotTransformation from 'zeppelin-tabledata/pivot';
+
+/**
+ * Base class for visualization
+ */
+export default class horizontalbar extends Nvd3ChartVisualization {
+  constructor(targetEl, config) {
+    super(targetEl, config)
+    this.pivot = new PivotTransformation(config);
+  }
+
+  type() {
+    return 'multiBarHorizontalChart';
+  };
+
+  render(pivot) {
+    var d3Data = this.d3DataFromPivot(
+      pivot.schema,
+      pivot.rows,
+      pivot.keys,
+      pivot.groups,
+      pivot.values,
+      true,
+      false,
+      true);
+
+    super.render(d3Data);
+  }
+
+  getTransformation() {
+    return this.pivot;
+  }
+  
+  /**
+   * Set new config
+   */
+  setConfig(config) {
+    super.setConfig(config);
+    this.pivot.setConfig(config);
+  };
+
+  configureChart(chart) {
+    var self = this;
+    chart.yAxis.axisLabelDistance(50);
+    chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d);});
+
+    this.chart.stacked(this.config.stacked);
+
+    var self = this;
+    this.chart.dispatch.on('stateChange', function(s) {
+      self.config.stacked = s.stacked;
+
+      // give some time to animation finish
+      setTimeout(function() {
+        self.emitConfig(self.config);
+      }, 500);
+    });
+  };
+}
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-horizontalbar/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/package.json b/zeppelin-examples/zeppelin-example-horizontalbar/package.json
new file mode 100644
index 0000000..60121d6
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-horizontalbar/package.json
@@ -0,0 +1,12 @@
+{
+  "name": "zeppelin_horizontalbar",
+  "description" : "Horizontal Bar chart (example)",
+  "version": "1.0.0",
+  "main": "horizontalbar",
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "zeppelin-tabledata": "*",
+    "zeppelin-vis": "*"
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java b/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java
deleted file mode 100644
index 6637821..0000000
--- a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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.
- */
-package org.apache.zeppelin.example.app.horizontalbar;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.zeppelin.helium.Application;
-import org.apache.zeppelin.helium.ApplicationContext;
-import org.apache.zeppelin.helium.ApplicationException;
-import org.apache.zeppelin.helium.ZeppelinApplicationDevServer;
-import org.apache.zeppelin.interpreter.InterpreterResult;
-import org.apache.zeppelin.resource.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Random;
-
-/**
- * Basic example application.
- * TableData for input
- */
-public class HorizontalBar extends Application {
-  private final Logger logger = LoggerFactory.getLogger(HorizontalBar.class);
-
-  InterpreterResult result;
-
-  public HorizontalBar(ApplicationContext context) {
-    super(context);
-  }
-
-  @Override
-  public void run(ResourceSet resources) throws ApplicationException, IOException {
-    // Get data from resource args
-    result = (InterpreterResult) resources.get(0).get();
-
-    // create element
-    println(String.format(
-        "<div id=\"horizontalbar_%s\" style=\"height:400px\"><svg></svg></div>",
-        context().getApplicationInstanceId()));
-    // write js
-    printResourceAsJavascript("example/app/horizontalbar/horizontalbar.js");
-  }
-
-  @Override
-  public void unload() throws ApplicationException {
-  }
-
-  /**
-   * Development mode
-   */
-  public static void main(String[] args) throws Exception {
-    LocalResourcePool pool = new LocalResourcePool("dev");
-    InputStream ins = ClassLoader.getSystemResourceAsStream(
-        "example/app/horizontalbar/horizontalbar_mockdata.txt");
-    InterpreterResult result = new InterpreterResult(
-        InterpreterResult.Code.SUCCESS,
-        InterpreterResult.Type.TABLE,
-        IOUtils.toString(ins));
-    pool.put(WellKnownResourceName.ZeppelinTableResult.name(), result);
-
-    ZeppelinApplicationDevServer devServer = new ZeppelinApplicationDevServer(
-        HorizontalBar.class.getName(),
-        pool.getAll());
-
-    devServer.start();
-    devServer.join();
-  }
-}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js b/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js
deleted file mode 100644
index fac2c8e..0000000
--- a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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.
- */
-
-var data = [];
-_.forEach($z.result.columnNames, function(col, series) {
-   if (series == 0) return;
-   var values = _.map($z.result.rows, function(row) {
-       return {
-           label: row[0],
-           value : parseFloat(row[series])
-       }
-   });
-
-   data.push({
-       key : col.name,
-       values : values
-   })
-});
-
-nv.addGraph(function() {
-    var chart = nv.models.multiBarHorizontalChart()
-        .x(function(d) { return d.label })
-        .y(function(d) { return d.value })
-        .margin({top: 30, right: 20, bottom: 50, left: 175})
-        .showValues(true)           //Show bar value next to each bar.
-        .tooltips(true)             //Show tooltips on hover.
-        .showControls(true);        //Allow user to switch between "Grouped" and "Stacked" mode.
-
-    chart.yAxis
-        .tickFormat(d3.format(',.2f'));
-
-    d3.select('#horizontalbar_' + $z.id + ' svg')
-        .datum(data)
-        .call(chart);
-
-    nv.utils.windowResize(chart.update);
-
-    return chart;
-});
-
-
-

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt b/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt
deleted file mode 100644
index adf322d..0000000
--- a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Label	Series1	Series2
-GroupA	-1.8746444827653	25.307646510375
-GroupB	-8.0961543492239	16.756779544553
-GroupC	-0.57072943117674	18.451534877007
-GroupD	-2.4174010336624	8.6142352811805
-GroupE	-0.72009071426284	7.8082472075876
-GroupF	-0.77154485523777	5.259101026956
-GroupG	-0.90152097798131	0.30947953487127
-GroupH	-0.91445417330854	0
-GroupI	-0.055746319141851	0

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json b/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json
index da57d06..c8ac28c 100644
--- a/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json
+++ b/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json
@@ -15,11 +15,10 @@
  * limitations under the License.
  */
 {
-  "type" : "APPLICATION",
-  "name" : "zeppelin.horizontalbar",
+  "type" : "VISUALIZATION",
+  "name" : "zeppelin_horizontalbar",
   "description" : "Horizontal Bar chart (example)",
-  "artifact" : "zeppelin-examples/zeppelin-example-horizontalbar/target/zeppelin-example-horizontalbar-0.7.0-SNAPSHOT.jar",
-  "className" : "org.apache.zeppelin.example.app.horizontalbar.HorizontalBar",
-  "resources" : [[":org.apache.zeppelin.interpreter.InterpreterResult"]],
-  "icon" : '<i class="fa fa-bar-chart" style="-webkit-transform: rotate(90deg) scaleX(-1);-moz-transform: rotate(90deg) scaleX(-1);-ms-transform: rotate(90deg) scaleX(-1);"></i>'
+  "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar",
+  "license" : "Apache-2.0",
+  "icon" : "<i class='fa fa-bar-chart rotate90flipX'></i>"
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java
index 1352642..84a2ab3 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java
@@ -30,14 +30,17 @@ public class HeliumPackage {
   private String className;      // entry point
   private String [][] resources; // resource classnames that requires
                                  // [[ .. and .. and .. ] or [ .. and .. and ..] ..]
+  private String license;
   private String icon;
+
   /**
    * Type of package
    */
   public static enum Type {
     INTERPRETER,
     NOTEBOOK_REPO,
-    APPLICATION
+    APPLICATION,
+    VISUALIZATION
   }
 
   public HeliumPackage(Type type,
@@ -45,13 +48,17 @@ public class HeliumPackage {
                        String description,
                        String artifact,
                        String className,
-                       String[][] resources) {
+                       String[][] resources,
+                       String license,
+                       String icon) {
     this.type = type;
     this.name = name;
     this.description = description;
     this.artifact = artifact;
     this.className = className;
     this.resources = resources;
+    this.license = license;
+    this.icon = icon;
   }
 
   @Override
@@ -93,6 +100,9 @@ public class HeliumPackage {
     return resources;
   }
 
+  public String getLicense() {
+    return license;
+  }
   public String getIcon() {
     return icon;
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
index c92699c..3924e28 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
@@ -79,7 +79,9 @@ public class ApplicationLoaderTest {
         "desc1",
         artifact,
         className,
-        new String[][]{{}});
+        new String[][]{{}},
+        "license",
+        "icon");
     return app1;
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-server/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml
index ea51fa7..6503e66 100644
--- a/zeppelin-server/pom.xml
+++ b/zeppelin-server/pom.xml
@@ -279,7 +279,10 @@
           <groupId>com.jcraft</groupId>
           <artifactId>jsch</artifactId>
         </exclusion>
-
+        <exclusion>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-compress</artifactId>
+        </exclusion>
       </exclusions>
     </dependency>
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
index 062f5b9..953188b 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
@@ -17,9 +17,11 @@
 
 package org.apache.zeppelin.rest;
 
+import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException;
 import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.io.FileUtils;
 import org.apache.zeppelin.helium.Helium;
-import org.apache.zeppelin.helium.HeliumApplicationFactory;
 import org.apache.zeppelin.helium.HeliumPackage;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.Notebook;
@@ -30,6 +32,9 @@ import org.slf4j.LoggerFactory;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.Response;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
 
 /**
  * Helium Rest Api
@@ -40,18 +45,14 @@ public class HeliumRestApi {
   Logger logger = LoggerFactory.getLogger(HeliumRestApi.class);
 
   private Helium helium;
-  private HeliumApplicationFactory applicationFactory;
   private Notebook notebook;
   private Gson gson = new Gson();
 
   public HeliumRestApi() {
   }
 
-  public HeliumRestApi(Helium helium,
-                       HeliumApplicationFactory heliumApplicationFactory,
-                       Notebook notebook) {
+  public HeliumRestApi(Helium helium, Notebook notebook) {
     this.helium  = helium;
-    this.applicationFactory = heliumApplicationFactory;
     this.notebook = notebook;
   }
 
@@ -101,8 +102,88 @@ public class HeliumRestApi {
     }
     HeliumPackage pkg = gson.fromJson(heliumPackage, HeliumPackage.class);
 
-    String appId = applicationFactory.loadAndRun(pkg, paragraph);
+    String appId = helium.getApplicationFactory().loadAndRun(pkg, paragraph);
     return new JsonResponse(Response.Status.OK, "", appId).build();
   }
 
+  @GET
+  @Path("visualizations/load")
+  @Produces("text/javascript")
+  public Response visualizationLoad(@QueryParam("refresh") String refresh) {
+    try {
+      File bundle;
+      if (refresh != null && refresh.equals("true")) {
+        bundle = helium.recreateVisualizationBundle();
+      } else {
+        bundle = helium.getVisualizationFactory().getCurrentBundle();
+      }
+
+      if (bundle == null) {
+        return Response.ok().build();
+      } else {
+        String visBundle = FileUtils.readFileToString(bundle);
+        return Response.ok(visBundle).build();
+      }
+    } catch (Exception e) {
+      logger.error(e.getMessage(), e);
+
+      // returning error will prevent zeppelin front-end render any notebook.
+      // visualization load fail doesn't need to block notebook rendering work.
+      // so it's better return ok instead of any error.
+      return Response.ok("ERROR: " + e.getMessage()).build();
+    }
+  }
+
+  @POST
+  @Path("enable/{packageName}")
+  public Response enablePackage(@PathParam("packageName") String packageName,
+                                String artifact) {
+    try {
+      helium.enable(packageName, artifact);
+      return new JsonResponse(Response.Status.OK).build();
+    } catch (IOException e) {
+      logger.error(e.getMessage(), e);
+      return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
+    } catch (TaskRunnerException e) {
+      logger.error(e.getMessage(), e);
+      return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
+    }
+  }
+
+  @POST
+  @Path("disable/{packageName}")
+  public Response enablePackage(@PathParam("packageName") String packageName) {
+    try {
+      helium.disable(packageName);
+      return new JsonResponse(Response.Status.OK).build();
+    } catch (IOException e) {
+      logger.error(e.getMessage(), e);
+      return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
+    } catch (TaskRunnerException e) {
+      logger.error(e.getMessage(), e);
+      return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
+    }
+  }
+
+  @GET
+  @Path("visualizationOrder")
+  public Response getVisualizationPackageOrder() {
+    List<String> order = helium.getVisualizationPackageOrder();
+    return new JsonResponse(Response.Status.OK, order).build();
+  }
+
+  @POST
+  @Path("visualizationOrder")
+  public Response setVisualizationPackageOrder(String orderedPackageNameList) {
+    List<String> orderedList = gson.fromJson(
+        orderedPackageNameList, new TypeToken<List<String>>(){}.getType());
+
+    try {
+      helium.setVisualizationPackageOrder(orderedList);
+    } catch (IOException | TaskRunnerException e) {
+      logger.error(e.getMessage(), e);
+      return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
+    }
+    return new JsonResponse(Response.Status.OK).build();
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index 342d5f9..b173d04 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -35,6 +35,7 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.dep.DependencyResolver;
 import org.apache.zeppelin.helium.Helium;
 import org.apache.zeppelin.helium.HeliumApplicationFactory;
+import org.apache.zeppelin.helium.HeliumVisualizationFactory;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
 import org.apache.zeppelin.notebook.Notebook;
 import org.apache.zeppelin.notebook.NotebookAuthorization;
@@ -82,7 +83,6 @@ public class ZeppelinServer extends Application {
   public static Server jettyWebServer;
   public static NotebookServer notebookWsServer;
   public static Helium helium;
-  public static HeliumApplicationFactory heliumApplicationFactory;
 
   private SchedulerFactory schedulerFactory;
   private InterpreterFactory replFactory;
@@ -98,8 +98,39 @@ public class ZeppelinServer extends Application {
     this.depResolver = new DependencyResolver(
         conf.getString(ConfVars.ZEPPELIN_INTERPRETER_LOCALREPO));
 
-    this.helium = new Helium(conf.getHeliumConfPath(), conf.getHeliumDefaultLocalRegistryPath());
-    this.heliumApplicationFactory = new HeliumApplicationFactory();
+    HeliumApplicationFactory heliumApplicationFactory = new HeliumApplicationFactory();
+    HeliumVisualizationFactory heliumVisualizationFactory;
+
+    if (isBinaryPackage(conf)) {
+      /* In binary package, zeppelin-web/src/app/visualization and zeppelin-web/src/app/tabledata
+       * are copied to lib/node_modules/zeppelin-vis, lib/node_modules/zeppelin-tabledata directory.
+       * Check zeppelin/zeppelin-distribution/src/assemble/distribution.xml to see how they're
+       * packaged into binary package.
+       */
+      heliumVisualizationFactory = new HeliumVisualizationFactory(
+          new File(conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO)),
+          new File(conf.getRelativeDir("lib/node_modules/zeppelin-tabledata")),
+          new File(conf.getRelativeDir("lib/node_modules/zeppelin-vis")));
+    } else {
+      heliumVisualizationFactory = new HeliumVisualizationFactory(
+          new File(conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO)),
+          new File(conf.getRelativeDir("zeppelin-web/src/app/tabledata")),
+          new File(conf.getRelativeDir("zeppelin-web/src/app/visualization")));
+    }
+
+    this.helium = new Helium(
+        conf.getHeliumConfPath(),
+        conf.getHeliumDefaultLocalRegistryPath(),
+        heliumVisualizationFactory,
+        heliumApplicationFactory);
+
+    // create visualization bundle
+    try {
+      heliumVisualizationFactory.bundle(helium.getVisualizationPackagesToBundle());
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+    }
+
     this.schedulerFactory = new SchedulerFactory();
     this.replFactory = new InterpreterFactory(conf, notebookWsServer,
         notebookWsServer, heliumApplicationFactory, depResolver, SecurityUtils.isAuthenticated());
@@ -333,7 +364,7 @@ public class ZeppelinServer extends Application {
     NotebookRepoRestApi notebookRepoApi = new NotebookRepoRestApi(notebookRepo, notebookWsServer);
     singletons.add(notebookRepoApi);
 
-    HeliumRestApi heliumApi = new HeliumRestApi(helium, heliumApplicationFactory, notebook);
+    HeliumRestApi heliumApi = new HeliumRestApi(helium, notebook);
     singletons.add(heliumApi);
 
     InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory);
@@ -353,5 +384,13 @@ public class ZeppelinServer extends Application {
 
     return singletons;
   }
+
+  /**
+   * Check if it is source build or binary package
+   * @return
+   */
+  private static boolean isBinaryPackage(ZeppelinConfiguration conf) {
+    return !new File(conf.getRelativeDir("zeppelin-web")).isDirectory();
+  }
 }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/.eslintrc
----------------------------------------------------------------------
diff --git a/zeppelin-web/.eslintrc b/zeppelin-web/.eslintrc
index ca8cd07..b4d3909 100644
--- a/zeppelin-web/.eslintrc
+++ b/zeppelin-web/.eslintrc
@@ -26,7 +26,8 @@
     "BootstrapDialog": false,
     "Handsontable": false,
     "moment": false,
-    "zeppelin" : false
+    "zeppelin" : false,
+    "process": false
   },
   "rules": {
     "no-bitwise": 2,

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/package.json b/zeppelin-web/package.json
index 49bc046..e25f0c8 100644
--- a/zeppelin-web/package.json
+++ b/zeppelin-web/package.json
@@ -12,8 +12,10 @@
     "build": "grunt pre-webpack-dist && webpack && grunt post-webpack-dist",
     "predev": "grunt pre-webpack-dev",
     "dev:server": "webpack-dev-server --hot",
+    "visdev:server": "HELIUM_VIS_DEV=true webpack-dev-server --hot",
     "dev:watch": "grunt watch-webpack-dev",
     "dev": "npm-run-all --parallel dev:server dev:watch",
+    "visdev": "npm-run-all --parallel visdev:server dev:watch",
     "pretest": "npm install karma-phantomjs-launcher",
     "test": "karma start test/karma.conf.js"
   },

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-web/pom.xml b/zeppelin-web/pom.xml
index fc13e53..f5fd73e 100644
--- a/zeppelin-web/pom.xml
+++ b/zeppelin-web/pom.xml
@@ -92,7 +92,8 @@
             <exclude>src/fonts/source-code-pro*</exclude>
             <exclude>src/fonts/google-fonts.css</exclude>
             <exclude>bower.json</exclude>
-            <exclude>package.json</exclude>
+            <exclude>**/package.json</exclude>
+            <exclude>**/.npmignore</exclude>
             <exclude>yarn.lock</exclude>
             <exclude>*.md</exclude>
           </excludes>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/app.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js
index fcfed28..f68b3da 100644
--- a/zeppelin-web/src/app/app.js
+++ b/zeppelin-web/src/app/app.js
@@ -46,25 +46,35 @@ var zeppelinWebApp = angular.module('zeppelinWebApp', [
     // withCredentials when running locally via grunt
     $httpProvider.defaults.withCredentials = true;
 
+    var visBundleLoad = {
+      load: ['heliumService', function(heliumService) {
+        return heliumService.load;
+      }]
+    };
+
     $routeProvider
       .when('/', {
         templateUrl: 'app/home/home.html'
       })
       .when('/notebook/:noteId', {
         templateUrl: 'app/notebook/notebook.html',
-        controller: 'NotebookCtrl'
+        controller: 'NotebookCtrl',
+        resolve: visBundleLoad
       })
       .when('/notebook/:noteId/paragraph?=:paragraphId', {
         templateUrl: 'app/notebook/notebook.html',
-        controller: 'NotebookCtrl'
+        controller: 'NotebookCtrl',
+        resolve: visBundleLoad
       })
       .when('/notebook/:noteId/paragraph/:paragraphId?', {
         templateUrl: 'app/notebook/notebook.html',
-        controller: 'NotebookCtrl'
+        controller: 'NotebookCtrl',
+        resolve: visBundleLoad
       })
       .when('/notebook/:noteId/revision/:revisionId', {
         templateUrl: 'app/notebook/notebook.html',
-        controller: 'NotebookCtrl'
+        controller: 'NotebookCtrl',
+        resolve: visBundleLoad
       })
       .when('/jobmanager', {
         templateUrl: 'app/jobmanager/jobmanager.html',
@@ -83,6 +93,10 @@ var zeppelinWebApp = angular.module('zeppelinWebApp', [
         templateUrl: 'app/credential/credential.html',
         controller: 'CredentialCtrl'
       })
+      .when('/helium', {
+        templateUrl: 'app/helium/helium.html',
+        controller: 'HeliumCtrl'
+      })    
       .when('/configuration', {
         templateUrl: 'app/configuration/configuration.html',
         controller: 'ConfigurationCtrl'

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/helium/helium.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium.controller.js b/zeppelin-web/src/app/helium/helium.controller.js
new file mode 100644
index 0000000..a344e80
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium.controller.js
@@ -0,0 +1,219 @@
+/*
+ * Licensed 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.
+ */
+
+(function() {
+
+  angular.module('zeppelinWebApp').controller('HeliumCtrl', HeliumCtrl);
+
+  HeliumCtrl.$inject = ['$scope', '$rootScope', '$sce', 'baseUrlSrv', 'ngToast', 'heliumService'];
+
+  function HeliumCtrl($scope, $rootScope, $sce, baseUrlSrv, ngToast, heliumService) {
+    $scope.packageInfos = {};
+    $scope.defaultVersions = {};
+    $scope.showVersions = {};
+    $scope.visualizationOrder = [];
+    $scope.visualizationOrderChanged = false;
+
+    var buildDefaultVersionListToDisplay = function(packageInfos) {
+      var defaultVersions = {};
+      // show enabled version if any version of package is enabled
+      for (var name in packageInfos) {
+        var pkgs = packageInfos[name];
+        for (var pkgIdx in pkgs) {
+          var pkg = pkgs[pkgIdx];
+          pkg.pkg.icon = $sce.trustAsHtml(pkg.pkg.icon);
+          if (pkg.enabled) {
+            defaultVersions[name] = pkg;
+            pkgs.splice(pkgIdx, 1);
+            break;
+          }
+        }
+
+        // show first available version if package is not enabled
+        if (!defaultVersions[name]) {
+          defaultVersions[name] = pkgs[0];
+          pkgs.splice(0, 1);
+        }
+      }
+      $scope.defaultVersions = defaultVersions;
+    };
+
+    var getAllPackageInfo = function() {
+      heliumService.getAllPackageInfo().
+        success(function(data, status) {
+          $scope.packageInfos = data.body;
+          buildDefaultVersionListToDisplay($scope.packageInfos);
+        }).
+        error(function(data, status) {
+          console.log('Can not load package info %o %o', status, data);
+        });
+    };
+
+    var getVisualizationOrder = function() {
+      heliumService.getVisualizationOrder().
+        success(function(data, status) {
+          $scope.visualizationOrder = data.body;
+        }).
+        error(function(data, status) {
+          console.log('Can not get visualization order %o %o', status, data);
+        });
+    };
+
+    $scope.visualizationOrderListeners = {
+      accept: function(sourceItemHandleScope, destSortableScope) {return true;},
+      itemMoved: function(event) {},
+      orderChanged: function(event) {
+        $scope.visualizationOrderChanged = true;
+      }
+    };
+
+    var init = function() {
+      getAllPackageInfo();
+      getVisualizationOrder();
+      $scope.visualizationOrderChanged = false;
+    };
+
+    init();
+
+    $scope.saveVisualizationOrder = function() {
+      var confirm = BootstrapDialog.confirm({
+        closable: false,
+        closeByBackdrop: false,
+        closeByKeyboard: false,
+        title: '',
+        message: 'Save changes?',
+        callback: function(result) {
+          if (result) {
+            confirm.$modalFooter.find('button').addClass('disabled');
+            confirm.$modalFooter.find('button:contains("OK")')
+              .html('<i class="fa fa-circle-o-notch fa-spin"></i> Enabling');
+            heliumService.setVisualizationOrder($scope.visualizationOrder).
+              success(function(data, status) {
+                init();
+                confirm.close();
+              }).
+              error(function(data, status) {
+                confirm.close();
+                console.log('Failed to save order');
+                BootstrapDialog.show({
+                  title: 'Error on saving order ',
+                  message: data.message
+                });
+              });
+            return false;
+          }
+        }
+      });
+    }
+
+    var getLicense = function(name, artifact) {
+      var pkg = _.filter($scope.defaultVersions[name], function(p) {
+        return p.artifact === artifact;
+      });
+
+      var license;
+      if (pkg.length === 0) {
+        pkg = _.filter($scope.packageInfos[name], function(p) {
+          return p.pkg.artifact === artifact;
+        });
+
+        if (pkg.length > 0) {
+          license  = pkg[0].pkg.license;
+        }
+      } else {
+        license = pkg[0].license;
+      }
+
+      if (!license) {
+        license = 'Unknown';
+      }
+      return license;
+    }
+
+    $scope.enable = function(name, artifact) {
+      var license = getLicense(name, artifact);
+
+      var confirm = BootstrapDialog.confirm({
+        closable: false,
+        closeByBackdrop: false,
+        closeByKeyboard: false,
+        title: '',
+        message: 'Do you want to enable ' + name + '?' +
+          '<div style="color:gray">' + artifact + '</div>' +
+          '<div style="border-top: 1px solid #efefef; margin-top: 10px; padding-top: 5px;">License</div>' +
+          '<div style="color:gray">' + license + '</div>',
+        callback: function(result) {
+          if (result) {
+            confirm.$modalFooter.find('button').addClass('disabled');
+            confirm.$modalFooter.find('button:contains("OK")')
+              .html('<i class="fa fa-circle-o-notch fa-spin"></i> Enabling');
+            heliumService.enable(name, artifact).
+              success(function(data, status) {
+                init();
+                confirm.close();
+              }).
+              error(function(data, status) {
+                confirm.close();
+                console.log('Failed to enable package %o %o. %o', name, artifact, data);
+                BootstrapDialog.show({
+                  title: 'Error on enabling ' + name,
+                  message: data.message
+                });
+              });
+            return false;
+          }
+        }
+      });
+    };
+
+    $scope.disable = function(name) {
+      var confirm = BootstrapDialog.confirm({
+        closable: false,
+        closeByBackdrop: false,
+        closeByKeyboard: false,
+        title: '',
+        message: 'Do you want to disable ' + name + '?',
+        callback: function(result) {
+          if (result) {
+            confirm.$modalFooter.find('button').addClass('disabled');
+            confirm.$modalFooter.find('button:contains("OK")')
+              .html('<i class="fa fa-circle-o-notch fa-spin"></i> Disabling');
+            heliumService.disable(name).
+              success(function(data, status) {
+                init();
+                confirm.close();
+              }).
+              error(function(data, status) {
+                confirm.close();
+                console.log('Failed to disable package %o. %o', name, data);
+                BootstrapDialog.show({
+                  title: 'Error on disabling ' + name,
+                  message: data.message
+                });
+              });
+            return false;
+          }
+        }
+      });
+    };
+
+    $scope.toggleVersions = function(pkgName) {
+      if ($scope.showVersions[pkgName]) {
+        $scope.showVersions[pkgName] = false;
+      } else {
+        $scope.showVersions[pkgName] = true;
+      }
+    };
+  }
+})();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/helium/helium.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium.css b/zeppelin-web/src/app/helium/helium.css
new file mode 100644
index 0000000..f17d6bd
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium.css
@@ -0,0 +1,107 @@
+/*
+ * Licensed 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.
+ */
+
+.heliumPackageContainer {
+  padding-bottom: 0px;
+  margin-bottom: 0px;
+}
+
+.heliumPackageList {
+  min-height: 25px;
+  margin-bottom: 15px;
+  border-bottom: 1px solid #EFEFEF;
+  padding-bottom: 13px;
+}
+
+.heliumPackageList:last-child {
+  border-bottom: none;
+}
+
+.heliumPackageList .heliumPackageHead {
+  height: 30px;
+}
+
+.heliumPackageList .heliumPackageHead .btn {
+  margin-top: 5px;
+}
+
+.heliumPackageList .heliumPackageIcon {
+  float: left;
+  width: 28px;
+  height: 22px;
+  padding: 5px 2px 0px 2px;
+}
+
+.heliumPackageList .heliumPackageName {
+  font-size: 20px;
+  font-weight: bold;
+  color: #3071a9;
+  float: left;
+  margin-top: 0;
+}
+
+.heliumPackageList .heliumPackageName span {
+  font-size: 10px;
+  color: #AAAAAA;
+}
+
+
+.heliumPackageList .heliumPackageDisabledArtifact {
+  color:gray;
+}
+
+.heliumPackageList .heliumPackageEnabledArtifact {
+  color:#444444;
+}
+
+.heliumPackageList .heliumPackageEnabledArtifact span,
+.heliumPackageList .heliumPackageDisabledArtifact span {
+  margin-left:3px;
+  cursor:pointer;
+  text-decoration:
+  underline;color:#3071a9
+}
+
+.heliumPackageList .heliumPackageDescription {
+  margin-top: 10px;
+}
+
+.heliumVisualizationOrder {
+  display: inline-block;
+}
+
+.heliumVisualizationOrder .as-sortable-item,
+.heliumVisualizationOrder .as-sortable-placeholder {
+  display: inline-block;
+  float: left;
+}
+
+.sortable-row .as-sortable-item-handle {
+  width: 35px;
+  height: 30px;
+}
+
+.sortable-row .as-sortable-item svg {
+  width: 100%;
+  height: 100%;
+}
+
+.heliumVisualizationOrder .saveLink {
+  margin-left:10px;
+  margin-top:5px;
+  cursor:pointer;
+  text-decoration:
+  underline;color:#3071a9;
+  display: inline-block;
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/helium/helium.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium.html b/zeppelin-web/src/app/helium/helium.html
new file mode 100644
index 0000000..546995c
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium.html
@@ -0,0 +1,86 @@
+<!--
+Licensed 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.
+-->
+<div class="interpreterHead">
+  <div class="header">
+    <div class="row">
+      <div class="col-md-12">
+        <h3 class="new_h3">
+          Helium
+        </h3>
+      </div>
+    </div>
+    <div ng-show="visualizationOrder.length > 1"
+         class="row heliumVisualizationOrder">
+      <div style="margin:0 0 5px 15px">Visualization package display order (drag and drop to reorder)</div>
+      <div class="col-md-12 sortable-row btn-group"
+           as-sortable="visualizationOrderListeners"
+           data-ng-model="visualizationOrder">
+        <div class="btn-group" data-ng-repeat="pkgName in visualizationOrder"
+             as-sortable-item>
+          <div class="btn btn-default btn-sm"
+               ng-bind-html='defaultVersions[pkgName].pkg.icon'
+               as-sortable-item-handle>
+          </div>
+        </div>
+        <span class="saveLink"
+           ng-show="visualizationOrderChanged"
+           ng-click="saveVisualizationOrder()">
+          save
+        </span>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="box width-full heliumPackageContainer">
+  <div class="row heliumPackageList"
+       ng-repeat="(pkgName, pkgInfo) in defaultVersions">
+    <div class="col-md-12">
+      <div class="heliumPackageHead">
+        <div class="heliumPackageIcon"
+             ng-bind-html=pkgInfo.pkg.icon></div>
+        <div class="heliumPackageName">{{pkgName}} <span>{{pkgInfo.pkg.type}}</span></div>
+        <div ng-show="!pkgInfo.enabled"
+             ng-click="enable(pkgName, pkgInfo.pkg.artifact)"
+             class="btn btn-success btn-xs"
+             style="float:right">Enable</div>
+        <div ng-show="pkgInfo.enabled"
+             ng-click="disable(pkgName)"
+             class="btn btn-info btn-xs"
+             style="float:right">Disable</div>
+      </div>
+      <div ng-class="{heliumPackageDisabledArtifact: !pkgInfo.enabled, heliumPackageEnabledArtifact: pkgInfo.enabled}">
+        {{pkgInfo.pkg.artifact}}
+        <span ng-show="packageInfos[pkgName].length > 0"
+              ng-click="toggleVersions(pkgName)">
+          versions
+        </span>
+      </div>
+      <ul class="heliumPackageVersions"
+           ng-show="showVersions[pkgName]">
+        <li class="heliumPackageDisabledArtifact"
+             ng-repeat="pkg in packageInfos[pkgName]">
+          {{pkg.pkg.artifact}} -
+          <span ng-click="enable(pkgName, pkg.pkg.artifact)"
+                style="margin-left:3px;cursor:pointer;text-decoration: underline;color:#3071a9">
+            enable
+          </span>
+        </li>
+      </ul>
+      <div class="heliumPackageDescription">
+        {{pkgInfo.pkg.description}}
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
index b48e0d7..0d2afb7 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
@@ -14,13 +14,14 @@ limitations under the License.
 
 <div id="{{id}}_switch"
      ng-if="(type == 'TABLE' || apps.length > 0 || suggestion.available && suggestion.available.length > 0) && !asIframe && !viewOnly"
-     class="chart-selector">
+     class="result-chart-selector">
 
   <div ng-if="type == 'TABLE'" class="btn-group">
     <button type="button" class="btn btn-default btn-sm"
             ng-repeat="viz in builtInTableDataVisualizationList track by $index"
             ng-class="{'active' : viz.id == graphMode && !config.helium.activeApp}"
-            ng-click="switchViz(viz.id)"><i ng-class="viz.icon"></i>
+            ng-click="switchViz(viz.id)"
+            ng-bind-html="viz.icon">
     </button>
   </div>
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
index e793199..6d56fe4 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
@@ -34,16 +34,18 @@ ResultCtrl.$inject = [
   '$http',
   '$q',
   '$templateRequest',
+  '$sce',
   'websocketMsgSrv',
   'baseUrlSrv',
   'ngToast',
   'saveAsService',
-  'noteVarShareService'
+  'noteVarShareService',
+  'heliumService'
 ];
 
 function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location,
-                    $timeout, $compile, $http, $q, $templateRequest, websocketMsgSrv,
-                    baseUrlSrv, ngToast, saveAsService, noteVarShareService) {
+                    $timeout, $compile, $http, $q, $templateRequest, $sce, websocketMsgSrv,
+                    baseUrlSrv, ngToast, saveAsService, noteVarShareService, heliumService) {
 
   /**
    * Built-in visualizations
@@ -52,36 +54,36 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
     {
       id: 'table',   // paragraph.config.graph.mode
       name: 'Table', // human readable name. tooltip
-      icon: 'fa fa-table'
+      icon: '<i class="fa fa-table"></i>'
     },
     {
       id: 'multiBarChart',
       name: 'Bar Chart',
-      icon: 'fa fa-bar-chart',
+      icon: '<i class="fa fa-bar-chart"></i>',
       transformation: 'pivot'
     },
     {
       id: 'pieChart',
       name: 'Pie Chart',
-      icon: 'fa fa-pie-chart',
+      icon: '<i class="fa fa-pie-chart"></i>',
       transformation: 'pivot'
     },
     {
       id: 'stackedAreaChart',
       name: 'Area Chart',
-      icon: 'fa fa-area-chart',
+      icon: '<i class="fa fa-area-chart"></i>',
       transformation: 'pivot'
     },
     {
       id: 'lineChart',
       name: 'Line Chart',
-      icon: 'fa fa-line-chart',
+      icon: '<i class="fa fa-line-chart"></i>',
       transformation: 'pivot'
     },
     {
       id: 'scatterChart',
       name: 'Scatter Chart',
-      icon: 'cf cf-scatter-chart'
+      icon: '<i class="cf cf-scatter-chart"></i>'
     }
   ];
 
@@ -150,6 +152,21 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
 
   $scope.init = function(result, config, paragraph, index) {
     console.log('result controller init %o %o %o', result, config, index);
+
+    // register helium plugin vis
+    var heliumVis = heliumService.get();
+    console.log('Helium visualizations %o', heliumVis);
+    heliumVis.forEach(function(vis) {
+      $scope.builtInTableDataVisualizationList.push({
+        id: vis.id,
+        name: vis.name,
+        icon: $sce.trustAsHtml(vis.icon)
+      });
+      builtInVisualizations[vis.id] = {
+        class: vis.class
+      };
+    });
+    
     updateData(result, config, paragraph, index);
     renderResult($scope.type);
   };
@@ -421,7 +438,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
                 builtInViz.instance.resize();
               });
             } catch (err) {
-              console.log('Graph drawing error %o', err);
+              console.error('Graph drawing error %o', err);
             }
           } else {
             $timeout(retryRenderer, 10);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/notebook/paragraph/result/result.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.css b/zeppelin-web/src/app/notebook/paragraph/result/result.css
index 91338ba..905b88c 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.css
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.css
@@ -12,9 +12,31 @@
  * limitations under the License.
  */
 
-.chart-selector {
+.result-chart-selector {
     margin-bottom: 10px; 
     position: relative; 
     display: inline-block; 
     vertical-align: middle;
 }
+
+.result-chart-selector button {
+  width: 35px;
+  height: 30px;
+}
+
+.result-chart-selector button svg {
+  width: 100%;
+  height: 100%;
+}
+
+.rotate90 {
+  -webkit-transform: rotate(90deg);
+  -moz-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+}
+
+.rotate90flipX {
+  -webkit-transform: rotate(90deg) scaleX(-1);
+  -moz-transform: rotate(90deg) scaleX(-1);
+  -ms-transform: rotate(90deg) scaleX(-1);
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/tabledata/.npmignore
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/.npmignore b/zeppelin-web/src/app/tabledata/.npmignore
new file mode 100644
index 0000000..0b84df0
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/.npmignore
@@ -0,0 +1 @@
+*.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/tabledata/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/package.json b/zeppelin-web/src/app/tabledata/package.json
new file mode 100644
index 0000000..e92abb2
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "zeppelin-tabledata",
+  "description": "tabledata api",
+  "version": "0.7.0-SNAPSHOT",
+  "main": "tabledata",
+  "dependencies": {
+    "json3": "~3.3.1",
+    "lodash": "~3.9.3"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache/zeppelin"
+  },
+  "license": "Apache-2.0"
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/300f7532/zeppelin-web/src/app/visualization/.npmignore
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/.npmignore b/zeppelin-web/src/app/visualization/.npmignore
new file mode 100644
index 0000000..2d19fc7
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/.npmignore
@@ -0,0 +1 @@
+*.html