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/02/01 23:34:00 UTC
[1/3] zeppelin git commit: [ZEPPELIN-2008] Introduce Spell
Repository: zeppelin
Updated Branches:
refs/heads/master 019df1f6b -> 0589e27e7
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/test/spec/controllers/paragraph.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/test/spec/controllers/paragraph.js b/zeppelin-web/test/spec/controllers/paragraph.js
index 4089d2d..1aa4a4a 100644
--- a/zeppelin-web/test/spec/controllers/paragraph.js
+++ b/zeppelin-web/test/spec/controllers/paragraph.js
@@ -41,10 +41,6 @@ describe('Controller: ParagraphCtrl', function() {
});
});
- it('should return "TEXT" by default when getResultType() is called with no parameter', function() {
- expect(scope.getResultType()).toEqual('TEXT');
- });
-
it('should have this array of values for "colWidthOption"', function() {
expect(scope.colWidthOption).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
});
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/webpack.config.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/webpack.config.js b/zeppelin-web/webpack.config.js
index d3a2681..8198b55 100644
--- a/zeppelin-web/webpack.config.js
+++ b/zeppelin-web/webpack.config.js
@@ -210,7 +210,7 @@ module.exports = function makeWebpackConfig () {
// Reference: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
'process.env': {
- HELIUM_VIS_DEV: process.env.HELIUM_VIS_DEV
+ HELIUM_BUNDLE_DEV: process.env.HELIUM_BUNDLE_DEV
}
})
)
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 0ef3237..e2e1b49 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
@@ -45,20 +45,20 @@ public class Helium {
private final File registryCacheDir;
private final Gson gson;
- private final HeliumVisualizationFactory visualizationFactory;
+ private final HeliumBundleFactory bundleFactory;
private final HeliumApplicationFactory applicationFactory;
public Helium(
String heliumConfPath,
String registryPaths,
File registryCacheDir,
- HeliumVisualizationFactory visualizationFactory,
+ HeliumBundleFactory bundleFactory,
HeliumApplicationFactory applicationFactory)
throws IOException {
this.heliumConfPath = heliumConfPath;
this.registryPaths = registryPaths;
this.registryCacheDir = registryCacheDir;
- this.visualizationFactory = visualizationFactory;
+ this.bundleFactory = bundleFactory;
this.applicationFactory = applicationFactory;
GsonBuilder builder = new GsonBuilder();
@@ -95,8 +95,8 @@ public class Helium {
return applicationFactory;
}
- public HeliumVisualizationFactory getVisualizationFactory() {
- return visualizationFactory;
+ public HeliumBundleFactory getBundleFactory() {
+ return bundleFactory;
}
private synchronized HeliumConf loadConf(String path) throws IOException {
@@ -145,14 +145,14 @@ public class Helium {
Map<String, List<HeliumPackageSearchResult>> all = getAllPackageInfo();
// clear visualization display order
- List<String> packageOrder = heliumConf.getVisualizationDisplayOrder();
+ List<String> packageOrder = heliumConf.getBundleDisplayOrder();
List<String> clearedOrder = new LinkedList<>();
for (String pkgName : packageOrder) {
if (all.containsKey(pkgName)) {
clearedOrder.add(pkgName);
}
}
- heliumConf.setVisualizationDisplayOrder(clearedOrder);
+ heliumConf.setBundleDisplayOrder(clearedOrder);
// clear enabled package
Map<String, String> enabledPackages = heliumConf.getEnabledPackages();
@@ -215,8 +215,8 @@ public class Helium {
return null;
}
- public File recreateVisualizationBundle() throws IOException {
- return visualizationFactory.bundle(getVisualizationPackagesToBundle(), true);
+ public File recreateBundle() throws IOException {
+ return bundleFactory.buildBundle(getBundlePackagesToBundle(), true);
}
public void enable(String name, String artifact) throws IOException {
@@ -231,8 +231,8 @@ public class Helium {
heliumConf.enablePackage(name, artifact);
// if package is visualization, rebuild bundle
- if (pkgInfo.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) {
- visualizationFactory.bundle(getVisualizationPackagesToBundle());
+ if (HeliumPackage.isBundleType(pkgInfo.getPkg().getType())) {
+ bundleFactory.buildBundle(getBundlePackagesToBundle());
}
save();
@@ -247,9 +247,9 @@ public class Helium {
heliumConf.disablePackage(name);
- HeliumPackageSearchResult pkg = getPackageInfo(name, artifact);
- if (pkg == null || pkg.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) {
- visualizationFactory.bundle(getVisualizationPackagesToBundle());
+ HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact);
+ if (pkgInfo == null || HeliumPackage.isBundleType(pkgInfo.getPkg().getType())) {
+ bundleFactory.buildBundle(getBundlePackagesToBundle());
}
save();
@@ -278,7 +278,7 @@ public class Helium {
for (List<HeliumPackageSearchResult> pkgs : getAllPackageInfo().values()) {
for (HeliumPackageSearchResult pkg : pkgs) {
- if (pkg.getPkg().getType() == HeliumPackage.Type.APPLICATION && pkg.isEnabled()) {
+ if (pkg.getPkg().getType() == HeliumType.APPLICATION && pkg.isEnabled()) {
ResourceSet resources = ApplicationLoader.findRequiredResourceSet(
pkg.getPkg().getResources(),
paragraph.getNote().getId(),
@@ -299,15 +299,15 @@ public class Helium {
}
/**
- * Get enabled visualization packages
+ * Get enabled buildBundle packages
*
- * @return ordered list of enabled visualization package
+ * @return ordered list of enabled buildBundle package
*/
- public List<HeliumPackage> getVisualizationPackagesToBundle() {
+ public List<HeliumPackage> getBundlePackagesToBundle() {
Map<String, List<HeliumPackageSearchResult>> allPackages = getAllPackageInfo();
- List<String> visOrder = heliumConf.getVisualizationDisplayOrder();
+ List<String> visOrder = heliumConf.getBundleDisplayOrder();
- List<HeliumPackage> orderedVisualizationPackages = new LinkedList<>();
+ List<HeliumPackage> orderedBundlePackages = new LinkedList<>();
// add enabled packages in visOrder
for (String name : visOrder) {
@@ -316,8 +316,8 @@ public class Helium {
continue;
}
for (HeliumPackageSearchResult pkgInfo : versions) {
- if (pkgInfo.getPkg().getType() == HeliumPackage.Type.VISUALIZATION && pkgInfo.isEnabled()) {
- orderedVisualizationPackages.add(pkgInfo.getPkg());
+ if (canBundle(pkgInfo)) {
+ orderedBundlePackages.add(pkgInfo.getPkg());
allPackages.remove(name);
break;
}
@@ -325,28 +325,35 @@ public class Helium {
}
// 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());
+ for (List<HeliumPackageSearchResult> pkgInfos : allPackages.values()) {
+ for (HeliumPackageSearchResult pkgInfo : pkgInfos) {
+ if (canBundle(pkgInfo)) {
+ orderedBundlePackages.add(pkgInfo.getPkg());
break;
}
}
}
- return orderedVisualizationPackages;
+ return orderedBundlePackages;
+ }
+
+ public boolean canBundle(HeliumPackageSearchResult pkgInfo) {
+ return (pkgInfo.isEnabled() &&
+ HeliumPackage.isBundleType(pkgInfo.getPkg().getType()));
}
/**
* Get enabled package list in order
* @return
*/
- public List<String> getVisualizationPackageOrder() {
+ public List<String> setVisualizationPackageOrder() {
List orderedPackageList = new LinkedList<>();
- List<HeliumPackage> packages = getVisualizationPackagesToBundle();
+ List<HeliumPackage> packages = getBundlePackagesToBundle();
for (HeliumPackage pkg : packages) {
- orderedPackageList.add(pkg.getName());
+ if (HeliumType.VISUALIZATION == pkg.getType()) {
+ orderedPackageList.add(pkg.getName());
+ }
}
return orderedPackageList;
@@ -354,10 +361,10 @@ public class Helium {
public void setVisualizationPackageOrder(List<String> orderedPackageList)
throws IOException {
- heliumConf.setVisualizationDisplayOrder(orderedPackageList);
+ heliumConf.setBundleDisplayOrder(orderedPackageList);
- // if package is visualization, rebuild bundle
- visualizationFactory.bundle(getVisualizationPackagesToBundle());
+ // if package is visualization, rebuild buildBundle
+ bundleFactory.buildBundle(getBundlePackagesToBundle());
save();
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumBundleFactory.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumBundleFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumBundleFactory.java
new file mode 100644
index 0000000..664030f
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumBundleFactory.java
@@ -0,0 +1,415 @@
+/*
+ * 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 HeliumBundleFactory {
+ Logger logger = LoggerFactory.getLogger(HeliumBundleFactory.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/";
+ public static final String HELIUM_LOCAL_REPO = "helium-bundle";
+ public static final String HELIUM_BUNDLE_CACHE = "helium.bundle.cache.js";
+ public static final String HELIUM_BUNDLE = "helium.bundle.js";
+ public static final String HELIUM_BUNDLES_VAR = "heliumBundles";
+
+ private final FrontendPluginFactory frontEndPluginFactory;
+ private final File workingDirectory;
+ private File tabledataModulePath;
+ private File visualizationModulePath;
+ private File spellModulePath;
+ private Gson gson;
+
+ String bundleCacheKey = "";
+ File currentCacheBundle;
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ public HeliumBundleFactory(
+ File moduleDownloadPath,
+ File tabledataModulePath,
+ File visualizationModulePath,
+ File spellModulePath) throws TaskRunnerException {
+ this(moduleDownloadPath);
+ this.tabledataModulePath = tabledataModulePath;
+ this.visualizationModulePath = visualizationModulePath;
+ this.spellModulePath = spellModulePath;
+ }
+
+ public HeliumBundleFactory(File moduleDownloadPath) throws TaskRunnerException {
+ this.workingDirectory = new File(moduleDownloadPath, HELIUM_LOCAL_REPO);
+ File installDirectory = workingDirectory;
+
+ frontEndPluginFactory = new FrontendPluginFactory(
+ workingDirectory, installDirectory);
+
+ currentCacheBundle = new File(workingDirectory, HELIUM_BUNDLE_CACHE);
+ 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 buildBundle(List<HeliumPackage> pkgs) throws IOException {
+ return buildBundle(pkgs, false);
+ }
+
+ public synchronized File buildBundle(List<HeliumPackage> pkgs, boolean forceRefresh)
+ throws IOException {
+ // package.json
+ URL pkgUrl = Resources.getResource("helium/package.json");
+ String pkgJson = Resources.toString(pkgUrl, Charsets.UTF_8);
+ StringBuilder dependencies = new StringBuilder();
+ StringBuilder cacheKeyBuilder = 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] + "\"");
+ cacheKeyBuilder.append(pkg.getName() + pkg.getArtifact());
+
+ File pkgInstallDir = new File(workingDirectory, "node_modules/" + pkg.getName());
+ if (pkgInstallDir.exists()) {
+ FileUtils.deleteDirectory(pkgInstallDir);
+ }
+
+ if (isLocalPackage(pkg)) {
+ FileUtils.copyDirectory(
+ new File(pkg.getArtifact()),
+ pkgInstallDir,
+ npmPackageCopyFilter);
+ }
+ }
+ pkgJson = pkgJson.replaceFirst("DEPENDENCIES", dependencies.toString());
+
+ // check if we can use previous buildBundle or not
+ if (cacheKeyBuilder.toString().equals(bundleCacheKey) &&
+ currentCacheBundle.isFile() && !forceRefresh) {
+ return currentCacheBundle;
+ }
+
+ // 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 = "bundles" + idx++;
+ loadJsImport.append(
+ "import " + className + " from \"" + moduleNameVersion[0] + "\"\n");
+
+ loadJsRegister.append(HELIUM_BUNDLES_VAR + ".push({\n");
+ loadJsRegister.append("id: \"" + moduleNameVersion[0] + "\",\n");
+ loadJsRegister.append("name: \"" + pkg.getName() + "\",\n");
+ loadJsRegister.append("icon: " + gson.toJson(pkg.getIcon()) + ",\n");
+ loadJsRegister.append("type: \"" + pkg.getType() + "\",\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());
+
+ copyFrameworkModuleToInstallPath(npmPackageCopyFilter);
+
+ try {
+ out.reset();
+ npmCommand("install --loglevel=error");
+ } catch (TaskRunnerException e) {
+ // ignore `(empty)` warning
+ String cause = new String(out.toByteArray());
+ if (!cause.contains("(empty)")) {
+ throw new IOException(cause);
+ }
+ }
+
+ try {
+ out.reset();
+ npmCommand("run bundle");
+ } catch (TaskRunnerException e) {
+ throw new IOException(new String(out.toByteArray()));
+ }
+
+ String bundleStdoutResult = new String(out.toByteArray());
+
+ File heliumBundle = new File(workingDirectory, HELIUM_BUNDLE);
+ if (!heliumBundle.isFile()) {
+ throw new IOException(
+ "Can't create bundle: \n" + bundleStdoutResult);
+ }
+
+ WebpackResult result = getWebpackResultFromOutput(bundleStdoutResult);
+ if (result.errors.length > 0) {
+ heliumBundle.delete();
+ throw new IOException(result.errors[0]);
+ }
+
+ synchronized (this) {
+ currentCacheBundle.delete();
+ FileUtils.moveFile(heliumBundle, currentCacheBundle);
+ bundleCacheKey = cacheKeyBuilder.toString();
+ }
+ return currentCacheBundle;
+ }
+
+ private void copyFrameworkModuleToInstallPath(FileFilter npmPackageCopyFilter)
+ throws IOException {
+ // install tabledata module
+ File tabledataModuleInstallPath = new File(workingDirectory,
+ "node_modules/zeppelin-tabledata");
+ if (tabledataModulePath != null) {
+ if (tabledataModuleInstallPath.exists()) {
+ FileUtils.deleteDirectory(tabledataModuleInstallPath);
+ }
+ FileUtils.copyDirectory(
+ tabledataModulePath,
+ tabledataModuleInstallPath,
+ npmPackageCopyFilter);
+ }
+
+ // install visualization module
+ File visModuleInstallPath = new File(workingDirectory,
+ "node_modules/zeppelin-vis");
+ if (visualizationModulePath != null) {
+ if (visModuleInstallPath.exists()) {
+ // when zeppelin-vis and zeppelin-table package is published to npm repository
+ // we don't need to remove module because npm install command will take care
+ // dependency version change. However, when two dependencies are copied manually
+ // into node_modules directory, changing vis package version results inconsistent npm
+ // install behavior.
+ //
+ // Remote vis package everytime and let npm download every time bundle as a workaround
+ FileUtils.deleteDirectory(visModuleInstallPath);
+ }
+ FileUtils.copyDirectory(visualizationModulePath, visModuleInstallPath, npmPackageCopyFilter);
+ }
+
+ // install spell module
+ File spellModuleInstallPath = new File(workingDirectory,
+ "node_modules/zeppelin-spell");
+ if (spellModulePath != null) {
+ if (spellModuleInstallPath.exists()) {
+ FileUtils.deleteDirectory(spellModuleInstallPath);
+ }
+
+ FileUtils.copyDirectory(
+ spellModulePath,
+ spellModuleInstallPath,
+ npmPackageCopyFilter);
+ }
+ }
+
+ 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 getCurrentCacheBundle() {
+ synchronized (this) {
+ if (currentCacheBundle.isFile()) {
+ return currentCacheBundle;
+ } 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() + " npm install --loglevel=error");
+ }
+
+ 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/0589e27e/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 5094934..d60aec7 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
@@ -26,7 +26,7 @@ public class HeliumConf {
Map<String, String> enabled = Collections.synchronizedMap(new HashMap<String, String>());
// enabled visualization package display order
- List<String> visualizationDisplayOrder = new LinkedList<>();
+ List<String> bundleDisplayOrder = new LinkedList<>();
public Map<String, String> getEnabledPackages() {
return new HashMap<>(enabled);
@@ -48,15 +48,15 @@ public class HeliumConf {
enabled.remove(name);
}
- public List<String> getVisualizationDisplayOrder() {
- if (visualizationDisplayOrder == null) {
+ public List<String> getBundleDisplayOrder() {
+ if (bundleDisplayOrder == null) {
return new LinkedList<String>();
} else {
- return visualizationDisplayOrder;
+ return bundleDisplayOrder;
}
}
- public void setVisualizationDisplayOrder(List<String> orderedPackageList) {
- visualizationDisplayOrder = orderedPackageList;
+ public void setBundleDisplayOrder(List<String> orderedPackageList) {
+ bundleDisplayOrder = orderedPackageList;
}
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
deleted file mode 100644
index 624f12a..0000000
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java
+++ /dev/null
@@ -1,376 +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.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 {
- return bundle(pkgs, false);
- }
-
- public synchronized File bundle(List<HeliumPackage> pkgs, boolean forceRefresh)
- throws IOException {
- // package.json
- URL pkgUrl = Resources.getResource("helium/package.json");
- String pkgJson = Resources.toString(pkgUrl, Charsets.UTF_8);
- StringBuilder dependencies = new StringBuilder();
- StringBuilder cacheKeyBuilder = 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] + "\"");
- cacheKeyBuilder.append(pkg.getName() + pkg.getArtifact());
-
- File pkgInstallDir = new File(workingDirectory, "node_modules/" + pkg.getName());
- if (pkgInstallDir.exists()) {
- FileUtils.deleteDirectory(pkgInstallDir);
- }
-
- if (isLocalPackage(pkg)) {
- FileUtils.copyDirectory(
- new File(pkg.getArtifact()),
- pkgInstallDir,
- npmPackageCopyFilter);
- }
- }
- pkgJson = pkgJson.replaceFirst("DEPENDENCIES", dependencies.toString());
-
- // check if we can use previous bundle or not
- if (cacheKeyBuilder.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) {
- if (tabledataModuleInstallPath.exists()) {
- FileUtils.deleteDirectory(tabledataModuleInstallPath);
- }
- FileUtils.copyDirectory(
- tabledataModulePath,
- tabledataModuleInstallPath,
- npmPackageCopyFilter);
- }
-
- // install visualization module
- File visModuleInstallPath = new File(workingDirectory,
- "node_modules/zeppelin-vis");
- if (visualizationModulePath != null) {
- if (visModuleInstallPath.exists()) {
- // when zeppelin-vis and zeppelin-table package is published to npm repository
- // we don't need to remove module because npm install command will take care
- // dependency version change. However, when two dependencies are copied manually
- // into node_modules directory, changing vis package version results inconsistent npm
- // install behavior.
- //
- // Remote vis package everytime and let npm download every time bundle as a workaround
- FileUtils.deleteDirectory(visModuleInstallPath);
- }
- FileUtils.copyDirectory(visualizationModulePath, visModuleInstallPath, npmPackageCopyFilter);
- }
-
- out.reset();
- try {
- npmCommand("install");
- npmCommand("run bundle");
- } catch (TaskRunnerException e) {
- throw new IOException(new String(out.toByteArray()));
- }
-
- 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 = cacheKeyBuilder.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/0589e27e/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
index 162baf8..a6d1546 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
@@ -172,7 +172,9 @@ public class Message {
PARAGRAPH_REMOVED, // [s-c] paragraph deleted
PARAGRAPH_MOVED, // [s-c] paragraph moved
NOTE_UPDATED, // [s-c] paragraph updated(name, config)
- RUN_ALL_PARAGRAPHS // [c-s] run all paragraphs
+ RUN_ALL_PARAGRAPHS, // [c-s] run all paragraphs
+ PARAGRAPH_EXECUTED_BY_SPELL, // [c-s] paragraph was executed by spell
+ RUN_PARAGRAPH_USING_SPELL // [s-c] run paragraph using spell
}
public static final Message EMPTY = new Message(null);
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
index e6ec612..cd4e470 100644
--- a/zeppelin-zengine/src/main/resources/helium/package.json
+++ b/zeppelin-zengine/src/main/resources/helium/package.json
@@ -1,5 +1,5 @@
{
- "name": "zeppelin-vis-bundle",
+ "name": "zeppelin-helium-bundle",
"main": "load",
"scripts": {
"bundle": "node/node node_modules/webpack/bin/webpack.js --display-error-details --json"
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
index 2b5015e..c318c10 100644
--- a/zeppelin-zengine/src/main/resources/helium/webpack.config.js
+++ b/zeppelin-zengine/src/main/resources/helium/webpack.config.js
@@ -18,7 +18,7 @@ module.exports = {
entry: ['./'],
output: {
path: './',
- filename: 'vis.bundle.js',
+ filename: 'helium.bundle.js',
},
resolve: {
root: __dirname + "/node_modules"
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 2588c4c..99cdeca 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
@@ -129,7 +129,7 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
public void testLoadRunUnloadApplication()
throws IOException, ApplicationException, InterruptedException {
// given
- HeliumPackage pkg1 = new HeliumPackage(HeliumPackage.Type.APPLICATION,
+ HeliumPackage pkg1 = new HeliumPackage(HeliumType.APPLICATION,
"name1",
"desc1",
"",
@@ -175,7 +175,7 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
@Test
public void testUnloadOnParagraphRemove() throws IOException {
// given
- HeliumPackage pkg1 = new HeliumPackage(HeliumPackage.Type.APPLICATION,
+ HeliumPackage pkg1 = new HeliumPackage(HeliumType.APPLICATION,
"name1",
"desc1",
"",
@@ -215,7 +215,7 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
@Test
public void testUnloadOnInterpreterUnbind() throws IOException {
// given
- HeliumPackage pkg1 = new HeliumPackage(HeliumPackage.Type.APPLICATION,
+ HeliumPackage pkg1 = new HeliumPackage(HeliumType.APPLICATION,
"name1",
"desc1",
"",
@@ -276,7 +276,7 @@ public class HeliumApplicationFactoryTest implements JobListenerFactory {
@Test
public void testUnloadOnInterpreterRestart() throws IOException {
// given
- HeliumPackage pkg1 = new HeliumPackage(HeliumPackage.Type.APPLICATION,
+ HeliumPackage pkg1 = new HeliumPackage(HeliumType.APPLICATION,
"name1",
"desc1",
"",
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumBundleFactoryTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumBundleFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumBundleFactoryTest.java
new file mode 100644
index 0000000..503cc07
--- /dev/null
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumBundleFactoryTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.*;
+
+public class HeliumBundleFactoryTest {
+ private File tmpDir;
+ private HeliumBundleFactory hbf;
+
+ @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/");
+
+ hbf = new HeliumBundleFactory(tmpDir,
+ new File(moduleDir, "tabledata"),
+ new File(moduleDir, "visualization"),
+ new File(moduleDir, "spell"));
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ FileUtils.deleteDirectory(tmpDir);
+ }
+
+ @Test
+ public void testInstallNpm() throws InstallationException {
+ assertTrue(new File(tmpDir,
+ HeliumBundleFactory.HELIUM_LOCAL_REPO + "/node/npm").isFile());
+ assertTrue(new File(tmpDir,
+ HeliumBundleFactory.HELIUM_LOCAL_REPO + "/node/node").isFile());
+ }
+
+ @Test
+ public void downloadPackage() throws TaskRunnerException {
+ HeliumPackage pkg = new HeliumPackage(
+ HeliumType.VISUALIZATION,
+ "lodash",
+ "lodash",
+ "lodash@3.9.3",
+ "",
+ null,
+ "license",
+ "icon"
+ );
+ hbf.install(pkg);
+ assertTrue(new File(tmpDir,
+ HeliumBundleFactory.HELIUM_LOCAL_REPO + "/node_modules/lodash").isDirectory());
+ }
+
+ @Test
+ public void bundlePackage() throws IOException, TaskRunnerException {
+ HeliumPackage pkg = new HeliumPackage(
+ HeliumType.VISUALIZATION,
+ "zeppelin-bubblechart",
+ "zeppelin-bubblechart",
+ "zeppelin-bubblechart@0.0.3",
+ "",
+ null,
+ "license",
+ "icon"
+ );
+ List<HeliumPackage> pkgs = new LinkedList<>();
+ pkgs.add(pkg);
+ File bundle = hbf.buildBundle(pkgs);
+ assertTrue(bundle.isFile());
+ long lastModified = bundle.lastModified();
+
+ // buildBundle again and check if it served from cache
+ bundle = hbf.buildBundle(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(
+ HeliumType.VISUALIZATION,
+ "vis1",
+ "vis1",
+ localPkg,
+ "",
+ null,
+ "license",
+ "fa fa-coffee"
+ );
+ List<HeliumPackage> pkgs = new LinkedList<>();
+ pkgs.add(pkg);
+ File bundle = hbf.buildBundle(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(
+ HeliumType.VISUALIZATION,
+ "vis2",
+ "vis2",
+ localPkg,
+ "",
+ null,
+ "license",
+ "fa fa-coffee"
+ );
+ List<HeliumPackage> pkgs = new LinkedList<>();
+ pkgs.add(pkg);
+ File bundle = null;
+ try {
+ bundle = hbf.buildBundle(pkgs);
+ // should throw exception
+ assertTrue(false);
+ } catch (IOException e) {
+ assertTrue(e.getMessage().contains("error in the package"));
+ }
+ assertNull(bundle);
+ }
+
+ @Test
+ public void switchVersion() throws IOException, TaskRunnerException {
+ URL res = Resources.getResource("helium/webpack.config.js");
+ String resDir = new File(res.getFile()).getParent();
+
+ HeliumPackage pkgV1 = new HeliumPackage(
+ HeliumType.VISUALIZATION,
+ "zeppelin-bubblechart",
+ "zeppelin-bubblechart",
+ "zeppelin-bubblechart@0.0.3",
+ "",
+ null,
+ "license",
+ "icon"
+ );
+
+ HeliumPackage pkgV2 = new HeliumPackage(
+ HeliumType.VISUALIZATION,
+ "zeppelin-bubblechart",
+ "zeppelin-bubblechart",
+ "zeppelin-bubblechart@0.0.1",
+ "",
+ null,
+ "license",
+ "icon"
+ );
+ List<HeliumPackage> pkgsV1 = new LinkedList<>();
+ pkgsV1.add(pkgV1);
+
+ List<HeliumPackage> pkgsV2 = new LinkedList<>();
+ pkgsV2.add(pkgV2);
+
+ File bundle1 = hbf.buildBundle(pkgsV1);
+ File bundle2 = hbf.buildBundle(pkgsV2);
+
+ assertNotSame(bundle1.lastModified(), bundle2.lastModified());
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 03d77b7..0f490d1 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
@@ -50,7 +50,7 @@ public class HeliumLocalRegistryTest {
// when
Gson gson = new Gson();
- HeliumPackage pkg1 = new HeliumPackage(HeliumPackage.Type.APPLICATION,
+ HeliumPackage pkg1 = new HeliumPackage(HeliumType.APPLICATION,
"app1",
"desc1",
"artifact1",
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 9db9477..1607c2c 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
@@ -24,7 +24,6 @@ import org.junit.Test;
import java.io.File;
import java.io.IOException;
-import java.net.URI;
import java.net.URISyntaxException;
import static org.junit.Assert.assertEquals;
@@ -79,7 +78,7 @@ public class HeliumTest {
// when
registry1.add(new HeliumPackage(
- HeliumPackage.Type.APPLICATION,
+ HeliumType.APPLICATION,
"name1",
"desc1",
"artifact1",
@@ -89,7 +88,7 @@ public class HeliumTest {
""));
registry2.add(new HeliumPackage(
- HeliumPackage.Type.APPLICATION,
+ HeliumType.APPLICATION,
"name2",
"desc2",
"artifact2",
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
deleted file mode 100644
index e5a61ed..0000000
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java
+++ /dev/null
@@ -1,193 +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.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.*;
-
-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);
- }
-
- @Test
- public void switchVersion() throws IOException, TaskRunnerException {
- URL res = Resources.getResource("helium/webpack.config.js");
- String resDir = new File(res.getFile()).getParent();
-
- HeliumPackage pkgV1 = new HeliumPackage(
- HeliumPackage.Type.VISUALIZATION,
- "zeppelin-bubblechart",
- "zeppelin-bubblechart",
- "zeppelin-bubblechart@0.0.3",
- "",
- null,
- "license",
- "icon"
- );
-
- HeliumPackage pkgV2 = new HeliumPackage(
- HeliumPackage.Type.VISUALIZATION,
- "zeppelin-bubblechart",
- "zeppelin-bubblechart",
- "zeppelin-bubblechart@0.0.1",
- "",
- null,
- "license",
- "icon"
- );
- List<HeliumPackage> pkgsV1 = new LinkedList<>();
- pkgsV1.add(pkgV1);
-
- List<HeliumPackage> pkgsV2 = new LinkedList<>();
- pkgsV2.add(pkgV2);
-
- File bundle1 = hvf.bundle(pkgsV1);
- File bundle2 = hvf.bundle(pkgsV2);
-
- assertNotSame(bundle1.lastModified(), bundle2.lastModified());
- }
-}
[3/3] zeppelin git commit: [ZEPPELIN-2008] Introduce Spell
Posted by mo...@apache.org.
[ZEPPELIN-2008] Introduce Spell
### What is this PR for?
Implemented **Spell** as one of Helium categories. *Technically, it's the frontend interpreter* runs on browser not backend.
Spell can provide many benefits.
1. Anyone can install, remove easily using helium package registry by #1936
2. Implementing spell is extremely easier rather than adding backend interpreter
3. Can use existing javsacript libraries. (e.g [flowchart.js](http://flowchart.js.org/), [sequence diagram js](https://github.com/bramp/js-sequence-diagrams), ...). This enable us to add many visualization tools. Imagine that you can implement some custom interpreters with few lines of code like [flowchart-spell-example](https://github.com/apache/zeppelin/compare/master...1ambda:ZEPPELIN-2008/introduce-spell?expand=1#diff-364845b20d68e4d94688e44fef03da98)
4. The most important thing is, spell is not only interpreter but also display system. Because it runs on browser. So we can use spell display system with another spell **Display System with Spell** (see the screenshot section below)
**In future**, we will be able to combine existing backend interpreters with spell like (**not supported in this PR cause we need to modify backend code a lot**)
```
// if we have markdown spell, we can use `%markdown` display in the spark interpreter
%spark
val calculated = doSomething()
println(s"%markdown _${calculated})
```
I added some examples. Checkout `echo`, `markdown`, `translator`, `flowchart` spells.
### What type of PR is it?
[Feature]
### Todos
* [x] - Add `SPELL` as one of Helium categories.
* [x] - Implement framework code (`zeppelin-spell`)
* [x] - Make some examples (flowchart, google translator, markdown, echo)
* [x] - Support custom display system
* [x] - Fix some bugs in `HeliumBundleFactory`
* [x] - Save spell rendering result into `note.json` while broadcasting to other websocket clients
* [x] - Fix `renderText` for stream output
### What is the Jira issue?
[ZEPPELIN-2008](https://issues.apache.org/jira/browse/ZEPPELIN-2008)
### How should this be tested?
- Build `mvn clean package -Phelium-dev -Pexamples -DskipTests;`
- Go to helium page `http://localhost:8080/#/helium`
- Enable all spells
- Go to a notebook and refresh
- Follow actions in the screenshots below.
### Screenshots (if appropriate)
#### Flowchart Spell (Sample)
![flowchart-spell](https://cloud.githubusercontent.com/assets/4968473/22275041/305f0eb8-e2ed-11e6-846a-9f1263ae46bc.gif)
#### Google Translator Spell (Sample)
![translator-spell](https://cloud.githubusercontent.com/assets/4968473/22280993/9820c238-e317-11e6-90f4-0e483312a09a.gif)
#### Display System with Spell
![display-spell](https://cloud.githubusercontent.com/assets/4968473/22275044/33694b78-e2ed-11e6-9ef0-188f4038381f.gif)
### Questions:
* Does the licenses files need update - NO
* Is there breaking changes for older versions? - NO
* Does this needs documentation? - YES, but framework can be enhanced so i would like to defer to write document right now.
Author: 1ambda <1a...@gmail.com>
Closes #1940 from 1ambda/ZEPPELIN-2008/introduce-spell and squashes the following commits:
c1b5356 [1ambda] fix: RAT issues
e07ecd3 [1ambda] fix: Set width for spell usage
6c91892 [1ambda] feat: Display magic, usage for spell
5be2890 [1ambda] feat: Support spell info
822a1d8 [1ambda] style: Remove useless func wrap for helium
35d0fcc [1ambda] fix: Update desc for spell examples
49e03fc [1ambda] fix: List visualziation bundles only in order
4029c02 [1ambda] fix: ParagraphIT, parameterizedQueryForm
08eba10 [1ambda] refactor: renderGraph in result.controller.js
69ce880 [1ambda] fix: Resolve append (stream) output
0f2d8b6 [1ambda] fix: Resolve output issue
fc4389e [1ambda] fix: Resolve RAT issues
c8c8f0e [1ambda] fix: Add setErrorMessage method to Job
4fec44c [1ambda] refactor: NotebookServer.java
1227d7d [1ambda] refactor: result controller retry
9fb7438 [1ambda] feat: Save spell result and propagate
3cdf2da [1ambda] fix: NPM installation error
72aadbf [1ambda] feat: Enhance translator spell
bd2b3ef [1ambda] style: Rename generator -> data
cac0667 [1ambda] style: Rename to Spell
e81cb03 [1ambda] example: Add echo, markdown
0fa7eda [1ambda] feat: Support custom display
c906da6 [1ambda] feat: Update examples to use single FrontIntpRes
5c49e6e [1ambda] feat: Automated display type checking in result
5810bf1 [1ambda] feat: Apply frontend interpreter to paragraph
a163044 [1ambda] feat: Add flowchart, translator examples
247d00f [1ambda] feat: Add frontend interpreter framework
e925967 [1ambda] feat: Support FRONTEND_INTERPRETER type in frontend
c02d00a [1ambda] feat: Support FRONTEND_INTERPRETER type in backend
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/0589e27e
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/0589e27e
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/0589e27e
Branch: refs/heads/master
Commit: 0589e27e7bb84ec81e1438bcbf3f2fd80ee5a963
Parents: 019df1f
Author: 1ambda <1a...@gmail.com>
Authored: Mon Jan 30 12:44:55 2017 +0900
Committer: Lee moon soo <mo...@apache.org>
Committed: Thu Feb 2 08:33:48 2017 +0900
----------------------------------------------------------------------
pom.xml | 1 +
.../src/assemble/distribution.xml | 4 +
zeppelin-examples/pom.xml | 4 +
.../zeppelin-example-spell-echo/index.js | 32 ++
.../zeppelin-example-spell-echo/package.json | 15 +
.../zeppelin-example-spell-echo/pom.xml | 116 ++++
.../zeppelin-example-spell-echo.json | 28 +
.../zeppelin-example-spell-flowchart/index.js | 108 ++++
.../package.json | 17 +
.../zeppelin-example-spell-flowchart/pom.xml | 116 ++++
.../zeppelin-example-spell-flowchart.json | 28 +
.../zeppelin-example-spell-markdown/index.js | 42 ++
.../package.json | 16 +
.../zeppelin-example-spell-markdown/pom.xml | 116 ++++
.../zeppelin-example-spell-markdown.json | 28 +
.../zeppelin-example-spell-translator/index.js | 93 ++++
.../package.json | 16 +
.../zeppelin-example-spell-translator/pom.xml | 116 ++++
.../zeppelin-example-spell-translator.json | 28 +
.../zeppelin/helium/ApplicationLoader.java | 2 +-
.../apache/zeppelin/helium/HeliumPackage.java | 25 +-
.../org/apache/zeppelin/helium/HeliumType.java | 29 +
.../zeppelin/helium/SpellPackageInfo.java | 34 ++
.../java/org/apache/zeppelin/scheduler/Job.java | 12 +-
.../zeppelin/helium/ApplicationLoaderTest.java | 4 +-
.../zeppelin/helium/HeliumPackageTest.java | 48 ++
.../org/apache/zeppelin/rest/HeliumRestApi.java | 21 +-
.../apache/zeppelin/server/ZeppelinServer.java | 24 +-
.../apache/zeppelin/socket/NotebookServer.java | 260 ++++++---
.../integration/ParagraphActionsIT.java | 3 +-
zeppelin-web/package.json | 2 +-
.../src/app/helium/helium.controller.js | 379 +++++++------
zeppelin-web/src/app/helium/helium.css | 34 +-
zeppelin-web/src/app/helium/helium.html | 32 +-
.../src/app/notebook/notebook.controller.js | 6 +-
.../notebook/paragraph/paragraph-control.html | 2 +-
.../paragraph-parameterizedQueryForm.html | 4 +-
.../notebook/paragraph/paragraph.controller.js | 367 +++++++++----
.../src/app/notebook/paragraph/paragraph.html | 4 +-
.../paragraph/result/result.controller.js | 534 +++++++++++--------
.../app/notebook/paragraph/result/result.html | 17 +-
zeppelin-web/src/app/spell/.npmignore | 1 +
zeppelin-web/src/app/spell/index.js | 25 +
zeppelin-web/src/app/spell/package.json | 13 +
zeppelin-web/src/app/spell/spell-base.js | 48 ++
zeppelin-web/src/app/spell/spell-result.js | 275 ++++++++++
.../src/components/helium/helium-type.js | 18 +
.../src/components/helium/helium.service.js | 115 ++--
.../websocketEvents/websocketEvents.factory.js | 2 +
.../websocketEvents/websocketMsg.service.js | 25 +
zeppelin-web/test/spec/controllers/paragraph.js | 4 -
zeppelin-web/webpack.config.js | 2 +-
.../java/org/apache/zeppelin/helium/Helium.java | 73 +--
.../zeppelin/helium/HeliumBundleFactory.java | 415 ++++++++++++++
.../org/apache/zeppelin/helium/HeliumConf.java | 12 +-
.../helium/HeliumVisualizationFactory.java | 376 -------------
.../zeppelin/notebook/socket/Message.java | 4 +-
.../src/main/resources/helium/package.json | 2 +-
.../src/main/resources/helium/webpack.config.js | 2 +-
.../helium/HeliumApplicationFactoryTest.java | 8 +-
.../helium/HeliumBundleFactoryTest.java | 197 +++++++
.../helium/HeliumLocalRegistryTest.java | 2 +-
.../org/apache/zeppelin/helium/HeliumTest.java | 5 +-
.../helium/HeliumVisualizationFactoryTest.java | 193 -------
64 files changed, 3243 insertions(+), 1341 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 7772369..a677443 100644
--- a/pom.xml
+++ b/pom.xml
@@ -893,6 +893,7 @@
<exclude>conf/notebook-authorization.json</exclude>
<exclude>conf/credentials.json</exclude>
<exclude>conf/zeppelin-env.sh</exclude>
+ <exclude>conf/helium.json</exclude>
<exclude>spark-*-bin*/**</exclude>
<exclude>.spark-dist/**</exclude>
<exclude>**/interpreter-setting.json</exclude>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-distribution/src/assemble/distribution.xml
----------------------------------------------------------------------
diff --git a/zeppelin-distribution/src/assemble/distribution.xml b/zeppelin-distribution/src/assemble/distribution.xml
index e8188e8..5c369e2 100644
--- a/zeppelin-distribution/src/assemble/distribution.xml
+++ b/zeppelin-distribution/src/assemble/distribution.xml
@@ -103,5 +103,9 @@
<outputDirectory>/lib/node_modules/zeppelin-tabledata</outputDirectory>
<directory>../zeppelin-web/src/app/tabledata</directory>
</fileSet>
+ <fileSet>
+ <outputDirectory>/lib/node_modules/zeppelin-spell</outputDirectory>
+ <directory>../zeppelin-web/src/app/spell</directory>
+ </fileSet>
</fileSets>
</assembly>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-examples/pom.xml b/zeppelin-examples/pom.xml
index 300ba57..e9f0473 100644
--- a/zeppelin-examples/pom.xml
+++ b/zeppelin-examples/pom.xml
@@ -36,6 +36,10 @@
<modules>
<module>zeppelin-example-clock</module>
<module>zeppelin-example-horizontalbar</module>
+ <module>zeppelin-example-spell-flowchart</module>
+ <module>zeppelin-example-spell-translator</module>
+ <module>zeppelin-example-spell-markdown</module>
+ <module>zeppelin-example-spell-echo</module>
</modules>
<build>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-echo/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-echo/index.js b/zeppelin-examples/zeppelin-example-spell-echo/index.js
new file mode 100644
index 0000000..955178e
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-echo/index.js
@@ -0,0 +1,32 @@
+/*
+ * 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 {
+ SpellBase,
+ SpellResult,
+ DefaultDisplayType,
+} from 'zeppelin-spell';
+
+export default class EchoSpell extends SpellBase {
+ constructor() {
+ super("%echo");
+ }
+
+ interpret(paragraphText) {
+ return new SpellResult(paragraphText);
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-echo/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-echo/package.json b/zeppelin-examples/zeppelin-example-spell-echo/package.json
new file mode 100644
index 0000000..2d9710e
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-echo/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "echo-spell",
+ "description" : "Return just what receive (example)",
+ "version": "1.0.0",
+ "main": "index",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "zeppelin-spell": "*"
+ },
+ "spell": {
+ "magic": "%echo",
+ "usage": "%echo <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-echo/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-echo/pom.xml b/zeppelin-examples/zeppelin-example-spell-echo/pom.xml
new file mode 100644
index 0000000..348abd2
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-echo/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>zeppelin-examples</artifactId>
+ <groupId>org.apache.zeppelin</groupId>
+ <version>0.8.0-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>zeppelin-example-spell-echo</artifactId>
+ <packaging>jar</packaging>
+ <version>0.8.0-SNAPSHOT</version>
+ <name>Zeppelin: Example Spell - Echo</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>zeppelin-interpreter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>helium-dev</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>${project.basedir}/../../helium</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <executions>
+ <execution>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+
+ <configuration>
+ <outputDirectory>${project.basedir}/../../helium/</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-echo/zeppelin-example-spell-echo.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-echo/zeppelin-example-spell-echo.json b/zeppelin-examples/zeppelin-example-spell-echo/zeppelin-example-spell-echo.json
new file mode 100644
index 0000000..f267b97
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-echo/zeppelin-example-spell-echo.json
@@ -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.
+ */
+{
+ "type" : "SPELL",
+ "name" : "echo-spell",
+ "description" : "Return just what receive (example)",
+ "artifact" : "./zeppelin-examples/zeppelin-example-spell-echo",
+ "license" : "Apache-2.0",
+ "icon" : "<i class='fa fa-repeat'></i>",
+ "spell": {
+ "magic": "%echo",
+ "usage": "%echo <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-flowchart/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-flowchart/index.js b/zeppelin-examples/zeppelin-example-spell-flowchart/index.js
new file mode 100644
index 0000000..655814a
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-flowchart/index.js
@@ -0,0 +1,108 @@
+/*
+ * 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 {
+ SpellBase,
+ SpellResult,
+ DefaultDisplayType,
+} from 'zeppelin-spell';
+
+import flowchart from 'flowchart.js';
+
+export default class FlowchartSpell extends SpellBase {
+ constructor() {
+ super("%flowchart");
+ }
+
+ interpret(paragraphText) {
+ /**
+ * `flowchart` library requires an existing DOM to render.
+ * but the DOM is not created yet when `interpret` is called.
+ * so Zeppelin allows to return callback function which accept a DOM element id.
+ * the callback function will executed when the DOM is ready.
+ */
+ const callback = (targetElemId) => {
+ let diagram = flowchart.parse(paragraphText);
+ diagram.drawSVG(targetElemId, this.getOption());
+ };
+
+ /**
+ * `interpret` method can return multiple results using `add()`
+ * but now, we return just 1 result
+ */
+ return new SpellResult(
+ callback
+ );
+ }
+
+ getOption() {
+ return {
+ 'x': 0,
+ 'y': 0,
+ 'line-width': 3,
+ 'line-length': 50,
+ 'text-margin': 10,
+ 'font-size': 14,
+ 'font-color': 'black',
+ 'line-color': 'black',
+ 'element-color': 'black',
+ 'fill': 'white',
+ 'yes-text': 'yes',
+ 'no-text': 'no',
+ 'arrow-end': 'block',
+ 'scale': 1,
+ // style symbol types
+ 'symbols': {
+ 'start': {
+ 'font-color': 'red',
+ 'element-color': 'green',
+ 'fill': 'yellow'
+ },
+ 'end':{
+ 'class': 'end-element'
+ }
+ },
+ // even flowstate support ;-)
+ 'flowstate' : {
+ 'past' : { 'fill' : '#CCCCCC', 'font-size' : 12},
+ 'current' : {'fill' : 'yellow', 'font-color' : 'red', 'font-weight' : 'bold'},
+ 'future' : { 'fill' : '#FFFF99'},
+ 'request' : { 'fill' : 'blue'},
+ 'invalid': {'fill' : '#444444'},
+ 'approved' : { 'fill' : '#58C4A3', 'font-size' : 12, 'yes-text' : 'APPROVED', 'no-text' : 'n/a' },
+ 'rejected' : { 'fill' : '#C45879', 'font-size' : 12, 'yes-text' : 'n/a', 'no-text' : 'REJECTED' }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-flowchart/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-flowchart/package.json b/zeppelin-examples/zeppelin-example-spell-flowchart/package.json
new file mode 100644
index 0000000..24be73b
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-flowchart/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "flowchart-spell",
+ "description" : "Draw flowchart using http://flowchart.js.org (example)",
+ "version": "1.0.0",
+ "main": "index",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "raphael": "2.2.0",
+ "flowchart.js": "^1.6.5",
+ "zeppelin-spell": "*"
+ },
+ "spell": {
+ "magic": "%flowchart",
+ "usage": "%flowchart <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-flowchart/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-flowchart/pom.xml b/zeppelin-examples/zeppelin-example-spell-flowchart/pom.xml
new file mode 100644
index 0000000..b3575c9
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-flowchart/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>zeppelin-examples</artifactId>
+ <groupId>org.apache.zeppelin</groupId>
+ <version>0.8.0-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>zeppelin-example-spell-flowchart</artifactId>
+ <packaging>jar</packaging>
+ <version>0.8.0-SNAPSHOT</version>
+ <name>Zeppelin: Example Spell - Flowchart</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>zeppelin-interpreter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>helium-dev</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>${project.basedir}/../../helium</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <executions>
+ <execution>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+
+ <configuration>
+ <outputDirectory>${project.basedir}/../../helium/</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-flowchart/zeppelin-example-spell-flowchart.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-flowchart/zeppelin-example-spell-flowchart.json b/zeppelin-examples/zeppelin-example-spell-flowchart/zeppelin-example-spell-flowchart.json
new file mode 100644
index 0000000..0ea6e41
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-flowchart/zeppelin-example-spell-flowchart.json
@@ -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.
+ */
+{
+ "type" : "SPELL",
+ "name" : "flowchart-spell",
+ "description" : "Draw flowchart using http://flowchart.js.org (example)",
+ "artifact" : "./zeppelin-examples/zeppelin-example-spell-flowchart",
+ "license" : "Apache-2.0",
+ "icon" : "<i class='fa fa-random'></i>",
+ "spell": {
+ "magic": "%flowchart",
+ "usage": "%flowchart <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-markdown/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-markdown/index.js b/zeppelin-examples/zeppelin-example-spell-markdown/index.js
new file mode 100644
index 0000000..db7959f
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-markdown/index.js
@@ -0,0 +1,42 @@
+/*
+ * 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 {
+ SpellBase,
+ SpellResult,
+ DefaultDisplayType,
+} from 'zeppelin-spell';
+
+import md from 'markdown';
+
+const markdown = md.markdown;
+
+export default class MarkdownSpell extends SpellBase {
+ constructor() {
+ super("%markdown");
+ }
+
+ interpret(paragraphText) {
+ const parsed = markdown.toHTML(paragraphText);
+
+ /**
+ * specify `DefaultDisplayType.HTML` since `parsed` will contain DOM
+ * otherwise it will be rendered as `DefaultDisplayType.TEXT` (default)
+ */
+ return new SpellResult(parsed, DefaultDisplayType.HTML);
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-markdown/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-markdown/package.json b/zeppelin-examples/zeppelin-example-spell-markdown/package.json
new file mode 100644
index 0000000..997a2a2
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-markdown/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "markdown-spell",
+ "description" : "Parse markdown using https://github.com/evilstreak/markdown-js (example)",
+ "version": "1.0.0",
+ "main": "index",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "markdown": "0.5.0",
+ "zeppelin-spell": "*"
+ },
+ "spell": {
+ "magic": "%markdown",
+ "usage": "%markdown <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-markdown/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-markdown/pom.xml b/zeppelin-examples/zeppelin-example-spell-markdown/pom.xml
new file mode 100644
index 0000000..b615ead
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-markdown/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>zeppelin-examples</artifactId>
+ <groupId>org.apache.zeppelin</groupId>
+ <version>0.8.0-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>zeppelin-example-spell-markdown</artifactId>
+ <packaging>jar</packaging>
+ <version>0.8.0-SNAPSHOT</version>
+ <name>Zeppelin: Example Spell - Markdown</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>zeppelin-interpreter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>helium-dev</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>${project.basedir}/../../helium</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <executions>
+ <execution>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+
+ <configuration>
+ <outputDirectory>${project.basedir}/../../helium/</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-markdown/zeppelin-example-spell-markdown.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-markdown/zeppelin-example-spell-markdown.json b/zeppelin-examples/zeppelin-example-spell-markdown/zeppelin-example-spell-markdown.json
new file mode 100644
index 0000000..48ad246
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-markdown/zeppelin-example-spell-markdown.json
@@ -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.
+ */
+{
+ "type" : "SPELL",
+ "name" : "markdown-spell",
+ "description" : "Parse markdown using https://github.com/evilstreak/markdown-js (example)",
+ "artifact" : "./zeppelin-examples/zeppelin-example-spell-markdown",
+ "license" : "Apache-2.0",
+ "icon" : "<i class='fa fa-bold'></i>",
+ "spell": {
+ "magic": "%markdown",
+ "usage": "%markdown <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-translator/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-translator/index.js b/zeppelin-examples/zeppelin-example-spell-translator/index.js
new file mode 100644
index 0000000..834e707
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-translator/index.js
@@ -0,0 +1,93 @@
+/*
+ * 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 {
+ SpellBase,
+ SpellResult,
+ DefaultDisplayType,
+} from 'zeppelin-spell';
+
+import 'whatwg-fetch';
+
+export default class TranslatorSpell extends SpellBase {
+ constructor() {
+ super("%translator");
+ }
+
+ interpret(paragraphText) {
+ const parsed = this.parseConfig(paragraphText);
+ const source = parsed.source;
+ const target = parsed.target;
+ const auth = parsed.auth;
+ const text = parsed.text;
+
+ /**
+ * SpellResult.add()
+ * - accepts not only `string` but also `promise` as a parameter
+ * - allows to add multiple output using the `add()` function
+ */
+ const result = new SpellResult()
+ .add('<h4>Translation Result</h4>', DefaultDisplayType.HTML)
+ // or use display system implicitly like
+ // .add('%html <h4>Translation From English To Korean</h4>')
+ .add(this.translate(source, target, auth, text));
+ return result;
+ }
+
+ parseConfig(text) {
+ const pattern = /^\s*(\S+)-(\S+)\s*(\S+)([\S\s]*)/g;
+ const match = pattern.exec(text);
+
+ if (!match) {
+ throw new Error(`Failed to parse configuration. See README`);
+ }
+
+ return {
+ source: match[1],
+ target: match[2],
+ auth: match[3],
+ text: match[4],
+ }
+ }
+
+ translate(source, target, auth, text) {
+ return fetch('https://translation.googleapis.com/language/translate/v2', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${auth}`,
+ },
+ body: JSON.stringify({
+ 'q': text,
+ 'source': source,
+ 'target': target,
+ 'format': 'text'
+ })
+ }).then(response => {
+ if (response.status === 200) {
+ return response.json()
+ }
+ throw new Error(`https://translation.googleapis.com/language/translate/v2 ${response.status} (${response.statusText})`);
+ }).then((json) => {
+ const extracted = json.data.translations.map(t => {
+ return t.translatedText;
+ });
+ return extracted.join('\n');
+ });
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-translator/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-translator/package.json b/zeppelin-examples/zeppelin-example-spell-translator/package.json
new file mode 100644
index 0000000..90624f8
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-translator/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "translator-spell",
+ "description" : "Translate langauges using Google API (examaple)",
+ "version": "1.0.0",
+ "main": "index",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "whatwg-fetch": "^2.0.1",
+ "zeppelin-spell": "*"
+ },
+ "spell": {
+ "magic": "%translator",
+ "usage": "%translator <source>-<target> <access-key> <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-translator/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-translator/pom.xml b/zeppelin-examples/zeppelin-example-spell-translator/pom.xml
new file mode 100644
index 0000000..09e6daa
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-translator/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>zeppelin-examples</artifactId>
+ <groupId>org.apache.zeppelin</groupId>
+ <version>0.8.0-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>zeppelin-example-spell-translator</artifactId>
+ <packaging>jar</packaging>
+ <version>0.8.0-SNAPSHOT</version>
+ <name>Zeppelin: Example Spell - Translator</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>zeppelin-interpreter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>helium-dev</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>${project.basedir}/../../helium</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <executions>
+ <execution>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+
+ <configuration>
+ <outputDirectory>${project.basedir}/../../helium/</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}</directory>
+ <includes>
+ <include>${project.artifactId}.json</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-examples/zeppelin-example-spell-translator/zeppelin-example-spell-translator.json
----------------------------------------------------------------------
diff --git a/zeppelin-examples/zeppelin-example-spell-translator/zeppelin-example-spell-translator.json b/zeppelin-examples/zeppelin-example-spell-translator/zeppelin-example-spell-translator.json
new file mode 100644
index 0000000..8f99783
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-spell-translator/zeppelin-example-spell-translator.json
@@ -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.
+ */
+{
+ "type" : "SPELL",
+ "name" : "translator-spell",
+ "description" : "Translate langauges using Google API (examaple)",
+ "artifact" : "./zeppelin-examples/zeppelin-example-spell-translator",
+ "license" : "Apache-2.0",
+ "icon" : "<i class='fa fa-globe '></i>",
+ "spell": {
+ "magic": "%translator",
+ "usage": "%translator <source>-<target> <access-key> <TEXT>"
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/ApplicationLoader.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/ApplicationLoader.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/ApplicationLoader.java
index eacef51..ddd061c 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/ApplicationLoader.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/ApplicationLoader.java
@@ -102,7 +102,7 @@ public class ApplicationLoader {
*/
public Application load(HeliumPackage packageInfo, ApplicationContext context)
throws Exception {
- if (packageInfo.getType() != HeliumPackage.Type.APPLICATION) {
+ if (packageInfo.getType() != HeliumType.APPLICATION) {
throw new ApplicationException(
"Can't instantiate " + packageInfo.getType() + " package using ApplicationLoader");
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 84a2ab3..e8e6b7c 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
@@ -23,7 +23,7 @@ import org.apache.zeppelin.annotation.Experimental;
*/
@Experimental
public class HeliumPackage {
- private Type type;
+ private HeliumType type;
private String name; // user friendly name of this application
private String description; // description
private String artifact; // artifact name e.g) groupId:artifactId:versionId
@@ -33,17 +33,9 @@ public class HeliumPackage {
private String license;
private String icon;
- /**
- * Type of package
- */
- public static enum Type {
- INTERPRETER,
- NOTEBOOK_REPO,
- APPLICATION,
- VISUALIZATION
- }
+ public SpellPackageInfo spell;
- public HeliumPackage(Type type,
+ public HeliumPackage(HeliumType type,
String name,
String description,
String artifact,
@@ -76,10 +68,15 @@ public class HeliumPackage {
return type == info.type && artifact.equals(info.artifact) && className.equals(info.className);
}
- public Type getType() {
+ public HeliumType getType() {
return type;
}
+ public static boolean isBundleType(HeliumType type) {
+ return (type == HeliumType.VISUALIZATION ||
+ type == HeliumType.SPELL);
+ }
+
public String getName() {
return name;
}
@@ -106,4 +103,8 @@ public class HeliumPackage {
public String getIcon() {
return icon;
}
+
+ public SpellPackageInfo getSpellInfo() {
+ return spell;
+ }
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumType.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumType.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumType.java
new file mode 100644
index 0000000..53360a0
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumType.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Type of Helium Package
+ */
+public enum HeliumType {
+ INTERPRETER,
+ NOTEBOOK_REPO,
+ APPLICATION,
+ VISUALIZATION,
+ SPELL
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/SpellPackageInfo.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/SpellPackageInfo.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/SpellPackageInfo.java
new file mode 100644
index 0000000..519d09d
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/SpellPackageInfo.java
@@ -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.
+ */
+
+package org.apache.zeppelin.helium;
+
+/**
+ * Info for Helium Spell Package.
+ */
+public class SpellPackageInfo {
+ private String magic;
+ private String usage;
+
+ public String getMagic() {
+ return magic;
+ }
+
+ public String getUsage() {
+ return usage;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java
index a690bef..76d90b9 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java
@@ -64,7 +64,6 @@ public abstract class Job {
}
}
-
private String jobName;
String id;
@@ -135,6 +134,13 @@ public abstract class Job {
return status;
}
+ /**
+ * just set status without notifying to listeners for spell.
+ */
+ public void setStatusWithoutNotification(Status status) {
+ this.status = status;
+ }
+
public void setStatus(Status status) {
if (this.status == status) {
return;
@@ -257,4 +263,8 @@ public abstract class Job {
}
public abstract void setResult(Object results);
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 3924e28..acb4d7f 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
@@ -20,8 +20,6 @@ package org.apache.zeppelin.helium;
import org.apache.commons.io.FileUtils;
import org.apache.zeppelin.dep.DependencyResolver;
import org.apache.zeppelin.interpreter.InterpreterOutput;
-import org.apache.zeppelin.interpreter.InterpreterOutputListener;
-import org.apache.zeppelin.interpreter.InterpreterResultMessageOutput;
import org.apache.zeppelin.resource.LocalResourcePool;
import org.junit.After;
import org.junit.Before;
@@ -74,7 +72,7 @@ public class ApplicationLoaderTest {
public HeliumPackage createPackageInfo(String className, String artifact) {
HeliumPackage app1 = new HeliumPackage(
- HeliumPackage.Type.APPLICATION,
+ HeliumType.APPLICATION,
"name1",
"desc1",
artifact,
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/HeliumPackageTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/HeliumPackageTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/HeliumPackageTest.java
new file mode 100644
index 0000000..aadae41
--- /dev/null
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/HeliumPackageTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.google.gson.Gson;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class HeliumPackageTest {
+
+ private Gson gson = new Gson();
+
+ @Test
+ public void parseSpellPackageInfo() {
+ String exampleSpell = "{\n" +
+ " \"type\" : \"SPELL\",\n" +
+ " \"name\" : \"echo-spell\",\n" +
+ " \"description\" : \"'%echo' - return just what receive (example)\",\n" +
+ " \"artifact\" : \"./zeppelin-examples/zeppelin-example-spell-echo\",\n" +
+ " \"license\" : \"Apache-2.0\",\n" +
+ " \"icon\" : \"<i class='fa fa-repeat'></i>\",\n" +
+ " \"spell\": {\n" +
+ " \"magic\": \"%echo\",\n" +
+ " \"usage\": \"%echo <TEXT>\"\n" +
+ " }\n" +
+ "}";
+
+ HeliumPackage p = gson.fromJson(exampleSpell, HeliumPackage.class);
+ assertEquals(p.getSpellInfo().getMagic(), "%echo");
+ assertEquals(p.getSpellInfo().getUsage(), "%echo <TEXT>");
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 e5cf70d..c318be5 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,7 +17,6 @@
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;
@@ -107,22 +106,22 @@ public class HeliumRestApi {
}
@GET
- @Path("visualizations/load")
+ @Path("bundle/load")
@Produces("text/javascript")
- public Response visualizationLoad(@QueryParam("refresh") String refresh) {
+ public Response bundleLoad(@QueryParam("refresh") String refresh) {
try {
File bundle;
if (refresh != null && refresh.equals("true")) {
- bundle = helium.recreateVisualizationBundle();
+ bundle = helium.recreateBundle();
} else {
- bundle = helium.getVisualizationFactory().getCurrentBundle();
+ bundle = helium.getBundleFactory().getCurrentCacheBundle();
}
if (bundle == null) {
return Response.ok().build();
} else {
- String visBundle = FileUtils.readFileToString(bundle);
- return Response.ok(visBundle).build();
+ String stringifiedBundle = FileUtils.readFileToString(bundle);
+ return Response.ok(stringifiedBundle).build();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -160,15 +159,15 @@ public class HeliumRestApi {
}
@GET
- @Path("visualizationOrder")
+ @Path("order/visualization")
public Response getVisualizationPackageOrder() {
- List<String> order = helium.getVisualizationPackageOrder();
+ List<String> order = helium.setVisualizationPackageOrder();
return new JsonResponse(Response.Status.OK, order).build();
}
@POST
- @Path("visualizationOrder")
- public Response setVisualizationPackageOrder(String orderedPackageNameList) {
+ @Path("order/visualization")
+ public Response getVisualizationPackageOrder(String orderedPackageNameList) {
List<String> orderedList = gson.fromJson(
orderedPackageNameList, new TypeToken<List<String>>(){}.getType());
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 6c4fcd8..371d0a1 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,7 +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.helium.HeliumBundleFactory;
import org.apache.zeppelin.interpreter.InterpreterFactory;
import org.apache.zeppelin.interpreter.InterpreterOutput;
import org.apache.zeppelin.notebook.Notebook;
@@ -102,7 +102,7 @@ public class ZeppelinServer extends Application {
InterpreterOutput.limit = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_OUTPUT_LIMIT);
HeliumApplicationFactory heliumApplicationFactory = new HeliumApplicationFactory();
- HeliumVisualizationFactory heliumVisualizationFactory;
+ HeliumBundleFactory heliumBundleFactory;
if (isBinaryPackage(conf)) {
/* In binary package, zeppelin-web/src/app/visualization and zeppelin-web/src/app/tabledata
@@ -110,28 +110,30 @@ public class ZeppelinServer extends Application {
* Check zeppelin/zeppelin-distribution/src/assemble/distribution.xml to see how they're
* packaged into binary package.
*/
- heliumVisualizationFactory = new HeliumVisualizationFactory(
+ heliumBundleFactory = new HeliumBundleFactory(
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")));
+ new File(conf.getRelativeDir("lib/node_modules/zeppelin-vis")),
+ new File(conf.getRelativeDir("lib/node_modules/zeppelin-spell")));
} else {
- heliumVisualizationFactory = new HeliumVisualizationFactory(
+ heliumBundleFactory = new HeliumBundleFactory(
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")));
+ new File(conf.getRelativeDir("zeppelin-web/src/app/visualization")),
+ new File(conf.getRelativeDir("zeppelin-web/src/app/spell")));
}
this.helium = new Helium(
conf.getHeliumConfPath(),
conf.getHeliumRegistry(),
- new File(
- conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO), "helium_registry_cache"),
- heliumVisualizationFactory,
+ new File(conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO),
+ "helium-registry-cache"),
+ heliumBundleFactory,
heliumApplicationFactory);
- // create visualization bundle
+ // create bundle
try {
- heliumVisualizationFactory.bundle(helium.getVisualizationPackagesToBundle());
+ heliumBundleFactory.buildBundle(helium.getBundlePackagesToBundle());
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 6e58e3d..68b015d 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -262,6 +262,9 @@ public class NotebookServer extends WebSocketServlet
case RUN_PARAGRAPH:
runParagraph(conn, userAndRoles, notebook, messagereceived);
break;
+ case PARAGRAPH_EXECUTED_BY_SPELL:
+ broadcastSpellExecution(conn, userAndRoles, notebook, messagereceived);
+ break;
case RUN_ALL_PARAGRAPHS:
runAllParagraphs(conn, userAndRoles, notebook, messagereceived);
break;
@@ -698,6 +701,63 @@ public class NotebookServer extends WebSocketServlet
.toString())));
}
+ /**
+ * @return false if user doesn't have reader permission for this paragraph
+ */
+ private boolean hasParagraphReaderPermission(NotebookSocket conn,
+ Notebook notebook, String noteId,
+ HashSet<String> userAndRoles,
+ String principal, String op)
+ throws IOException {
+
+ NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
+ if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
+ permissionError(conn, op, principal, userAndRoles,
+ notebookAuthorization.getOwners(noteId));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return false if user doesn't have writer permission for this paragraph
+ */
+ private boolean hasParagraphWriterPermission(NotebookSocket conn,
+ Notebook notebook, String noteId,
+ HashSet<String> userAndRoles,
+ String principal, String op)
+ throws IOException {
+
+ NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
+ if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
+ permissionError(conn, op, principal, userAndRoles,
+ notebookAuthorization.getOwners(noteId));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return false if user doesn't have owner permission for this paragraph
+ */
+ private boolean hasParagraphOwnerPermission(NotebookSocket conn,
+ Notebook notebook, String noteId,
+ HashSet<String> userAndRoles,
+ String principal, String op)
+ throws IOException {
+
+ NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
+ if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
+ permissionError(conn, op, principal, userAndRoles,
+ notebookAuthorization.getOwners(noteId));
+ return false;
+ }
+
+ return true;
+ }
+
private void sendNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
@@ -713,13 +773,13 @@ public class NotebookServer extends WebSocketServlet
String user = fromMessage.principal;
Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
if (note != null) {
- if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
- permissionError(conn, "read", fromMessage.principal, userAndRoles,
- notebookAuthorization.getReaders(noteId));
+
+ if (!hasParagraphReaderPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "read")) {
return;
}
+
addConnectionToNote(note.getId(), conn);
if (note.isPersonalizedMode()) {
@@ -743,12 +803,11 @@ public class NotebookServer extends WebSocketServlet
}
if (note != null) {
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
- permissionError(conn, "read", fromMessage.principal, userAndRoles,
- notebookAuthorization.getReaders(noteId));
+ if (!hasParagraphReaderPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "read")) {
return;
}
+
addConnectionToNote(note.getId(), conn);
conn.send(serializeMessage(new Message(OP.NOTE).put("note", note)));
sendAllAngularObjects(note, user, conn);
@@ -770,10 +829,8 @@ public class NotebookServer extends WebSocketServlet
return;
}
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "update", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "update")) {
return;
}
@@ -804,10 +861,8 @@ public class NotebookServer extends WebSocketServlet
return;
}
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
- permissionError(conn, "persoanlized ", fromMessage.principal, userAndRoles,
- notebookAuthorization.getOwners(noteId));
+ if (!hasParagraphOwnerPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "persoanlized")) {
return;
}
@@ -836,10 +891,8 @@ public class NotebookServer extends WebSocketServlet
return;
}
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
- permissionError(conn, "rename", fromMessage.principal, userAndRoles,
- notebookAuthorization.getOwners(noteId));
+ if (!hasParagraphOwnerPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "rename")) {
return;
}
@@ -870,12 +923,10 @@ public class NotebookServer extends WebSocketServlet
return;
}
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
for (Note note : notebook.getNotesUnderFolder(oldFolderId)) {
String noteId = note.getId();
- if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
- permissionError(conn, op + " folder of '" + note.getName() + "'", fromMessage.principal,
- userAndRoles, notebookAuthorization.getOwners(noteId));
+ if (!hasParagraphOwnerPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, op + " folder of '" + note.getName() + "'")) {
return;
}
}
@@ -960,11 +1011,8 @@ public class NotebookServer extends WebSocketServlet
return;
}
- Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
- permissionError(conn, "remove", fromMessage.principal, userAndRoles,
- notebookAuthorization.getOwners(noteId));
+ if (!hasParagraphOwnerPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "remove")) {
return;
}
@@ -982,13 +1030,12 @@ public class NotebookServer extends WebSocketServlet
return;
}
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
List<Note> notes = notebook.getNotesUnderFolder(folderId);
for (Note note : notes) {
String noteId = note.getId();
- if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
- permissionError(conn, "remove folder of '" + note.getName() + "'", fromMessage.principal,
- userAndRoles, notebookAuthorization.getOwners(noteId));
+
+ if (!hasParagraphOwnerPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "remove folder of '" + note.getName() + "'")) {
return;
}
}
@@ -1107,17 +1154,16 @@ public class NotebookServer extends WebSocketServlet
Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
String noteId = getOpenNoteId(conn);
- final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return;
}
+ final Note note = notebook.getNote(noteId);
Paragraph p = note.getParagraph(paragraphId);
+ AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
if (note.isPersonalizedMode()) {
p = p.getUserParagraphMap().get(subject.getUser());
}
@@ -1154,14 +1200,13 @@ public class NotebookServer extends WebSocketServlet
if (StringUtils.isBlank(noteId)) {
return;
}
- Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "clear output", fromMessage.principal, userAndRoles,
- notebookAuthorization.getOwners(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "clear output")) {
return;
}
+ Note note = notebook.getNote(noteId);
note.clearAllParagraphOutput();
broadcastNote(note);
}
@@ -1193,17 +1238,16 @@ public class NotebookServer extends WebSocketServlet
return;
}
String noteId = getOpenNoteId(conn);
- final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return;
}
/** We dont want to remove the last paragraph */
+ final Note note = notebook.getNote(noteId);
if (!note.isLastParagraph(paragraphId)) {
+ AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
Paragraph para = note.removeParagraph(subject.getUser(), paragraphId);
note.persist(subject);
if (para != null) {
@@ -1219,14 +1263,14 @@ public class NotebookServer extends WebSocketServlet
if (paragraphId == null) {
return;
}
+
String noteId = getOpenNoteId(conn);
- final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return;
}
+
+ final Note note = notebook.getNote(noteId);
note.clearParagraphOutput(paragraphId);
Paragraph paragraph = note.getParagraph(paragraphId);
broadcastParagraph(note, paragraph);
@@ -1470,14 +1514,13 @@ public class NotebookServer extends WebSocketServlet
final int newIndex = (int) Double.parseDouble(fromMessage.get("index").toString());
String noteId = getOpenNoteId(conn);
final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return;
}
+ AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
note.moveParagraph(paragraphId, newIndex);
note.persist(subject);
broadcast(note.getId(),
@@ -1489,11 +1532,10 @@ public class NotebookServer extends WebSocketServlet
final int index = (int) Double.parseDouble(fromMessage.get("index").toString());
String noteId = getOpenNoteId(conn);
final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return null;
}
@@ -1524,14 +1566,13 @@ public class NotebookServer extends WebSocketServlet
}
String noteId = getOpenNoteId(conn);
- final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return;
}
+ final Note note = notebook.getNote(noteId);
Paragraph p = note.getParagraph(paragraphId);
p.abort();
}
@@ -1544,11 +1585,8 @@ public class NotebookServer extends WebSocketServlet
return;
}
- Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "run all paragraphs", fromMessage.principal, userAndRoles,
- notebookAuthorization.getOwners(noteId));
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "run all paragraphs")) {
return;
}
@@ -1567,6 +1605,7 @@ public class NotebookServer extends WebSocketServlet
Map<String, Object> params = (Map<String, Object>) raw.get("params");
Map<String, Object> config = (Map<String, Object>) raw.get("config");
+ Note note = notebook.getNote(noteId);
Paragraph p = setParagraphUsingMessage(note, fromMessage,
paragraphId, text, title, params, config);
@@ -1574,6 +1613,45 @@ public class NotebookServer extends WebSocketServlet
}
}
+ private void broadcastSpellExecution(NotebookSocket conn, HashSet<String> userAndRoles,
+ Notebook notebook, Message fromMessage)
+ throws IOException {
+
+ final String paragraphId = (String) fromMessage.get("id");
+ if (paragraphId == null) {
+ return;
+ }
+
+ String noteId = getOpenNoteId(conn);
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
+ return;
+ }
+
+ String text = (String) fromMessage.get("paragraph");
+ String title = (String) fromMessage.get("title");
+ Status status = Status.valueOf((String) fromMessage.get("status"));
+ Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
+ Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
+
+ final Note note = notebook.getNote(noteId);
+ Paragraph p = setParagraphUsingMessage(note, fromMessage, paragraphId,
+ text, title, params, config);
+ p.setResult(fromMessage.get("results"));
+ p.setErrorMessage((String) fromMessage.get("errorMessage"));
+ p.setStatusWithoutNotification(status);
+
+ addNewParagraphIfLastParagraphIsExecuted(note, p);
+ if (!persistNoteWithAuthInfo(conn, note, p)) {
+ return;
+ }
+
+ // broadcast to other clients only
+ broadcastExcept(note.getId(),
+ new Message(OP.RUN_PARAGRAPH_USING_SPELL).put("paragraph", p), conn);
+ }
+
private void runParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
@@ -1582,11 +1660,9 @@ public class NotebookServer extends WebSocketServlet
}
String noteId = getOpenNoteId(conn);
- final Note note = notebook.getNote(noteId);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "write", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "write")) {
return;
}
@@ -1594,14 +1670,15 @@ public class NotebookServer extends WebSocketServlet
String title = (String) fromMessage.get("title");
Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
+
+ final Note note = notebook.getNote(noteId);
Paragraph p = setParagraphUsingMessage(note, fromMessage, paragraphId,
text, title, params, config);
persistAndExecuteSingleParagraph(conn, note, p);
}
- private void persistAndExecuteSingleParagraph(NotebookSocket conn,
- Note note, Paragraph p) throws IOException {
+ private void addNewParagraphIfLastParagraphIsExecuted(Note note, Paragraph p) {
// if it's the last paragraph and empty, let's add a new one
boolean isTheLastParagraph = note.isLastParagraph(p.getId());
if (!(p.getText().trim().equals(p.getMagic()) ||
@@ -1610,15 +1687,30 @@ public class NotebookServer extends WebSocketServlet
Paragraph newPara = note.addParagraph(p.getAuthenticationInfo());
broadcastNewParagraph(note, newPara);
}
+ }
+ /**
+ * @return false if failed to save a note
+ */
+ private boolean persistNoteWithAuthInfo(NotebookSocket conn,
+ Note note, Paragraph p) throws IOException {
try {
note.persist(p.getAuthenticationInfo());
+ return true;
} catch (FileSystemException ex) {
LOG.error("Exception from run", ex);
conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
"Oops! There is something wrong with the notebook file system. "
+ "Please check the logs for more details.")));
// don't run the paragraph when there is error on persisting the note information
+ return false;
+ }
+ }
+
+ private void persistAndExecuteSingleParagraph(NotebookSocket conn,
+ Note note, Paragraph p) throws IOException {
+ addNewParagraphIfLastParagraphIsExecuted(note, p);
+ if (!persistNoteWithAuthInfo(conn, note, p)) {
return;
}
@@ -1701,10 +1793,8 @@ public class NotebookServer extends WebSocketServlet
String revisionId = (String) fromMessage.get("revisionId");
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
- NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
- if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
- permissionError(conn, "update", fromMessage.principal, userAndRoles,
- notebookAuthorization.getWriters(noteId));
+ if (!hasParagraphWriterPermission(conn, notebook, noteId,
+ userAndRoles, fromMessage.principal, "update")) {
return;
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
index feade7f..458b8d4 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
@@ -270,7 +270,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
collector.checkThat("Before Run Output field contains ",
driver.findElements(By.xpath(xpathToOutputField)).size(),
CoreMatchers.equalTo(0));
- driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@ng-click='runParagraph(getEditorValue())']")).click();
+ runParagraph(1);
waitForParagraph(1, "FINISHED");
collector.checkThat("After Run Output field contains ",
driver.findElement(By.xpath(xpathToOutputField)).getText(),
@@ -286,7 +286,6 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
} catch (Exception e) {
handleException("Exception in ParagraphActionsIT while testClearOutputButton ", e);
}
-
}
@Test
[2/3] zeppelin git commit: [ZEPPELIN-2008] Introduce Spell
Posted by mo...@apache.org.
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/package.json b/zeppelin-web/package.json
index 0ad6398..c9bba37 100644
--- a/zeppelin-web/package.json
+++ b/zeppelin-web/package.json
@@ -12,7 +12,7 @@
"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:helium": "HELIUM_BUNDLE_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",
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
index a344e80..b68c1bb 100644
--- a/zeppelin-web/src/app/helium/helium.controller.js
+++ b/zeppelin-web/src/app/helium/helium.controller.js
@@ -12,208 +12,205 @@
* 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);
+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.bundleOrder = [];
+ $scope.bundleOrderChanged = 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;
}
}
- $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;
+
+ // show first available version if package is not enabled
+ if (!defaultVersions[name]) {
+ defaultVersions[name] = pkgs[0];
+ pkgs.splice(0, 1);
}
- };
-
- 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;
- }
- }
- });
}
+ $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 getBundleOrder = function() {
+ heliumService.getVisualizationPackageOrder().
+ success(function(data, status) {
+ $scope.bundleOrder = data.body;
+ }).
+ error(function(data, status) {
+ console.log('Can not get bundle order %o %o', status, data);
+ });
+ };
+
+ $scope.bundleOrderListeners = {
+ accept: function(sourceItemHandleScope, destSortableScope) {return true;},
+ itemMoved: function(event) {},
+ orderChanged: function(event) {
+ $scope.bundleOrderChanged = true;
+ }
+ };
+
+ var init = function() {
+ getAllPackageInfo();
+ getBundleOrder();
+ $scope.bundleOrderChanged = false;
+ };
+
+ init();
+
+ $scope.saveBundleOrder = 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.setVisualizationPackageOrder($scope.bundleOrder).
+ 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 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;
- });
+ 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 (pkg.length > 0) {
+ license = pkg[0].pkg.license;
}
+ } else {
+ license = pkg[0].license;
+ }
- if (!license) {
- license = 'Unknown';
- }
- return 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.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.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;
}
- };
- }
-})();
+ });
+ };
+
+ $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/0589e27e/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
index f17d6bd..e66797d 100644
--- a/zeppelin-web/src/app/helium/helium.css
+++ b/zeppelin-web/src/app/helium/helium.css
@@ -51,11 +51,33 @@
margin-top: 0;
}
-.heliumPackageList .heliumPackageName span {
- font-size: 10px;
+.heliumPackageList .heliumPackageName .heliumType {
+ font-size: 13px;
color: #AAAAAA;
}
+.spellInfo {
+ margin-top: 15px;
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.spellInfo .spellInfoDesc {
+ font-size: 13px;
+ color: #AAAAAA;
+}
+
+.spellInfo .spellInfoValue {
+ font-size: 13px;
+ font-style: italic;
+ color: #444444;
+}
+
+.spellInfo .spellUsage {
+ margin-top: 8px;
+ margin-bottom: 4px;
+ width: 500px;
+}
.heliumPackageList .heliumPackageDisabledArtifact {
color:gray;
@@ -77,12 +99,12 @@
margin-top: 10px;
}
-.heliumVisualizationOrder {
+.heliumBundleOrder {
display: inline-block;
}
-.heliumVisualizationOrder .as-sortable-item,
-.heliumVisualizationOrder .as-sortable-placeholder {
+.heliumBundleOrder .as-sortable-item,
+.heliumBundleOrder .as-sortable-placeholder {
display: inline-block;
float: left;
}
@@ -97,7 +119,7 @@
height: 100%;
}
-.heliumVisualizationOrder .saveLink {
+.heliumBundleOrder .saveLink {
margin-left:10px;
margin-top:5px;
cursor:pointer;
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
index 546995c..341ade3 100644
--- a/zeppelin-web/src/app/helium/helium.html
+++ b/zeppelin-web/src/app/helium/helium.html
@@ -20,13 +20,13 @@ limitations under the License.
</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 ng-show="bundleOrder.length > 1"
+ class="row heliumBundleOrder">
+ <div style="margin:0 0 5px 15px">Bundle 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="bundleOrderListeners"
+ data-ng-model="bundleOrder">
+ <div class="btn-group" data-ng-repeat="pkgName in bundleOrder"
as-sortable-item>
<div class="btn btn-default btn-sm"
ng-bind-html='defaultVersions[pkgName].pkg.icon'
@@ -34,8 +34,8 @@ limitations under the License.
</div>
</div>
<span class="saveLink"
- ng-show="visualizationOrderChanged"
- ng-click="saveVisualizationOrder()">
+ ng-show="bundleOrderChanged"
+ ng-click="saveBundleOrder()">
save
</span>
</div>
@@ -50,7 +50,10 @@ limitations under the License.
<div class="heliumPackageHead">
<div class="heliumPackageIcon"
ng-bind-html=pkgInfo.pkg.icon></div>
- <div class="heliumPackageName">{{pkgName}} <span>{{pkgInfo.pkg.type}}</span></div>
+ <div class="heliumPackageName">
+ {{pkgName}}
+ <span class="heliumType">{{pkgInfo.pkg.type}}</span>
+ </div>
<div ng-show="!pkgInfo.enabled"
ng-click="enable(pkgName, pkgInfo.pkg.artifact)"
class="btn btn-success btn-xs"
@@ -81,6 +84,17 @@ limitations under the License.
<div class="heliumPackageDescription">
{{pkgInfo.pkg.description}}
</div>
+ <div ng-if="pkgInfo.pkg.type === 'SPELL' && pkgInfo.pkg.spell"
+ class="spellInfo">
+ <div>
+ <span class="spellInfoDesc">MAGIC</span>
+ <span class="spellInfoValue">{{pkgInfo.pkg.spell.magic}} </span>
+ </div>
+ <div>
+ <span class="spellInfoDesc">USAGE</span>
+ <pre class="spellUsage">{{pkgInfo.pkg.spell.usage}} </pre>
+ </div>
+ </div>
</div>
</div>
</div>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/notebook/notebook.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js
index ccf64b7..b434b64 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -28,12 +28,14 @@ NotebookCtrl.$inject = [
'ngToast',
'noteActionSrv',
'noteVarShareService',
- 'TRASH_FOLDER_ID'
+ 'TRASH_FOLDER_ID',
+ 'heliumService',
];
function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope,
$http, websocketMsgSrv, baseUrlSrv, $timeout, saveAsService,
- ngToast, noteActionSrv, noteVarShareService, TRASH_FOLDER_ID) {
+ ngToast, noteActionSrv, noteVarShareService, TRASH_FOLDER_ID,
+ heliumService) {
ngToast.dismiss();
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
index 644761e..351fb5f 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
@@ -25,7 +25,7 @@ limitations under the License.
<!-- Run / Cancel button -->
<span ng-if="!revisionView">
<span class="icon-control-play" style="cursor:pointer;color:#3071A9" tooltip-placement="top" tooltip="Run this paragraph (Shift+Enter)"
- ng-click="runParagraph(getEditorValue())"
+ ng-click="runParagraphFromButton(getEditorValue())"
ng-show="paragraph.status!='RUNNING' && paragraph.status!='PENDING' && paragraph.config.enabled"></span>
<span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C" tooltip-placement="top"
tooltip="Cancel (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+c)"
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
index 117e11c..65d13b7 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
@@ -22,14 +22,14 @@ limitations under the License.
<input class="form-control input-sm"
ng-if="!paragraph.settings.forms[formulaire.name].options"
- ng-enter="runParagraph(getEditorValue())"
+ ng-enter="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}" />
<select class="form-control input-sm"
ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
- ng-enter="runParagraph(getEditorValue())"
+ ng-enter="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}"
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index ef35b49..da82080 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -12,6 +12,10 @@
* limitations under the License.
*/
+import {
+ SpellResult,
+} from '../../spell';
+
angular.module('zeppelinWebApp').controller('ParagraphCtrl', ParagraphCtrl);
ParagraphCtrl.$inject = [
@@ -29,15 +33,19 @@ ParagraphCtrl.$inject = [
'baseUrlSrv',
'ngToast',
'saveAsService',
- 'noteVarShareService'
+ 'noteVarShareService',
+ 'heliumService'
];
function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $location,
$timeout, $compile, $http, $q, websocketMsgSrv,
- baseUrlSrv, ngToast, saveAsService, noteVarShareService) {
+ baseUrlSrv, ngToast, saveAsService, noteVarShareService,
+ heliumService) {
var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_';
$scope.parentNote = null;
- $scope.paragraph = null;
+ $scope.paragraph = {};
+ $scope.paragraph.results = {};
+ $scope.paragraph.results.msg = [];
$scope.originalText = '';
$scope.editor = null;
@@ -219,21 +227,77 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
websocketMsgSrv.cancelParagraphRun(paragraph.id);
};
- $scope.runParagraph = function(data) {
- websocketMsgSrv.runParagraph($scope.paragraph.id, $scope.paragraph.title,
- data, $scope.paragraph.config, $scope.paragraph.settings.params);
- $scope.originalText = angular.copy(data);
- $scope.dirtyText = undefined;
+ $scope.propagateSpellResult = function(paragraphId, paragraphTitle,
+ paragraphText, paragraphResults,
+ paragraphStatus, paragraphErrorMessage,
+ paragraphConfig, paragraphSettingsParam) {
+ websocketMsgSrv.paragraphExecutedBySpell(
+ paragraphId, paragraphTitle,
+ paragraphText, paragraphResults,
+ paragraphStatus, paragraphErrorMessage,
+ paragraphConfig, paragraphSettingsParam);
+ };
- if ($scope.paragraph.config.editorSetting.editOnDblClick) {
- closeEditorAndOpenTable($scope.paragraph);
- } else if (editorSetting.isOutputHidden &&
- !$scope.paragraph.config.editorSetting.editOnDblClick) {
- // %md/%angular repl make output to be hidden by default after running
- // so should open output if repl changed from %md/%angular to another
- openEditorAndOpenTable($scope.paragraph);
+ $scope.handleSpellError = function(paragraphText, error,
+ digestRequired, propagated) {
+ const errorMessage = error.stack;
+ $scope.paragraph.status = 'ERROR';
+ $scope.paragraph.errorMessage = errorMessage;
+ console.error('Failed to execute interpret() in spell\n', error);
+ if (digestRequired) { $scope.$digest(); }
+
+ if (!propagated) {
+ $scope.propagateSpellResult(
+ $scope.paragraph.id, $scope.paragraph.title,
+ paragraphText, [], $scope.paragraph.status, errorMessage,
+ $scope.paragraph.config, $scope.paragraph.settings.params);
+ }
+ };
+
+ $scope.runParagraphUsingSpell = function(spell, paragraphText,
+ magic, digestRequired, propagated) {
+ $scope.paragraph.results = {};
+ $scope.paragraph.errorMessage = '';
+ if (digestRequired) { $scope.$digest(); }
+
+ try {
+ // remove magic from paragraphText
+ const splited = paragraphText.split(magic);
+ // remove leading spaces
+ const textWithoutMagic = splited[1].replace(/^\s+/g, '');
+ const spellResult = spell.interpret(textWithoutMagic);
+ const parsed = spellResult.getAllParsedDataWithTypes(
+ heliumService.getAllSpells(), magic, textWithoutMagic);
+
+ // handle actual result message in promise
+ parsed.then(resultsMsg => {
+ const status = 'FINISHED';
+ $scope.paragraph.status = status;
+ $scope.paragraph.results.code = status;
+ $scope.paragraph.results.msg = resultsMsg;
+ $scope.paragraph.config.tableHide = false;
+ if (digestRequired) { $scope.$digest(); }
+
+ if (!propagated) {
+ const propagable = SpellResult.createPropagable(resultsMsg);
+ $scope.propagateSpellResult(
+ $scope.paragraph.id, $scope.paragraph.title,
+ paragraphText, propagable, status, '',
+ $scope.paragraph.config, $scope.paragraph.settings.params);
+ }
+ }).catch(error => {
+ $scope.handleSpellError(paragraphText, error,
+ digestRequired, propagated);
+ });
+ } catch (error) {
+ $scope.handleSpellError(paragraphText, error,
+ digestRequired, propagated);
}
- editorSetting.isOutputHidden = $scope.paragraph.config.editorSetting.editOnDblClick;
+ };
+
+ $scope.runParagraphUsingBackendInterpreter = function(paragraphText) {
+ websocketMsgSrv.runParagraph($scope.paragraph.id, $scope.paragraph.title,
+ paragraphText, $scope.paragraph.config, $scope.paragraph.settings.params);
};
$scope.saveParagraph = function(paragraph) {
@@ -251,10 +315,49 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
commitParagraph(paragraph);
};
- $scope.run = function(paragraph, editorValue) {
- if (editorValue && !$scope.isRunning(paragraph)) {
- $scope.runParagraph(editorValue);
+ /**
+ * @param paragraphText to be parsed
+ * @param digestRequired true if calling `$digest` is required
+ * @param propagated true if update request is sent from other client
+ */
+ $scope.runParagraph = function(paragraphText, digestRequired, propagated) {
+ if (!paragraphText || $scope.isRunning($scope.paragraph)) {
+ return;
}
+
+ const magic = SpellResult.extractMagic(paragraphText);
+ const spell = heliumService.getSpellByMagic(magic);
+
+ if (spell) {
+ $scope.runParagraphUsingSpell(
+ spell, paragraphText, magic, digestRequired, propagated);
+ } else {
+ $scope.runParagraphUsingBackendInterpreter(paragraphText);
+ }
+
+ $scope.originalText = angular.copy(paragraphText);
+ $scope.dirtyText = undefined;
+
+ if ($scope.paragraph.config.editorSetting.editOnDblClick) {
+ closeEditorAndOpenTable($scope.paragraph);
+ } else if (editorSetting.isOutputHidden &&
+ !$scope.paragraph.config.editorSetting.editOnDblClick) {
+ // %md/%angular repl make output to be hidden by default after running
+ // so should open output if repl changed from %md/%angular to another
+ openEditorAndOpenTable($scope.paragraph);
+ }
+ editorSetting.isOutputHidden = $scope.paragraph.config.editorSetting.editOnDblClick;
+ };
+
+ $scope.runParagraphFromShortcut = function(paragraphText) {
+ // passing `digestRequired` as true to update view immediately
+ // without this, results cannot be rendered in view more than once
+ $scope.runParagraph(paragraphText, true, false);
+ };
+
+ $scope.runParagraphFromButton = function(paragraphText) {
+ // we come here from the view, so we don't need to call `$digest()`
+ $scope.runParagraph(paragraphText, false, false)
};
$scope.moveUp = function(paragraph) {
@@ -807,15 +910,6 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
editor.navigateFileEnd();
};
- $scope.getResultType = function(paragraph) {
- var pdata = (paragraph) ? paragraph : $scope.paragraph;
- if (pdata.results && pdata.results.type) {
- return pdata.results.type;
- } else {
- return 'TEXT';
- }
- };
-
$scope.parseTableCell = function(cell) {
if (!isNaN(cell)) {
if (cell.length === 0 || Number(cell) > Number.MAX_SAFE_INTEGER || Number(cell) < Number.MIN_SAFE_INTEGER) {
@@ -974,101 +1068,146 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
}
});
- $scope.$on('updateParagraph', function(event, data) {
- if (data.paragraph.id === $scope.paragraph.id &&
- (data.paragraph.dateCreated !== $scope.paragraph.dateCreated ||
- data.paragraph.dateFinished !== $scope.paragraph.dateFinished ||
- data.paragraph.dateStarted !== $scope.paragraph.dateStarted ||
- data.paragraph.dateUpdated !== $scope.paragraph.dateUpdated ||
- data.paragraph.status !== $scope.paragraph.status ||
- data.paragraph.jobName !== $scope.paragraph.jobName ||
- data.paragraph.title !== $scope.paragraph.title ||
- isEmpty(data.paragraph.results) !== isEmpty($scope.paragraph.results) ||
- data.paragraph.errorMessage !== $scope.paragraph.errorMessage ||
- !angular.equals(data.paragraph.settings, $scope.paragraph.settings) ||
- !angular.equals(data.paragraph.config, $scope.paragraph.config))
- ) {
- var statusChanged = (data.paragraph.status !== $scope.paragraph.status);
- var resultRefreshed = (data.paragraph.dateFinished !== $scope.paragraph.dateFinished) ||
- isEmpty(data.paragraph.results) !== isEmpty($scope.paragraph.results) ||
- data.paragraph.status === 'ERROR' || (data.paragraph.status === 'FINISHED' && statusChanged);
-
- if ($scope.paragraph.text !== data.paragraph.text) {
- if ($scope.dirtyText) { // check if editor has local update
- if ($scope.dirtyText === data.paragraph.text) { // when local update is the same from remote, clear local update
- $scope.paragraph.text = data.paragraph.text;
- $scope.dirtyText = undefined;
- $scope.originalText = angular.copy(data.paragraph.text);
- } else { // if there're local update, keep it.
- $scope.paragraph.text = data.paragraph.text;
- }
- } else {
- $scope.paragraph.text = data.paragraph.text;
- $scope.originalText = angular.copy(data.paragraph.text);
+ /**
+ * @returns {boolean} true if updated is needed
+ */
+ function isUpdateRequired(oldPara, newPara) {
+ return (newPara.id === oldPara.id &&
+ (newPara.dateCreated !== oldPara.dateCreated ||
+ newPara.dateFinished !== oldPara.dateFinished ||
+ newPara.dateStarted !== oldPara.dateStarted ||
+ newPara.dateUpdated !== oldPara.dateUpdated ||
+ newPara.status !== oldPara.status ||
+ newPara.jobName !== oldPara.jobName ||
+ newPara.title !== oldPara.title ||
+ isEmpty(newPara.results) !== isEmpty(oldPara.results) ||
+ newPara.errorMessage !== oldPara.errorMessage ||
+ !angular.equals(newPara.settings, oldPara.settings) ||
+ !angular.equals(newPara.config, oldPara.config)))
+ }
+
+ $scope.updateAllScopeTexts = function(oldPara, newPara) {
+ if (oldPara.text !== newPara.text) {
+ if ($scope.dirtyText) { // check if editor has local update
+ if ($scope.dirtyText === newPara.text) { // when local update is the same from remote, clear local update
+ $scope.paragraph.text = newPara.text;
+ $scope.dirtyText = undefined;
+ $scope.originalText = angular.copy(newPara.text);
+
+ } else { // if there're local update, keep it.
+ $scope.paragraph.text = newPara.text;
}
+ } else {
+ $scope.paragraph.text = newPara.text;
+ $scope.originalText = angular.copy(newPara.text);
}
+ }
+ };
- /** broadcast update to result controller **/
- if (data.paragraph.results && data.paragraph.results.msg) {
- for (var i in data.paragraph.results.msg) {
- var newResult = data.paragraph.results.msg ? data.paragraph.results.msg[i] : {};
- var oldResult = ($scope.paragraph.results && $scope.paragraph.results.msg) ?
- $scope.paragraph.results.msg[i] : {};
- var newConfig = data.paragraph.config.results ? data.paragraph.config.results[i] : {};
- var oldConfig = $scope.paragraph.config.results ? $scope.paragraph.config.results[i] : {};
- if (!angular.equals(newResult, oldResult) ||
- !angular.equals(newConfig, oldConfig)) {
- $rootScope.$broadcast('updateResult', newResult, newConfig, data.paragraph, parseInt(i));
- }
- }
- }
+ $scope.updateParagraphObjectWhenUpdated = function(newPara) {
+ // resize col width
+ if ($scope.paragraph.config.colWidth !== newPara.colWidth) {
+ $rootScope.$broadcast('paragraphResized', $scope.paragraph.id);
+ }
- // resize col width
- if ($scope.paragraph.config.colWidth !== data.paragraph.colWidth) {
- $rootScope.$broadcast('paragraphResized', $scope.paragraph.id);
- }
+ /** push the rest */
+ $scope.paragraph.aborted = newPara.aborted;
+ $scope.paragraph.user = newPara.user;
+ $scope.paragraph.dateUpdated = newPara.dateUpdated;
+ $scope.paragraph.dateCreated = newPara.dateCreated;
+ $scope.paragraph.dateFinished = newPara.dateFinished;
+ $scope.paragraph.dateStarted = newPara.dateStarted;
+ $scope.paragraph.errorMessage = newPara.errorMessage;
+ $scope.paragraph.jobName = newPara.jobName;
+ $scope.paragraph.title = newPara.title;
+ $scope.paragraph.lineNumbers = newPara.lineNumbers;
+ $scope.paragraph.status = newPara.status;
+ if (newPara.status !== 'RUNNING') {
+ $scope.paragraph.results = newPara.results;
+ }
+ $scope.paragraph.settings = newPara.settings;
+ if ($scope.editor) {
+ $scope.editor.setReadOnly($scope.isRunning(newPara));
+ }
- /** push the rest */
- $scope.paragraph.aborted = data.paragraph.aborted;
- $scope.paragraph.user = data.paragraph.user;
- $scope.paragraph.dateUpdated = data.paragraph.dateUpdated;
- $scope.paragraph.dateCreated = data.paragraph.dateCreated;
- $scope.paragraph.dateFinished = data.paragraph.dateFinished;
- $scope.paragraph.dateStarted = data.paragraph.dateStarted;
- $scope.paragraph.errorMessage = data.paragraph.errorMessage;
- $scope.paragraph.jobName = data.paragraph.jobName;
- $scope.paragraph.title = data.paragraph.title;
- $scope.paragraph.lineNumbers = data.paragraph.lineNumbers;
- $scope.paragraph.status = data.paragraph.status;
- if (data.paragraph.status !== 'RUNNING') {
- $scope.paragraph.results = data.paragraph.results;
- }
- $scope.paragraph.settings = data.paragraph.settings;
- if ($scope.editor) {
- $scope.editor.setReadOnly($scope.isRunning(data.paragraph));
- }
+ if (!$scope.asIframe) {
+ $scope.paragraph.config = newPara.config;
+ initializeDefault(newPara.config);
+ } else {
+ newPara.config.editorHide = true;
+ newPara.config.tableHide = false;
+ $scope.paragraph.config = newPara.config;
+ }
+ };
- if (!$scope.asIframe) {
- $scope.paragraph.config = data.paragraph.config;
- initializeDefault(data.paragraph.config);
- } else {
- data.paragraph.config.editorHide = true;
- data.paragraph.config.tableHide = false;
- $scope.paragraph.config = data.paragraph.config;
+ $scope.updateParagraph = function(oldPara, newPara, updateCallback) {
+ // 1. get status, refreshed
+ const statusChanged = (newPara.status !== oldPara.status);
+ const resultRefreshed = (newPara.dateFinished !== oldPara.dateFinished) ||
+ isEmpty(newPara.results) !== isEmpty(oldPara.results) ||
+ newPara.status === 'ERROR' || (newPara.status === 'FINISHED' && statusChanged);
+
+ // 2. update texts managed by $scope
+ $scope.updateAllScopeTexts(oldPara, newPara);
+
+ // 3. execute callback to update result
+ updateCallback();
+
+ // 4. update remaining paragraph objects
+ $scope.updateParagraphObjectWhenUpdated(newPara);
+
+ // 5. handle scroll down by key properly if new paragraph is added
+ if (statusChanged || resultRefreshed) {
+ // when last paragraph runs, zeppelin automatically appends new paragraph.
+ // this broadcast will focus to the newly inserted paragraph
+ const paragraphs = angular.element('div[id$="_paragraphColumn_main"]');
+ if (paragraphs.length >= 2 && paragraphs[paragraphs.length - 2].id.indexOf($scope.paragraph.id) === 0) {
+ // rendering output can took some time. So delay scrolling event firing for sometime.
+ setTimeout(() => { $rootScope.$broadcast('scrollToCursor'); }, 500);
}
+ }
+ };
+
+ $scope.$on('runParagraphUsingSpell', function(event, data) {
+ const oldPara = $scope.paragraph;
+ let newPara = data.paragraph;
+ const updateCallback = () => {
+ $scope.runParagraph(newPara.text, true, true);
+ };
+
+ if (!isUpdateRequired(oldPara, newPara)) {
+ return;
+ }
+
+ $scope.updateParagraph(oldPara, newPara, updateCallback)
+ });
- if (statusChanged || resultRefreshed) {
- // when last paragraph runs, zeppelin automatically appends new paragraph.
- // this broadcast will focus to the newly inserted paragraph
- var paragraphs = angular.element('div[id$="_paragraphColumn_main"]');
- if (paragraphs.length >= 2 && paragraphs[paragraphs.length - 2].id.indexOf($scope.paragraph.id) === 0) {
- // rendering output can took some time. So delay scrolling event firing for sometime.
- setTimeout(function() {
- $rootScope.$broadcast('scrollToCursor');
- }, 500);
+ $scope.$on('updateParagraph', function(event, data) {
+ const oldPara = $scope.paragraph;
+ const newPara = data.paragraph;
+
+ if (!isUpdateRequired(oldPara, newPara)) {
+ return;
+ }
+
+ const updateCallback = () => {
+ // broadcast `updateResult` message to trigger result update
+ if (newPara.results && newPara.results.msg) {
+ for (let i in newPara.results.msg) {
+ const newResult = newPara.results.msg ? newPara.results.msg[i] : {};
+ const oldResult = (oldPara.results && oldPara.results.msg) ?
+ oldPara.results.msg[i] : {};
+ const newConfig = newPara.config.results ? newPara.config.results[i] : {};
+ const oldConfig = oldPara.config.results ? oldPara.config.results[i] : {};
+ if (!angular.equals(newResult, oldResult) ||
+ !angular.equals(newConfig, oldConfig)) {
+ $rootScope.$broadcast('updateResult', newResult, newConfig, newPara, parseInt(i));
+ }
}
}
- }
+ };
+
+ $scope.updateParagraph(oldPara, newPara, updateCallback)
});
$scope.$on('updateProgress', function(event, data) {
@@ -1092,7 +1231,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
// move focus to next paragraph
$scope.$emit('moveFocusToNextParagraph', paragraphId);
} else if (keyEvent.shiftKey && keyCode === 13) { // Shift + Enter
- $scope.run($scope.paragraph, $scope.getEditorValue());
+ $scope.runParagraphFromShortcut($scope.getEditorValue());
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 67) { // Ctrl + Alt + c
$scope.cancelParagraph($scope.paragraph);
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 68) { // Ctrl + Alt + d
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/notebook/paragraph/paragraph.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.html b/zeppelin-web/src/app/notebook/paragraph/paragraph.html
index 95ad9eb..0de5e64 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.html
@@ -58,9 +58,7 @@ limitations under the License.
ng-init="init(result, paragraph.config.results[$index], paragraph, $index)"
ng-include src="'app/notebook/paragraph/result/result.html'">
</div>
- <div id="{{paragraph.id}}_error"
- class="error text"
- ng-if="paragraph.status == 'ERROR'"
+ <div id="{{paragraph.id}}_error" class="error text"
ng-bind="paragraph.errorMessage">
</div>
</div>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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 40f8248..5757e1a 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
@@ -19,6 +19,10 @@ import PiechartVisualization from '../../../visualization/builtins/visualization
import AreachartVisualization from '../../../visualization/builtins/visualization-areachart';
import LinechartVisualization from '../../../visualization/builtins/visualization-linechart';
import ScatterchartVisualization from '../../../visualization/builtins/visualization-scatterchart';
+import {
+ DefaultDisplayType,
+ SpellResult,
+} from '../../../spell'
angular.module('zeppelinWebApp').controller('ResultCtrl', ResultCtrl);
@@ -150,13 +154,13 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
// image data
$scope.imageData;
- $scope.init = function(result, config, paragraph, index) {
- console.log('result controller init %o %o %o', result, config, index);
+ // queue for append output
+ const textResultQueueForAppend = [];
+ $scope.init = function(result, config, paragraph, index) {
// register helium plugin vis
- var heliumVis = heliumService.get();
- console.log('Helium visualizations %o', heliumVis);
- heliumVis.forEach(function(vis) {
+ var visBundles = heliumService.getVisualizationBundles();
+ visBundles.forEach(function(vis) {
$scope.builtInTableDataVisualizationList.push({
id: vis.id,
name: vis.name,
@@ -171,11 +175,30 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
renderResult($scope.type);
};
+ function isDOMLoaded(targetElemId) {
+ const elem = angular.element(`#${targetElemId}`);
+ return elem.length;
+ }
+
+ function retryUntilElemIsLoaded(targetElemId, callback) {
+ function retry() {
+ if (!isDOMLoaded(targetElemId)) {
+ $timeout(retry, 10);
+ return;
+ }
+
+ const elem = angular.element(`#${targetElemId}`);
+ callback(elem);
+ }
+
+ $timeout(retry);
+ }
+
$scope.$on('updateResult', function(event, result, newConfig, paragraphRef, index) {
if (paragraph.id !== paragraphRef.id || index !== resultIndex) {
return;
}
- console.log('updateResult %o %o %o %o', result, newConfig, paragraphRef, index);
+
var refresh = !angular.equals(newConfig, $scope.config) ||
!angular.equals(result.type, $scope.type) ||
!angular.equals(result.data, data);
@@ -196,14 +219,10 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
if (paragraph.id === data.paragraphId &&
resultIndex === data.index &&
(paragraph.status === 'RUNNING' || paragraph.status === 'PENDING')) {
- appendTextOutput(data.data);
- }
- });
- $scope.$on('updateParagraphOutput', function(event, data) {
- if (paragraph.id === data.paragraphId &&
- resultIndex === data.index) {
- clearTextOutput();
+ if (DefaultDisplayType.TEXT !== $scope.type) {
+ $scope.type = DefaultDisplayType.TEXT;
+ }
appendTextOutput(data.data);
}
});
@@ -250,8 +269,40 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
};
- var renderResult = function(type, refresh) {
- var activeApp;
+ $scope.createDisplayDOMId = function(baseDOMId, type) {
+ if (type === DefaultDisplayType.TABLE) {
+ return `${baseDOMId}_graph`;
+ } else if (type === DefaultDisplayType.HTML) {
+ return `${baseDOMId}_html`;
+ } else if (type === DefaultDisplayType.ANGULAR) {
+ return `${baseDOMId}_angular`;
+ } else if (type === DefaultDisplayType.TEXT) {
+ return `${baseDOMId}_text`;
+ } else if (type === DefaultDisplayType.ELEMENT) {
+ return `${baseDOMId}_elem`;
+ } else {
+ console.error(`Cannot create display DOM Id due to unknown display type: ${type}`);
+ }
+ };
+
+ $scope.renderDefaultDisplay = function(targetElemId, type, data, refresh) {
+ if (type === DefaultDisplayType.TABLE) {
+ $scope.renderGraph(targetElemId, $scope.graphMode, refresh);
+ } else if (type === DefaultDisplayType.HTML) {
+ renderHtml(targetElemId, data);
+ } else if (type === DefaultDisplayType.ANGULAR) {
+ renderAngular(targetElemId, data);
+ } else if (type === DefaultDisplayType.TEXT) {
+ renderText(targetElemId, data);
+ } else if (type === DefaultDisplayType.ELEMENT) {
+ renderElem(targetElemId, data);
+ } else {
+ console.error(`Unknown Display Type: ${type}`);
+ }
+ };
+
+ const renderResult = function(type, refresh) {
+ let activeApp;
if (enableHelium) {
getSuggestions();
getApplicationStates();
@@ -259,228 +310,291 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
if (activeApp) {
- var app = _.find($scope.apps, {id: activeApp});
- renderApp(app);
+ const appState = _.find($scope.apps, {id: activeApp});
+ renderApp(`p${appState.id}`, appState);
} else {
- if (type === 'TABLE') {
- $scope.renderGraph($scope.graphMode, refresh);
- } else if (type === 'HTML') {
- renderHtml();
- } else if (type === 'ANGULAR') {
- renderAngular();
- } else if (type === 'TEXT') {
- renderText();
+ if (!DefaultDisplayType[type]) {
+ const spell = heliumService.getSpellByMagic(type);
+ if (!spell) {
+ console.error(`Can't execute spell due to unknown display type: ${type}`);
+ return;
+ }
+ $scope.renderCustomDisplay(type, data, spell);
+ } else {
+ const targetElemId = $scope.createDisplayDOMId(`p${$scope.id}`, type);
+ $scope.renderDefaultDisplay(targetElemId, type, data, refresh);
}
}
};
- var renderHtml = function() {
- var retryRenderer = function() {
- var htmlEl = angular.element('#p' + $scope.id + '_html');
- if (htmlEl.length) {
- try {
- htmlEl.html(data);
+ $scope.isDefaultDisplay = function() {
+ return DefaultDisplayType[$scope.type];
+ };
- htmlEl.find('pre code').each(function(i, e) {
- hljs.highlightBlock(e);
- });
- /*eslint new-cap: [2, {"capIsNewExceptions": ["MathJax.Hub.Queue"]}]*/
- MathJax.Hub.Queue(['Typeset', MathJax.Hub, htmlEl[0]]);
- } catch (err) {
- console.log('HTML rendering error %o', err);
+ /**
+ * Render multiple sub results for custom display
+ */
+ $scope.renderCustomDisplay = function(type, data, spell) {
+ // get result from intp
+
+ const spellResult = spell.interpret(data.trim());
+ const parsed = spellResult.getAllParsedDataWithTypes(
+ heliumService.getAllSpells());
+
+ // custom display result can include multiple subset results
+ parsed.then(dataWithTypes => {
+ const containerDOMId = `p${$scope.id}_custom`;
+ const afterLoaded = () => {
+ const containerDOM = angular.element(`#${containerDOMId}`);
+ // Spell.interpret() can create multiple outputs
+ for(let i = 0; i < dataWithTypes.length; i++) {
+ const dt = dataWithTypes[i];
+ const data = dt.data;
+ const type = dt.type;
+
+ // prepare each DOM to be filled
+ const subResultDOMId = $scope.createDisplayDOMId(`p${$scope.id}_custom_${i}`, type);
+ const subResultDOM = document.createElement('div');
+ containerDOM.append(subResultDOM);
+ subResultDOM.setAttribute('id', subResultDOMId);
+
+ $scope.renderDefaultDisplay(subResultDOMId, type, data, true);
}
- } else {
- $timeout(retryRenderer, 10);
+ };
+
+ retryUntilElemIsLoaded(containerDOMId, afterLoaded);
+ }).catch(error => {
+ console.error(`Failed to render custom display: ${$scope.type}\n` + error);
+ });
+ };
+
+ /**
+ * generates actually object which will be consumed from `data` property
+ * feed it to the success callback.
+ * if error occurs, the error is passed to the failure callback
+ *
+ * @param data {Object or Function}
+ * @param type {string} Display Type
+ * @param successCallback
+ * @param failureCallback
+ */
+ const handleData = function(data, type, successCallback, failureCallback) {
+ if (SpellResult.isFunction(data)) {
+ try {
+ successCallback(data());
+ } catch (error) {
+ failureCallback(error);
+ console.error(`Failed to handle ${type} type, function data\n`, error);
+ }
+ } else if (SpellResult.isObject(data)) {
+ try {
+ successCallback(data);
+ } catch (error) {
+ console.error(`Failed to handle ${type} type, object data\n`, error);
}
+ }
+ };
+
+ const renderElem = function(targetElemId, data) {
+ const afterLoaded = () => {
+ const elem = angular.element(`#${targetElemId}`);
+ handleData(() => { data(targetElemId) }, DefaultDisplayType.ELEMENT,
+ () => {}, /** HTML element will be filled with data. thus pass empty success callback */
+ (error) => { elem.html(`${error.stack}`); }
+ );
};
- $timeout(retryRenderer);
+
+ retryUntilElemIsLoaded(targetElemId, afterLoaded);
};
- var renderAngular = function() {
- var retryRenderer = function() {
- if (angular.element('#p' + $scope.id + '_angular').length) {
- try {
- angular.element('#p' + $scope.id + '_angular').html(data);
+ const renderHtml = function(targetElemId, data) {
+ const afterLoaded = () => {
+ const elem = angular.element(`#${targetElemId}`);
+ handleData(data, DefaultDisplayType.HTML,
+ (generated) => {
+ elem.html(generated);
+ elem.find('pre code').each(function(i, e) {
+ hljs.highlightBlock(e);
+ });
+ /*eslint new-cap: [2, {"capIsNewExceptions": ["MathJax.Hub.Queue"]}]*/
+ MathJax.Hub.Queue(['Typeset', MathJax.Hub, elem[0]]);
+ },
+ (error) => { elem.html(`${error.stack}`); }
+ );
+ };
- var paragraphScope = noteVarShareService.get(paragraph.id + '_paragraphScope');
- $compile(angular.element('#p' + $scope.id + '_angular').contents())(paragraphScope);
- } catch (err) {
- console.log('ANGULAR rendering error %o', err);
- }
- } else {
- $timeout(retryRenderer, 10);
- }
+ retryUntilElemIsLoaded(targetElemId, afterLoaded);
+ };
+
+ const renderAngular = function(targetElemId, data) {
+ const afterLoaded = () => {
+ const elem = angular.element(`#${targetElemId}`);
+ const paragraphScope = noteVarShareService.get(`${paragraph.id}_paragraphScope`);
+ handleData(data, DefaultDisplayType.ANGULAR,
+ (generated) => {
+ elem.html(generated);
+ $compile(elem.contents())(paragraphScope);
+ },
+ (error) => { elem.html(`${error.stack}`); }
+ );
};
- $timeout(retryRenderer);
+
+ retryUntilElemIsLoaded(targetElemId, afterLoaded);
};
- var getTextEl = function (paragraphId) {
- return angular.element('#p' + $scope.id + '_text');
- }
+ const getTextResultElemId = function (resultId) {
+ return `p${resultId}_text`;
+ };
- var textRendererInitialized = false;
- var renderText = function() {
- var retryRenderer = function() {
- var textEl = getTextEl($scope.id);
- if (textEl.length) {
- // clear all lines before render
- clearTextOutput();
- textRendererInitialized = true;
-
- if (data) {
- appendTextOutput(data);
- } else {
- flushAppendQueue();
- }
+ const renderText = function(targetElemId, data) {
+ const afterLoaded = () => {
+ const elem = angular.element(`#${targetElemId}`);
+ handleData(data, DefaultDisplayType.TEXT,
+ (generated) => {
+ // clear all lines before render
+ removeChildrenDOM(targetElemId);
+
+ if (generated) {
+ const divDOM = angular.element('<div></div>').text(generated);
+ elem.append(divDOM);
+ }
- getTextEl($scope.id).bind('mousewheel', function(e) {
- $scope.keepScrollDown = false;
- });
- } else {
- $timeout(retryRenderer, 10);
- }
+ elem.bind('mousewheel', (e) => { $scope.keepScrollDown = false; });
+ },
+ (error) => { elem.html(`${error.stack}`); }
+ );
};
- $timeout(retryRenderer);
+
+ retryUntilElemIsLoaded(targetElemId, afterLoaded);
};
- var clearTextOutput = function() {
- var textEl = getTextEl($scope.id);
- if (textEl.length) {
- textEl.children().remove();
+ const removeChildrenDOM = function(targetElemId) {
+ const elem = angular.element(`#${targetElemId}`);
+ if (elem.length) {
+ elem.children().remove();
}
};
- var textAppendQueueBeforeInitialize = [];
+ function appendTextOutput(data) {
+ const elemId = getTextResultElemId($scope.id);
+ textResultQueueForAppend.push(data);
- var flushAppendQueue = function() {
- while (textAppendQueueBeforeInitialize.length > 0) {
- appendTextOutput(textAppendQueueBeforeInitialize.pop());
+ // if DOM is not loaded, just push data and return
+ if (!isDOMLoaded(elemId)) {
+ return;
}
- };
- var appendTextOutput = function(msg) {
- if (!textRendererInitialized) {
- textAppendQueueBeforeInitialize.push(msg);
- } else {
- flushAppendQueue();
- var textEl = getTextEl($scope.id);
- if (textEl.length) {
- var lines = msg.split('\n');
- for (var i = 0; i < lines.length; i++) {
- textEl.append(angular.element('<div></div>').text(lines[i]));
- }
+ const elem = angular.element(`#${elemId}`);
+
+ // pop all stacked data and append to the DOM
+ while (textResultQueueForAppend.length > 0) {
+ const stacked = textResultQueueForAppend.pop();
+
+ const lines = stacked.split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ elem.append(angular.element('<div></div>').text(lines[i]));
}
+
if ($scope.keepScrollDown) {
- var doc = getTextEl($scope.id);
+ const doc = angular.element(`#${elemId}`);
doc[0].scrollTop = doc[0].scrollHeight;
}
}
- };
+ }
- $scope.renderGraph = function(type, refresh) {
+ $scope.renderGraph = function(graphElemId, graphMode, refresh) {
// set graph height
- var height = $scope.config.graph.height;
- var graphContainerEl = angular.element('#p' + $scope.id + '_graph');
- graphContainerEl.height(height);
+ const height = $scope.config.graph.height;
+ const graphElem = angular.element(`#${graphElemId}`);
+ graphElem.height(height);
- if (!type) {
- type = 'table';
- }
+ if (!graphMode) { graphMode = 'table'; }
+ const tableElemId = `p${$scope.id}_${graphMode}`;
- var builtInViz = builtInVisualizations[type];
- if (builtInViz) {
- // deactive previsouly active visualization
- for (var t in builtInVisualizations) {
- var v = builtInVisualizations[t].instance;
+ const builtInViz = builtInVisualizations[graphMode];
+ if (!builtInViz) { return; }
- if (t !== type && v && v.isActive()) {
- v.deactivate();
- break;
- }
- }
+ // deactive previsouly active visualization
+ for (let t in builtInVisualizations) {
+ const v = builtInVisualizations[t].instance;
- if (!builtInViz.instance) { // not instantiated yet
- // render when targetEl is available
- var retryRenderer = function() {
- var targetEl = angular.element('#p' + $scope.id + '_' + type);
- var transformationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + type);
- var visualizationSettingTargetEl = angular.element('#vizsetting' + $scope.id + '_' + type);
- if (targetEl.length) {
- try {
- // set height
- targetEl.height(height);
-
- // instantiate visualization
- var config = getVizConfig(type);
- var Visualization = builtInViz.class;
- builtInViz.instance = new Visualization(targetEl, config);
-
- // inject emitter, $templateRequest
- var emitter = function(graphSetting) {
- commitVizConfigChange(graphSetting, type);
- };
- builtInViz.instance._emitter = emitter;
- builtInViz.instance._compile = $compile;
- builtInViz.instance._createNewScope = createNewScope;
- var transformation = builtInViz.instance.getTransformation();
- transformation._emitter = emitter;
- transformation._templateRequest = $templateRequest;
- transformation._compile = $compile;
- transformation._createNewScope = createNewScope;
-
- // render
- var transformed = transformation.transform(tableData);
- transformation.renderSetting(transformationSettingTargetEl);
- builtInViz.instance.render(transformed);
- builtInViz.instance.renderSetting(visualizationSettingTargetEl);
- builtInViz.instance.activate();
- angular.element(window).resize(function() {
- builtInViz.instance.resize();
- });
- } catch (err) {
- console.error('Graph drawing error %o', err);
- }
- } else {
- $timeout(retryRenderer, 10);
- }
- };
- $timeout(retryRenderer);
- } else if (refresh) {
- console.log('Refresh data %o', tableData);
- // when graph options or data are changed
- var retryRenderer = function() {
- var targetEl = angular.element('#p' + $scope.id + '_' + type);
- var transformationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + type);
- var visualizationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + type);
- if (targetEl.length) {
- var config = getVizConfig(type);
- targetEl.height(height);
- var transformation = builtInViz.instance.getTransformation();
- transformation.setConfig(config);
- var transformed = transformation.transform(tableData);
- transformation.renderSetting(transformationSettingTargetEl);
- builtInViz.instance.setConfig(config);
- builtInViz.instance.render(transformed);
- builtInViz.instance.renderSetting(visualizationSettingTargetEl);
- } else {
- $timeout(retryRenderer, 10);
- }
- };
- $timeout(retryRenderer);
- } else {
- var retryRenderer = function() {
- var targetEl = angular.element('#p' + $scope.id + '_' + type);
- if (targetEl.length) {
- targetEl.height(height);
- builtInViz.instance.activate();
- } else {
- $timeout(retryRenderer, 10);
- }
- };
- $timeout(retryRenderer);
+ if (t !== graphMode && v && v.isActive()) {
+ v.deactivate();
+ break;
}
}
+
+ if (!builtInViz.instance) { // not instantiated yet
+ // render when targetEl is available
+ const afterLoaded = (loadedElem) => {
+ try {
+ const transformationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + graphMode);
+ const visualizationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + graphMode);
+ // set height
+ loadedElem.height(height);
+
+ // instantiate visualization
+ const config = getVizConfig(graphMode);
+ const Visualization = builtInViz.class;
+ builtInViz.instance = new Visualization(loadedElem, config);
+
+ // inject emitter, $templateRequest
+ const emitter = function(graphSetting) {
+ commitVizConfigChange(graphSetting, graphMode);
+ };
+ builtInViz.instance._emitter = emitter;
+ builtInViz.instance._compile = $compile;
+ builtInViz.instance._createNewScope = createNewScope;
+ const transformation = builtInViz.instance.getTransformation();
+ transformation._emitter = emitter;
+ transformation._templateRequest = $templateRequest;
+ transformation._compile = $compile;
+ transformation._createNewScope = createNewScope;
+
+ // render
+ const transformed = transformation.transform(tableData);
+ transformation.renderSetting(transformationSettingTargetEl);
+ builtInViz.instance.render(transformed);
+ builtInViz.instance.renderSetting(visualizationSettingTargetEl);
+ builtInViz.instance.activate();
+ angular.element(window).resize(() => {
+ builtInViz.instance.resize();
+ });
+ } catch (err) {
+ console.error('Graph drawing error %o', err);
+ }
+ };
+
+ retryUntilElemIsLoaded(tableElemId, afterLoaded);
+ } else if (refresh) {
+ // when graph options or data are changed
+ console.log('Refresh data %o', tableData);
+
+ const afterLoaded = (loadedElem) => {
+ const transformationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + graphMode);
+ const visualizationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + graphMode);
+ const config = getVizConfig(graphMode);
+ loadedElem.height(height);
+ const transformation = builtInViz.instance.getTransformation();
+ transformation.setConfig(config);
+ const transformed = transformation.transform(tableData);
+ transformation.renderSetting(transformationSettingTargetEl);
+ builtInViz.instance.setConfig(config);
+ builtInViz.instance.render(transformed);
+ builtInViz.instance.renderSetting(visualizationSettingTargetEl);
+ };
+
+ retryUntilElemIsLoaded(tableElemId, afterLoaded);
+ } else {
+ const afterLoaded = (loadedElem) => {
+ loadedElem.height(height);
+ builtInViz.instance.activate();
+ };
+
+ retryUntilElemIsLoaded(tableElemId, afterLoaded);
+ }
};
+
$scope.switchViz = function(newMode) {
var newConfig = angular.copy($scope.config);
var newParams = angular.copy(paragraph.settings.params);
@@ -728,23 +842,17 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
});
};
- var renderApp = function(appState) {
- var retryRenderer = function() {
- var targetEl = angular.element(document.getElementById('p' + appState.id));
- console.log('retry renderApp %o', targetEl);
- if (targetEl.length) {
- try {
- console.log('renderApp %o', appState);
- targetEl.html(appState.output);
- $compile(targetEl.contents())(getAppScope(appState));
- } catch (err) {
- console.log('App rendering error %o', err);
- }
- } else {
- $timeout(retryRenderer, 1000);
+ const renderApp = function(targetElemId, appState) {
+ const afterLoaded = (loadedElem) => {
+ try {
+ console.log('renderApp %o', appState);
+ loadedElem.html(appState.output);
+ $compile(loadedElem.contents())(getAppScope(appState));
+ } catch (err) {
+ console.log('App rendering error %o', err);
}
};
- $timeout(retryRenderer);
+ retryUntilElemIsLoaded(targetElemId, afterLoaded);
};
/*
@@ -927,4 +1035,4 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
}
});
-};
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/notebook/paragraph/result/result.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.html b/zeppelin-web/src/app/notebook/paragraph/result/result.html
index df09c4d..5b251e5 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.html
@@ -37,8 +37,7 @@ limitations under the License.
<!-- graph -->
<div id="p{{id}}_graph"
class="graphContainer"
- ng-class="{'noOverflow': graphMode=='table'}"
- >
+ ng-class="{'noOverflow': graphMode=='table'}">
<div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
id="p{{id}}_{{viz.id}}"
ng-show="graphMode == viz.id">
@@ -67,13 +66,19 @@ limitations under the License.
tooltip="Scroll Top"></div>
</div>
- <div id="p{{id}}_html"
- class="resultContained"
+ <div id="p{{id}}_custom" class="resultContained"
+ ng-if="!isDefaultDisplay()">
+ </div>
+
+ <div id="p{{id}}_elem" class="resultContained"
+ ng-if="type == 'ELEMENT'">
+ </div>
+
+ <div id="p{{id}}_html" class="resultContained"
ng-if="type == 'HTML'">
</div>
- <div id="p{{id}}_angular"
- class="resultContained"
+ <div id="p{{id}}_angular" class="resultContained"
ng-if="type == 'ANGULAR'">
</div>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/spell/.npmignore
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/spell/.npmignore b/zeppelin-web/src/app/spell/.npmignore
new file mode 100644
index 0000000..0b84df0
--- /dev/null
+++ b/zeppelin-web/src/app/spell/.npmignore
@@ -0,0 +1 @@
+*.html
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/spell/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/spell/index.js b/zeppelin-web/src/app/spell/index.js
new file mode 100644
index 0000000..8ec4753
--- /dev/null
+++ b/zeppelin-web/src/app/spell/index.js
@@ -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.
+ */
+
+export {
+ DefaultDisplayType,
+ SpellResult,
+} from './spell-result';
+
+export {
+ SpellBase,
+} from './spell-base';
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/spell/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/spell/package.json b/zeppelin-web/src/app/spell/package.json
new file mode 100644
index 0000000..7003e06
--- /dev/null
+++ b/zeppelin-web/src/app/spell/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "zeppelin-spell",
+ "description": "Zeppelin Spell Framework",
+ "version": "0.8.0-SNAPSHOT",
+ "main": "index",
+ "dependencies": {
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/apache/zeppelin"
+ },
+ "license": "Apache-2.0"
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/spell/spell-base.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/spell/spell-base.js b/zeppelin-web/src/app/spell/spell-base.js
new file mode 100644
index 0000000..85c85e5
--- /dev/null
+++ b/zeppelin-web/src/app/spell/spell-base.js
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/*eslint-disable no-unused-vars */
+import {
+ DefaultDisplayType,
+ SpellResult,
+} from './spell-result';
+/*eslint-enable no-unused-vars */
+
+export class SpellBase {
+ constructor(magic) {
+ this.magic = magic;
+ }
+
+ /**
+ * Consumes text and return `SpellResult`.
+ *
+ * @param paragraphText {string} which doesn't include magic
+ * @return {SpellResult}
+ */
+ interpret(paragraphText) {
+ throw new Error('SpellBase.interpret() should be overrided');
+ }
+
+ /**
+ * return magic for this spell.
+ * (e.g `%flowchart`)
+ * @return {string}
+ */
+ getMagic() {
+ return this.magic;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/app/spell/spell-result.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/spell/spell-result.js b/zeppelin-web/src/app/spell/spell-result.js
new file mode 100644
index 0000000..d62e97a
--- /dev/null
+++ b/zeppelin-web/src/app/spell/spell-result.js
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ */
+
+export const DefaultDisplayType = {
+ ELEMENT: 'ELEMENT',
+ TABLE: 'TABLE',
+ HTML: 'HTML',
+ ANGULAR: 'ANGULAR',
+ TEXT: 'TEXT',
+};
+
+export const DefaultDisplayMagic = {
+ '%element': DefaultDisplayType.ELEMENT,
+ '%table': DefaultDisplayType.TABLE,
+ '%html': DefaultDisplayType.HTML,
+ '%angular': DefaultDisplayType.ANGULAR,
+ '%text': DefaultDisplayType.TEXT,
+};
+
+export class DataWithType {
+ constructor(data, type, magic, text) {
+ this.data = data;
+ this.type = type;
+
+ /**
+ * keep for `DefaultDisplayType.ELEMENT` (function data type)
+ * to propagate a result to other client.
+ *
+ * otherwise we will send function as `data` and it will not work
+ * since they don't have context where they are created.
+ */
+
+ this.magic = magic;
+ this.text = text;
+ }
+
+ static handleDefaultMagic(m) {
+ // let's use default display type instead of magic in case of default
+ // to keep consistency with backend interpreter
+ if (DefaultDisplayMagic[m]) {
+ return DefaultDisplayMagic[m];
+ } else {
+ return m;
+ }
+ }
+
+ static createPropagable(dataWithType) {
+ if (!SpellResult.isFunction(dataWithType.data)) {
+ return dataWithType;
+ }
+
+ const data = dataWithType.getText();
+ const type = dataWithType.getMagic();
+
+ return new DataWithType(data, type);
+ }
+
+ /**
+ * consume 1 data and produce multiple
+ * @param data {string}
+ * @param customDisplayType
+ * @return {Array<DataWithType>}
+ */
+ static parseStringData(data, customDisplayMagic) {
+ function availableMagic(magic) {
+ return magic && (DefaultDisplayMagic[magic] || customDisplayMagic[magic]);
+ }
+
+ const splited = data.split('\n');
+
+ const gensWithTypes = [];
+ let mergedGens = [];
+ let previousMagic = DefaultDisplayType.TEXT;
+
+ // create `DataWithType` whenever see available display type.
+ for(let i = 0; i < splited.length; i++) {
+ const g = splited[i];
+ const magic = SpellResult.extractMagic(g);
+
+ // create `DataWithType` only if see new magic
+ if (availableMagic(magic) && mergedGens.length > 0) {
+ gensWithTypes.push(new DataWithType(mergedGens.join(''), previousMagic));
+ mergedGens = [];
+ }
+
+ // accumulate `data` to mergedGens
+ if (availableMagic(magic)) {
+ const withoutMagic = g.split(magic)[1];
+ mergedGens.push(`${withoutMagic}\n`);
+ previousMagic = DataWithType.handleDefaultMagic(magic);
+ } else {
+ mergedGens.push(`${g}\n`);
+ }
+ }
+
+ // cleanup the last `DataWithType`
+ if (mergedGens.length > 0) {
+ previousMagic = DataWithType.handleDefaultMagic(previousMagic);
+ gensWithTypes.push(new DataWithType(mergedGens.join(''), previousMagic));
+ }
+
+ return gensWithTypes;
+ }
+
+ /**
+ * get 1 `DataWithType` and produce multiples using available displays
+ * return an wrapped with a promise to generalize result output which can be
+ * object, function or promise
+ * @param dataWithType {DataWithType}
+ * @param availableDisplays {Object} Map for available displays
+ * @param magic
+ * @param textWithoutMagic
+ * @return {Promise<Array<DataWithType>>}
+ */
+ static produceMultipleData(dataWithType, customDisplayType,
+ magic, textWithoutMagic) {
+ const data = dataWithType.getData();
+ const type = dataWithType.getType();
+
+ // if the type is specified, just return it
+ // handle non-specified dataWithTypes only
+ if (type) {
+ return new Promise((resolve) => { resolve([dataWithType]); });
+ }
+
+ let wrapped;
+
+ if (SpellResult.isFunction(data)) {
+ // if data is a function, we consider it as ELEMENT type.
+ wrapped = new Promise((resolve) => {
+ const dt = new DataWithType(
+ data, DefaultDisplayType.ELEMENT, magic, textWithoutMagic);
+ const result = [dt];
+ return resolve(result);
+ });
+ } else if (SpellResult.isPromise(data)) {
+ // if data is a promise,
+ wrapped = data.then(generated => {
+ const result =
+ DataWithType.parseStringData(generated, customDisplayType);
+ return result;
+ })
+
+ } else {
+ // if data is a object, parse it to multiples
+ wrapped = new Promise((resolve) => {
+ const result =
+ DataWithType.parseStringData(data, customDisplayType);
+ return resolve(result);
+ });
+ }
+
+ return wrapped;
+ }
+
+ /**
+ * `data` can be promise, function or just object
+ * - if data is an object, it will be used directly.
+ * - if data is a function, it will be called with DOM element id
+ * where the final output is rendered.
+ * - if data is a promise, the post processing logic
+ * will be called in `then()` of this promise.
+ * @returns {*} `data` which can be object, function or promise.
+ */
+ getData() {
+ return this.data;
+ }
+
+ /**
+ * Value of `type` might be empty which means
+ * data can be separated into multiples
+ * by `SpellResult.parseStringData()`
+ * @returns {string}
+ */
+ getType() {
+ return this.type;
+ }
+
+ getMagic() {
+ return this.magic;
+ }
+
+ getText() {
+ return this.text;
+ }
+}
+
+export class SpellResult {
+ constructor(resultData, resultType) {
+ this.dataWithTypes = [];
+ this.add(resultData, resultType);
+ }
+
+ static isFunction(data) {
+ return (data && typeof data === 'function');
+ }
+
+ static isPromise(data) {
+ return (data && typeof data.then === 'function');
+ }
+
+ static isObject(data) {
+ return (data &&
+ !SpellResult.isFunction(data) &&
+ !SpellResult.isPromise(data));
+ }
+
+ static extractMagic(allParagraphText) {
+ const pattern = /^\s*%(\S+)\s*/g;
+ try {
+ let match = pattern.exec(allParagraphText);
+ if (match) {
+ return `%${match[1].trim()}`;
+ }
+ } catch (error) {
+ // failed to parse, ignore
+ }
+
+ return undefined;
+ }
+
+ static createPropagable(resultMsg) {
+ return resultMsg.map(dt => {
+ return DataWithType.createPropagable(dt);
+ })
+ }
+
+ add(resultData, resultType) {
+ if (resultData) {
+ this.dataWithTypes.push(
+ new DataWithType(resultData, resultType));
+ }
+
+ return this;
+ }
+
+ /**
+ * @param customDisplayType
+ * @param textWithoutMagic
+ * @return {Promise<Array<DataWithType>>}
+ */
+ getAllParsedDataWithTypes(customDisplayType, magic, textWithoutMagic) {
+ const promises = this.dataWithTypes.map(dt => {
+ return DataWithType.produceMultipleData(
+ dt, customDisplayType, magic, textWithoutMagic);
+ });
+
+ // some promises can include an array so we need to flatten them
+ const flatten = Promise.all(promises).then(values => {
+ return values.reduce((acc, cur) => {
+ if (Array.isArray(cur)) {
+ return acc.concat(cur);
+ } else {
+ return acc.concat([cur]);
+ }
+ })
+ });
+
+ return flatten;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/components/helium/helium-type.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/helium/helium-type.js b/zeppelin-web/src/components/helium/helium-type.js
new file mode 100644
index 0000000..0ef4eb6
--- /dev/null
+++ b/zeppelin-web/src/components/helium/helium-type.js
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+export const HeliumType = {
+ VISUALIZATION: 'VISUALIZATION',
+ SPELL: 'SPELL',
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/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
index ae44425..a8664d3 100644
--- a/zeppelin-web/src/components/helium/helium.service.js
+++ b/zeppelin-web/src/components/helium/helium.service.js
@@ -12,51 +12,80 @@
* limitations under the License.
*/
-(function() {
+import { HeliumType, } from './helium-type';
- angular.module('zeppelinWebApp').service('heliumService', heliumService);
+angular.module('zeppelinWebApp').service('heliumService', heliumService);
- heliumService.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
+heliumService.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
- function heliumService($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 url = baseUrlSrv.getRestApiBase() + '/helium/bundle/load';
+ if (process.env.HELIUM_BUNDLE_DEV) {
+ url = url + '?refresh=true';
+ }
+ // name `heliumBundles` should be same as `HelumBundleFactory.HELIUM_BUNDLES_VAR`
+ var heliumBundles = [];
+ // map for `{ magic: interpreter }`
+ let spellPerMagic = {};
+ let visualizationBundles = [];
+
+ // load should be promise
+ this.load = $http.get(url).success(function(response) {
+ if (response.substring(0, 'ERROR:'.length) !== 'ERROR:') {
+ // evaluate bundles
+ eval(response);
+
+ // extract bundles by type
+ heliumBundles.map(b => {
+ if (b.type === HeliumType.SPELL) {
+ const spell = new b.class(); // eslint-disable-line new-cap
+ spellPerMagic[spell.getMagic()] = spell;
+ } else if (b.type === HeliumType.VISUALIZATION) {
+ visualizationBundles.push(b);
+ }
+ });
+ } else {
+ console.error(response);
}
- 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);
- };
- };
-})();
+ });
+
+ /**
+ * @param magic {string} e.g `%flowchart`
+ * @returns {SpellBase} undefined if magic is not registered
+ */
+ this.getSpellByMagic = function(magic) {
+ return spellPerMagic[magic];
+ };
+
+ /**
+ * @returns {Object} map for `{ magic : spell }`
+ */
+ this.getAllSpells = function() {
+ return spellPerMagic;
+ };
+
+ this.getVisualizationBundles = function() {
+ return visualizationBundles;
+ };
+
+ this.getVisualizationPackageOrder = function() {
+ return $http.get(baseUrlSrv.getRestApiBase() + '/helium/order/visualization');
+ };
+
+ this.setVisualizationPackageOrder = function(list) {
+ return $http.post(baseUrlSrv.getRestApiBase() + '/helium/order/visualization', 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/0589e27e/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
index 5436f34..aceffbb 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
@@ -105,6 +105,8 @@ function websocketEvents($rootScope, $websocket, $location, baseUrlSrv) {
} else if (op === 'PARAGRAPH') {
$rootScope.$broadcast('updateParagraph', data);
+ } else if (op === 'RUN_PARAGRAPH_USING_SPELL') {
+ $rootScope.$broadcast('runParagraphUsingSpell', data);
} else if (op === 'PARAGRAPH_APPEND_OUTPUT') {
$rootScope.$broadcast('appendParagraphOutput', data);
} else if (op === 'PARAGRAPH_UPDATE_OUTPUT') {
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/0589e27e/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
index d597ff4..4fd4b95 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
@@ -159,6 +159,31 @@ function websocketMsgSrv($rootScope, websocketEvents) {
websocketEvents.sendNewEvent({op: 'CANCEL_PARAGRAPH', data: {id: paragraphId}});
},
+ paragraphExecutedBySpell: function(paragraphId, paragraphTitle,
+ paragraphText, paragraphResultsMsg,
+ paragraphStatus, paragraphErrorMessage,
+ paragraphConfig, paragraphParams) {
+ websocketEvents.sendNewEvent({
+ op: 'PARAGRAPH_EXECUTED_BY_SPELL',
+ data: {
+ id: paragraphId,
+ title: paragraphTitle,
+ paragraph: paragraphText,
+ results: {
+ code: paragraphStatus,
+ msg: paragraphResultsMsg.map(dataWithType => {
+ let serializedData = dataWithType.data;
+ return { type: dataWithType.type, data: serializedData, };
+ })
+ },
+ status: paragraphStatus,
+ errorMessage: paragraphErrorMessage,
+ config: paragraphConfig,
+ params: paragraphParams
+ }
+ });
+ },
+
runParagraph: function(paragraphId, paragraphTitle, paragraphData, paragraphConfig, paragraphParams) {
websocketEvents.sendNewEvent({
op: 'RUN_PARAGRAPH',