Propagate bundle error to the front-end

This commit is contained in:
Lee moon soo 2017-01-07 20:10:19 -08:00
parent 0fe5e00402
commit 47de6d9684
11 changed files with 220 additions and 32 deletions

View file

@ -129,7 +129,7 @@ public class HeliumRestApi {
// returning error will prevent zeppelin front-end render any notebook.
// visualization load fail doesn't need to block notebook rendering work.
// so it's better return ok instead of any error.
return Response.ok().build();
return Response.ok("ERROR: " + e.getMessage()).build();
}
}
@ -142,10 +142,10 @@ public class HeliumRestApi {
return new JsonResponse(Response.Status.OK).build();
} catch (IOException e) {
logger.error(e.getMessage(), e);
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR).build();
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
} catch (TaskRunnerException e) {
logger.error(e.getMessage(), e);
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR).build();
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
}
}
@ -157,10 +157,10 @@ public class HeliumRestApi {
return new JsonResponse(Response.Status.OK).build();
} catch (IOException e) {
logger.error(e.getMessage(), e);
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR).build();
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
} catch (TaskRunnerException e) {
logger.error(e.getMessage(), e);
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR).build();
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
}
}
}

View file

@ -125,7 +125,11 @@ public class ZeppelinServer extends Application {
heliumApplicationFactory);
// create visualization bundle
heliumVisualizationFactory.bundle(helium.getVisualizationPackagesToBundle());
try {
heliumVisualizationFactory.bundle(helium.getVisualizationPackagesToBundle());
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
this.schedulerFactory = new SchedulerFactory();
this.replFactory = new InterpreterFactory(conf, notebookWsServer,

View file

@ -77,7 +77,7 @@
}).
error(function(data, status) {
confirm.close();
console.log('Failed to enable package %o %o', name, artifact);
console.log('Failed to enable package %o %o. %o', name, artifact, data);
BootstrapDialog.show({
title: 'Error on enabling ' + name,
message: data.message
@ -108,7 +108,7 @@
}).
error(function(data, status) {
confirm.close();
console.log('Failed to disable package %o', name);
console.log('Failed to disable package %o. %o', name, data);
BootstrapDialog.show({
title: 'Error on disabling ' + name,
message: data.message

View file

@ -16,9 +16,9 @@
angular.module('zeppelinWebApp').service('heliumService', heliumService);
heliumService.$inject = ['$http', 'baseUrlSrv'];
heliumService.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
function heliumService($http, baseUrlSrv) {
function heliumService($http, baseUrlSrv, ngToast) {
var url = baseUrlSrv.getRestApiBase() + '/helium/visualizations/load';
if (process.env.HELIUM_VIS_DEV) {
@ -28,7 +28,11 @@
// load should be promise
this.load = $http.get(url).success(function(response) {
eval(response);
if (response.substring(0, 'ERROR:'.length) !== 'ERROR:') {
eval(response);
} else {
console.log(response);
}
});
this.get = function() {

View file

@ -214,6 +214,7 @@ public class Helium {
}
heliumConf.disablePackage(name);
HeliumPackageSearchResult pkg = getPackageInfo(name, artifact);
if (pkg == null || pkg.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) {
visualizationFactory.bundle(getVisualizationPackagesToBundle());

View file

@ -21,15 +21,12 @@ 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.slf4j.ILoggerFactory;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.WriterAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
@ -54,6 +51,8 @@ public class HeliumVisualizationFactory {
String bundleCacheKey = "";
File currentBundle;
ByteArrayOutputStream out = new ByteArrayOutputStream();
public HeliumVisualizationFactory(
File moduleDownloadPath,
File tabledataModulePath,
@ -74,6 +73,7 @@ public class HeliumVisualizationFactory {
currentBundle = new File(workingDirectory, "vis.bundle.cache.js");
gson = new Gson();
installNodeAndNpm();
configureLogger();
}
private void installNodeAndNpm() throws InstallationException, TaskRunnerException {
@ -95,13 +95,25 @@ public class HeliumVisualizationFactory {
return bundle(pkgs, false);
}
public File bundle(List<HeliumPackage> pkgs, boolean forceRefresh)
public synchronized File bundle(List<HeliumPackage> pkgs, boolean forceRefresh)
throws IOException, TaskRunnerException {
// package.json
URL pkgUrl = Resources.getResource("helium/package.json");
String pkgJson = Resources.toString(pkgUrl, Charsets.UTF_8);
StringBuilder dependencies = new StringBuilder();
FileFilter npmPackageCopyFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
String fileName = pathname.getName();
if (fileName.startsWith(".") || fileName.startsWith("#") || fileName.startsWith("~")) {
return false;
} else {
return true;
}
}
};
for (HeliumPackage pkg : pkgs) {
String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
if (moduleNameVersion == null) {
@ -114,8 +126,10 @@ public class HeliumVisualizationFactory {
dependencies.append("\"" + moduleNameVersion[0] + "\": \"" + moduleNameVersion[1] + "\"");
if (isLocalPackage(pkg)) {
FileUtils.copyDirectory(new File(pkg.getArtifact()),
new File(workingDirectory, "node_modules/" + pkg.getName()));
FileUtils.copyDirectory(
new File(pkg.getArtifact()),
new File(workingDirectory, "node_modules/" + pkg.getName()),
npmPackageCopyFilter);
}
}
pkgJson = pkgJson.replaceFirst("DEPENDENCIES", dependencies.toString());
@ -133,19 +147,22 @@ public class HeliumVisualizationFactory {
StringBuilder loadJsImport = new StringBuilder();
StringBuilder loadJsRegister = new StringBuilder();
long idx = 0;
for (HeliumPackage pkg : pkgs) {
String [] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
if (moduleNameVersion == null) {
continue;
}
String className = "vis" + idx++;
loadJsImport.append(
"import " + moduleNameVersion[0] + " from \"" + moduleNameVersion[0] + "\"\n");
"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: " + moduleNameVersion[0] + "\n");
loadJsRegister.append("class: " + className + "\n");
loadJsRegister.append("})\n");
}
@ -158,22 +175,30 @@ public class HeliumVisualizationFactory {
File tabledataModuleInstallPath = new File(workingDirectory,
"node_modules/zeppelin-tabledata");
if (tabledataModulePath != null && !tabledataModuleInstallPath.exists()) {
FileUtils.copyDirectory(tabledataModulePath, tabledataModuleInstallPath);
FileUtils.copyDirectory(tabledataModulePath, tabledataModuleInstallPath, npmPackageCopyFilter);
}
// install visualization module
File visModuleInstallPath = new File(workingDirectory,
"node_modules/zeppelin-vis");
if (visualizationModulePath != null && !visModuleInstallPath.exists()) {
FileUtils.copyDirectory(visualizationModulePath, visModuleInstallPath);
FileUtils.copyDirectory(visualizationModulePath, visModuleInstallPath, npmPackageCopyFilter);
}
out.reset();
npmCommand("install");
npmCommand("run bundle");
File visBundleJs = new File(workingDirectory, "vis.bundle.js");
if (!visBundleJs.isFile()) {
throw new IOException("Failed to create visualization bundle");
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) {
@ -184,6 +209,43 @@ public class HeliumVisualizationFactory {
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()) {
@ -198,7 +260,7 @@ public class HeliumVisualizationFactory {
return (pkg.getArtifact().startsWith(".") || pkg.getArtifact().startsWith("/"));
}
private String [] getNpmModuleNameAndVersion(HeliumPackage pkg) {
private String[] getNpmModuleNameAndVersion(HeliumPackage pkg) {
String artifact = pkg.getArtifact();
if (isLocalPackage(pkg)) {
@ -247,8 +309,20 @@ public class HeliumVisualizationFactory {
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");
npmLogger.addAppender(new WriterAppender(
new PatternLayout("%m%n"),
out
));
}
}

View file

@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.helium;
/**
* Represetns webpack json format result
*/
public class WebpackResult {
public final String [] errors = new String[0];
public final String [] warnings = new String[0];
}

View file

@ -2,7 +2,7 @@
"name": "zeppelin-vis-bundle",
"main": "load",
"scripts": {
"bundle": "node/node node_modules/webpack/bin/webpack.js --display-error-details"
"bundle": "node/node node_modules/webpack/bin/webpack.js --display-error-details --json"
},
"dependencies": {
DEPENDENCIES

View file

@ -31,6 +31,7 @@ import java.util.LinkedList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class HeliumVisualizationFactoryTest {
@ -39,8 +40,9 @@ public class HeliumVisualizationFactoryTest {
@Before
public void setUp() throws InstallationException, TaskRunnerException {
tmpDir = new File(System.getProperty("java.io.tmpdir") + "/ZeppelinLTest_" + System.currentTimeMillis());
tmpDir.mkdirs();
//tmpDir = new File(System.getProperty("java.io.tmpdir") + "/ZeppelinLTest_" + System.currentTimeMillis());
tmpDir = new File("/tmp/npm");
//tmpDir.mkdirs();
// get module dir
URL res = Resources.getResource("helium/webpack.config.js");
@ -54,7 +56,7 @@ public class HeliumVisualizationFactoryTest {
@After
public void tearDown() throws IOException {
FileUtils.deleteDirectory(tmpDir);
//FileUtils.deleteDirectory(tmpDir);
}
@Test
@ -84,7 +86,7 @@ public class HeliumVisualizationFactoryTest {
HeliumPackage.Type.VISUALIZATION,
"lodash",
"lodash",
"lodash^3.9.3",
"lodash^1.8.3",
"",
null,
"icon"
@ -121,4 +123,32 @@ public class HeliumVisualizationFactoryTest {
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,
"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) {
e.getMessage().contains("error in the package");
}
assertNull(bundle);
}
}

View file

@ -0,0 +1,12 @@
{
"name": "vis2",
"version": "1.0.0",
"description": "",
"main": "vis2",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"zeppelin-tabledata": "*",
"zeppelin-vis": "*"
}
}

View file

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Visualization from 'zeppelin-vis'
import PassthroughTransformation from 'zeppelin-tabledata/passthrough'
/**
* Base class for visualization
*/
export default class vis2 extends Visualization {
constructor(targetEl, config) {
super(targetEl, config)
this.passthrough = new PassthroughTransformation(config);
}
render(tableData) {
this.targetEl.html('Vis2')
error in the package
}
getTransformation() {
return this.passthrough
}
}