From d91b310cbb985eac1e12ab9528a3be9e8e1b834c Mon Sep 17 00:00:00 2001 From: Prabhjyot Singh Date: Sat, 7 Jan 2017 18:07:00 +0530 Subject: [PATCH 001/234] [ZEPPELIN-1906] Use multiple InterpreterResult for displaying multiple JDBC queries ### What is this PR for? Use multiple InterpreterResult for displaying multiple JDBC queries. IMO since other sql editors allows to execute multiple sql separated with ";" and ours display mechanism being more powerful, hence, it should also allow the same. ### What type of PR is it? [Improvement] ### What is the Jira issue? * [ZEPPELIN-1906](https://issues.apache.org/jira/browse/ZEPPELIN-1906) ### How should this be tested? Try running following in a paragraph (with Postgres setting) and check for output. ``` %jdbc create table test_temp_table (id int); select column_name, data_type, character_maximum_length from INFORMATION_SCHEMA.COLUMNS where table_name = 'test_temp_table'; SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; drop table test_temp_table; SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; ``` ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? N/A * Is there breaking changes for older versions? N/A * Does this needs documentation? N/A Author: Prabhjyot Singh Closes #1845 from prabhjyotsingh/ZEPPELIN-1906 and squashes the following commits: b27352a [Prabhjyot Singh] on error show previous output. f9fd5c6 [Prabhjyot Singh] allow last query to be without ";" b3e742e [Prabhjyot Singh] fixing checkstyle-fail-build ac4663d [Prabhjyot Singh] add block comment f3da37f [Prabhjyot Singh] replace regex with slightly better logic. e6727b5 [Prabhjyot Singh] add testcase for spliting sql. c096e76 [Prabhjyot Singh] remove extra empty lines e675190 [Prabhjyot Singh] user same connection instead of creating new everytime f5ab796 [Prabhjyot Singh] Use multiple InterpreterResult for displaying multiple JDBC queries (cherry picked from commit 8464971c7aab0734f96d0a5a11d842b8e595324a) Signed-off-by: Prabhjyot Singh --- .../apache/zeppelin/jdbc/JDBCInterpreter.java | 173 ++++++++++++------ .../zeppelin/jdbc/JDBCInterpreterTest.java | 50 ++++- 2 files changed, 166 insertions(+), 57 deletions(-) diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java index aaf4fc7e778..778dcf28229 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -15,12 +15,26 @@ package org.apache.zeppelin.jdbc; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; -import java.io.*; -import java.nio.charset.StandardCharsets; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; + import java.security.PrivilegedExceptionAction; -import java.sql.*; -import java.util.*; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import org.apache.commons.dbcp2.ConnectionFactory; import org.apache.commons.dbcp2.DriverManagerConnectionFactory; @@ -30,10 +44,7 @@ import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.hadoop.security.UserGroupInformation; -import org.apache.zeppelin.interpreter.Interpreter; -import org.apache.zeppelin.interpreter.InterpreterContext; -import org.apache.zeppelin.interpreter.InterpreterException; -import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; import org.apache.zeppelin.jdbc.security.JDBCSecurityImpl; @@ -443,6 +454,57 @@ private boolean isDDLCommand(int updatedCount, int columnCount) throws SQLExcept return updatedCount < 0 && columnCount <= 0 ? true : false; } + /* + inspired from https://github.com/postgres/pgadmin3/blob/794527d97e2e3b01399954f3b79c8e2585b908dd/ + pgadmin/dlg/dlgProperty.cpp#L999-L1045 + */ + protected ArrayList splitSqlQueries(String sql) { + ArrayList queries = new ArrayList<>(); + StringBuilder query = new StringBuilder(); + Character character; + + Boolean antiSlash = false; + Boolean quoteString = false; + Boolean doubleQuoteString = false; + + for (int item = 0; item < sql.length(); item++) { + character = sql.charAt(item); + + if (character.equals('\\')) { + antiSlash = true; + } + if (character.equals('\'')) { + if (antiSlash) { + antiSlash = false; + } else if (quoteString) { + quoteString = false; + } else if (!doubleQuoteString) { + quoteString = true; + } + } + if (character.equals('"')) { + if (antiSlash) { + antiSlash = false; + } else if (doubleQuoteString) { + doubleQuoteString = false; + } else if (!quoteString) { + doubleQuoteString = true; + } + } + + if (character.equals(';') && !antiSlash && !quoteString && !doubleQuoteString) { + queries.add(query.toString()); + query = new StringBuilder(); + } else if (item == sql.length() - 1) { + query.append(character); + queries.add(query.toString()); + } else { + query.append(character); + } + } + return queries; + } + private InterpreterResult executeSql(String propertyKey, String sql, InterpreterContext interpreterContext) { Connection connection; @@ -451,60 +513,68 @@ private InterpreterResult executeSql(String propertyKey, String sql, String paragraphId = interpreterContext.getParagraphId(); String user = interpreterContext.getAuthenticationInfo().getUser(); + InterpreterResult interpreterResult = new InterpreterResult(InterpreterResult.Code.SUCCESS); + try { - String results = null; connection = getConnection(propertyKey, interpreterContext); - if (connection == null) { return new InterpreterResult(Code.ERROR, "Prefix not found."); } - statement = connection.createStatement(); - if (statement == null) { - return new InterpreterResult(Code.ERROR, "Prefix not found."); - } + ArrayList multipleSqlArray = splitSqlQueries(sql); + for (int i = 0; i < multipleSqlArray.size(); i++) { + String sqlToExecute = multipleSqlArray.get(i); + statement = connection.createStatement(); + if (statement == null) { + return new InterpreterResult(Code.ERROR, "Prefix not found."); + } - try { - getJDBCConfiguration(user).saveStatement(paragraphId, statement); + try { + getJDBCConfiguration(user).saveStatement(paragraphId, statement); - boolean isResultSetAvailable = statement.execute(sql); - if (isResultSetAvailable) { - resultSet = statement.getResultSet(); + boolean isResultSetAvailable = statement.execute(sqlToExecute); + if (isResultSetAvailable) { + resultSet = statement.getResultSet(); - // Regards that the command is DDL. - if (isDDLCommand(statement.getUpdateCount(), resultSet.getMetaData().getColumnCount())) { - results = "Query executed successfully."; + // Regards that the command is DDL. + if (isDDLCommand(statement.getUpdateCount(), + resultSet.getMetaData().getColumnCount())) { + interpreterResult.add(InterpreterResult.Type.TEXT, + "Query executed successfully."); + } else { + interpreterResult.add( + getResults(resultSet, !containsIgnoreCase(sqlToExecute, EXPLAIN_PREDICATE))); + } } else { - results = getResults(resultSet, !containsIgnoreCase(sql, EXPLAIN_PREDICATE)); + // Response contains either an update count or there are no results. + int updateCount = statement.getUpdateCount(); + interpreterResult.add(InterpreterResult.Type.TEXT, + "Query executed successfully. Affected rows : " + + updateCount); + } + } finally { + if (resultSet != null) { + try { + resultSet.close(); + } catch (SQLException e) { /*ignored*/ } + } + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { /*ignored*/ } } - } else { - // Response contains either an update count or there are no results. - int updateCount = statement.getUpdateCount(); - results = "Query executed successfully. Affected rows : " + updateCount; - } - //In case user ran an insert/update/upsert statement - if (connection.getAutoCommit() != true) connection.commit(); - - } finally { - if (resultSet != null) { - try { - resultSet.close(); - } catch (SQLException e) { /*ignored*/ } - } - if (statement != null) { - try { - statement.close(); - } catch (SQLException e) { /*ignored*/ } - } - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { /*ignored*/ } } - getJDBCConfiguration(user).removeStatement(paragraphId); } - return new InterpreterResult(Code.SUCCESS, results); - + //In case user ran an insert/update/upsert statement + if (connection != null) { + try { + if (!connection.getAutoCommit()) { + connection.commit(); + } + connection.close(); + } catch (SQLException e) { /*ignored*/ } + } + getJDBCConfiguration(user).removeStatement(paragraphId); } catch (Exception e) { logger.error("Cannot run " + sql, e); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -517,9 +587,10 @@ private InterpreterResult executeSql(String propertyKey, String sql, } catch (SQLException e1) { e1.printStackTrace(); } - - return new InterpreterResult(Code.ERROR, errorMsg); + interpreterResult.add(errorMsg); + return new InterpreterResult(Code.ERROR, interpreterResult.message()); } + return interpreterResult; } /** diff --git a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java b/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java index 18bda72f8d6..0c683224903 100644 --- a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java +++ b/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java @@ -15,9 +15,6 @@ package org.apache.zeppelin.jdbc; import static java.lang.String.format; -import static org.apache.zeppelin.interpreter.Interpreter.logger; -import static org.apache.zeppelin.interpreter.Interpreter.register; -import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_KEY; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_DRIVER; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_PASSWORD; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_USER; @@ -29,19 +26,17 @@ import java.nio.file.Files; import java.nio.file.Path; import java.sql.*; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.zeppelin.interpreter.InterpreterContext; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; -import org.apache.zeppelin.jdbc.JDBCInterpreter; import org.apache.zeppelin.scheduler.FIFOScheduler; import org.apache.zeppelin.scheduler.ParallelScheduler; import org.apache.zeppelin.scheduler.Scheduler; import org.apache.zeppelin.user.AuthenticationInfo; -import org.apache.zeppelin.user.Credentials; import org.apache.zeppelin.user.UserCredentials; import org.apache.zeppelin.user.UsernamePassword; import org.junit.Before; @@ -171,6 +166,49 @@ public void testSelectQuery() throws SQLException, IOException { assertEquals("ID\tNAME\na\ta_name\nb\tb_name\n", interpreterResult.message().get(0).getData()); } + @Test + public void testSplitSqlQuery() throws SQLException, IOException { + String sqlQuery = "insert into test_table(id, name) values ('a', ';\"');" + + "select * from test_table;" + + "select * from test_table WHERE ID = \";'\";" + + "select * from test_table WHERE ID = ';'"; + + Properties properties = new Properties(); + JDBCInterpreter t = new JDBCInterpreter(properties); + t.open(); + ArrayList multipleSqlArray = t.splitSqlQueries(sqlQuery); + assertEquals(4, multipleSqlArray.size()); + assertEquals("insert into test_table(id, name) values ('a', ';\"')", multipleSqlArray.get(0)); + assertEquals("select * from test_table", multipleSqlArray.get(1)); + assertEquals("select * from test_table WHERE ID = \";'\"", multipleSqlArray.get(2)); + assertEquals("select * from test_table WHERE ID = ';'", multipleSqlArray.get(3)); + } + + @Test + public void testSelectMultipleQuries() throws SQLException, IOException { + Properties properties = new Properties(); + properties.setProperty("common.max_count", "1000"); + properties.setProperty("common.max_retry", "3"); + properties.setProperty("default.driver", "org.h2.Driver"); + properties.setProperty("default.url", getJdbcConnection()); + properties.setProperty("default.user", ""); + properties.setProperty("default.password", ""); + JDBCInterpreter t = new JDBCInterpreter(properties); + t.open(); + + String sqlQuery = "select * from test_table;" + + "select * from test_table WHERE ID = ';';"; + InterpreterResult interpreterResult = t.interpret(sqlQuery, interpreterContext); + assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code()); + assertEquals(2, interpreterResult.message().size()); + + assertEquals(InterpreterResult.Type.TABLE, interpreterResult.message().get(0).getType()); + assertEquals("ID\tNAME\na\ta_name\nb\tb_name\nc\tnull\n", interpreterResult.message().get(0).getData()); + + assertEquals(InterpreterResult.Type.TABLE, interpreterResult.message().get(1).getType()); + assertEquals("ID\tNAME\n", interpreterResult.message().get(1).getData()); + } + @Test public void testSelectQueryWithNull() throws SQLException, IOException { Properties properties = new Properties(); From b8637f5e5fa4aab8859e6bc90fa7886ba9157b24 Mon Sep 17 00:00:00 2001 From: Alexander Shoshin Date: Wed, 11 Jan 2017 11:45:31 +0300 Subject: [PATCH 002/234] [ZEPPELIN-1787] Add an example of Flink Notebook ### What is this PR for? This PR will add an example of batch processing with Flink to Zeppelin tutorial notebooks. There are no any Flink notebooks in the tutorial at the moment. ### What type of PR is it? Improvement ### What is the Jira issue? [ZEPPELIN-1787](https://issues.apache.org/jira/browse/ZEPPELIN-1787) ### How should this be tested? You should open `Using Flink for batch processing` notebook from the `Zeppelin Tutorial` folder and run all paragraphs one by one ### Questions: * Does the licenses files need update? - **no** * Is there breaking changes for older versions? - **no** * Does this needs documentation? - **no** Author: Alexander Shoshin Closes #1758 from AlexanderShoshin/ZEPPELIN-1787 and squashes the following commits: 83cbffb [Alexander Shoshin] remove localhost url 5255e17 [Alexander Shoshin] Merge branch 'master' into ZEPPELIN-1787 0b9df56 [Alexander Shoshin] add a link for this notebook to Zeppelin documentation 593c47d [Alexander Shoshin] convert notebook to 0.7.0 format 9013620 [Alexander Shoshin] convert notebook to 0.6.2 format fe2a39e [Alexander Shoshin] add download instruction, change "wget" to "curl" f64b60a [Alexander Shoshin] [ZEPPELIN-1787] Add an example of Flink Notebook (cherry picked from commit 0da08d1d726129f6b684c99b1af8802907475d8a) Signed-off-by: ahyoungryu --- docs/interpreter/flink.md | 2 +- notebook/2C35YU814/note.json | 806 +++++++++++++++++++++++++++++++++++ 2 files changed, 807 insertions(+), 1 deletion(-) create mode 100644 notebook/2C35YU814/note.json diff --git a/docs/interpreter/flink.md b/docs/interpreter/flink.md index 2c1708775bf..2cf31257ad6 100644 --- a/docs/interpreter/flink.md +++ b/docs/interpreter/flink.md @@ -53,7 +53,7 @@ At the "Interpreters" menu, you have to create a new Flink interpreter and provi For more information about Flink configuration, you can find it [here](https://ci.apache.org/projects/flink/flink-docs-release-1.0/setup/config.html). ## How to test it's working -In example, by using the [Zeppelin notebook](https://www.zeppelinhub.com/viewer/notebooks/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL05GTGFicy96ZXBwZWxpbi1ub3RlYm9va3MvbWFzdGVyL25vdGVib29rcy8yQVFFREs1UEMvbm90ZS5qc29u) is from Till Rohrmann's presentation [Interactive data analysis with Apache Flink](http://www.slideshare.net/tillrohrmann/data-analysis-49806564) for Apache Flink Meetup. +You can find an example of Flink usage in the Zeppelin Tutorial folder or try the following word count example, by using the [Zeppelin notebook](https://www.zeppelinhub.com/viewer/notebooks/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL05GTGFicy96ZXBwZWxpbi1ub3RlYm9va3MvbWFzdGVyL25vdGVib29rcy8yQVFFREs1UEMvbm90ZS5qc29u) from Till Rohrmann's presentation [Interactive data analysis with Apache Flink](http://www.slideshare.net/tillrohrmann/data-analysis-49806564) for Apache Flink Meetup. ``` %sh diff --git a/notebook/2C35YU814/note.json b/notebook/2C35YU814/note.json new file mode 100644 index 00000000000..09ed8c6e01c --- /dev/null +++ b/notebook/2C35YU814/note.json @@ -0,0 +1,806 @@ +{ + "paragraphs": [ + { + "text": "%md\n### Intro\nThis notebook is an example of how to use **Apache Flink** for processing simple data sets. We will take an open airline data set from [stat-computing.org](http://stat-computing.org) and find out who was the most popular carrier during 1998-2000 years. Next we will build a chart that shows flights distribution by months and look how it changes from year to year. We will use Zeppelin `%table` display system to build charts.", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 11:55:42 AM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952101049_-1120777567", + "id": "20170109-115501_192763014", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003eIntro\u003c/h3\u003e\n\u003cp\u003eThis notebook is an example of how to use \u003cstrong\u003eApache Flink\u003c/strong\u003e for processing simple data sets. We will take an open airline data set from \u003ca href\u003d\"http://stat-computing.org\"\u003estat-computing.org\u003c/a\u003e and find out who was the most popular carrier during 1998-2000 years. Next we will build a chart that shows flights distribution by months and look how it changes from year to year. We will use Zeppelin \u003ccode\u003e%table\u003c/code\u003e display system to build charts.\u003c/p\u003e\n\u003c/div\u003e" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:55:01 AM", + "dateStarted": "Jan 9, 2017 11:55:42 AM", + "dateFinished": "Jan 9, 2017 11:55:44 AM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%md\n### Getting the data\nFirst we need to download and unpack the data. We will get three big data sets with flight details (one pack for each year) and a small one with carriers names. In total we will get for about 1,5 GB of data. To be able to process such amount of data it is recommended to increase `shell.command.timeout.millisecs` value in `%sh` interpreter settings up to several minutes. You can find interpreters configuration by clicking on `Interpreter` in a drop-down menu from the top right corner of the Zeppelin web-ui.", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 11:56:08 AM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "editorHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952142017_284386712", + "id": "20170109-115542_1487437739", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003eGetting the data\u003c/h3\u003e\n\u003cp\u003eFirst we need to download and unpack the data. We will get three big data sets with flight details (one pack for each year) and a small one with carriers names. In total we will get for about 1,5 GB of data. To be able to process such amount of data it is recommended to increase \u003ccode\u003eshell.command.timeout.millisecs\u003c/code\u003e value in \u003ccode\u003e%sh\u003c/code\u003e interpreter settings up to several minutes. You can find interpreters configuration by clicking on \u003ccode\u003eInterpreter\u003c/code\u003e in a drop-down menu from the top right corner of the Zeppelin web-ui.\u003c/p\u003e\n\u003c/div\u003e" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:55:42 AM", + "dateStarted": "Jan 9, 2017 11:56:07 AM", + "dateFinished": "Jan 9, 2017 11:56:07 AM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%sh\n\nrm /tmp/flights98.csv.bz2\ncurl -o /tmp/flights98.csv.bz2 \"http://stat-computing.org/dataexpo/2009/1998.csv.bz2\"\nrm /tmp/flights98.csv\nbzip2 -d /tmp/flights98.csv.bz2\nchmod 666 /tmp/flights98.csv", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 11:59:02 AM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "sh", + "editOnDblClick": false + }, + "editorMode": "ace/mode/sh", + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952167547_-566831096", + "id": "20170109-115607_1634441713", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "rm: cannot remove \u0027/tmp/flights98.csv.bz2\u0027: No such file or directory\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r 0 73.1M 0 64295 0 0 51646 0 0:24:44 0:00:01 0:24:43 51642\r 0 73.1M 0 358k 0 0 160k 0 0:07:47 0:00:02 0:07:45 160k\r 1 73.1M 1 1209k 0 0 373k 0 0:03:20 0:00:03 0:03:17 373k\r 4 73.1M 4 3204k 0 0 773k 0 0:01:36 0:00:04 0:01:32 773k\r 7 73.1M 7 5508k 0 0 1071k 0 0:01:09 0:00:05 0:01:04 1145k\r 10 73.1M 10 7875k 0 0 1280k 0 0:00:58 0:00:06 0:00:52 1592k\r 13 73.1M 13 10.1M 0 0 1458k 0 0:00:51 0:00:07 0:00:44 2049k\r 17 73.1M 17 12.7M 0 0 1608k 0 0:00:46 0:00:08 0:00:38 2422k\r 20 73.1M 20 14.9M 0 0 1671k 0 0:00:44 0:00:09 0:00:35 2413k\r 23 73.1M 23 17.1M 0 0 1728k 0 0:00:43 0:00:10 0:00:33 2403k\r 26 73.1M 26 19.4M 0 0 1787k 0 0:00:41 0:00:11 0:00:30 2411k\r 29 73.1M 29 21.7M 0 0 1837k 0 0:00:40 0:00:12 0:00:28 2379k\r 32 73.1M 32 24.1M 0 0 1879k 0 0:00:39 0:00:13 0:00:26 2322k\r 36 73.1M 36 26.4M 0 0 1916k 0 0:00:39 0:00:14 0:00:25 2365k\r 39 73.1M 39 28.5M 0 0 1930k 0 0:00:38 0:00:15 0:00:23 2341k\r 41 73.1M 41 30.6M 0 0 1943k 0 0:00:38 0:00:16 0:00:22 2292k\r 44 73.1M 44 32.6M 0 0 1947k 0 0:00:38 0:00:17 0:00:21 2215k\r 47 73.1M 47 34.6M 0 0 1952k 0 0:00:38 0:00:18 0:00:20 2145k\r 50 73.1M 50 36.6M 0 0 1960k 0 0:00:38 0:00:19 0:00:19 2082k\r 52 73.1M 52 38.3M 0 0 1947k 0 0:00:38 0:00:20 0:00:18 1998k\r 55 73.1M 55 40.4M 0 0 1956k 0 0:00:38 0:00:21 0:00:17 1996k\r 57 73.1M 57 42.2M 0 0 1951k 0 0:00:38 0:00:22 0:00:16 1965k\r 60 73.1M 60 44.0M 0 0 1948k 0 0:00:38 0:00:23 0:00:15 1932k\r 62 73.1M 62 45.4M 0 0 1927k 0 0:00:38 0:00:24 0:00:14 1803k\r 63 73.1M 63 46.5M 0 0 1896k 0 0:00:39 0:00:25 0:00:14 1688k\r 65 73.1M 65 47.7M 0 0 1868k 0 0:00:40 0:00:26 0:00:14 1496k\r 66 73.1M 66 48.8M 0 0 1843k 0 0:00:40 0:00:27 0:00:13 1363k\r 68 73.1M 68 50.0M 0 0 1820k 0 0:00:41 0:00:28 0:00:13 1227k\r 69 73.1M 69 51.1M 0 0 1786k 0 0:00:41 0:00:29 0:00:12 1126k\r 71 73.1M 71 52.0M 0 0 1769k 0 0:00:42 0:00:30 0:00:12 1131k\r 72 73.1M 72 53.0M 0 0 1744k 0 0:00:42 0:00:31 0:00:11 1098k\r 73 73.1M 73 54.0M 0 0 1723k 0 0:00:43 0:00:32 0:00:11 1070k\r 75 73.1M 75 55.1M 0 0 1702k 0 0:00:43 0:00:33 0:00:10 1040k\r 76 73.1M 76 56.0M 0 0 1681k 0 0:00:44 0:00:34 0:00:10 1048k\r 77 73.1M 77 56.9M 0 0 1659k 0 0:00:45 0:00:35 0:00:10 993k\r 79 73.1M 79 57.8M 0 0 1638k 0 0:00:45 0:00:36 0:00:09 972k\r 80 73.1M 80 58.7M 0 0 1618k 0 0:00:46 0:00:37 0:00:09 946k\r 81 73.1M 81 59.6M 0 0 1600k 0 0:00:46 0:00:38 0:00:08 921k\r 82 73.1M 82 60.5M 0 0 1582k 0 0:00:47 0:00:39 0:00:08 906k\r 83 73.1M 83 61.4M 0 0 1566k 0 0:00:47 0:00:40 0:00:07 917k\r 85 73.1M 85 62.1M 0 0 1546k 0 0:00:48 0:00:41 0:00:07 887k\r 86 73.1M 86 63.0M 0 0 1532k 0 0:00:48 0:00:42 0:00:06 892k\r 87 73.1M 87 63.9M 0 0 1517k 0 0:00:49 0:00:43 0:00:06 882k\r 88 73.1M 88 64.8M 0 0 1503k 0 0:00:49 0:00:44 0:00:05 878k\r 89 73.1M 89 65.6M 0 0 1489k 0 0:00:50 0:00:45 0:00:05 872k\r 91 73.1M 91 66.5M 0 0 1477k 0 0:00:50 0:00:46 0:00:04 904k\r 92 73.1M 92 67.4M 0 0 1465k 0 0:00:51 0:00:47 0:00:04 897k\r 93 73.1M 93 68.2M 0 0 1451k 0 0:00:51 0:00:48 0:00:03 889k\r 94 73.1M 94 69.2M 0 0 1441k 0 0:00:51 0:00:49 0:00:02 897k\r 95 73.1M 95 70.1M 0 0 1430k 0 0:00:52 0:00:50 0:00:02 904k\r 97 73.1M 97 71.0M 0 0 1421k 0 0:00:52 0:00:51 0:00:01 910k\r 98 73.1M 98 71.9M 0 0 1413k 0 0:00:52 0:00:52 --:--:-- 923k\r 99 73.1M 99 72.8M 0 0 1403k 0 0:00:53 0:00:53 --:--:-- 941k\r100 73.1M 100 73.1M 0 0 1401k 0 0:00:53 0:00:53 --:--:-- 941k\nrm: cannot remove \u0027/tmp/flights98.csv\u0027: No such file or directory\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:56:07 AM", + "dateStarted": "Jan 9, 2017 11:57:37 AM", + "dateFinished": "Jan 9, 2017 11:58:50 AM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%sh\n\nrm /tmp/flights99.csv.bz2\ncurl -o /tmp/flights99.csv.bz2 \"http://stat-computing.org/dataexpo/2009/1999.csv.bz2\"\nrm /tmp/flights99.csv\nbzip2 -d /tmp/flights99.csv.bz2\nchmod 666 /tmp/flights99.csv", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 11:59:59 AM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "sh", + "editOnDblClick": false + }, + "editorMode": "ace/mode/sh", + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952257873_-1874269156", + "id": "20170109-115737_1346880844", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "rm: cannot remove \u0027/tmp/flights99.csv.bz2\u0027: No such file or directory\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r 0 75.7M 0 5520 0 0 9851 0 2:14:25 --:--:-- 2:14:25 9839\r 0 75.7M 0 88819 0 0 64302 0 0:20:35 0:00:01 0:20:34 64268\r 0 75.7M 0 181k 0 0 25316 0 0:52:18 0:00:07 0:52:11 25316\r 0 75.7M 0 548k 0 0 67331 0 0:19:39 0:00:08 0:19:31 67327\r 1 75.7M 1 817k 0 0 89344 0 0:14:49 0:00:09 0:14:40 89337\r 1 75.7M 1 1042k 0 0 100k 0 0:12:54 0:00:10 0:12:44 105k\r 3 75.7M 3 2461k 0 0 218k 0 0:05:55 0:00:11 0:05:44 239k\r 6 75.7M 6 5069k 0 0 412k 0 0:03:08 0:00:12 0:02:56 985k\r 11 75.7M 11 9165k 0 0 690k 0 0:01:52 0:00:13 0:01:39 1744k\r 14 75.7M 14 11.2M 0 0 796k 0 0:01:37 0:00:14 0:01:23 2109k\r 19 75.7M 19 14.8M 0 0 995k 0 0:01:17 0:00:15 0:01:02 2910k\r 24 75.7M 24 18.6M 0 0 1174k 0 0:01:06 0:00:16 0:00:50 3331k\r 29 75.7M 29 22.5M 0 0 1338k 0 0:00:57 0:00:17 0:00:40 3613k\r 35 75.7M 35 26.5M 0 0 1486k 0 0:00:52 0:00:18 0:00:34 3603k\r 40 75.7M 40 30.3M 0 0 1610k 0 0:00:48 0:00:19 0:00:29 4025k\r 45 75.7M 45 34.2M 0 0 1731k 0 0:00:44 0:00:20 0:00:24 3980k\r 50 75.7M 50 38.2M 0 0 1840k 0 0:00:42 0:00:21 0:00:21 4011k\r 55 75.7M 55 42.2M 0 0 1940k 0 0:00:39 0:00:22 0:00:17 4020k\r 60 75.7M 60 46.2M 0 0 2032k 0 0:00:38 0:00:23 0:00:15 4026k\r 65 75.7M 65 49.9M 0 0 2106k 0 0:00:36 0:00:24 0:00:12 4017k\r 70 75.7M 70 53.5M 0 0 2169k 0 0:00:35 0:00:25 0:00:10 3945k\r 75 75.7M 75 57.2M 0 0 2229k 0 0:00:34 0:00:26 0:00:08 3884k\r 80 75.7M 80 61.1M 0 0 2293k 0 0:00:33 0:00:27 0:00:06 3868k\r 86 75.7M 86 65.5M 0 0 2372k 0 0:00:32 0:00:28 0:00:04 3956k\r 92 75.7M 92 70.4M 0 0 2464k 0 0:00:31 0:00:29 0:00:02 4200k\r100 75.7M 100 75.7M 0 0 2565k 0 0:00:30 0:00:30 --:--:-- 4585k\nrm: cannot remove \u0027/tmp/flights99.csv\u0027: No such file or directory\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:57:37 AM", + "dateStarted": "Jan 9, 2017 11:59:04 AM", + "dateFinished": "Jan 9, 2017 11:59:53 AM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%sh\n\nrm /tmp/flights00.csv.bz2\ncurl -o /tmp/flights00.csv.bz2 \"http://stat-computing.org/dataexpo/2009/2000.csv.bz2\"\nrm /tmp/flights00.csv\nbzip2 -d /tmp/flights00.csv.bz2\nchmod 666 /tmp/flights00.csv", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:01:42 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "sh", + "editOnDblClick": false + }, + "editorMode": "ace/mode/sh", + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952312038_-1315320949", + "id": "20170109-115832_608069986", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "rm: cannot remove \u0027/tmp/flights00.csv.bz2\u0027: No such file or directory\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0\r 0 78.7M 0 5520 0 0 3016 0 7:36:06 0:00:01 7:36:05 3014\r 0 78.7M 0 39987 0 0 15337 0 1:29:41 0:00:02 1:29:39 15332\r 0 78.7M 0 87755 0 0 24531 0 0:56:04 0:00:03 0:56:01 24526\r 0 78.7M 0 157k 0 0 33950 0 0:40:31 0:00:04 0:40:27 33944\r 0 78.7M 0 221k 0 0 40878 0 0:33:39 0:00:05 0:33:34 53734\r 0 78.7M 0 308k 0 0 47250 0 0:29:06 0:00:06 0:29:00 63943\r 0 78.7M 0 398k 0 0 52806 0 0:26:03 0:00:07 0:25:56 71903\r 0 78.7M 0 437k 0 0 36667 0 0:37:31 0:00:12 0:37:19 41697\r 0 78.7M 0 703k 0 0 57158 0 0:24:04 0:00:12 0:23:52 71137\r 1 78.7M 1 851k 0 0 64259 0 0:21:24 0:00:13 0:21:11 80471\r 1 78.7M 1 1171k 0 0 82442 0 0:16:41 0:00:14 0:16:27 109k\r 1 78.7M 1 1546k 0 0 79861 0 0:17:13 0:00:19 0:16:54 97134\r 3 78.7M 3 3181k 0 0 154k 0 0:08:41 0:00:20 0:08:21 327k\r 4 78.7M 4 3466k 0 0 160k 0 0:08:21 0:00:21 0:08:00 308k\r 4 78.7M 4 3565k 0 0 136k 0 0:09:50 0:00:26 0:09:24 216k\r 8 78.7M 8 7196k 0 0 270k 0 0:04:57 0:00:26 0:04:31 501k\r 10 78.7M 10 8459k 0 0 307k 0 0:04:22 0:00:27 0:03:55 894k\r 11 78.7M 11 9386k 0 0 327k 0 0:04:06 0:00:28 0:03:38 768k\r 15 78.7M 15 11.9M 0 0 413k 0 0:03:14 0:00:29 0:02:45 1093k\r 18 78.7M 18 14.5M 0 0 487k 0 0:02:45 0:00:30 0:02:15 2553k\r 22 78.7M 22 17.7M 0 0 574k 0 0:02:20 0:00:31 0:01:49 2195k\r 25 78.7M 25 19.9M 0 0 626k 0 0:02:08 0:00:32 0:01:36 2375k\r 28 78.7M 28 22.1M 0 0 676k 0 0:01:59 0:00:33 0:01:26 2726k\r 31 78.7M 31 24.7M 0 0 734k 0 0:01:49 0:00:34 0:01:15 2643k\r 34 78.7M 34 27.3M 0 0 789k 0 0:01:42 0:00:35 0:01:07 2638k\r 38 78.7M 38 30.0M 0 0 841k 0 0:01:35 0:00:36 0:00:59 2513k\r 40 78.7M 40 32.1M 0 0 874k 0 0:01:32 0:00:37 0:00:55 2457k\r 43 78.7M 43 34.1M 0 0 906k 0 0:01:28 0:00:38 0:00:50 2445k\r 45 78.7M 45 35.7M 0 0 925k 0 0:01:27 0:00:39 0:00:48 2250k\r 47 78.7M 47 37.4M 0 0 946k 0 0:01:25 0:00:40 0:00:45 2062k\r 49 78.7M 49 39.3M 0 0 968k 0 0:01:23 0:00:41 0:00:42 1907k\r 52 78.7M 52 41.0M 0 0 987k 0 0:01:21 0:00:42 0:00:39 1859k\r 54 78.7M 54 42.5M 0 0 1000k 0 0:01:20 0:00:43 0:00:37 1729k\r 55 78.7M 55 43.9M 0 0 1008k 0 0:01:19 0:00:44 0:00:35 1651k\r 57 78.7M 57 45.4M 0 0 1020k 0 0:01:18 0:00:45 0:00:33 1625k\r 59 78.7M 59 46.6M 0 0 1027k 0 0:01:18 0:00:46 0:00:32 1512k\r 60 78.7M 60 47.7M 0 0 1027k 0 0:01:18 0:00:47 0:00:31 1376k\r 61 78.7M 61 48.6M 0 0 1024k 0 0:01:18 0:00:48 0:00:30 1236k\r 62 78.7M 62 49.5M 0 0 1020k 0 0:01:18 0:00:49 0:00:29 1125k\r 64 78.7M 64 50.4M 0 0 1021k 0 0:01:18 0:00:50 0:00:28 1027k\r 65 78.7M 65 51.3M 0 0 1018k 0 0:01:19 0:00:51 0:00:28 941k\r 66 78.7M 66 52.1M 0 0 1016k 0 0:01:19 0:00:52 0:00:27 910k\r 67 78.7M 67 53.0M 0 0 1014k 0 0:01:19 0:00:53 0:00:26 909k\r 68 78.7M 68 53.7M 0 0 1006k 0 0:01:20 0:00:54 0:00:26 868k\r 69 78.7M 69 54.6M 0 0 1006k 0 0:01:20 0:00:55 0:00:25 858k\r 70 78.7M 70 55.3M 0 0 1002k 0 0:01:20 0:00:56 0:00:24 831k\r 71 78.7M 71 56.1M 0 0 998k 0 0:01:20 0:00:57 0:00:23 807k\r 72 78.7M 72 56.9M 0 0 994k 0 0:01:21 0:00:58 0:00:23 787k\r 73 78.7M 73 57.6M 0 0 991k 0 0:01:21 0:00:59 0:00:22 823k\r 74 78.7M 74 58.4M 0 0 988k 0 0:01:21 0:01:00 0:00:21 784k\r 75 78.7M 75 59.2M 0 0 985k 0 0:01:21 0:01:01 0:00:20 791k\r 76 78.7M 76 60.0M 0 0 982k 0 0:01:22 0:01:02 0:00:20 797k\r 77 78.7M 77 60.8M 0 0 980k 0 0:01:22 0:01:03 0:00:19 808k\r 78 78.7M 78 61.6M 0 0 977k 0 0:01:22 0:01:04 0:00:18 812k\r 79 78.7M 79 62.4M 0 0 975k 0 0:01:22 0:01:05 0:00:17 824k\r 80 78.7M 80 63.4M 0 0 976k 0 0:01:22 0:01:06 0:00:16 870k\r 82 78.7M 82 64.9M 0 0 984k 0 0:01:21 0:01:07 0:00:14 1006k\r 85 78.7M 85 66.9M 0 0 1000k 0 0:01:20 0:01:08 0:00:12 1254k\r 88 78.7M 88 69.4M 0 0 1022k 0 0:01:18 0:01:09 0:00:09 1602k\r 92 78.7M 92 72.5M 0 0 1053k 0 0:01:16 0:01:10 0:00:06 2064k\r 96 78.7M 96 76.1M 0 0 1089k 0 0:01:13 0:01:11 0:00:02 2600k\r100 78.7M 100 78.7M 0 0 1116k 0 0:01:12 0:01:12 --:--:-- 3022k\nrm: cannot remove \u0027/tmp/flights00.csv\u0027: No such file or directory\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:58:32 AM", + "dateStarted": "Jan 9, 2017 12:00:01 PM", + "dateFinished": "Jan 9, 2017 12:01:34 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%sh\n\nrm /tmp/carriers.csv\ncurl -o /tmp/carriers.csv \"http://stat-computing.org/dataexpo/2009/carriers.csv\"\nchmod 666 /tmp/carriers.csv", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:01:48 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "sh", + "editOnDblClick": false + }, + "editorMode": "ace/mode/sh", + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952329229_2136292082", + "id": "20170109-115849_1794095031", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "rm: cannot remove \u0027/tmp/carriers.csv\u0027: No such file or directory\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r 9 43758 9 4140 0 0 7588 0 0:00:05 --:--:-- 0:00:05 7582\r100 43758 100 43758 0 0 46357 0 --:--:-- --:--:-- --:--:-- 46353\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:58:49 AM", + "dateStarted": "Jan 9, 2017 12:01:44 PM", + "dateFinished": "Jan 9, 2017 12:01:45 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%md\n### Preparing the data\nThe `flights\u003cYY\u003e.csv` contains various data but we only need the information about the year, the month and the carrier who served the flight. Let\u0027s retrieve this information and create `DataSets`.", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:01:51 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952363836_-1769111757", + "id": "20170109-115923_963126574", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003ePreparing the data\u003c/h3\u003e\n\u003cp\u003eThe \u003ccode\u003eflights\u0026lt;YY\u0026gt;.csv\u003c/code\u003e contains various data but we only need the information about the year, the month and the carrier who served the flight. Let\u0026rsquo;s retrieve this information and create \u003ccode\u003eDataSets\u003c/code\u003e.\u003c/p\u003e\n\u003c/div\u003e" + } + ] + }, + "dateCreated": "Jan 9, 2017 11:59:23 AM", + "dateStarted": "Jan 9, 2017 12:01:51 PM", + "dateFinished": "Jan 9, 2017 12:01:53 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%flink\n\ncase class Flight(year: Int, month: Int, carrierCode: String)\ncase class Carrier(code: String, name: String)\n\nval flights98 \u003d benv.readCsvFile[Flight](\"/tmp/flights98.csv\", ignoreFirstLine \u003d true, includedFields \u003d Array(0, 1, 8))\nval flights99 \u003d benv.readCsvFile[Flight](\"/tmp/flights99.csv\", ignoreFirstLine \u003d true, includedFields \u003d Array(0, 1, 8))\nval flights00 \u003d benv.readCsvFile[Flight](\"/tmp/flights00.csv\", ignoreFirstLine \u003d true, includedFields \u003d Array(0, 1, 8))\nval flights \u003d flights98.union(flights99).union(flights00)\nval carriers \u003d benv.readCsvFile[Carrier](\"/tmp/carriers.csv\", ignoreFirstLine \u003d true, quoteCharacter \u003d \u0027\"\u0027)", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:02:38 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "lineNumbers": true, + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952511284_-589624871", + "id": "20170109-120151_872852428", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "defined class Flight\ndefined class Carrier\nflights98: org.apache.flink.api.scala.DataSet[Flight] \u003d org.apache.flink.api.scala.DataSet@7cd81fd5\nflights99: org.apache.flink.api.scala.DataSet[Flight] \u003d org.apache.flink.api.scala.DataSet@58242e79\nflights00: org.apache.flink.api.scala.DataSet[Flight] \u003d org.apache.flink.api.scala.DataSet@13f866c0\nflights: org.apache.flink.api.scala.DataSet[Flight] \u003d org.apache.flink.api.scala.DataSet@2aad2530\ncarriers: org.apache.flink.api.scala.DataSet[Carrier] \u003d org.apache.flink.api.scala.DataSet@148c977b\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:01:51 PM", + "dateStarted": "Jan 9, 2017 12:02:10 PM", + "dateFinished": "Jan 9, 2017 12:02:29 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%md\n### Choosing the carrier\nNow we will search for the most popular carrier during the whole time period.", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:03:08 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952530113_212237809", + "id": "20170109-120210_773710997", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003eChoosing the carrier\u003c/h3\u003e\n\u003cp\u003eNow we will search for the most popular carrier during the whole time period.\u003c/p\u003e\n\u003c/div\u003e" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:02:10 PM", + "dateStarted": "Jan 9, 2017 12:03:08 PM", + "dateFinished": "Jan 9, 2017 12:03:08 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%flink\n\nimport org.apache.flink.api.common.operators.Order\nimport org.apache.flink.api.java.aggregation.Aggregations\n\ncase class CarrierFlightsCount(carrierCode: String, count: Int)\ncase class CountByMonth(month: Int, count: Int)\n\nval carriersFlights \u003d flights\n .map(f \u003d\u003e CarrierFlightsCount(f.carrierCode, 1))\n .groupBy(\"carrierCode\")\n .sum(\"count\")\n\nval maxFlights \u003d carriersFlights\n .aggregate(Aggregations.MAX, \"count\")\n\nval bestCarrier \u003d carriersFlights\n .join(maxFlights)\n .where(\"count\")\n .equalTo(\"count\")\n .map(_._1)\n \nval carrierName \u003d bestCarrier\n .join(carriers)\n .where(\"carrierCode\")\n .equalTo(\"code\")\n .map(_._2.name)\n .collect\n .head", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:04:04 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "lineNumbers": true, + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952588708_-1770095793", + "id": "20170109-120308_1328511597", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "import org.apache.flink.api.common.operators.Order\nimport org.apache.flink.api.java.aggregation.Aggregations\ndefined class CarrierFlightsCount\ndefined class CountByMonth\ncarriersFlights: org.apache.flink.api.scala.AggregateDataSet[CarrierFlightsCount] \u003d org.apache.flink.api.scala.AggregateDataSet@2c59be0b\nmaxFlights: org.apache.flink.api.scala.AggregateDataSet[CarrierFlightsCount] \u003d org.apache.flink.api.scala.AggregateDataSet@53e5fad9\nbestCarrier: org.apache.flink.api.scala.DataSet[CarrierFlightsCount] \u003d org.apache.flink.api.scala.DataSet@64b7b1b3\ncarrierName: String \u003d Delta Air Lines Inc.\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:03:08 PM", + "dateStarted": "Jan 9, 2017 12:03:41 PM", + "dateFinished": "Jan 9, 2017 12:03:58 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%flink\n\nprintln(s\"\"\"The most popular carrier is:\n$carrierName\n\"\"\")", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:09:18 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "lineNumbers": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952621624_-1222400539", + "id": "20170109-120341_952212268", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "The most popular carrier is:\nDelta Air Lines Inc.\n\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:03:41 PM", + "dateStarted": "Jan 9, 2017 12:04:09 PM", + "dateFinished": "Jan 9, 2017 12:04:10 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%md\n### Calculating flights\nThe last step is to filter **Delta Air Lines** flights and group them by months.", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:04:26 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952649646_-1553253944", + "id": "20170109-120409_2003276881", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003eCalculating flights\u003c/h3\u003e\n\u003cp\u003eThe last step is to filter \u003cstrong\u003eDelta Air Lines\u003c/strong\u003e flights and group them by months.\u003c/p\u003e\n\u003c/div\u003e" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:04:09 PM", + "dateStarted": "Jan 9, 2017 12:04:26 PM", + "dateFinished": "Jan 9, 2017 12:04:26 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "title": "flights grouping", + "text": "%flink\n\ndef countFlightsPerMonth(flights: DataSet[Flight],\n carrier: DataSet[CarrierFlightsCount]) \u003d {\n val carrierFlights \u003d flights\n .join(carrier)\n .where(\"carrierCode\")\n .equalTo(\"carrierCode\")\n .map(_._1)\n \n carrierFlights\n .map(flight \u003d\u003e CountByMonth(flight.month, 1))\n .groupBy(\"month\")\n .sum(\"count\")\n .sortPartition(\"month\", Order.ASCENDING)\n}\n\nval bestCarrierFlights_98 \u003d countFlightsPerMonth(flights98, bestCarrier)\nval bestCarrierFlights_99 \u003d countFlightsPerMonth(flights99, bestCarrier)\nval bestCarrierFlights_00 \u003d countFlightsPerMonth(flights00, bestCarrier)", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:05:06 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "lineNumbers": true, + "title": true, + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952665972_667547355", + "id": "20170109-120425_2018337048", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "countFlightsPerMonth: (flights: org.apache.flink.api.scala.DataSet[Flight], carrier: org.apache.flink.api.scala.DataSet[CarrierFlightsCount])org.apache.flink.api.scala.DataSet[CountByMonth]\nbestCarrierFlights_98: org.apache.flink.api.scala.DataSet[CountByMonth] \u003d org.apache.flink.api.scala.PartitionSortedDataSet@2aa64309\nbestCarrierFlights_99: org.apache.flink.api.scala.DataSet[CountByMonth] \u003d org.apache.flink.api.scala.PartitionSortedDataSet@35fe60c4\nbestCarrierFlights_00: org.apache.flink.api.scala.DataSet[CountByMonth] \u003d org.apache.flink.api.scala.PartitionSortedDataSet@4621410f\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:04:25 PM", + "dateStarted": "Jan 9, 2017 12:04:50 PM", + "dateFinished": "Jan 9, 2017 12:04:51 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "title": "making a results table", + "text": "%flink\n\ndef monthAsString(month: Int): String \u003d {\n month match {\n case 1 \u003d\u003e \"Jan\"\n case 2 \u003d\u003e \"Feb\"\n case 3 \u003d\u003e \"Mar\"\n case 4 \u003d\u003e \"Apr\"\n case 5 \u003d\u003e \"May\"\n case 6 \u003d\u003e \"Jun\"\n case 7 \u003d\u003e \"Jul\"\n case 8 \u003d\u003e \"Aug\"\n case 9 \u003d\u003e \"Sept\"\n case 10 \u003d\u003e \"Oct\"\n case 11 \u003d\u003e \"Nov\"\n case 12 \u003d\u003e \"Dec\"\n }\n}\n\n// We should put all the results into a common DataFrame\n// to show them in a common picture\nval bestCarrierFlights \u003d bestCarrierFlights_98\n .join(bestCarrierFlights_99)\n .where(\"month\")\n .equalTo(\"month\")\n .map(tuple \u003d\u003e (tuple._1.month, tuple._1.count, tuple._2.count))\n .join(bestCarrierFlights_00)\n .where(0)\n .equalTo(\"month\")\n .map(tuple \u003d\u003e (tuple._1._1, tuple._1._2, tuple._1._3, tuple._2.count))\n .collect\n \nvar flightsByMonthTable \u003d s\"Month\\t1998\\t1999\\t2000\\n\"\nbestCarrierFlights.foreach(data \u003d\u003e flightsByMonthTable +\u003d s\"${monthAsString(data._1)}\\t${data._2}\\t${data._3}\\t${data._4}\\n\")", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:06:03 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "lineNumbers": true, + "title": true, + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952690164_-1061667443", + "id": "20170109-120450_1574916350", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "monthAsString: (month: Int)String\nbestCarrierFlights: Seq[(Int, Int, Int, Int)] \u003d Buffer((1,78523,77745,78055), (2,71101,70498,71090), (3,78906,77812,78453), (4,75726,75343,75247), (5,77937,77226,76797), (6,75432,75840,74846), (7,77521,77264,75776), (8,78104,78141,77654), (9,74840,75067,73696), (10,76145,77829,77425), (11,73552,74411,73659), (12,77308,76954,75331))\nflightsByMonthTable: String \u003d \n\"Month\t1998\t1999\t2000\n\"\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:04:50 PM", + "dateStarted": "Jan 9, 2017 12:05:24 PM", + "dateFinished": "Jan 9, 2017 12:05:59 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "title": "\"Delta Air Lines\" flights count by months", + "text": "%flink\n\nprintln(s\"\"\"%table\n$flightsByMonthTable\n\"\"\")", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:06:17 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": { + "0": { + "graph": { + "mode": "lineChart", + "height": 300.0, + "optionOpen": false, + "setting": { + "lineChart": {} + }, + "commonSetting": {}, + "keys": [ + { + "name": "Month", + "index": 0.0, + "aggr": "sum" + } + ], + "groups": [], + "values": [ + { + "name": "1998", + "index": 1.0, + "aggr": "sum" + }, + { + "name": "1999", + "index": 2.0, + "aggr": "sum" + }, + { + "name": "2000", + "index": 3.0, + "aggr": "sum" + } + ] + }, + "helium": {} + } + }, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "title": true, + "lineNumbers": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952724460_191505697", + "id": "20170109-120524_2037622815", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TABLE", + "data": "Month\t1998\t1999\t2000\nJan\t78523\t77745\t78055\nFeb\t71101\t70498\t71090\nMar\t78906\t77812\t78453\nApr\t75726\t75343\t75247\nMay\t77937\t77226\t76797\nJun\t75432\t75840\t74846\nJul\t77521\t77264\t75776\nAug\t78104\t78141\t77654\nSept\t74840\t75067\t73696\nOct\t76145\t77829\t77425\nNov\t73552\t74411\t73659\nDec\t77308\t76954\t75331\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:05:24 PM", + "dateStarted": "Jan 9, 2017 12:06:07 PM", + "dateFinished": "Jan 9, 2017 12:06:08 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%md\n### Results\nLooking at this chart we can say that February is the most unpopular month, but this is only because it has less days (28 or 29) than the other months (30 or 31). To receive more fair picture we should calculate the average flights count per day for each month.", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:06:34 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952767719_-1010557136", + "id": "20170109-120607_67673280", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003eResults\u003c/h3\u003e\n\u003cp\u003eLooking at this chart we can say that February is the most unpopular month, but this is only because it has less days (28 or 29) than the other months (30 or 31). To receive more fair picture we should calculate the average flights count per day for each month.\u003c/p\u003e\n\u003c/div\u003e" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:06:07 PM", + "dateStarted": "Jan 9, 2017 12:06:34 PM", + "dateFinished": "Jan 9, 2017 12:06:34 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%flink\n\ndef daysInMonth(month: Int, year: Int): Int \u003d {\n month match {\n case 1 \u003d\u003e 31\n case 2 \u003d\u003e if (year % 4 \u003d\u003d 0) {\n 29\n } else {\n 28\n }\n case 3 \u003d\u003e 31\n case 4 \u003d\u003e 30\n case 5 \u003d\u003e 31\n case 6 \u003d\u003e 30\n case 7 \u003d\u003e 31\n case 8 \u003d\u003e 31\n case 9 \u003d\u003e 30\n case 10 \u003d\u003e 31\n case 11 \u003d\u003e 30\n case 12 \u003d\u003e 31\n }\n}\n\n\nvar flightsByDayTable \u003d s\"Month\\t1998\\t1999\\t2000\\n\"\n\nbestCarrierFlights.foreach(data \u003d\u003e flightsByDayTable +\u003d s\"${monthAsString(data._1)}\\t${data._2/daysInMonth(data._1,1998)}\\t${data._3/daysInMonth(data._1,1999)}\\t${data._4/daysInMonth(data._1,2000)}\\n\")", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:06:58 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "lineNumbers": true, + "tableHide": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952794097_-785833130", + "id": "20170109-120634_492170963", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "daysInMonth: (month: Int, year: Int)Int\nflightsByDayTable: String \u003d \n\"Month\t1998\t1999\t2000\n\"\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:06:34 PM", + "dateStarted": "Jan 9, 2017 12:06:53 PM", + "dateFinished": "Jan 9, 2017 12:06:53 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "title": "\"Delta Air Lines\" flights count by days", + "text": "%flink\n\nprintln(s\"\"\"%table\n$flightsByDayTable\n\"\"\")", + "user": "anonymous", + "dateUpdated": "Jan 9, 2017 12:10:56 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": { + "0": { + "graph": { + "mode": "lineChart", + "height": 300.0, + "optionOpen": false, + "setting": { + "lineChart": {} + }, + "commonSetting": {}, + "keys": [ + { + "name": "Month", + "index": 0.0, + "aggr": "sum" + } + ], + "groups": [], + "values": [ + { + "name": "1998", + "index": 1.0, + "aggr": "sum" + }, + { + "name": "1999", + "index": 2.0, + "aggr": "sum" + }, + { + "name": "2000", + "index": 3.0, + "aggr": "sum" + } + ] + }, + "helium": {} + } + }, + "editorSetting": { + "language": "scala", + "editOnDblClick": false + }, + "editorMode": "ace/mode/scala", + "title": true, + "lineNumbers": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952813391_1847418990", + "id": "20170109-120653_1870236569", + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TABLE", + "data": "Month\t1998\t1999\t2000\nJan\t2533\t2507\t2517\nFeb\t2539\t2517\t2451\nMar\t2545\t2510\t2530\nApr\t2524\t2511\t2508\nMay\t2514\t2491\t2477\nJun\t2514\t2528\t2494\nJul\t2500\t2492\t2444\nAug\t2519\t2520\t2504\nSept\t2494\t2502\t2456\nOct\t2456\t2510\t2497\nNov\t2451\t2480\t2455\nDec\t2493\t2482\t2430\n" + } + ] + }, + "dateCreated": "Jan 9, 2017 12:06:53 PM", + "dateStarted": "Jan 9, 2017 12:07:22 PM", + "dateFinished": "Jan 9, 2017 12:07:23 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%flink\n", + "dateUpdated": "Jan 9, 2017 12:07:22 PM", + "config": {}, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483952842919_587228425", + "id": "20170109-120722_939892827", + "dateCreated": "Jan 9, 2017 12:07:22 PM", + "status": "READY", + "progressUpdateIntervalMs": 500 + } + ], + "name": "Zeppelin Tutorial/Using Flink for batch processing", + "id": "2C35YU814", + "angularObjects": { + "2C4PVECE6:shared_process": [], + "2C4US9MUF:shared_process": [], + "2C4FYNB4G:shared_process": [], + "2C4GX28KP:shared_process": [], + "2C648AXXN:shared_process": [], + "2C3MSEJ2F:shared_process": [], + "2C6F2N6BT:shared_process": [], + "2C3US2RTN:shared_process": [], + "2C3TYMD6K:shared_process": [], + "2C3FDPZRX:shared_process": [], + "2C5TEARYX:shared_process": [], + "2C5D6NSNG:shared_process": [], + "2C6FVVEAD:shared_process": [], + "2C582KNWG:shared_process": [], + "2C6ZMVGM7:shared_process": [], + "2C6UYQG8R:shared_process": [], + "2C666VZT2:shared_process": [], + "2C4JRCY3K:shared_process": [], + "2C64W5T9D:shared_process": [] + }, + "config": { + "looknfeel": "default" + }, + "info": {} +} From 6a15e7bf30876005b79f7593e02d5a4dc9f691d2 Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Wed, 11 Jan 2017 15:22:19 +0800 Subject: [PATCH 003/234] ZEPPELIN-1867. Update document for pig interpreter and add one sample note ### What is this PR for? * Minor update for pig interpreter * Add one sample pig tutorial note which do the same thing as the spark tutorial note. ### What type of PR is it? [Improvement | Documentation ] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1867 ### How should this be tested? Tested manually ### Screenshots (if appropriate) ![image](https://cloud.githubusercontent.com/assets/164491/21839221/8a4ffa04-d811-11e6-9096-f4f9da22ea49.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1830 from zjffdu/ZEPPELIN-1867 and squashes the following commits: 1c0d819 [Jeff Zhang] rename note name 50198a1 [Jeff Zhang] add more description of tutorial note 88385f2 [Jeff Zhang] Add pig tutorial note 25216f8 [Jeff Zhang] ZEPPELIN-1867. Update document for pig interpreter and add one sample note (cherry picked from commit 3d2d4b6f9804ecc5c157c1b4a3885ee01890884e) Signed-off-by: ahyoungryu --- docs/interpreter/pig.md | 11 +- notebook/2C57UKYWR/note.json | 316 +++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 notebook/2C57UKYWR/note.json diff --git a/docs/interpreter/pig.md b/docs/interpreter/pig.md index 227656bba78..a7781692e98 100644 --- a/docs/interpreter/pig.md +++ b/docs/interpreter/pig.md @@ -26,6 +26,7 @@ group: manual ## Supported runtime mode - Local - MapReduce + - Tez_Local (Only Tez 0.7 is supported) - Tez (Only Tez 0.7 is supported) ## How to use @@ -40,6 +41,10 @@ group: manual HADOOP\_CONF\_DIR needs to be specified in `ZEPPELIN_HOME/conf/zeppelin-env.sh`. +- Tez Local Mode + + Nothing needs to be done for tez local mode + - Tez Mode HADOOP\_CONF\_DIR and TEZ\_CONF\_DIR needs to be specified in `ZEPPELIN_HOME/conf/zeppelin-env.sh`. @@ -57,7 +62,7 @@ At the Interpreters menu, you have to create a new Pig interpreter. Pig interpre zeppelin.pig.execType mapreduce - Execution mode for pig runtime. local | mapreduce | tez + Execution mode for pig runtime. local | mapreduce | tez_local | tez zeppelin.pig.includeJobStats @@ -94,4 +99,6 @@ c = group b by Category; foreach c generate group as category, COUNT($1) as count; ``` -Data is shared between `%pig` and `%pig.query`, so that you can do some common work in `%pig`, and do different kinds of query based on the data of `%pig`. +Data is shared between `%pig` and `%pig.query`, so that you can do some common work in `%pig`, and do different kinds of query based on the data of `%pig`. +Besides, we recommend you to specify alias explicitly so that the visualization can display the column name correctly. Here, we name `COUNT($1)` as `count`, if you don't do this, +then we will name it using position, here we will use `col_1` to represent `COUNT($1)` if you don't specify alias for it. There's one pig tutorial note in zeppelin for your reference. diff --git a/notebook/2C57UKYWR/note.json b/notebook/2C57UKYWR/note.json new file mode 100644 index 00000000000..2b6ef8fcf37 --- /dev/null +++ b/notebook/2C57UKYWR/note.json @@ -0,0 +1,316 @@ +{ + "paragraphs": [ + { + "text": "%md\n\n\n### [Apache Pig](http://pig.apache.org/) is a platform for analyzing large data sets that consists of a high-level language for expressing data analysis programs, coupled with infrastructure for evaluating these programs. The salient property of Pig programs is that their structure is amenable to substantial parallelization, which in turns enables them to handle very large data sets.\n\nPig\u0027s language layer currently consists of a textual language called Pig Latin, which has the following key properties:\n\n* Ease of programming. It is trivial to achieve parallel execution of simple, \"embarrassingly parallel\" data analysis tasks. Complex tasks comprised of multiple interrelated data transformations are explicitly encoded as data flow sequences, making them easy to write, understand, and maintain.\n* Optimization opportunities. The way in which tasks are encoded permits the system to optimize their execution automatically, allowing the user to focus on semantics rather than efficiency.\n* Extensibility. Users can create their own functions to do special-purpose processing.\n", + "user": "user1", + "dateUpdated": "Jan 6, 2017 3:55:03 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch3\u003e\u003ca href\u003d\"http://pig.apache.org/\"\u003eApache Pig\u003c/a\u003e is a platform for analyzing large data sets that consists of a high-level language for expressing data analysis programs, coupled with infrastructure for evaluating these programs. The salient property of Pig programs is that their structure is amenable to substantial parallelization, which in turns enables them to handle very large data sets.\u003c/h3\u003e\n\u003cp\u003ePig\u0026rsquo;s language layer currently consists of a textual language called Pig Latin, which has the following key properties:\u003c/p\u003e\n\u003cul\u003e\n \u003cli\u003eEase of programming. It is trivial to achieve parallel execution of simple, \u0026ldquo;embarrassingly parallel\u0026rdquo; data analysis tasks. Complex tasks comprised of multiple interrelated data transformations are explicitly encoded as data flow sequences, making them easy to write, understand, and maintain.\u003c/li\u003e\n \u003cli\u003eOptimization opportunities. The way in which tasks are encoded permits the system to optimize their execution automatically, allowing the user to focus on semantics rather than efficiency.\u003c/li\u003e\n \u003cli\u003eExtensibility. Users can create their own functions to do special-purpose processing.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/div\u003e" + } + ] + }, + "apps": [], + "jobName": "paragraph_1483277502513_1156234051", + "id": "20170101-213142_1565013608", + "dateCreated": "Jan 1, 2017 9:31:42 PM", + "dateStarted": "Jan 6, 2017 3:55:03 PM", + "dateFinished": "Jan 6, 2017 3:55:04 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%md\n\nThis pig tutorial use pig to do the same thing as spark tutorial. The default mode is mapreduce, you can also use other modes like local/tez_local/tez. For mapreduce mode, you need to have hadoop installed and export `HADOOP_CONF_DIR` in `zeppelin-env.sh`\n\nThe tutorial consists of 3 steps.\n\n* Use shell interpreter to download bank.csv and upload it to hdfs\n* use `%pig` to process the data\n* use `%pig.query` to query the data", + "user": "user1", + "dateUpdated": "Jan 6, 2017 3:55:18 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "markdown", + "editOnDblClick": true + }, + "editorMode": "ace/mode/markdown", + "editorHide": true, + "tableHide": false + }, + "settings": { + "params": {}, + "forms": {} + }, + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "HTML", + "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003cp\u003eThis pig tutorial use pig to do the same thing as spark tutorial. The default mode is mapreduce, you can also use other modes like local/tez_local/tez. For mapreduce mode, you need to have hadoop installed and export \u003ccode\u003eHADOOP_CONF_DIR\u003c/code\u003e in \u003ccode\u003ezeppelin-env.sh\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe tutorial consists of 3 steps.\u003c/p\u003e\n\u003cul\u003e\n \u003cli\u003eUse shell interpreter to download bank.csv and upload it to hdfs\u003c/li\u003e\n \u003cli\u003euse \u003ccode\u003e%pig\u003c/code\u003e to process the data\u003c/li\u003e\n \u003cli\u003euse \u003ccode\u003e%pig.query\u003c/code\u003e to query the data\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/div\u003e" + } + ] + }, + "apps": [], + "jobName": "paragraph_1483689316217_-629483391", + "id": "20170106-155516_1050601059", + "dateCreated": "Jan 6, 2017 3:55:16 PM", + "dateStarted": "Jan 6, 2017 3:55:18 PM", + "dateFinished": "Jan 6, 2017 3:55:18 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%pig\n\nbankText \u003d load \u0027bank.csv\u0027 using PigStorage(\u0027;\u0027);\nbank \u003d foreach bankText generate $0 as age, $1 as job, $2 as marital, $3 as education, $5 as balance; \nbank \u003d filter bank by age !\u003d \u0027\"age\"\u0027;\nbank \u003d foreach bank generate (int)age, REPLACE(job,\u0027\"\u0027,\u0027\u0027) as job, REPLACE(marital, \u0027\"\u0027, \u0027\u0027) as marital, (int)(REPLACE(balance, \u0027\"\u0027, \u0027\u0027)) as balance;\n\n-- The following statement is optional, it depends on whether your needs.\n-- store bank into \u0027clean_bank.csv\u0027 using PigStorage(\u0027;\u0027);\n\n\n", + "user": "user1", + "dateUpdated": "Jan 6, 2017 3:57:11 PM", + "config": { + "colWidth": 12.0, + "editorMode": "ace/mode/pig", + "results": {}, + "enabled": true, + "editorSetting": { + "language": "pig", + "editOnDblClick": false + } + }, + "settings": { + "params": {}, + "forms": {} + }, + "results": { + "code": "SUCCESS", + "msg": [] + }, + "apps": [], + "jobName": "paragraph_1483277250237_-466604517", + "id": "20161228-140640_1560978333", + "dateCreated": "Jan 1, 2017 9:27:30 PM", + "dateStarted": "Jan 6, 2017 3:57:11 PM", + "dateFinished": "Jan 6, 2017 3:57:13 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%pig.query\n\nbank_data \u003d filter bank by age \u003c 30;\nb \u003d group bank_data by age;\nforeach b generate group, COUNT($1);\n\n", + "user": "user1", + "dateUpdated": "Jan 6, 2017 3:57:15 PM", + "config": { + "colWidth": 4.0, + "editorMode": "ace/mode/pig", + "results": { + "0": { + "graph": { + "mode": "multiBarChart", + "height": 300.0, + "optionOpen": false + }, + "helium": {} + } + }, + "enabled": true, + "editorSetting": { + "language": "pig", + "editOnDblClick": false + } + }, + "settings": { + "params": {}, + "forms": {} + }, + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TABLE", + "data": "group\tnull\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n" + } + ] + }, + "apps": [], + "jobName": "paragraph_1483277250238_-465450270", + "id": "20161228-140730_1903342877", + "dateCreated": "Jan 1, 2017 9:27:30 PM", + "dateStarted": "Jan 6, 2017 3:57:15 PM", + "dateFinished": "Jan 6, 2017 3:57:16 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%pig.query\n\nbank_data \u003d filter bank by age \u003c ${maxAge\u003d40};\nb \u003d group bank_data by age;\nforeach b generate group, COUNT($1);", + "user": "user1", + "dateUpdated": "Jan 6, 2017 3:57:18 PM", + "config": { + "colWidth": 4.0, + "editorMode": "ace/mode/pig", + "results": { + "0": { + "graph": { + "mode": "pieChart", + "height": 300.0, + "optionOpen": false + }, + "helium": {} + } + }, + "enabled": true, + "editorSetting": { + "language": "pig", + "editOnDblClick": false + } + }, + "settings": { + "params": { + "maxAge": "36" + }, + "forms": { + "maxAge": { + "name": "maxAge", + "defaultValue": "40", + "hidden": false + } + } + }, + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TABLE", + "data": "group\tnull\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n30\t150\n31\t199\n32\t224\n33\t186\n34\t231\n35\t180\n" + } + ] + }, + "apps": [], + "jobName": "paragraph_1483277250239_-465835019", + "id": "20161228-154918_1551591203", + "dateCreated": "Jan 1, 2017 9:27:30 PM", + "dateStarted": "Jan 6, 2017 3:57:18 PM", + "dateFinished": "Jan 6, 2017 3:57:19 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%pig.query\n\nbank_data \u003d filter bank by marital\u003d\u003d\u0027${marital\u003dsingle,single|divorced|married}\u0027;\nb \u003d group bank_data by age;\nforeach b generate group, COUNT($1) as c;\n\n\n", + "user": "user1", + "dateUpdated": "Jan 6, 2017 3:57:24 PM", + "config": { + "colWidth": 4.0, + "editorMode": "ace/mode/pig", + "results": { + "0": { + "graph": { + "mode": "scatterChart", + "height": 300.0, + "optionOpen": false + }, + "helium": {} + } + }, + "enabled": true, + "editorSetting": { + "language": "pig", + "editOnDblClick": false + } + }, + "settings": { + "params": { + "marital": "married" + }, + "forms": { + "marital": { + "name": "marital", + "defaultValue": "single", + "options": [ + { + "value": "single" + }, + { + "value": "divorced" + }, + { + "value": "married" + } + ], + "hidden": false + } + } + }, + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TABLE", + "data": "group\tc\n23\t3\n24\t11\n25\t11\n26\t18\n27\t26\n28\t23\n29\t37\n30\t56\n31\t104\n32\t105\n33\t103\n34\t142\n35\t109\n36\t117\n37\t100\n38\t99\n39\t88\n40\t105\n41\t97\n42\t91\n43\t79\n44\t68\n45\t76\n46\t82\n47\t78\n48\t91\n49\t87\n50\t74\n51\t63\n52\t66\n53\t75\n54\t56\n55\t68\n56\t50\n57\t78\n58\t67\n59\t56\n60\t36\n61\t15\n62\t5\n63\t7\n64\t6\n65\t4\n66\t7\n67\t5\n68\t1\n69\t5\n70\t5\n71\t5\n72\t4\n73\t6\n74\t2\n75\t3\n76\t1\n77\t5\n78\t2\n79\t3\n80\t6\n81\t1\n83\t2\n86\t1\n87\t1\n" + } + ] + }, + "apps": [], + "jobName": "paragraph_1483277250240_-480070728", + "id": "20161228-142259_575675591", + "dateCreated": "Jan 1, 2017 9:27:30 PM", + "dateStarted": "Jan 6, 2017 3:57:20 PM", + "dateFinished": "Jan 6, 2017 3:57:20 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%pig\n", + "dateUpdated": "Jan 1, 2017 9:27:30 PM", + "config": {}, + "settings": { + "params": {}, + "forms": {} + }, + "apps": [], + "jobName": "paragraph_1483277250240_-480070728", + "id": "20161228-155036_1854903164", + "dateCreated": "Jan 1, 2017 9:27:30 PM", + "status": "READY", + "errorMessage": "", + "progressUpdateIntervalMs": 500 + } + ], + "name": "Zeppelin Tutorial/Basic Features (Pig)", + "id": "2C57UKYWR", + "angularObjects": { + "2C3DR183X:shared_process": [], + "2C5VH924X:shared_process": [], + "2C686X8ZH:shared_process": [], + "2C66Z9XPQ:shared_process": [], + "2C3JKFMJU:shared_process": [], + "2C69WE69N:shared_process": [], + "2C3RWCVAG:shared_process": [], + "2C4HKDCQW:shared_process": [], + "2C4BJDRRZ:shared_process": [], + "2C6V3D44K:shared_process": [], + "2C3VECEG2:shared_process": [], + "2C5SRRXHM:shared_process": [], + "2C5DCRVGM:shared_process": [], + "2C66GE1VB:shared_process": [], + "2C3PTPMUH:shared_process": [], + "2C48Y7FSJ:shared_process": [], + "2C4ZD49PF:shared_process": [], + "2C63XW4XE:shared_process": [], + "2C4UB1UZA:shared_process": [], + "2C5S1R21W:shared_process": [], + "2C3SQSB7V:shared_process": [] + }, + "config": {}, + "info": {} +} From ffdc5e972e2d5dd36aab099c71a6e852ed0c9260 Mon Sep 17 00:00:00 2001 From: soralee Date: Wed, 11 Jan 2017 15:26:23 +0900 Subject: [PATCH 004/234] [ZEPPELIN-1864] Improvement to show folder and note after searching note ### What is this PR for? This PR is for improvement to show folder and note after using the filter. And I found some bugs and fixed like this. the following list is improvement and bug fixed. 1. After using the filter in Zeppelin Home, every note is shown by the form [FolderName/NoteName] like below. It would be nice to show folder icon and note icon as previous status. ![z1864_f_b](https://cloud.githubusercontent.com/assets/8110458/21604777/79dbb228-d1e8-11e6-974d-737520729d68.gif) 2. When using the filter, some functions and icons next to note and folder are disappeared. ![z1864_f_2_b](https://cloud.githubusercontent.com/assets/8110458/21605057/f5a0143e-d1e9-11e6-86f3-ebff5be4c41d.gif) 3. When using the filter, the `Rename Note` function doesn't work. ( When I was handling this PR, this bug was discovered.) ![z1864_f_4_b](https://cloud.githubusercontent.com/assets/8110458/21606384/71305796-d1f2-11e6-91a4-2ec14b8b4959.gif) ### What type of PR is it? [ Bug Fix | Improvement ] ### Todos * [x] - improve to show folder and note when finishing to use filter. * [x] - some functions and icons next to note and folder are appeared when using filter. * [x] - fix `Rename Note` function when using the filter. ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1864 ### How should this be tested? - **[Test 1]** Before/After using the filter, check the list structure of your notes. It must be same appearance. - **[Test 2]** When using the filter, check the notes. Some icons and functions next to note and folder must be appeared. - **[Test 3]** When using the filter, click `Rename Note`. It must be appeared the path of Note. ### Screenshots (if appropriate) **Test 1**. ![z1864_f_1_a](https://cloud.githubusercontent.com/assets/8110458/21606849/a79dbf82-d1f5-11e6-8d48-3a1977099ab1.gif) **Test 2**. ![z1864_f_2_a](https://cloud.githubusercontent.com/assets/8110458/21606880/d1cf9550-d1f5-11e6-9311-ee54424d7e76.gif) **Test 3**. ![z1864_f_4_a](https://cloud.githubusercontent.com/assets/8110458/21606272/9e0b096a-d1f1-11e6-8bfb-ab1ea1a30b88.gif) ### Questions: * Does the licenses files need update? N/A * Is there breaking changes for older versions? N/A * Does this needs documentation? N/A Author: soralee Closes #1834 from soralee/ZEPPELIN-1864 and squashes the following commits: 6fb53aa [soralee] resolve conflict fd2c243 [soralee] resolve conflict 3e4b8ae [soralee] fix for that filter of navbar and home don't work 61680e5 [soralee] Improvement to show folder and note after searching note 69d6d6d [soralee] Improvement to show folder and note after searching note (cherry picked from commit d393a5b52d345adc4da807065a25757572202075) Signed-off-by: ahyoungryu --- zeppelin-web/src/app/home/home.controller.js | 17 ++++- zeppelin-web/src/app/home/home.html | 73 ++++++++++--------- .../navbar/navbar-noteList-elem.html | 7 +- .../components/navbar/navbar.controller.js | 13 ++++ .../src/components/navbar/navbar.html | 9 ++- 5 files changed, 79 insertions(+), 40 deletions(-) diff --git a/zeppelin-web/src/app/home/home.controller.js b/zeppelin-web/src/app/home/home.controller.js index c27f8110f1e..a0140377cd8 100644 --- a/zeppelin-web/src/app/home/home.controller.js +++ b/zeppelin-web/src/app/home/home.controller.js @@ -42,6 +42,7 @@ function HomeCtrl($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arra $scope.isReloading = false; $scope.TRASH_FOLDER_ID = TRASH_FOLDER_ID; + $scope.query = {q: ''}; var initHome = function() { websocketMsgSrv.getHomeNote(); @@ -88,8 +89,8 @@ function HomeCtrl($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arra } }); - $scope.renameNote = function(node) { - noteActionSrv.renameNote(node.id, node.path); + $scope.renameNote = function(nodeId, nodePath) { + noteActionSrv.renameNote(nodeId, nodePath); }; $scope.moveNoteToTrash = function(noteId) { @@ -131,4 +132,16 @@ function HomeCtrl($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arra $scope.clearAllParagraphOutput = function(noteId) { noteActionSrv.clearAllParagraphOutput(noteId); }; + + $scope.isFilterNote = function(note) { + if (!$scope.query.q) { + return true; + } + + var noteName = note.name; + if (noteName.toLowerCase().indexOf($scope.query.q.toLowerCase()) > -1) { + return true; + } + return false; + }; } diff --git a/zeppelin-web/src/app/home/home.html b/zeppelin-web/src/app/home/home.html index 394eb28fff4..e6a628ff0b3 100644 --- a/zeppelin-web/src/app/home/home.html +++ b/zeppelin-web/src/app/home/home.html @@ -11,18 +11,20 @@ See the License for the specific language governing permissions and limitations under the License. --> - - +
@@ -141,16 +148,14 @@
{{noteName(note)}} -
-
  • -
  • -
    -
  • - - - {{noteName(note)}} -
  • +
    +
  • +
    +
  • +
  • diff --git a/zeppelin-web/src/components/navbar/navbar-noteList-elem.html b/zeppelin-web/src/components/navbar/navbar-noteList-elem.html index 6898217d1a4..18de94f9036 100644 --- a/zeppelin-web/src/components/navbar/navbar-noteList-elem.html +++ b/zeppelin-web/src/components/navbar/navbar-noteList-elem.html @@ -12,16 +12,17 @@ limitations under the License. --> - - + + {{noteName(note)}} +
  • - + {{noteName(note)}}
    diff --git a/zeppelin-web/src/components/navbar/navbar.controller.js b/zeppelin-web/src/components/navbar/navbar.controller.js index 8622971ee78..23baf5abb78 100644 --- a/zeppelin-web/src/components/navbar/navbar.controller.js +++ b/zeppelin-web/src/components/navbar/navbar.controller.js @@ -41,6 +41,7 @@ function NavCtrl($scope, $rootScope, $http, $routeParams, $location, vm.searchForm = searchService; vm.showLoginWindow = showLoginWindow; vm.TRASH_FOLDER_ID = TRASH_FOLDER_ID; + vm.isFilterNote = isFilterNote; $scope.query = {q: ''}; @@ -68,6 +69,18 @@ function NavCtrl($scope, $rootScope, $http, $routeParams, $location, loadNotes(); } + function isFilterNote(note) { + if (!$scope.query.q) { + return true; + } + + var noteName = note.name; + if (noteName.toLowerCase().indexOf($scope.query.q.toLowerCase()) > -1) { + return true; + } + return false; + } + function isActive(noteId) { return ($routeParams.noteId === noteId); } diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index cb95cc82d8a..a4770559693 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -32,10 +32,17 @@
  • -
  • +
  • +
    +
  • +
  • +
    +
  • Job
  • From 0981d34f92de4ee3ec7f59f53c6d4f70b747c9ca Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 10 Jan 2017 17:03:03 +0300 Subject: [PATCH 005/234] [MINOR] Small Selenium test fixes ### What is this PR for? Remove unused imports; Replace "Thread.sleep" (unstable solution); Add "deleteTestNotebook()" method -> testWidth. ### What type of PR is it? [Refactoring] ### Todos * [ ] - Task ### What is the Jira issue? * Open an issue on Jira https://issues.apache.org/jira/browse/ZEPPELIN/ * Put link here, and add [ZEPPELIN-*Jira number*] in PR title, eg. [ZEPPELIN-533] ### How should this be tested? Travis-CI ### Screenshots (if appropriate) https://travis-ci.org/apache/zeppelin/builds/190644901 -> https://travis-ci.org/apache/zeppelin/jobs/190644918 ### Questions: * Does the licenses files need update? (no) * Is there breaking changes for older versions? (no) * Does this needs documentation? (no) Author: Unknown Closes #1882 from bitchelov/automationTestFix and squashes the following commits: f898c59 [Unknown] [MINOR] Small Selenium test fixes (cherry picked from commit 434215668e9db51706230420f8ce2c1c268b2457) Signed-off-by: Jongyoul Lee --- .../java/org/apache/zeppelin/AbstractZeppelinIT.java | 11 +++-------- .../zeppelin/integration/ParagraphActionsIT.java | 8 +------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java index 645109c3de0..f8498685580 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java @@ -34,8 +34,6 @@ import java.io.File; import java.util.Date; -import java.util.LinkedList; -import java.util.List; import java.util.concurrent.TimeUnit; abstract public class AbstractZeppelinIT { @@ -111,17 +109,14 @@ protected void createNewNote() { WebDriverWait block = new WebDriverWait(driver, MAX_BROWSER_TIMEOUT_SEC); block.until(ExpectedConditions.visibilityOfElementLocated(By.id("noteNameModal"))); clickAndWait(By.id("createNoteButton")); - - try { - Thread.sleep(500); // wait for notebook list updated - } catch (InterruptedException e) { - } + block.until(ExpectedConditions.invisibilityOfElementLocated(By.className("pull-right"))); } protected void deleteTestNotebook(final WebDriver driver) { + WebDriverWait block = new WebDriverWait(driver, MAX_BROWSER_TIMEOUT_SEC); driver.findElement(By.xpath(".//*[@id='main']//button[@ng-click='moveNoteToTrash(note.id)']")) .sendKeys(Keys.ENTER); - ZeppelinITUtils.sleep(1000, true); + block.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(".//*[@id='main']//button[@ng-click='moveNoteToTrash(note.id)']"))); driver.findElement(By.xpath("//div[@class='modal-dialog'][contains(.,'This note will be moved to trash')]" + "//div[@class='modal-footer']//button[contains(.,'OK')]")).click(); ZeppelinITUtils.sleep(100, true); 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 d93a6e5e4dc..feade7faa3d 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 @@ -123,7 +123,6 @@ public void testCreateNewButton() throws Exception { driver.findElements(By.xpath("//div[@ng-controller=\"ParagraphCtrl\"]")).size(), CoreMatchers.equalTo(3)); - ZeppelinITUtils.sleep(1000, false); deleteTestNotebook(driver); } catch (Exception e) { @@ -162,7 +161,6 @@ public void testRemoveButton() throws Exception { collector.checkThat("After Remove : Number of paragraphs are", newNosOfParas, CoreMatchers.equalTo(oldNosOfParas - 1)); - ZeppelinITUtils.sleep(1000, false); deleteTestNotebook(driver); } catch (Exception e) { @@ -215,7 +213,6 @@ public void testMoveUpAndDown() throws Exception { collector.checkThat("The paragraph1 value contains", driver.findElement(By.xpath(getParagraphXPath(2) + "//div[contains(@class, 'editor')]")).getText(), CoreMatchers.equalTo("2")); - ZeppelinITUtils.sleep(1000, false); deleteTestNotebook(driver); } catch (Exception e) { @@ -251,7 +248,6 @@ public void testDisableParagraphRunButton() throws Exception { getParagraphStatus(1), CoreMatchers.equalTo("READY") ); - deleteTestNotebook(driver); } catch (Exception e) { @@ -285,7 +281,6 @@ public void testClearOutputButton() throws Exception { collector.checkThat("After Clear Output field contains ", driver.findElements(By.xpath(xpathToOutputField)).size(), CoreMatchers.equalTo(0)); - ZeppelinITUtils.sleep(1000, false); deleteTestNotebook(driver); } catch (Exception e) { @@ -315,6 +310,7 @@ public void testWidth() throws Exception { driver.findElement(By.xpath("//div[contains(@class,'col-md-" + newWidth + "')]")).isDisplayed(), CoreMatchers.equalTo(true)); } + deleteTestNotebook(driver); } catch (Exception e) { handleException("Exception in ParagraphActionsIT while testWidth ", e); } @@ -384,7 +380,6 @@ public void testTitleButton() throws Exception { collector.checkThat("After Page Refresh : The title field contains ", driver.findElement(By.xpath(xpathToTitle)).getText(), CoreMatchers.equalTo("NEW TITLE")); - ZeppelinITUtils.sleep(1000, false); deleteTestNotebook(driver); } catch (Exception e) { @@ -434,7 +429,6 @@ public void testShowAndHideLineNumbers() throws Exception { collector.checkThat("After \"Hide line number\" the Line Number is Enabled", driver.findElement(By.xpath(xpathToLineNumberField)).isDisplayed(), CoreMatchers.equalTo(false)); - ZeppelinITUtils.sleep(1000, false); deleteTestNotebook(driver); } catch (Exception e) { From ab87537bb2c99857428f712979dd3feb8d971809 Mon Sep 17 00:00:00 2001 From: Khalid Huseynov Date: Tue, 10 Jan 2017 20:48:26 -0800 Subject: [PATCH 006/234] [ZEPPELIN-1730, 1587] add spark impersonation through --proxy-user option ### What is this PR for? This is to add spark impersonation using --proxy-user option. note that it enables also to use spark impersonation without having logged user as system user with configured ssh. ### What type of PR is it? Improvement ### Todos * [x] - add `--proxy-user` * [x] - try on standalone spark 1.6.2 * [x] - try on yarn-client mode spark 2.0.1 ### What is the Jira issue? Directly solves [ZEPPELIN-1730](https://issues.apache.org/jira/browse/ZEPPELIN-1730) and also solves [ZEPPELIN-1587](https://issues.apache.org/jira/browse/ZEPPELIN-1587) according to discussion in #1566 since using `--proxy-user` in `spark-submit` is preferable method. ### How should this be tested? 1. switch your spark cluster to `per user` and `isolated` mode 2. set up `user impersonation` flag 3. run some job using that spark interpreter 4. spark context should be created with currently logged in user credentials on behalf of system user ### Screenshots (if appropriate) standalone ![spark_sc_impersonation](https://cloud.githubusercontent.com/assets/1642088/21639292/24240286-d224-11e6-8099-9bc74a06f0c2.gif) yarn-client screen shot 2017-01-04 at 10 00 13 am ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? yes Author: Khalid Huseynov Closes #1840 from khalidhuseynov/feat/spark-proxy-user and squashes the following commits: e4251de [Khalid Huseynov] update doc with env var dc61cae [Khalid Huseynov] check for env spark_proxy in interpreter.sh 8b66740 [Khalid Huseynov] add spark_proxy_user to env.sh 892b7e4 [Khalid Huseynov] add note in docs 4c3dba9 [Khalid Huseynov] add --proxy-user option for spark (cherry picked from commit 5e0aacf8a8f187702452d7cd2ee83b26c56dec90) Signed-off-by: Jongyoul Lee --- bin/interpreter.sh | 19 +++++++++++++------ conf/zeppelin-env.sh.template | 3 +++ docs/manual/userimpersonation.md | 12 +++++++----- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/bin/interpreter.sh b/bin/interpreter.sh index 300b18a358a..0132b42b76c 100755 --- a/bin/interpreter.sh +++ b/bin/interpreter.sh @@ -171,7 +171,7 @@ elif [[ "${INTERPRETER_ID}" == "pig" ]]; then if [[ -n "${HADOOP_CONF_DIR}" ]] && [[ -d "${HADOOP_CONF_DIR}" ]]; then ZEPPELIN_INTP_CLASSPATH+=":${HADOOP_CONF_DIR}" fi - + # autodetect TEZ_CONF_DIR if [[ -n "${TEZ_CONF_DIR}" ]]; then ZEPPELIN_INTP_CLASSPATH+=":${TEZ_CONF_DIR}" @@ -187,19 +187,26 @@ addJarInDirForIntp "${LOCAL_INTERPRETER_REPO}" CLASSPATH+=":${ZEPPELIN_INTP_CLASSPATH}" if [[ ! -z "$ZEPPELIN_IMPERSONATE_USER" ]]; then - INTERPRETER_RUN_COMMAND=${ZEPPELIN_IMPERSONATE_RUN_CMD}" '" - if [[ -f "${ZEPPELIN_CONF_DIR}/zeppelin-env.sh" ]]; then - INTERPRETER_RUN_COMMAND+=" source "${ZEPPELIN_CONF_DIR}'/zeppelin-env.sh;' + suid="$(id -u ${ZEPPELIN_IMPERSONATE_USER})" + if [[ -n "${suid}" || -z "${SPARK_SUBMIT}" ]]; then + INTERPRETER_RUN_COMMAND=${ZEPPELIN_IMPERSONATE_RUN_CMD}" '" + if [[ -f "${ZEPPELIN_CONF_DIR}/zeppelin-env.sh" ]]; then + INTERPRETER_RUN_COMMAND+=" source "${ZEPPELIN_CONF_DIR}'/zeppelin-env.sh;' + fi fi fi if [[ -n "${SPARK_SUBMIT}" ]]; then - INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} ${SPARK_APP_JAR} ${PORT}` + if [[ -n "$ZEPPELIN_IMPERSONATE_USER" ]] && [[ "$ZEPPELIN_IMPERSONATE_SPARK_PROXY_USER" != "false" ]]; then + INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} --proxy-user ${ZEPPELIN_IMPERSONATE_USER} ${SPARK_APP_JAR} ${PORT}` + else + INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} ${SPARK_APP_JAR} ${PORT}` + fi else INTERPRETER_RUN_COMMAND+=' '` echo ${ZEPPELIN_RUNNER} ${JAVA_INTP_OPTS} ${ZEPPELIN_INTP_MEM} -cp ${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${CLASSPATH} ${ZEPPELIN_SERVER} ${PORT} ` fi -if [[ ! -z "$ZEPPELIN_IMPERSONATE_USER" ]]; then +if [[ ! -z "$ZEPPELIN_IMPERSONATE_USER" ]] && [[ -n "${suid}" || -z "${SPARK_SUBMIT}" ]]; then INTERPRETER_RUN_COMMAND+="'" fi diff --git a/conf/zeppelin-env.sh.template b/conf/zeppelin-env.sh.template index 64db29d6e8f..7e777b66e4a 100644 --- a/conf/zeppelin-env.sh.template +++ b/conf/zeppelin-env.sh.template @@ -82,4 +82,7 @@ # export ZEPPELINHUB_API_ADDRESS # Refers to the address of the ZeppelinHub service in use # export ZEPPELINHUB_API_TOKEN # Refers to the Zeppelin instance token of the user # export ZEPPELINHUB_USER_KEY # Optional, when using Zeppelin with authentication. + +#### Zeppelin impersonation configuration # export ZEPPELIN_IMPERSONATE_CMD # Optional, when user want to run interpreter as end web user. eg) 'sudo -H -u ${ZEPPELIN_IMPERSONATE_USER} bash -c ' +# export ZEPPELIN_IMPERSONATE_SPARK_PROXY_USER #Optional, by default is true; can be set to false if you don't want to use --proxy-user option with Spark interpreter when impersonation enabled diff --git a/docs/manual/userimpersonation.md b/docs/manual/userimpersonation.md index f0f01b49c3d..6b592e7eb3d 100644 --- a/docs/manual/userimpersonation.md +++ b/docs/manual/userimpersonation.md @@ -43,10 +43,10 @@ cat ~/.ssh/id_rsa.pub | ssh user1@localhost 'cat >> .ssh/authorized_keys' ``` export ZEPPELIN_IMPERSONATE_CMD='sudo -H -u ${ZEPPELIN_IMPERSONATE_USER} bash -c ' ``` - + * Start zeppelin server. - +
    @@ -57,13 +57,13 @@ export ZEPPELIN_IMPERSONATE_CMD='sudo -H -u ${ZEPPELIN_IMPERSONATE_USER} bash -c - +

    - + * Go to interpreter setting page, and enable "User Impersonate" in any of the interpreter (in my example its shell interpreter) - + * Test with a simple paragraph ``` @@ -71,3 +71,5 @@ export ZEPPELIN_IMPERSONATE_CMD='sudo -H -u ${ZEPPELIN_IMPERSONATE_USER} bash -c whoami ``` + +Note that usage of "User Impersonate" option will enable Spark interpreter to use `--proxy-user` option with current user by default. If you want to disable `--proxy-user` option, then refer to `ZEPPELIN_IMPERSONATE_SPARK_PROXY_USER` variable in `conf/zeppelin-env.sh` From 7aa52d828fcfcd23f4a9c1c08279e0db8911fad0 Mon Sep 17 00:00:00 2001 From: AhyoungRyu Date: Thu, 12 Jan 2017 15:15:14 +0900 Subject: [PATCH 007/234] [MINOR] Rename Pig tutorial note to consider priority ### What is this PR for? After #1830 merged, Pig tutorial note placed as a first under `Zeppelin Tutorial` folder. I told to zjffdu, I thought the note name should be same with Spark ("Basic Feature (Spark)") in [this comment](https://github.com/apache/zeppelin/pull/1830#discussion_r95522394) (because they have same contents). But considering the number of Spark and Pig users, the Spark tutorial note needs to be placed as first I think. ### What type of PR is it? Rename Pig tutorial note ### What is the Jira issue? N/A ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: AhyoungRyu Closes #1889 from AhyoungRyu/rename/pigTutorialNote and squashes the following commits: f08fd69 [AhyoungRyu] Rename Pig tutorial note to consider priority (cherry picked from commit b9c667ae1dfcbc6d80d4fc65f64572acee96ede6) Signed-off-by: ahyoungryu --- notebook/2C57UKYWR/note.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/2C57UKYWR/note.json b/notebook/2C57UKYWR/note.json index 2b6ef8fcf37..8292592655f 100644 --- a/notebook/2C57UKYWR/note.json +++ b/notebook/2C57UKYWR/note.json @@ -286,7 +286,7 @@ "progressUpdateIntervalMs": 500 } ], - "name": "Zeppelin Tutorial/Basic Features (Pig)", + "name": "Zeppelin Tutorial/Using Pig for querying data", "id": "2C57UKYWR", "angularObjects": { "2C3DR183X:shared_process": [], From 94bbd41422ab98d8ee787163598e0aaa49fc2bc6 Mon Sep 17 00:00:00 2001 From: astroshim Date: Tue, 10 Jan 2017 22:02:00 -0800 Subject: [PATCH 008/234] [ZEPPELIN-1229] Clear browser cache with version hashtag. ### What is this PR for? This PR avoids browser cache. ### What type of PR is it? Bug Fix ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1229 ### How should this be tested? 1. Run old version of zeppelin(I was using 0.6.1) and show main page up in your browser. 2. Stop zeppelin. 3. Run this PR of Zeppelin and show page up on the same browser of 1. 4. check main page changed. - before (Job menu doesn't show) ![2017-01-06 01_50_29](https://cloud.githubusercontent.com/assets/3348133/21728478/43aec6d0-d3fb-11e6-84ce-9e1b18853611.gif) - after (Job menu show) ![2017-01-06 01_55_10](https://cloud.githubusercontent.com/assets/3348133/21728482/4a794e72-d3fb-11e6-8934-d6f92d65889b.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: astroshim Closes #1858 from astroshim/ZEPPELIN-1229 and squashes the following commits: 18defbe [astroshim] modify regx 85e0a05 [astroshim] remove arguments in code ca09eac [astroshim] change path 13d8056 [astroshim] trying another way 6b554e0 [astroshim] Merge branch 'master' into ZEPPELIN-1229 655dc3d [astroshim] tag all html c94de90 [astroshim] delete unnecessary codes 9d9f76f [astroshim] fix ZEPPELIN-1229 (cherry picked from commit 02323e283c6cce12bb06bf5327420c2d2da5cbe2) Signed-off-by: Mina Lee --- zeppelin-web/Gruntfile.js | 28 +++++++++++++++++++++++++++- zeppelin-web/package.json | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/zeppelin-web/Gruntfile.js b/zeppelin-web/Gruntfile.js index 42668474107..ab2c539bed6 100644 --- a/zeppelin-web/Gruntfile.js +++ b/zeppelin-web/Gruntfile.js @@ -31,12 +31,16 @@ module.exports = function(grunt) { // Time how long tasks take. Can help when optimizing build times require('time-grunt')(grunt); + require('grunt-replace')(grunt); + // Configurable paths for the application var appConfig = { app: require('./bower.json').appPath || 'src', dist: 'dist' }; + var buildtime = Date.now(); + // Define the configuration for all the tasks grunt.initConfig({ @@ -415,6 +419,27 @@ module.exports = function(grunt) { ], }, + replace: { + dist: { + options: { + patterns: [ + { + match: /(templateUrl:"[^\.\s]+\.html)/g, + replacement: '$1' + '?v=' + buildtime + }, + { + match: /(ng-include src="'[^\.\s]+\.html)/g, + replacement: '$1' + '?v=' + buildtime + } + ] + }, + files: [ + {src: ['dist/**/*.html'], dest: './'}, + {src: ['dist/*.js'], dest: './'} + ] + } + }, + // Test settings karma: { unit: { @@ -451,7 +476,8 @@ module.exports = function(grunt) { 'uglify', 'usemin', 'htmlmin', - 'cacheBust' + 'replace', + 'cacheBust', ]); grunt.registerTask('default', [ diff --git a/zeppelin-web/package.json b/zeppelin-web/package.json index ed1cb16ac59..49bc0467690 100644 --- a/zeppelin-web/package.json +++ b/zeppelin-web/package.json @@ -52,6 +52,7 @@ "grunt-newer": "^0.7.0", "grunt-ng-annotate": "^0.10.0", "grunt-postcss": "^0.7.1", + "grunt-replace": "^1.0.1", "grunt-svgmin": "^0.4.0", "grunt-usemin": "^2.1.1", "grunt-wiredep": "~2.0.0", From b0883a80d04918ce3fa79053469ce5cd96e9f63f Mon Sep 17 00:00:00 2001 From: Khalid Huseynov Date: Tue, 10 Jan 2017 23:22:35 -0800 Subject: [PATCH 009/234] [ZEPPELIN-1936] Fix user impersonation setting propagation ### What is this PR for? This is to fix bug of creating interpreter with impersonation enabled ### What type of PR is it? Bug Fix ### Todos * [x] - fix model ### What is the Jira issue? [ZEPPELIN-1936](https://issues.apache.org/jira/browse/ZEPPELIN-1936) ### How should this be tested? create new interpreter with "User Impersonation" enabled ### Screenshots (if appropriate) before: ![before_imp_setting](https://cloud.githubusercontent.com/assets/1642088/21839491/e187fa18-d78c-11e6-864d-755fc40f0096.gif) after: ![after_imp_setting](https://cloud.githubusercontent.com/assets/1642088/21839721/2756a7f0-d78e-11e6-9efd-4f3fd29df616.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Khalid Huseynov Closes #1884 from khalidhuseynov/fix/new-interp-impersonation-setting and squashes the following commits: d5f8393 [Khalid Huseynov] fix model on interpreter-create (cherry picked from commit 7290200322174fa643625034af2a8c61737dad06) Signed-off-by: Mina Lee --- .../app/interpreter/interpreter-create/interpreter-create.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html index 5d1ba67152e..b8e37abbeb6 100644 --- a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html +++ b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html @@ -201,7 +201,7 @@
    Option
    From 55addf1a3194f40ac48eb97dd98bf8b9c3d98b2e Mon Sep 17 00:00:00 2001 From: cloverhearts Date: Thu, 12 Jan 2017 18:53:10 -0800 Subject: [PATCH 010/234] [HOTFIX : ZEPPELIN-1932] paragraph blur error ### What is this PR for? When one or more hidden editors are present, clicking on the editor will cause a blur error. This means that when a paragraph is hidden through ng-if, Caused by calling the blur method in the absence of an editor object. ### What type of PR is it? Hot Fix ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1932 ### How should this be tested? 1. create paragraph and open debug console. 2. enable hide paragraph. 3. page refresh 4. click to anywhere paragraph. ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: cloverhearts Closes #1879 from cloverhearts/hotfix/paragraphOnfocusEvent and squashes the following commits: 7071638 [cloverhearts] fix show title and paragraph context issue 8f4d0bf [cloverhearts] fixed readonly event error 5ecfabb [cloverhearts] check editor object is undeifned. (cherry picked from commit 2b0e2a41cb7e0d7fc160d5c9193413ffe4d94f68) Signed-off-by: Mina Lee --- .../paragraph/paragraph.controller.js | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 6c612dd6cb2..5dbe1a00583 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -192,6 +192,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat }; $scope.$watch($scope.getEditor, function(newValue, oldValue) { + if (!$scope.editor) { + return; + } if (newValue === null || newValue === undefined) { console.log('editor isnt loaded yet, returning'); return; @@ -267,7 +270,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat }; $scope.copyPara = function(position) { - var editorValue = $scope.editor.getValue(); + var editorValue = $scope.getEditorValue(); if (editorValue) { $scope.copyParagraph(editorValue, position); } @@ -395,15 +398,19 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat }; $scope.showLineNumbers = function(paragraph) { - paragraph.config.lineNumbers = true; - $scope.editor.renderer.setShowGutter(true); - commitParagraph(paragraph); + if ($scope.editor) { + paragraph.config.lineNumbers = true; + $scope.editor.renderer.setShowGutter(true); + commitParagraph(paragraph); + } }; $scope.hideLineNumbers = function(paragraph) { - paragraph.config.lineNumbers = false; - $scope.editor.renderer.setShowGutter(false); - commitParagraph(paragraph); + if ($scope.editor) { + paragraph.config.lineNumbers = false; + $scope.editor.renderer.setShowGutter(false); + commitParagraph(paragraph); + } }; $scope.columnWidthClass = function(n) { @@ -761,7 +768,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat }; $scope.getEditorValue = function() { - return $scope.editor.getValue(); + return !$scope.editor ? $scope.paragraph.text : $scope.editor.getValue(); }; $scope.getProgress = function() { @@ -1035,7 +1042,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat $scope.paragraph.status = data.paragraph.status; $scope.paragraph.results = data.paragraph.results; $scope.paragraph.settings = data.paragraph.settings; - $scope.editor.setReadOnly($scope.isRunning(data.paragraph)); + if ($scope.editor) { + $scope.editor.setReadOnly($scope.isRunning(data.paragraph)); + } if (!$scope.asIframe) { $scope.paragraph.config = data.paragraph.config; @@ -1081,7 +1090,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.editor.getValue()); + $scope.run($scope.paragraph, $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 @@ -1133,6 +1142,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat }); $scope.$on('focusParagraph', function(event, paragraphId, cursorPos, mouseEvent) { + if (!$scope.editor) { + return; + } if ($scope.paragraph.id === paragraphId) { // focus editor if (!$scope.paragraph.config.editorHide) { @@ -1159,7 +1171,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat }); $scope.$on('saveInterpreterBindings', function(event, paragraphId) { - if ($scope.paragraph.id === paragraphId) { + if ($scope.paragraph.id === paragraphId && $scope.editor) { setInterpreterBindings = true; setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue()); } @@ -1177,8 +1189,10 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat ), 1000); deferred.promise.then(function(data) { - $scope.editor.focus(); - $scope.goToEnd($scope.editor); + if ($scope.editor) { + $scope.editor.focus(); + $scope.goToEnd($scope.editor); + } }); } }); From 88e2ac2798780a589771bda22029bbd876910189 Mon Sep 17 00:00:00 2001 From: 1ambda <1amb4a@gmail.com> Date: Wed, 11 Jan 2017 07:56:35 +0900 Subject: [PATCH 011/234] [ZEPPELIN-1917] Improve python.conda interpreter ### What is this PR for? Add missing commands to the `python.conda` interpreter - `conda info` - `conda list` - `conda create` - `conda install` - `conda uninstall (alias of remove)` - `conda env *` #### Implementation Detail The reason I modified `PythonProcess` is due to NPE ```java // https://github.com/apache/zeppelin/blob/master/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java#L107-L118 public String sendAndGetResult(String cmd) throws IOException { writer.println(cmd); writer.println(); writer.println("\"" + STATEMENT_END + "\""); StringBuilder output = new StringBuilder(); String line = null; // NPE when line is null while (!(line = reader.readLine()).contains(STATEMENT_END)) { logger.debug("Read line from python shell : " + line); output.append(line + "\n"); } return output.toString(); } ``` ``` java.lang.NullPointerException at org.apache.zeppelin.python.PythonProcess.sendAndGetResult(PythonProcess.java:113) at org.apache.zeppelin.python.PythonInterpreter.sendCommandToPython(PythonInterpreter.java:250) at org.apache.zeppelin.python.PythonInterpreter.bootStrapInterpreter(PythonInterpreter.java:272) at org.apache.zeppelin.python.PythonInterpreter.open(PythonInterpreter.java:100) at org.apache.zeppelin.python.PythonCondaInterpreter.restartPythonProcess(PythonCondaInterpreter.java:139) at org.apache.zeppelin.python.PythonCondaInterpreter.interpret(PythonCondaInterpreter.java:88) at org.apache.zeppelin.interpreter.LazyOpenInterpreter.interpret(LazyOpenInterpreter.java:94) at org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:494) at org.apache.zeppelin.scheduler.Job.run(Job.java:175) at org.apache.zeppelin.scheduler.FIFOScheduler$1.run(FIFOScheduler.java:139) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745) ``` ### What type of PR is it? [Improvement | Refactoring] ### Todos * [x] - info * [x] - list * [x] - create * [x] - install * [x] - uninstall (= remove) * [x] - env * ### What is the Jira issue? [ZEPPELIN-1917](https://issues.apache.org/jira/browse/ZEPPELIN-1917) ### How should this be tested? 1. Install [miniconda](http://conda.pydata.org/miniconda.html) 2. Make sure that your python interpreter can use `conda` (check the Interpreter Binding page) 3. Remove `test` conda env since we will create in the following section ```sh $ conda env remove --yes --name test ``` 4. Run these commands with `%python.conda` ``` %python.conda info %python.conda env list %python.conda create --name test # you should be able to see `test` in the list %python.conda env list %python.conda activate pymysql %python.conda install pymysql # you should be able to import %python import pymysql.cursors %python.conda uninstall pymysql %python.conda deactivate pymysql # you should be able to see `No module named pymysql.cursor` since we deactivated %python import pymysql.cursors ``` ### Screenshots (if appropriate) ![conda-screenshot](https://cloud.githubusercontent.com/assets/4968473/21747565/98c0e366-d5ad-11e6-8000-e293996089fa.gif) ### Questions: * Does the licenses files need update? - NO * Is there breaking changes for older versions? - NO * Does this needs documentation? - NO Author: 1ambda <1amb4a@gmail.com> Closes #1868 from 1ambda/ZEPPELIN-1917/improve-conda-interpreter and squashes the following commits: 3ba171a [1ambda] fix: Wrap output style 292ed6d [1ambda] refactor: Throw exception in runCommand 2d4aa7d [1ambda] test: Add some tests 49a4a11 [1ambda] feat: Supports other env commands 6eb7e92 [1ambda] fix: NPE in PythonProcess 9c5dd86 [1ambda] refactor: Activate, Deactivate f955889 [1ambda] fix: minor 935cb89 [1ambda] refactor: Abstract commands b1c4c9f [1ambda] feat: Add conda remove (uninstall) e539c42 [1ambda] feat: Add conda install 4f58fa2 [1ambda] feat: Add conda create 7da132d [1ambda] docs: Add missing conda list description 929ca8a [1ambda] feat: Make conda output beautiful 0c6ebb4 [1ambda] feat: Add list conda command 017c76f [1ambda] refactor: Import InterpreterResult.{Code, Type} to short codes b8a5154 [1ambda] refactor: Simplify exception flow so private funcs don't need care exceptions 64d4bef [1ambda] style: Rename some funcs afc456d [1ambda] refactor: Add private to member vars f36fc74 [1ambda] feat: Add info command 2eb9bf5 [1ambda] style: Remove useless newlines bd2564e [1ambda] refactor: PythonCondaInterpreter.interpret f0d69bc [1ambda] fix: Use specific command for env list in conda (cherry picked from commit 034fdc6735e075c89f727bb6bc6fddbc89b639c4) Signed-off-by: Lee moon soo --- .../python/PythonCondaInterpreter.java | 319 +++++++++++++----- .../apache/zeppelin/python/PythonProcess.java | 5 +- .../output_templates/conda_usage.html | 25 +- .../python/PythonCondaInterpreterTest.java | 63 ++-- 4 files changed, 307 insertions(+), 105 deletions(-) diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java b/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java index 304e1f06312..455d7866957 100644 --- a/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java +++ b/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java @@ -16,14 +16,16 @@ */ package org.apache.zeppelin.python; +import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.interpreter.*; +import org.apache.zeppelin.interpreter.InterpreterResult.Code; +import org.apache.zeppelin.interpreter.InterpreterResult.Type; import org.apache.zeppelin.scheduler.Scheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; -import java.util.HashMap; -import java.util.Properties; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -36,11 +38,17 @@ public class PythonCondaInterpreter extends Interpreter { public static final String CONDA_PYTHON_PATH = "/bin/python"; public static final String DEFAULT_ZEPPELIN_PYTHON = "python"; - Pattern condaEnvListPattern = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)"); - Pattern listPattern = Pattern.compile("env\\s*list\\s?"); - Pattern activatePattern = Pattern.compile("activate\\s*(.*)"); - Pattern deactivatePattern = Pattern.compile("deactivate"); - Pattern helpPattern = Pattern.compile("help"); + public static final Pattern PATTERN_OUTPUT_ENV_LIST = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)"); + public static final Pattern PATTERN_COMMAND_ENV_LIST = Pattern.compile("env\\s*list\\s?"); + public static final Pattern PATTERN_COMMAND_ENV = Pattern.compile("env\\s*(.*)"); + public static final Pattern PATTERN_COMMAND_LIST = Pattern.compile("list"); + public static final Pattern PATTERN_COMMAND_CREATE = Pattern.compile("create\\s*(.*)"); + public static final Pattern PATTERN_COMMAND_ACTIVATE = Pattern.compile("activate\\s*(.*)"); + public static final Pattern PATTERN_COMMAND_DEACTIVATE = Pattern.compile("deactivate"); + public static final Pattern PATTERN_COMMAND_INSTALL = Pattern.compile("install\\s*(.*)"); + public static final Pattern PATTERN_COMMAND_UNINSTALL = Pattern.compile("uninstall\\s*(.*)"); + public static final Pattern PATTERN_COMMAND_HELP = Pattern.compile("help"); + public static final Pattern PATTERN_COMMAND_INFO = Pattern.compile("info"); public PythonCondaInterpreter(Properties property) { super(property); @@ -59,33 +67,53 @@ public void close() { @Override public InterpreterResult interpret(String st, InterpreterContext context) { InterpreterOutput out = context.out; + Matcher activateMatcher = PATTERN_COMMAND_ACTIVATE.matcher(st); + Matcher createMatcher = PATTERN_COMMAND_CREATE.matcher(st); + Matcher installMatcher = PATTERN_COMMAND_INSTALL.matcher(st); + Matcher uninstallMatcher = PATTERN_COMMAND_UNINSTALL.matcher(st); + Matcher envMatcher = PATTERN_COMMAND_ENV.matcher(st); - Matcher listMatcher = listPattern.matcher(st); - Matcher activateMatcher = activatePattern.matcher(st); - Matcher deactivateMatcher = deactivatePattern.matcher(st); - Matcher helpMatcher = helpPattern.matcher(st); - - if (st == null || st.isEmpty() || listMatcher.matches()) { - listEnv(out, getCondaEnvs()); - return new InterpreterResult(InterpreterResult.Code.SUCCESS); - } else if (activateMatcher.matches()) { - String envName = activateMatcher.group(1); - changePythonEnvironment(envName); - restartPythonProcess(); - return new InterpreterResult(InterpreterResult.Code.SUCCESS, "\"" + envName + "\" activated"); - } else if (deactivateMatcher.matches()) { - changePythonEnvironment(null); - restartPythonProcess(); - return new InterpreterResult(InterpreterResult.Code.SUCCESS, "Deactivated"); - } else if (helpMatcher.matches()) { - printUsage(out); - return new InterpreterResult(InterpreterResult.Code.SUCCESS); - } else { - return new InterpreterResult(InterpreterResult.Code.ERROR, "Not supported command: " + st); + try { + if (PATTERN_COMMAND_ENV_LIST.matcher(st).matches()) { + String result = runCondaEnvList(); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else if (envMatcher.matches()) { + // `envMatcher` should be used after `listEnvMatcher` + String result = runCondaEnv(getRestArgsFromMatcher(envMatcher)); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else if (PATTERN_COMMAND_LIST.matcher(st).matches()) { + String result = runCondaList(); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else if (createMatcher.matches()) { + String result = runCondaCreate(getRestArgsFromMatcher(createMatcher)); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else if (activateMatcher.matches()) { + String envName = activateMatcher.group(1).trim(); + return runCondaActivate(envName); + } else if (PATTERN_COMMAND_DEACTIVATE.matcher(st).matches()) { + return runCondaDeactivate(); + } else if (installMatcher.matches()) { + String result = runCondaInstall(getRestArgsFromMatcher(installMatcher)); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else if (uninstallMatcher.matches()) { + String result = runCondaUninstall(getRestArgsFromMatcher(uninstallMatcher)); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else if (st == null || PATTERN_COMMAND_HELP.matcher(st).matches()) { + runCondaHelp(out); + return new InterpreterResult(Code.SUCCESS); + } else if (PATTERN_COMMAND_INFO.matcher(st).matches()) { + String result = runCondaInfo(); + return new InterpreterResult(Code.SUCCESS, Type.HTML, result); + } else { + return new InterpreterResult(Code.ERROR, "Not supported command: " + st); + } + } catch (RuntimeException | IOException | InterruptedException e) { + throw new InterpreterException(e); } } - private void changePythonEnvironment(String envName) { + private void changePythonEnvironment(String envName) + throws IOException, InterruptedException { PythonInterpreter python = getPythonInterpreter(); String binPath = null; if (envName == null) { @@ -94,7 +122,7 @@ private void changePythonEnvironment(String envName) { binPath = DEFAULT_ZEPPELIN_PYTHON; } } else { - HashMap envList = getCondaEnvs(); + Map envList = getCondaEnvs(); for (String name : envList.keySet()) { if (envName.equals(name)) { binPath = envList.get(name) + CONDA_PYTHON_PATH; @@ -114,7 +142,8 @@ private void restartPythonProcess() { protected PythonInterpreter getPythonInterpreter() { LazyOpenInterpreter lazy = null; PythonInterpreter python = null; - Interpreter p = getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName()); + Interpreter p = + getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName()); while (p instanceof WrappedInterpreter) { if (p instanceof LazyOpenInterpreter) { @@ -130,59 +159,75 @@ protected PythonInterpreter getPythonInterpreter() { return python; } - private HashMap getCondaEnvs() { - HashMap envList = null; + public static String runCondaCommandForTextOutput(String title, List commands) + throws IOException, InterruptedException { - StringBuilder sb = createStringBuilder(); - try { - int exit = runCommand(sb, "conda", "env", "list"); - if (exit == 0) { - envList = new HashMap(); - String[] lines = sb.toString().split("\n"); - for (String s : lines) { - if (s == null || s.isEmpty() || s.startsWith("#")) { - continue; - } - Matcher match = condaEnvListPattern.matcher(s); - - if (!match.matches()) { - continue; - } - envList.put(match.group(1), match.group(2)); - } - } - } catch (IOException | InterruptedException e) { - throw new InterpreterException(e); - } + String result = runCommand(commands); + return wrapCondaBasicOutputStyle(title, result); + } + + private String runCondaCommandForTableOutput(String title, List commands) + throws IOException, InterruptedException { + + StringBuilder sb = new StringBuilder(); + String result = runCommand(commands); + + // use table output for pretty output + Map envPerName = parseCondaCommonStdout(result); + return wrapCondaTableOutputStyle(title, envPerName); + } + + protected Map getCondaEnvs() + throws IOException, InterruptedException { + String result = runCommand("conda", "env", "list"); + Map envList = parseCondaCommonStdout(result); return envList; } - private void listEnv(InterpreterOutput out, HashMap envList) { - try { - out.setType(InterpreterResult.Type.HTML); - out.write("

    Conda environments

    \n"); - // start table - out.write("
    \n"); + private String runCondaEnvList() throws IOException, InterruptedException { + return wrapCondaTableOutputStyle("Environment List", getCondaEnvs()); + } - for (String name : envList.keySet()) { - String path = envList.get(name); + private String runCondaEnv(List restArgs) + throws IOException, InterruptedException { - out.write(String.format("
    " + - "
    %s
    " + - "
    %s
    " + - "
    \n", - name, path)); - } - // end table - out.write("

    \n"); - out.write("%python.conda help for the usage\n"); - } catch (IOException e) { - throw new InterpreterException(e); + restArgs.add(0, "conda"); + restArgs.add(1, "env"); + restArgs.add(3, "--yes"); // --yes should be inserted after command + + return runCondaCommandForTextOutput(null, restArgs); + } + + private InterpreterResult runCondaActivate(String envName) + throws IOException, InterruptedException { + + if (null == envName || envName.isEmpty()) { + return new InterpreterResult(Code.ERROR, "Env name should be specified"); } + + changePythonEnvironment(envName); + restartPythonProcess(); + + return new InterpreterResult(Code.SUCCESS, "'" + envName + "' is activated"); } + private InterpreterResult runCondaDeactivate() + throws IOException, InterruptedException { - private void printUsage(InterpreterOutput out) { + changePythonEnvironment(null); + restartPythonProcess(); + return new InterpreterResult(Code.SUCCESS, "Deactivated"); + } + + private String runCondaList() throws IOException, InterruptedException { + List commands = new ArrayList(); + commands.add("conda"); + commands.add("list"); + + return runCondaCommandForTableOutput("Installed Package List", commands); + } + + private void runCondaHelp(InterpreterOutput out) { try { out.setType(InterpreterResult.Type.HTML); out.writeResource("output_templates/conda_usage.html"); @@ -191,6 +236,98 @@ private void printUsage(InterpreterOutput out) { } } + private String runCondaInfo() throws IOException, InterruptedException { + List commands = new ArrayList(); + commands.add("conda"); + commands.add("info"); + + return runCondaCommandForTextOutput("Conda Information", commands); + } + + private String runCondaCreate(List restArgs) + throws IOException, InterruptedException { + restArgs.add(0, "conda"); + restArgs.add(1, "create"); + restArgs.add(2, "--yes"); + + return runCondaCommandForTextOutput("Environment Creation", restArgs); + } + + private String runCondaInstall(List restArgs) + throws IOException, InterruptedException { + + restArgs.add(0, "conda"); + restArgs.add(1, "install"); + restArgs.add(2, "--yes"); + + return runCondaCommandForTextOutput("Package Installation", restArgs); + } + + private String runCondaUninstall(List restArgs) + throws IOException, InterruptedException { + + restArgs.add(0, "conda"); + restArgs.add(1, "uninstall"); + restArgs.add(2, "--yes"); + + return runCondaCommandForTextOutput("Package Uninstallation", restArgs); + } + + public static String wrapCondaBasicOutputStyle(String title, String content) { + StringBuilder sb = new StringBuilder(); + if (null != title && !title.isEmpty()) { + sb.append("

    ").append(title).append("

    \n") + .append("

    \n"); + } + sb.append("
    \n") + .append(content) + .append("
    "); + + return sb.toString(); + } + + public static String wrapCondaTableOutputStyle(String title, Map kv) { + StringBuilder sb = new StringBuilder(); + + if (null != title && !title.isEmpty()) { + sb.append("

    ").append(title).append("

    \n"); + } + + sb.append("
    \n"); + for (String name : kv.keySet()) { + String path = kv.get(name); + + sb.append(String.format("
    " + + "
    %s
    " + + "
    %s
    " + + "
    \n", + name, path)); + } + sb.append("
    \n"); + + return sb.toString(); + } + + public static Map parseCondaCommonStdout(String out) + throws IOException, InterruptedException { + + Map kv = new LinkedHashMap(); + String[] lines = out.split("\n"); + for (String s : lines) { + if (s == null || s.isEmpty() || s.startsWith("#")) { + continue; + } + Matcher match = PATTERN_OUTPUT_ENV_LIST.matcher(s); + + if (!match.matches()) { + continue; + } + kv.put(match.group(1), match.group(2)); + } + + return kv; + } + @Override public void cancel(InterpreterContext context) { @@ -206,7 +343,6 @@ public int getProgress(InterpreterContext context) { return 0; } - /** * Use python interpreter's scheduler. * To make sure %python.conda paragraph and %python paragraph runs sequentially @@ -221,9 +357,12 @@ public Scheduler getScheduler() { } } - protected int runCommand(StringBuilder sb, String ... command) + public static String runCommand(List commands) throws IOException, InterruptedException { - ProcessBuilder builder = new ProcessBuilder(command); + + StringBuilder sb = new StringBuilder(); + + ProcessBuilder builder = new ProcessBuilder(commands); builder.redirectErrorStream(true); Process process = builder.start(); InputStream stdout = process.getInputStream(); @@ -234,10 +373,28 @@ protected int runCommand(StringBuilder sb, String ... command) sb.append("\n"); } int r = process.waitFor(); // Let the process finish. - return r; + + if (r != 0) { + throw new RuntimeException("Failed to execute `" + + StringUtils.join(commands, " ") + "` exited with " + r); + } + + return sb.toString(); + } + + public static String runCommand(String ... command) + throws IOException, InterruptedException { + + List list = new ArrayList<>(command.length); + for (String arg : command) { + list.add(arg); + } + + return runCommand(list); } - protected StringBuilder createStringBuilder() { - return new StringBuilder(); + public static List getRestArgsFromMatcher(Matcher m) { + // Arrays.asList just returns fixed-size, so we should use ctor instead of + return new ArrayList<>(Arrays.asList(m.group(1).split(" "))); } } diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java index ef9bd2dc2bd..578ffeb8f99 100644 --- a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java +++ b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java @@ -110,10 +110,13 @@ public String sendAndGetResult(String cmd) throws IOException { writer.println("\"" + STATEMENT_END + "\""); StringBuilder output = new StringBuilder(); String line = null; - while (!(line = reader.readLine()).contains(STATEMENT_END)) { + + while ((line = reader.readLine()) != null && + !line.contains(STATEMENT_END)) { logger.debug("Read line from python shell : " + line); output.append(line + "\n"); } + return output.toString(); } diff --git a/python/src/main/resources/output_templates/conda_usage.html b/python/src/main/resources/output_templates/conda_usage.html index 79191b8806a..e1146fcf396 100644 --- a/python/src/main/resources/output_templates/conda_usage.html +++ b/python/src/main/resources/output_templates/conda_usage.html @@ -12,6 +12,18 @@ limitations under the License. -->

    Usage

    +
    + Get the Conda Infomation +
    %python.conda info
    +
    +
    + List the Conda environments +
    %python.conda env list
    +
    +
    + Create a conda enviornment +
    %python.conda create --name [ENV NAME]
    +
    Activate an environment (python interpreter will be restarted)
    %python.conda activate [ENV NAME]
    @@ -21,7 +33,14 @@

    Usage

    %python.conda deactivate
    - List the Conda environments -
    %python.conda
    + Get installed package list inside the current environment +
    %python.conda list
    +
    +
    + Install Package +
    %python.conda install [PACKAGE NAME]
    +
    +
    + Uninstall Package +
    %python.conda uninstall [PACKAGE NAME]
    - diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java b/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java index b654d2e44ac..c6d2a84886b 100644 --- a/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java +++ b/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java @@ -23,13 +23,11 @@ import org.junit.Test; import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Properties; +import java.util.*; +import java.util.regex.Matcher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; public class PythonCondaInterpreterTest { @@ -49,35 +47,32 @@ public void setUp() { doReturn(python).when(conda).getPythonInterpreter(); } - private void setCondaEnvs() throws IOException, InterruptedException { - StringBuilder sb = new StringBuilder(); - sb.append("#comment\n\nenv1 * /path1\nenv2\t/path2\n"); - - doReturn(sb).when(conda).createStringBuilder(); - doReturn(0).when(conda) - .runCommand(any(StringBuilder.class), anyString(), anyString(), anyString()); + private void setMockCondaEnvList() throws IOException, InterruptedException { + Map envList = new LinkedHashMap(); + envList.put("env1", "/path1"); + envList.put("env2", "/path2"); + doReturn(envList).when(conda).getCondaEnvs(); } @Test public void testListEnv() throws IOException, InterruptedException { - setCondaEnvs(); + setMockCondaEnvList(); // list available env InterpreterContext context = getInterpreterContext(); - InterpreterResult result = conda.interpret("", context); + InterpreterResult result = conda.interpret("env list", context); assertEquals(InterpreterResult.Code.SUCCESS, result.code()); - context.out.flush(); - String out = new String(context.out.toByteArray()); - assertTrue(out.contains(">env1<")); - assertTrue(out.contains(">/path1<")); - assertTrue(out.contains(">env2<")); - assertTrue(out.contains(">/path2<")); + assertTrue(result.toString().contains(">env1<")); + assertTrue(result.toString().contains("/path1<")); + assertTrue(result.toString().contains(">env2<")); + assertTrue(result.toString().contains("/path2<")); } @Test public void testActivateEnv() throws IOException, InterruptedException { - setCondaEnvs(); + setMockCondaEnvList(); + InterpreterContext context = getInterpreterContext(); conda.interpret("activate env1", context); verify(python, times(1)).open(); @@ -94,6 +89,34 @@ public void testDeactivate() { verify(python).setPythonCommand("python"); } + @Test + public void testParseCondaCommonStdout() + throws IOException, InterruptedException { + + StringBuilder sb = new StringBuilder() + .append("# comment1\n") + .append("# comment2\n") + .append("env1 /location1\n") + .append("env2 /location2\n"); + + Map locationPerEnv = + PythonCondaInterpreter.parseCondaCommonStdout(sb.toString()); + + assertEquals("/location1", locationPerEnv.get("env1")); + assertEquals("/location2", locationPerEnv.get("env2")); + } + + @Test + public void testGetRestArgsFromMatcher() { + Matcher m = + PythonCondaInterpreter.PATTERN_COMMAND_ENV.matcher("env remove --name test --yes"); + m.matches(); + + List restArgs = PythonCondaInterpreter.getRestArgsFromMatcher(m); + List expected = Arrays.asList(new String[]{"remove", "--name", "test", "--yes"}); + assertEquals(expected, restArgs); + } + private InterpreterContext getInterpreterContext() { return new InterpreterContext( "noteId", From 8aea6a52eecc7cd5c75e0ae90d8b907cba2d537f Mon Sep 17 00:00:00 2001 From: robbins Date: Thu, 12 Jan 2017 15:15:29 +0000 Subject: [PATCH 012/234] [ZEPPELIN-1560] avoid generating minus sign in package name ### What is this PR for? using Object.hashCode() as part of the REPL wrapper class name can cause a compilation error as hashCode can validly return a negative integer. We would like this fix backported to 0.6 and later streams. ### What type of PR is it? Bug Fix ### Todos * [ ] - Task ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1560 ### How should this be tested? regression tests with openJdk + tests with IBM jvm ### Screenshots (if appropriate) N/A ### Questions: * Does the licenses files need update? NO * Is there breaking changes for older versions? NO * Does this needs documentation? NO Author: robbins Closes #1894 from robbinspg/ZPPELIN-1560 and squashes the following commits: eeef3ad [robbins] [ZEPPELIN-1560] avoid generating minus sign in package name (cherry picked from commit 068c8946211823ac98fc7dbf8061da6e9a2712c3) Signed-off-by: Lee moon soo --- .../java/org/apache/zeppelin/spark/SparkInterpreter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java index 301dd230ba1..16bc4bad458 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java @@ -745,8 +745,12 @@ public void open() { * * In Spark 2.x, REPL generated wrapper class name should compatible with the pattern * ^(\$line(?:\d+)\.\$read)(?:\$\$iw)+$ + * + * As hashCode() can return a negative integer value and the minus character '-' is invalid + * in a package name we change it to a numeric value '0' which still conforms to the regexp. + * */ - System.setProperty("scala.repl.name.line", "$line" + this.hashCode()); + System.setProperty("scala.repl.name.line", ("$line" + this.hashCode()).replace('-', '0')); // To prevent 'File name too long' error on some file system. MutableSettings.IntSetting numClassFileSetting = settings.maxClassfileName(); From 893c23dd9d48497f3aaeab2fa28917850203369d Mon Sep 17 00:00:00 2001 From: cloverhearts Date: Fri, 13 Jan 2017 16:02:55 -0800 Subject: [PATCH 013/234] [HOTFIX] Does not working settings menu in zeppelin web graph ui ### What is this PR for? Does not working settings menu in zeppelin web graph ui ### What type of PR is it? Bug Fix ### How should this be tested? 1. toggle settings menu in zeppelin graph ui ### Screenshots (if appropriate) #### Before ![incorrect](https://cloud.githubusercontent.com/assets/10525473/21949815/1733ed6a-d9aa-11e6-92c8-8da98200cf43.gif) #### After ![correct](https://cloud.githubusercontent.com/assets/10525473/21949805/045c9a34-d9aa-11e6-86dc-445ce229f2a8.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: cloverhearts Closes #1899 from cloverhearts/ZEEPELIN-SETTING-PROBLEM-WEB-UI and squashes the following commits: e3453b7 [cloverhearts] invalid render life cycle for setting (cherry picked from commit 59e15c03d6a405e07dec59fb6e7f0ad87150794f) Signed-off-by: Lee moon soo --- zeppelin-web/src/app/notebook/paragraph/result/result.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.html b/zeppelin-web/src/app/notebook/paragraph/result/result.html index 97d453adbca..df09c4d5f59 100644 --- a/zeppelin-web/src/app/notebook/paragraph/result/result.html +++ b/zeppelin-web/src/app/notebook/paragraph/result/result.html @@ -24,7 +24,7 @@
    Date: Wed, 11 Jan 2017 14:38:01 +0900 Subject: [PATCH 014/234] [ZEPPELIN-1883] Can't import spark submitted packages in PySpark ### What is this PR for? Fixed importing packages in pyspack requested by `SPARK_SUBMIT_OPTION` ### What type of PR is it? [Bug Fix] ### Todos Nothing ### What is the Jira issue? [ZEPPELIN-1883](https://issues.apache.org/jira/browse/ZEPPELIN-1883) ### How should this be tested? 0. Download Apache Spark 1.6.2 (since it's the most recent for pyspark-cassandra) 1. Set `SPARK_HOME` and `SPARK_SUBMIT_OPTION` in `conf/zeppelin-env.sh` like ```sh export SPARK_HOME="~/github/apache-spark/1.6.2-bin-hadoop2.6" export SPARK_SUBMIT_OPTIONS="--packages com.datastax.spark:spark-cassandra-connector_2.10:1.6.2,TargetHolding:pyspark-cassandra:0.3.5 --exclude-packages org.slf4j:slf4j-api" ``` 2. Check before that you can run `spark-submit` or not ``` ./bin/spark-submit --packages com.datastax.spark:spark-cassandra-connector_2.10:1.6.2,TargetHolding:pyspark-cassandra:0.3.5 --exclude-packages org.slf4j:slf4j-api --class org.apache.spark.examples.SparkPi lib/spark-examples-1.6.2-hadoop2.6.0.jar ``` 3. Test whether submitted packages can be import or not ``` %pyspark import pyspark_cassandra ``` ### Screenshots (if appropriate) ``` import pyspark_cassandra Traceback (most recent call last): File "/var/folders/lr/8g9y625n5j39rz6qhkg8s6640000gn/T/zeppelin_pyspark-5266742863961917074.py", line 267, in raise Exception(traceback.format_exc()) Exception: Traceback (most recent call last): File "/var/folders/lr/8g9y625n5j39rz6qhkg8s6640000gn/T/zeppelin_pyspark-5266742863961917074.py", line 265, in exec(code) File "", line 1, in ImportError: No module named pyspark_cassandra ``` ### Questions: * Does the licenses files need update? - NO * Is there breaking changes for older versions? - NO * Does this needs documentation? - NO Author: 1ambda <1amb4a@gmail.com> Closes #1831 from 1ambda/ZEPPELIN-1883/cant-import-submitted-packages-in-pyspark and squashes the following commits: 585d48a [1ambda] Use spark.jars instead of classpath f76d2c8 [1ambda] fix: Do not extend PYTHONPATH in yarn-client c735bd5 [1ambda] fix: Import spark submit packages in pyspark (cherry picked from commit cb8e4187029fdd7b892d84f23efd51acaed65f78) Signed-off-by: Lee moon soo --- .../zeppelin/spark/PySparkInterpreter.java | 17 +++++++++++++++-- .../apache/zeppelin/spark/SparkInterpreter.java | 7 +++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java index 58f17e943aa..5a8e0407158 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java @@ -153,7 +153,6 @@ public void open() { } urls = urlList.toArray(urls); - ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); try { URLClassLoader newCl = new URLClassLoader(urls, oldCl); @@ -169,11 +168,25 @@ public void open() { private Map setupPySparkEnv() throws IOException{ Map env = EnvironmentUtils.getProcEnvironment(); + if (!env.containsKey("PYTHONPATH")) { SparkConf conf = getSparkConf(); - env.put("PYTHONPATH", conf.get("spark.submit.pyFiles").replaceAll(",", ":") + + env.put("PYTHONPATH", conf.get("spark.submit.pyFiles").replaceAll(",", ":") + ":../interpreter/lib/python"); } + + // get additional class paths when using SPARK_SUBMIT and not using YARN-CLIENT + // also, add all packages to PYTHONPATH since there might be transitive dependencies + if (SparkInterpreter.useSparkSubmit() && + !getSparkInterpreter().isYarnMode()) { + + String sparkSubmitJars = getSparkConf().get("spark.jars").replace(",", ":"); + + if (!"".equals(sparkSubmitJars)) { + env.put("PYTHONPATH", env.get("PYTHONPATH") + sparkSubmitJars); + } + } + return env; } diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java index 16bc4bad458..788230336cd 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java @@ -295,7 +295,7 @@ private DepInterpreter getDepInterpreter() { return (DepInterpreter) p; } - private boolean isYarnMode() { + public boolean isYarnMode() { return getProperty("master").startsWith("yarn"); } @@ -555,7 +555,7 @@ static final String toString(Object o) { return (o instanceof String) ? (String) o : ""; } - private boolean useSparkSubmit() { + public static boolean useSparkSubmit() { return null != System.getenv("SPARK_SUBMIT"); } @@ -726,7 +726,6 @@ public void open() { pathSettings.v_$eq(classpath); settings.scala$tools$nsc$settings$ScalaSettings$_setter_$classpath_$eq(pathSettings); - // set classloader for scala compiler settings.explicitParentLoader_$eq(new Some<>(Thread.currentThread() .getContextClassLoader())); @@ -979,7 +978,7 @@ public void populateSparkWebUrl(InterpreterContext ctx) { } } - private List currentClassPath() { + public List currentClassPath() { List paths = classPath(Thread.currentThread().getContextClassLoader()); String[] cps = System.getProperty("java.class.path").split(File.pathSeparator); if (cps != null) { From 0f98dca046d6c21756d9f30672c867073535df7d Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 12 Dec 2016 17:14:12 +0300 Subject: [PATCH 015/234] [MINOR] Remove unused import. Naming convention. ### What is this PR for? Change method name (Method names should comply with a naming convention). Remove unused imports. Using append makes code simpler to read `sb.append("\nAccessTime = ").append(accessTime);` instead `sb.append("\nAccessTime = " + accessTime);` ### What type of PR is it? [Refactoring] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1839 ### How should this be tested? HDFSFileInterpreterTest.java (still working) ### Questions: * Does the licenses files need update? (no) * Is there breaking changes for older versions? (no) * Does this needs documentation? (no) Remove this unused import, naming convention Author: Unknown Closes #1747 from bitchelov/hdfsFileInterpreterSmallChanges and squashes the following commits: 6d27bb8 [Unknown] Minor changes (cherry picked from commit 69bc353d3c047ad8ad29584ce9e3b6c84d7ace7e) Signed-off-by: Jongyoul Lee --- .../zeppelin/file/HDFSFileInterpreter.java | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java b/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java index c2caa11d5ae..1b2b01c3cf4 100644 --- a/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java +++ b/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java @@ -23,9 +23,7 @@ import com.google.gson.Gson; import org.apache.commons.lang.StringUtils; -import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterException; -import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; /** @@ -75,19 +73,19 @@ public class OneFileStatus { public String type; public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("\nAccessTime = " + accessTime); - sb.append("\nBlockSize = " + blockSize); - sb.append("\nChildrenNum = " + childrenNum); - sb.append("\nFileId = " + fileId); - sb.append("\nGroup = " + group); - sb.append("\nLength = " + length); - sb.append("\nModificationTime = " + modificationTime); - sb.append("\nOwner = " + owner); - sb.append("\nPathSuffix = " + pathSuffix); - sb.append("\nPermission = " + permission); - sb.append("\nReplication = " + replication); - sb.append("\nStoragePolicy = " + storagePolicy); - sb.append("\nType = " + type); + sb.append("\nAccessTime = ").append(accessTime); + sb.append("\nBlockSize = ").append(blockSize); + sb.append("\nChildrenNum = ").append(childrenNum); + sb.append("\nFileId = ").append(fileId); + sb.append("\nGroup = ").append(group); + sb.append("\nLength = ").append(length); + sb.append("\nModificationTime = ").append(modificationTime); + sb.append("\nOwner = ").append(owner); + sb.append("\nPathSuffix = ").append(pathSuffix); + sb.append("\nPermission = ").append(permission); + sb.append("\nReplication = ").append(replication); + sb.append("\nStoragePolicy = ").append(storagePolicy); + sb.append("\nType = ").append(type); return sb.toString(); } } @@ -162,7 +160,7 @@ private String listPermission(OneFileStatus fs){ private String listDate(OneFileStatus fs) { return new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date(fs.modificationTime)); } - private String ListOne(String path, OneFileStatus fs) { + private String listOne(String path, OneFileStatus fs) { if (args.flags.contains(new Character('l'))) { StringBuilder sb = new StringBuilder(); sb.append(listPermission(fs) + "\t"); @@ -194,7 +192,7 @@ public String listFile(String filePath) { String str = cmd.runCommand(cmd.getFileStatus, filePath, null); SingleFileStatus sfs = gson.fromJson(str, SingleFileStatus.class); if (sfs != null) { - return ListOne(filePath, sfs.FileStatus); + return listOne(filePath, sfs.FileStatus); } } catch (Exception e) { logger.error("listFile: " + filePath, e); @@ -218,7 +216,7 @@ public String listAll(String path) { allFiles.FileStatuses.FileStatus != null) { for (OneFileStatus fs : allFiles.FileStatuses.FileStatus) - all = all + ListOne(path, fs) + '\n'; + all = all + listOne(path, fs) + '\n'; } } return all; From 054e0846dc558a48653c71d71438fe952775a82b Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Fri, 13 Jan 2017 11:30:13 +0800 Subject: [PATCH 016/234] ZEPPELIN-1293. Livy Interpreter: Automatically attach or create to a new session ### What is this PR for? By default, livy session will expire after one hour. This PR would create session automatically for user if session is expired, and would also display the session expire information in frontend. The expire message would only display at the first time of session recreation, after that the message won't be displayed. ### What type of PR is it? [Improvement ] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1293 ### How should this be tested? Tested manually. ![image](https://cloud.githubusercontent.com/assets/164491/21761175/2473c0c0-d68c-11e6-8f39-9e87333c6168.png) ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1861 from zjffdu/ZEPPELIN-1293 and squashes the following commits: e174593 [Jeff Zhang] minor update on warning message 30c3569 [Jeff Zhang] address comments 88f0d9a [Jeff Zhang] ZEPPELIN-1293. Livy Interpreter: Automatically attach or create to a new session --- .../zeppelin/livy/BaseLivyInterprereter.java | 74 +++++++++++++++---- .../livy/LivySparkSQLInterpreter.java | 65 ++++++++-------- 2 files changed, 97 insertions(+), 42 deletions(-) diff --git a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java index 0c8c8e2057d..8ed4622b0b4 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java @@ -21,10 +21,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; import org.apache.commons.lang3.StringUtils; -import org.apache.zeppelin.interpreter.Interpreter; -import org.apache.zeppelin.interpreter.InterpreterContext; -import org.apache.zeppelin.interpreter.InterpreterResult; -import org.apache.zeppelin.interpreter.InterpreterUtils; +import org.apache.zeppelin.interpreter.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; @@ -39,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; /** * Base class for livy interpreters. @@ -48,10 +46,11 @@ public abstract class BaseLivyInterprereter extends Interpreter { protected static final Logger LOGGER = LoggerFactory.getLogger(BaseLivyInterprereter.class); private static Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); - protected SessionInfo sessionInfo; + protected volatile SessionInfo sessionInfo; private String livyURL; private long sessionCreationTimeout; protected boolean displayAppInfo; + private AtomicBoolean sessionExpired = new AtomicBoolean(false); public BaseLivyInterprereter(Properties property) { super(property); @@ -90,16 +89,17 @@ protected void initLivySession() throws LivyException { // livy 0.2 don't return appId and sparkUiUrl in response so that we need to get it // explicitly by ourselves. sessionInfo.appId = extractStatementResult( - interpret("sc.applicationId", false).message() + interpret("sc.applicationId", false, false).message() .get(0).getData()); } interpret( - "val webui=sc.getClass.getMethod(\"ui\").invoke(sc).asInstanceOf[Some[_]].get", false); + "val webui=sc.getClass.getMethod(\"ui\").invoke(sc).asInstanceOf[Some[_]].get", + false, false); if (StringUtils.isEmpty(sessionInfo.appInfo.get("sparkUiUrl"))) { sessionInfo.webUIAddress = extractStatementResult( interpret( - "webui.getClass.getMethod(\"appUIAddress\").invoke(webui)", false) + "webui.getClass.getMethod(\"appUIAddress\").invoke(webui)", false, false) .message().get(0).getData()); } else { sessionInfo.webUIAddress = sessionInfo.appInfo.get("sparkUiUrl"); @@ -120,7 +120,7 @@ public InterpreterResult interpret(String st, InterpreterContext context) { } try { - return interpret(st, this.displayAppInfo); + return interpret(st, this.displayAppInfo, true); } catch (LivyException e) { LOGGER.error("Fail to interpret:" + st, e); return new InterpreterResult(InterpreterResult.Code.ERROR, @@ -206,9 +206,26 @@ private SessionInfo getSessionInfo(int sessionId) throws LivyException { return SessionInfo.fromJson(callRestAPI("/sessions/" + sessionId, "GET")); } - public InterpreterResult interpret(String code, boolean displayAppInfo) + public InterpreterResult interpret(String code, boolean displayAppInfo, + boolean appendSessionExpired) throws LivyException { - StatementInfo stmtInfo = executeStatement(new ExecuteRequest(code)); + StatementInfo stmtInfo = null; + boolean sessionExpired = false; + try { + stmtInfo = executeStatement(new ExecuteRequest(code)); + } catch (SessionNotFoundException e) { + LOGGER.warn("Livy session {} is expired, new session will be created.", sessionInfo.id); + sessionExpired = true; + // we don't want to create multiple sessions because it is possible to have multiple thread + // to call this method, like LivySparkSQLInterpreter which use ParallelScheduler. So we need + // to check session status again in this sync block + synchronized (this) { + if (isSessionExpired()) { + initLivySession(); + } + } + stmtInfo = executeStatement(new ExecuteRequest(code)); + } // pull the statement status while (!stmtInfo.isAvailable()) { try { @@ -219,7 +236,38 @@ public InterpreterResult interpret(String code, boolean displayAppInfo) } stmtInfo = getStatementInfo(stmtInfo.id); } - return getResultFromStatementInfo(stmtInfo, displayAppInfo); + if (appendSessionExpired) { + return appendSessionExpire(getResultFromStatementInfo(stmtInfo, displayAppInfo), + sessionExpired); + } else { + return getResultFromStatementInfo(stmtInfo, displayAppInfo); + } + } + + private boolean isSessionExpired() throws LivyException { + try { + getSessionInfo(sessionInfo.id); + return false; + } catch (SessionNotFoundException e) { + return true; + } catch (LivyException e) { + throw e; + } + } + + private InterpreterResult appendSessionExpire(InterpreterResult result, boolean sessionExpired) { + if (sessionExpired) { + InterpreterResult result2 = new InterpreterResult(result.code()); + result2.add(InterpreterResult.Type.HTML, + "Previous livy session is expired, new livy session is created. " + + "Paragraphs that depend on this paragraph need to be re-executed!" + ""); + for (InterpreterResultMessage message : result.message()) { + result2.add(message.getType(), message.getData()); + } + return result2; + } else { + return result; + } } private InterpreterResult getResultFromStatementInfo(StatementInfo stmtInfo, @@ -340,7 +388,7 @@ private String callRestAPI(String targetURL, String method, String jsonData) || response.getStatusCode().value() == 201 || response.getStatusCode().value() == 404) { String responseBody = response.getBody(); - if (responseBody.matches("Session '\\d+' not found.")) { + if (responseBody.matches("\"Session '\\d+' not found.\"")) { throw new SessionNotFoundException(responseBody); } else { return responseBody; diff --git a/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java b/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java index cdc8b5b5560..0e78860ceab 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java @@ -51,7 +51,7 @@ public void open() { // As we don't know whether livyserver use spark2 or spark1, so we will detect SparkSession // to judge whether it is using spark2. try { - InterpreterResult result = sparkInterpreter.interpret("spark", false); + InterpreterResult result = sparkInterpreter.interpret("spark", false, false); if (result.code() == InterpreterResult.Code.SUCCESS && result.message().get(0).getData().contains("org.apache.spark.sql.SparkSession")) { LOGGER.info("SparkSession is detected so we are using spark 2.x for session {}", @@ -59,7 +59,7 @@ public void open() { isSpark2 = true; } else { // spark 1.x - result = sparkInterpreter.interpret("sqlContext", false); + result = sparkInterpreter.interpret("sqlContext", false, false); if (result.code() == InterpreterResult.Code.SUCCESS) { LOGGER.info("sqlContext is detected."); } else if (result.code() == InterpreterResult.Code.ERROR) { @@ -68,7 +68,7 @@ public void open() { LOGGER.info("sqlContext is not detected, try to create SQLContext by ourselves"); result = sparkInterpreter.interpret( "val sqlContext = new org.apache.spark.sql.SQLContext(sc)\n" - + "import sqlContext.implicits._", false); + + "import sqlContext.implicits._", false, false); if (result.code() == InterpreterResult.Code.ERROR) { throw new LivyException("Fail to create SQLContext," + result.message().get(0).getData()); @@ -113,37 +113,44 @@ public InterpreterResult interpret(String line, InterpreterContext context) { } else { sqlQuery = "sqlContext.sql(\"\"\"" + line + "\"\"\").show(" + maxResult + ")"; } - InterpreterResult res = sparkInterpreter.interpret(sqlQuery, this.displayAppInfo); - - if (res.code() == InterpreterResult.Code.SUCCESS) { - StringBuilder resMsg = new StringBuilder(); - resMsg.append("%table "); - String[] rows = res.message().get(0).getData().split("\n"); - String[] headers = rows[1].split("\\|"); - for (int head = 1; head < headers.length; head++) { - resMsg.append(headers[head].trim()).append("\t"); - } - resMsg.append("\n"); - if (rows[3].indexOf("+") == 0) { - - } else { - for (int cols = 3; cols < rows.length - 1; cols++) { - String[] col = rows[cols].split("\\|"); - for (int data = 1; data < col.length; data++) { - resMsg.append(col[data].trim()).append("\t"); + InterpreterResult result = sparkInterpreter.interpret(sqlQuery, this.displayAppInfo, true); + + if (result.code() == InterpreterResult.Code.SUCCESS) { + InterpreterResult result2 = new InterpreterResult(InterpreterResult.Code.SUCCESS); + for (InterpreterResultMessage message : result.message()) { + // convert Text type to Table type. We assume the text type must be the sql output. This + // assumption is correct for now. Ideally livy should return table type. We may do it in + // the future release of livy. + if (message.getType() == InterpreterResult.Type.TEXT) { + StringBuilder resMsg = new StringBuilder(); + String[] rows = message.getData().split("\n"); + String[] headers = rows[1].split("\\|"); + for (int head = 1; head < headers.length; head++) { + resMsg.append(headers[head].trim()).append("\t"); } resMsg.append("\n"); + if (rows[3].indexOf("+") == 0) { + + } else { + for (int cols = 3; cols < rows.length - 1; cols++) { + String[] col = rows[cols].split("\\|"); + for (int data = 1; data < col.length; data++) { + resMsg.append(col[data].trim()).append("\t"); + } + resMsg.append("\n"); + } + } + if (rows[rows.length - 1].indexOf("only") == 0) { + resMsg.append("" + rows[rows.length - 1] + "."); + } + result2.add(InterpreterResult.Type.TABLE, resMsg.toString()); + } else { + result2.add(message.getType(), message.getData()); } } - if (rows[rows.length - 1].indexOf("only") == 0) { - resMsg.append("" + rows[rows.length - 1] + "."); - } - - return new InterpreterResult(InterpreterResult.Code.SUCCESS, - resMsg.toString() - ); + return result2; } else { - return res; + return result; } } catch (Exception e) { LOGGER.error("Exception in LivySparkSQLInterpreter while interpret ", e); From 8dc4721eaec0684ef3df6c25a8668badb4ed2ab5 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Thu, 12 Jan 2017 10:58:06 -0800 Subject: [PATCH 017/234] [ZEPPELIN-1619] Load js package as a plugin visualization ### What is this PR for? Current helium plugin application api (experimental) requires create library in java class, and need to create both backend / frontend code in the package. Which is good if your plugin requires both frontend and backend code running. However, when user just want to make new visualization which completely runs on front-end side in javascript, creating helium application in java project and taking care of backend code can be bit of overhead and barrier for javascript developers. This PR adds capability to load pure javascript package as a visualization. ### how it works 1. create (copy, download) 'helium package json' file into `ZEPPELIN_HOME/helium` directory. The json file point visualization js package in npm repository or local file system in `artifact` field. `type` field in the json file need to be `VISUALIZATION` Here's an example (zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json) ``` { "type" : "VISUALIZATION", "name" : "zeppelin_horizontalbar", "description" : "Horizontal Bar chart (example)", "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar", "icon" : "" } ``` 2. Go to helium GUI menu. (e.g. http://localhost:8080/#/helium). The menu will list all available packages. writing_visualization_helium_menu 3. click 'enable' in any package want to use. Once a visualization package is enabled, `HeliumVisualizationFactory` will collect all enabled visualizations and create js bundle on the fly. 4. js bundle will be loaded on notebook and additional visualization becomes available ![image](https://cloud.githubusercontent.com/assets/1540981/21749729/709b2b3e-d559-11e6-8318-7f2871e7c39a.png) ### Programming API to create new plugin visualization. Simply extends [visualization.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js) and overrides some methods, such as ``` /** * get transformation */ getTransformation() { // override this }; /** * Method will be invoked when data or configuration changed */ render(tableData) { // override this }; /** * Refresh visualization. */ refresh() { // override this }; /** * method will be invoked when visualization need to be destroyed. * Don't need to destroy this.targetEl. */ destroy() { // override this }; /** * return { * template : angular template string or url (url should end with .html), * scope : an object to bind to template scope * } */ getSetting() { // override this }; ``` This is exactly the same api that built-in visualization uses. an example implementation included `zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js`. Actually [all built-in visualizations](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization/builtins) are example ### Packaging and publishing visualization Each visualization will need `package.json` file (e.g. `zeppelin-examples/zeppelin-example-horizontalbar/package.json`) to be packaged. Package can be published in npm repository or package can be deployed to the local filesystem. `zeppelin-examples/zeppelin-example-horizontalbar/` is an example package that is deployed in the local filesystem ### Development mode First, locally install and enable your development package by setting `artifact` field to the development directory. Then run zeppelin-web in visualization development mode with following command ``` cd zeppelin-web npm run visdev ``` When you have change in your local development package, just reload your notebook. Then Zeppelin will automatically rebuild / reload the package. Any feedback would be appreciated! ### What type of PR is it? Feature ### Todos * [x] - Load plugin visualization js package on runtime * [x] - Make the feature works in zeppelin Binary package * [x] - Show loading indicator while 'enable' / 'disable' package * [x] - Add document * [x] - Add license of new dependency * [x] - Development mode * [x] - Propagate error to front-end * [x] - Display multiple versions of a package. ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1619 ### How should this be tested? Build Zeppelin with `-Pexamples` flag. That'll install example visualization package `horizontalbar`. You'll able to select `horizontalbar` along with other built-in visualizations ![image](https://cloud.githubusercontent.com/assets/1540981/21655057/27d61740-d26d-11e6-88f2-02c653e102c6.png) To test npm online package install capability, Place [zeppelin-bubble.json](https://github.com/Leemoonsoo/zeppelin-bubble/blob/master/zeppelin-bubble.json) in hour local registry (`ZEPPELIN_HOME/helium`) and enable it in Helium gui menu. And then zeppelin will download package from npm repository and load. ![bubblechart](https://cloud.githubusercontent.com/assets/1540981/21749717/280aa430-d559-11e6-9209-889a4f86d7e2.gif) ### Questions: * Does the licenses files need update? yes * Is there breaking changes for older versions? no * Does this needs documentation? yes Author: Lee moon soo Closes #1842 from Leemoonsoo/ZEPPELIN-1619-rebased and squashes the following commits: 7c49bbb [Lee moon soo] Let Zeppelin continue to bootstrap on offline 816bdec [Lee moon soo] Display license of package when enabling 28fb37d [Lee moon soo] beautifulize helium menu 295768e [Lee moon soo] fix drag and drop visualization reorder bb304db [Lee moon soo] Sort version in decreasing order e7f18f1 [Lee moon soo] fix english in docs and labels c7b187f [Lee moon soo] Merge branch 'master' into ZEPPELIN-1619-rebased 4c87983 [Lee moon soo] Merge remote-tracking branch 'apache-github/master' into ZEPPELIN-1619-rebased ecd925b [Lee moon soo] Merge remote-tracking branch 'apache-github/master' into ZEPPELIN-1619-rebased a92cadd [Lee moon soo] Use minifiable syntax cec534c [Lee moon soo] Reduce log message f373f1d [Lee moon soo] Ignore removed package e18d9a4 [Lee moon soo] Ability to customize order of visualization package display cd74396 [Lee moon soo] Add rest api doc 9de5d6d [Lee moon soo] exclude .npmignore and package.json from zeppelin-web rat check 08abded [Lee moon soo] exclude package.json from rat check 661c26b [Lee moon soo] update screenshot and keep experimental tag only in docs 4515805 [Lee moon soo] Display multiple versions of a package 408c512 [Lee moon soo] Make unittest test bundling with proper vis package on npm registry fb7a147 [Lee moon soo] display svg icon 47de6d9 [Lee moon soo] Propagate bundle error to the front-end 0fe5e00 [Lee moon soo] visualization development mode 022e8f6 [Lee moon soo] exclude zeppelin-examples/zeppelin-example-horizontalbar/package.json file from rat check 2ef3b69 [Lee moon soo] Add new dependency license f943d33 [Lee moon soo] Add doc f494dbd [Lee moon soo] package npm dependency module in binary package b655fa6 [Lee moon soo] use any version of dependency in example. so zeppelin version bumpup doesn't need to take care of them 2aec52d [Lee moon soo] show loading indicator while enable/disable package 6c380f6 [Lee moon soo] refactor code to fix HeliumTest e142336 [Lee moon soo] update unittest 7d5e0ae [Lee moon soo] Resolve dependency conflict c50a524 [Lee moon soo] Add conf/helium.json in .gitignore 1c7b73a [Lee moon soo] add result.css d2823ad [Lee moon soo] load visualization and tabledata module from source instead npm if accessible 4e1b061 [Lee moon soo] Convert horizontalbar to VISUALIZATION example a5a935b [Lee moon soo] connect visualization factory with restapi 4b21252 [Lee moon soo] initial implementation of helium menu 0c4da2e [Lee moon soo] pass bundled visualization to result.controller.js f5ce99e [Lee moon soo] import helium service js 1663582 [Lee moon soo] initial implementation of helium menu 74d52d4 [Lee moon soo] bundle visualization package from npm repository on the fly (cherry picked from commit 300f7532342d1ea47b85d3b777a8797a3e2248d4) Signed-off-by: Lee moon soo --- .gitignore | 1 + .travis.yml | 3 +- .../themes/zeppelin/_navigation.html | 4 +- .../writing_visualization_example.png | Bin 0 -> 80870 bytes .../writing_visualization_helium_menu.png | Bin 0 -> 177591 bytes .../writingzeppelinvisualization.md | 212 ++++++++++ docs/rest-api/rest-helium.md | 378 ++++++++++++++++++ pom.xml | 3 +- .../src/assemble/distribution.xml | 8 + zeppelin-distribution/src/bin_license/LICENSE | 2 + .../zeppelin-example-clock.json | 1 + .../horizontalbar.js | 77 ++++ .../package.json | 12 + .../app/horizontalbar/HorizontalBar.java | 85 ---- .../app/horizontalbar/horizontalbar.js | 56 --- .../horizontalbar/horizontalbar_mockdata.txt | 10 - .../zeppelin-example-horizontalbar.json | 11 +- .../apache/zeppelin/helium/HeliumPackage.java | 14 +- .../helium/ApplicationLoaderTest.java | 4 +- zeppelin-server/pom.xml | 5 +- .../apache/zeppelin/rest/HeliumRestApi.java | 95 ++++- .../zeppelin/server/ZeppelinServer.java | 47 ++- zeppelin-web/.eslintrc | 3 +- zeppelin-web/package.json | 2 + zeppelin-web/pom.xml | 3 +- zeppelin-web/src/app/app.js | 22 +- .../src/app/helium/helium.controller.js | 219 ++++++++++ zeppelin-web/src/app/helium/helium.css | 107 +++++ zeppelin-web/src/app/helium/helium.html | 86 ++++ .../result/result-chart-selector.html | 5 +- .../paragraph/result/result.controller.js | 37 +- .../app/notebook/paragraph/result/result.css | 24 +- zeppelin-web/src/app/tabledata/.npmignore | 1 + zeppelin-web/src/app/tabledata/package.json | 15 + zeppelin-web/src/app/visualization/.npmignore | 1 + .../src/app/visualization/package.json | 16 + .../src/components/helium/helium.service.js | 62 +++ .../src/components/navbar/navbar.html | 1 + zeppelin-web/src/index.html | 1 + zeppelin-web/src/index.js | 2 + zeppelin-web/webpack.config.js | 9 +- zeppelin-zengine/pom.xml | 22 +- .../org/apache/zeppelin/helium/Helium.java | 221 +++++++++- .../apache/zeppelin/helium/HeliumConf.java | 42 +- .../helium/HeliumPackageSearchResult.java | 8 +- .../helium/HeliumVisualizationFactory.java | 351 ++++++++++++++++ .../apache/zeppelin/helium/NpmPackage.java | 28 ++ .../apache/zeppelin/helium/WebpackResult.java | 25 ++ .../src/main/resources/helium/package.json | 15 + .../main/resources/helium/webpack.config.js | 34 ++ .../helium/HeliumApplicationFactoryTest.java | 12 +- .../helium/HeliumLocalRegistryTest.java | 4 +- .../apache/zeppelin/helium/HeliumTest.java | 22 +- .../HeliumVisualizationFactoryTest.java | 157 ++++++++ .../test/resources/helium/vis1/package.json | 12 + .../src/test/resources/helium/vis1/vis1.js | 37 ++ .../test/resources/helium/vis2/package.json | 12 + .../src/test/resources/helium/vis2/vis2.js | 38 ++ 58 files changed, 2453 insertions(+), 231 deletions(-) create mode 100644 docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png create mode 100644 docs/assets/themes/zeppelin/img/docs-img/writing_visualization_helium_menu.png create mode 100644 docs/development/writingzeppelinvisualization.md create mode 100644 docs/rest-api/rest-helium.md create mode 100644 zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js create mode 100644 zeppelin-examples/zeppelin-example-horizontalbar/package.json delete mode 100644 zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java delete mode 100644 zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js delete mode 100644 zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt create mode 100644 zeppelin-web/src/app/helium/helium.controller.js create mode 100644 zeppelin-web/src/app/helium/helium.css create mode 100644 zeppelin-web/src/app/helium/helium.html create mode 100644 zeppelin-web/src/app/tabledata/.npmignore create mode 100644 zeppelin-web/src/app/tabledata/package.json create mode 100644 zeppelin-web/src/app/visualization/.npmignore create mode 100644 zeppelin-web/src/app/visualization/package.json create mode 100644 zeppelin-web/src/components/helium/helium.service.js create mode 100644 zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java create mode 100644 zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java create mode 100644 zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java create mode 100644 zeppelin-zengine/src/main/resources/helium/package.json create mode 100644 zeppelin-zengine/src/main/resources/helium/webpack.config.js create mode 100644 zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java create mode 100644 zeppelin-zengine/src/test/resources/helium/vis1/package.json create mode 100644 zeppelin-zengine/src/test/resources/helium/vis1/vis1.js create mode 100644 zeppelin-zengine/src/test/resources/helium/vis2/package.json create mode 100644 zeppelin-zengine/src/test/resources/helium/vis2/vis2.js diff --git a/.gitignore b/.gitignore index 29472283483..5b638fae9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ conf/interpreter.json conf/notebook-authorization.json conf/shiro.ini conf/credentials.json +conf/helium.json # other generated files spark/dependency-reduced-pom.xml diff --git a/.travis.yml b/.travis.yml index 1e046c4cb36..48e8aa7ed3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -106,8 +106,7 @@ after_success: after_failure: - echo "Travis exited with ${TRAVIS_TEST_RESULT}" - - cat target/rat.txt - - cat zeppelin-server/target/rat.txt + - find . -name rat.txt | xargs cat - cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.log - cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.out - cat zeppelin-web/npm-debug.log diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html index 42b38f46c20..b13ef68542d 100644 --- a/docs/_includes/themes/zeppelin/_navigation.html +++ b/docs/_includes/themes/zeppelin/_navigation.html @@ -103,6 +103,7 @@
  • Notebook API
  • Configuration API
  • Credential API
  • +
  • Helium API
  • Security
  • Shiro Authentication
  • @@ -118,7 +119,8 @@
  • Contibute
  • Writing Zeppelin Interpreter
  • -
  • Writing Zeppelin Application (Experimental)
  • +
  • Writing Zeppelin Visualization (Experimental)
  • +
  • Writing Zeppelin Application (Experimental)
  • How to contribute (code)
  • How to contribute (website)
  • diff --git a/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png b/docs/assets/themes/zeppelin/img/docs-img/writing_visualization_example.png new file mode 100644 index 0000000000000000000000000000000000000000..219d7c8b4e5cf2456aa796725ad2caf6b2e09865 GIT binary patch literal 80870 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 E2Nc(8Un%HU4y$j9VEEB^T;ecYeO^ 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

    asTg2CJOng40kuUmhY2CeW0xN8 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 z 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 z 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 JD_1ZH_%B7btIq%c literal 0 HcmV?d00001 diff --git a/docs/development/writingzeppelinvisualization.md b/docs/development/writingzeppelinvisualization.md new file mode 100644 index 00000000000..d7c2268f4a9 --- /dev/null +++ b/docs/development/writingzeppelinvisualization.md @@ -0,0 +1,212 @@ +--- +layout: page +title: "Writing a new Visualization(Experimental)" +description: "Apache Zeppelin Application is a package that runs on Interpreter process and displays it's output inside of the notebook. Make your own Application in Apache Zeppelin is quite easy." +group: development +--- + +{% include JB/setup %} + +# Writing a new Visualization (Experimental) + +

    + +## What is Apache Zeppelin Visualization + +Apache Zeppelin Visualization is a pluggable package that can be loaded/unloaded on runtime through Helium framework in Zeppelin. A Visualization is a javascript npm package and user can use them just like any other built-in visualization in notebook. + + +## How it works + + +#### 1. Load Helium package files from registry +Zeppelin needs to know what Visualization packages are available. Zeppelin searches _Helium package file_ from local registry (by default helium/ directory) by default. +_Helium package file_ provides informations like name, artifact, and so on. It's similar to _package.json_ in npm package. + +Here's an example `helium/zeppelin-example-horizontalbar.json` + +``` +{ + "type" : "VISUALIZATION", + "name" : "zeppelin_horizontalbar", + "description" : "Horizontal Bar chart (example)", + "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar", + "license" : "Apache-2.0", + "icon" : "" +} +``` + +Check [Create helium package file](#3-create-helium-package-file) section to learn about it. + + +#### 2. Enable packages +Once Zeppelin loads _Helium package files_ from local registry, available packages are displayed in Helium menu. + +Click 'enable' button. + + + + +#### 3. Create and load visualization bundle on the fly + +Once a Visualization package is enabled, [HeliumVisualizationFactory](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationPackage.java) creates a js bundle. The js bundle is served by `helium/visualization/load` rest api endpoint. + + +#### 4. Run visualization + +Zeppelin shows additional button for loaded Visualizations. +User can use just like any other built-in visualizations. + + + + + +## Write new Visualization + +#### 1. Create a npm package + +Create a [package.json](https://docs.npmjs.com/files/package.json) in your new Visualization directory. Normally, you can add any dependencies in package.json however Zeppelin Visualization package only allows two dependencies: [zeppelin-vis](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization) and [zeppelin-tabledata](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/tabledata). + +Here's an example + +``` +{ + "name": "zeppelin_horizontalbar", + "description" : "Horizontal Bar chart", + "version": "1.0.0", + "main": "horizontalbar", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "zeppelin-tabledata": "*", + "zeppelin-vis": "*" + } +} +``` + +#### 2. Create your own visualization + +To create your own visualization, you need to create a js file and import [Visualization](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js) class from [zeppelin-vis](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization) package and extend the class. [zeppelin-tabledata](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/tabledata) package provides some useful transformations, like pivot, you can use in your visualization. (you can create your own transformation, too). + +[Visualization](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js) class, there're several methods that you need to override and implement. Here's simple visualization that just prints `Hello world`. + +``` +import Visualization from 'zeppelin-vis' +import PassthroughTransformation from 'zeppelin-tabledata/passthrough' + +export default class helloworld extends Visualization { + constructor(targetEl, config) { + super(targetEl, config) + this.passthrough = new PassthroughTransformation(config); + } + + render(tableData) { + this.targetEl.html('Hello world!') + } + + getTransformation() { + return this.passthrough + } +} +``` + +To learn more about `Visualization` class, check [visualization.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/visualization/visualization.js). + +You can check complete visualization package example [here](https://github.com/apache/zeppelin/tree/master/zeppelin-examples/zeppelin-example-horizontalbar). + +Zeppelin's built-in visualization uses the same API, so you can check [built-in visualizations](https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization/builtins) as additional examples. + + +#### 3. Create __Helium package file__ + +__Helium Package file__ is a json file that provides information about the application. +Json file contains the following information + +``` +{ + "type" : "VISUALIZATION", + "name" : "zeppelin_horizontalbar", + "description" : "Horizontal Bar chart (example)", + "license" : "Apache-2.0", + "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar", + "icon" : "" +} +``` + +##### type + +When you're creating a visualization, 'type' should be 'VISUALIZATION'. +Check [application](./writingzeppelinapplication.html) type if you're interested in the other types of package. + +##### name + +Name of visualization. Should be unique. Allows `[A-Za-z90-9_]`. + + +##### description + +A short description about visualization. + +##### artifact + +Location of the visualization npm package. Support npm package with version or local filesystem path. + +e.g. + +When artifact exists in npm repository + +``` +artifact: "my-visualiztion@1.0.0" +``` + + +When artifact exists in local file system + +``` +artifact: "/path/to/my/visualization" +``` + +##### license + +License information. + +e.g. + +``` +license: "Apache-2.0" +``` + +##### icon + +Icon to be used in visualization select button. String in this field will be rendered as a HTML tag. + +e.g. + +``` +icon: "" +``` + + +#### 4. Run in dev mode + +Place your __Helium package file__ in local registry (ZEPPELIN_HOME/helium). +Run Zeppelin. And then run zeppelin-web in visualization dev mode. + +``` +cd zeppelin-web +yarn run visdev +``` + +You can browse localhost:9000. Everytime refresh your browser, Zeppelin will rebuild your visualization and reload changes. diff --git a/docs/rest-api/rest-helium.md b/docs/rest-api/rest-helium.md new file mode 100644 index 00000000000..8d2ff4d44f3 --- /dev/null +++ b/docs/rest-api/rest-helium.md @@ -0,0 +1,378 @@ +--- +layout: page +title: "Apache Zeppelin Helium REST API" +description: "This page contains Apache Zeppelin Helium REST API information." +group: rest-api +--- + +{% include JB/setup %} + +# Apache Zeppelin Helium REST API + +
    + +## Overview +Apache Zeppelin provides several REST APIs for interaction and remote activation of zeppelin functionality. +All REST APIs are available starting with the following endpoint `http://[zeppelin-server]:[zeppelin-port]/api`. +Note that Apache Zeppelin REST APIs receive or return JSON objects, it is recommended for you to install some JSON viewers such as [JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc). + +If you work with Apache Zeppelin and find a need for an additional REST API, please [file an issue or send us an email](http://zeppelin.apache.org/community.html). + +## Helium REST API List + +### List of all available helium packages + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```GET``` method returns all the available helium packages in configured registries.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/all```
    Success code200
    Fail code 500
    Sample JSON response +
    +{
    +  "status": "OK",
    +  "message": "",
    +  "body": {
    +    "zeppelin.clock": [
    +      {
    +        "registry": "local",
    +        "pkg": {
    +          "type": "APPLICATION",
    +          "name": "zeppelin.clock",
    +          "description": "Clock (example)",
    +          "artifact": "zeppelin-examples\/zeppelin-example-clock\/target\/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
    +          "className": "org.apache.zeppelin.example.app.clock.Clock",
    +          "resources": [
    +            [
    +              ":java.util.Date"
    +            ]
    +          ],
    +          "icon": "icon"
    +        },
    +        "enabled": false
    +      }
    +    ],
    +    "zeppelin-bubblechart": [
    +      {
    +        "registry": "local",
    +        "pkg": {
    +          "type": "VISUALIZATION",
    +          "name": "zeppelin-bubblechart",
    +          "description": "Animated bubble chart",
    +          "artifact": ".\/..\/helium\/zeppelin-bubble",
    +          "icon": "icon"
    +        },
    +        "enabled": true
    +      },
    +      {
    +        "registry": "local",
    +        "pkg": {
    +          "type": "VISUALIZATION",
    +          "name": "zeppelin-bubblechart",
    +          "description": "Animated bubble chart",
    +          "artifact": "zeppelin-bubblechart@0.0.2",
    +          "icon": "icon"
    +        },
    +        "enabled": false
    +      }
    +    ],
    +    "zeppelin_horizontalbar": [
    +      {
    +        "registry": "local",
    +        "pkg": {
    +          "type": "VISUALIZATION",
    +          "name": "zeppelin_horizontalbar",
    +          "description": "Horizontal Bar chart (example)",
    +          "artifact": ".\/zeppelin-examples\/zeppelin-example-horizontalbar",
    +          "icon": "icon"
    +        },
    +        "enabled": true
    +      }
    +    ]
    +  }
    +}
    +        
    +
    + +
    +### Suggest Helium application + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```GET``` method returns suggested helium application for the paragraph.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/suggest/[Note ID]/[Paragraph ID]```
    Success code200
    Fail code + 404 on note or paragraph not exists
    + 500 +
    Sample JSON response +
    +{
    +  "status": "OK",
    +  "message": "",
    +  "body": {
    +    "available": [
    +      {
    +        "registry": "local",
    +        "pkg": {
    +          "type": "APPLICATION",
    +          "name": "zeppelin.clock",
    +          "description": "Clock (example)",
    +          "artifact": "zeppelin-examples\/zeppelin-example-clock\/target\/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
    +          "className": "org.apache.zeppelin.example.app.clock.Clock",
    +          "resources": [
    +            [
    +              ":java.util.Date"
    +            ]
    +          ],
    +          "icon": "icon"
    +        },
    +        "enabled": true
    +      }
    +    ]
    +  }
    +}
    +        
    +
    + +
    +### Load helium Application on a paragraph + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```GET``` method returns a helium Application id on success.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/load/[Note ID]/[Paragraph ID]```
    Success code200
    Fail code + 404 on note or paragraph not exists
    + 500 for any other errors +
    Sample JSON response +
    +{
    +  "status": "OK",
    +  "message": "",
    +  "body": "app_2C5FYRZ1E-20170108-040449_2068241472zeppelin_clock"
    +}
    +        
    +
    + +
    +### Load bundled visualization script + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```GET``` method returns bundled helium visualization javascript. When refresh=true (optional) is provided, Zeppelin rebuild bundle. otherwise, provided from cache
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizations/load[?refresh=true]```
    Success code200 reponse body is executable javascript
    Fail code + 200 reponse body is error message string starts with ERROR:
    +
    + +
    +### Enable package + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```POST``` method enables a helium package. Needs artifact name in input payload
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/enable/[Package Name]```
    Success code200
    Fail code 500
    Sample input +
    +zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNAPSHOT.jar
    +        
    +
    Sample JSON response +
    +{"status":"OK"}
    +        
    +
    + +
    +### Disable package + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```POST``` method disables a helium package.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/disable/[Package Name]```
    Success code200
    Fail code 500
    Sample JSON response + {"status":"OK"} +
    +
    + +### Get visualization display order + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```GET``` method returns display order of enabled visualization packages.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizationOrder```
    Success code200
    Fail code 500
    Sample JSON response + {"status":"OK","body":["zeppelin_horizontalbar","zeppelin-bubblechart"]} +
    + + +
    + +### Set visualization display order + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DescriptionThis ```POST``` method sets visualization packages display order.
    URL```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizationOrder```
    Success code200
    Fail code 500
    Sample JSON input + ["zeppelin-bubblechart", "zeppelin_horizontalbar"] +
    Sample JSON response + {"status":"OK"} +
    \ No newline at end of file diff --git a/pom.xml b/pom.xml index dcd359bb8e1..481015ccb67 100644 --- a/pom.xml +++ b/pom.xml @@ -940,7 +940,8 @@ docs/_site/** docs/Gemfile.lock - **/horizontalbar_mockdata.txt + + **/package.json R/lib/** diff --git a/zeppelin-distribution/src/assemble/distribution.xml b/zeppelin-distribution/src/assemble/distribution.xml index 371aed39949..e8188e878d3 100644 --- a/zeppelin-distribution/src/assemble/distribution.xml +++ b/zeppelin-distribution/src/assemble/distribution.xml @@ -95,5 +95,13 @@ /lib/interpreter ../zeppelin-interpreter/target/lib + + /lib/node_modules/zeppelin-vis + ../zeppelin-web/src/app/visualization + + + /lib/node_modules/zeppelin-tabledata + ../zeppelin-web/src/app/tabledata + diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE index 7cce0621b55..1197ea74a92 100644 --- a/zeppelin-distribution/src/bin_license/LICENSE +++ b/zeppelin-distribution/src/bin_license/LICENSE @@ -214,6 +214,8 @@ The following components are provided under Apache License. (Apache 2.0) Maven Wagon HTTP Shared 1.0 (org.apache.maven.wagon:wagon-http-shared:1.0 - https://mvnrepository.com/artifact/org.apache.maven.wagon/wagon-http-shared/1.0) (Apache 2.0) Commons HTTP Client 3.1 (commons-httpclient:commons-httpclient:3.1 - https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient/3.1) (Apache 2.0) Scalatest 2.2.4 (org.scalatest:scalatest_2.10:2.2.4 - https://github.com/scalatest/scalatest) + (Apache 2.0) frontend-maven-plugin 1.3 (com.github.eirslett:frontend-maven-plugin:1.3 - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE + (Apache 2.0) frontend-plugin-core 1.3 (com.github.eirslett:frontend-plugin-core) - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE ======================================================================== MIT licenses diff --git a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json index a7526f5a359..3e48697c5a7 100644 --- a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json +++ b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json @@ -21,5 +21,6 @@ "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNAPSHOT.jar", "className" : "org.apache.zeppelin.example.app.clock.Clock", "resources" : [[":java.util.Date"]], + "license" : "Apache-2.0", "icon" : '' } diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js b/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js new file mode 100644 index 00000000000..d574a894b77 --- /dev/null +++ b/zeppelin-examples/zeppelin-example-horizontalbar/horizontalbar.js @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Nvd3ChartVisualization from 'zeppelin-vis/builtins/visualization-nvd3chart'; +import PivotTransformation from 'zeppelin-tabledata/pivot'; + +/** + * Base class for visualization + */ +export default class horizontalbar extends Nvd3ChartVisualization { + constructor(targetEl, config) { + super(targetEl, config) + this.pivot = new PivotTransformation(config); + } + + type() { + return 'multiBarHorizontalChart'; + }; + + render(pivot) { + var d3Data = this.d3DataFromPivot( + pivot.schema, + pivot.rows, + pivot.keys, + pivot.groups, + pivot.values, + true, + false, + true); + + super.render(d3Data); + } + + getTransformation() { + return this.pivot; + } + + /** + * Set new config + */ + setConfig(config) { + super.setConfig(config); + this.pivot.setConfig(config); + }; + + configureChart(chart) { + var self = this; + chart.yAxis.axisLabelDistance(50); + chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d);}); + + this.chart.stacked(this.config.stacked); + + var self = this; + this.chart.dispatch.on('stateChange', function(s) { + self.config.stacked = s.stacked; + + // give some time to animation finish + setTimeout(function() { + self.emitConfig(self.config); + }, 500); + }); + }; +} + diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/package.json b/zeppelin-examples/zeppelin-example-horizontalbar/package.json new file mode 100644 index 00000000000..60121d6e8cb --- /dev/null +++ b/zeppelin-examples/zeppelin-example-horizontalbar/package.json @@ -0,0 +1,12 @@ +{ + "name": "zeppelin_horizontalbar", + "description" : "Horizontal Bar chart (example)", + "version": "1.0.0", + "main": "horizontalbar", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "zeppelin-tabledata": "*", + "zeppelin-vis": "*" + } +} diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java b/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java deleted file mode 100644 index 6637821cfc8..00000000000 --- a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/java/org/apache/zeppelin/example/app/horizontalbar/HorizontalBar.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.zeppelin.example.app.horizontalbar; - -import org.apache.commons.io.IOUtils; -import org.apache.zeppelin.helium.Application; -import org.apache.zeppelin.helium.ApplicationContext; -import org.apache.zeppelin.helium.ApplicationException; -import org.apache.zeppelin.helium.ZeppelinApplicationDevServer; -import org.apache.zeppelin.interpreter.InterpreterResult; -import org.apache.zeppelin.resource.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Random; - -/** - * Basic example application. - * TableData for input - */ -public class HorizontalBar extends Application { - private final Logger logger = LoggerFactory.getLogger(HorizontalBar.class); - - InterpreterResult result; - - public HorizontalBar(ApplicationContext context) { - super(context); - } - - @Override - public void run(ResourceSet resources) throws ApplicationException, IOException { - // Get data from resource args - result = (InterpreterResult) resources.get(0).get(); - - // create element - println(String.format( - "
    ", - context().getApplicationInstanceId())); - // write js - printResourceAsJavascript("example/app/horizontalbar/horizontalbar.js"); - } - - @Override - public void unload() throws ApplicationException { - } - - /** - * Development mode - */ - public static void main(String[] args) throws Exception { - LocalResourcePool pool = new LocalResourcePool("dev"); - InputStream ins = ClassLoader.getSystemResourceAsStream( - "example/app/horizontalbar/horizontalbar_mockdata.txt"); - InterpreterResult result = new InterpreterResult( - InterpreterResult.Code.SUCCESS, - InterpreterResult.Type.TABLE, - IOUtils.toString(ins)); - pool.put(WellKnownResourceName.ZeppelinTableResult.name(), result); - - ZeppelinApplicationDevServer devServer = new ZeppelinApplicationDevServer( - HorizontalBar.class.getName(), - pool.getAll()); - - devServer.start(); - devServer.join(); - } -} diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js b/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js deleted file mode 100644 index fac2c8eb877..00000000000 --- a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var data = []; -_.forEach($z.result.columnNames, function(col, series) { - if (series == 0) return; - var values = _.map($z.result.rows, function(row) { - return { - label: row[0], - value : parseFloat(row[series]) - } - }); - - data.push({ - key : col.name, - values : values - }) -}); - -nv.addGraph(function() { - var chart = nv.models.multiBarHorizontalChart() - .x(function(d) { return d.label }) - .y(function(d) { return d.value }) - .margin({top: 30, right: 20, bottom: 50, left: 175}) - .showValues(true) //Show bar value next to each bar. - .tooltips(true) //Show tooltips on hover. - .showControls(true); //Allow user to switch between "Grouped" and "Stacked" mode. - - chart.yAxis - .tickFormat(d3.format(',.2f')); - - d3.select('#horizontalbar_' + $z.id + ' svg') - .datum(data) - .call(chart); - - nv.utils.windowResize(chart.update); - - return chart; -}); - - - diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt b/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt deleted file mode 100644 index adf322d547c..00000000000 --- a/zeppelin-examples/zeppelin-example-horizontalbar/src/main/resources/example/app/horizontalbar/horizontalbar_mockdata.txt +++ /dev/null @@ -1,10 +0,0 @@ -Label Series1 Series2 -GroupA -1.8746444827653 25.307646510375 -GroupB -8.0961543492239 16.756779544553 -GroupC -0.57072943117674 18.451534877007 -GroupD -2.4174010336624 8.6142352811805 -GroupE -0.72009071426284 7.8082472075876 -GroupF -0.77154485523777 5.259101026956 -GroupG -0.90152097798131 0.30947953487127 -GroupH -0.91445417330854 0 -GroupI -0.055746319141851 0 diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json b/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json index da57d060535..c8ac28cb501 100644 --- a/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json +++ b/zeppelin-examples/zeppelin-example-horizontalbar/zeppelin-example-horizontalbar.json @@ -15,11 +15,10 @@ * limitations under the License. */ { - "type" : "APPLICATION", - "name" : "zeppelin.horizontalbar", + "type" : "VISUALIZATION", + "name" : "zeppelin_horizontalbar", "description" : "Horizontal Bar chart (example)", - "artifact" : "zeppelin-examples/zeppelin-example-horizontalbar/target/zeppelin-example-horizontalbar-0.7.0-SNAPSHOT.jar", - "className" : "org.apache.zeppelin.example.app.horizontalbar.HorizontalBar", - "resources" : [[":org.apache.zeppelin.interpreter.InterpreterResult"]], - "icon" : '' + "artifact" : "./zeppelin-examples/zeppelin-example-horizontalbar", + "license" : "Apache-2.0", + "icon" : "" } 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 13526420e39..84a2ab33b3d 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/helium/HeliumPackage.java @@ -30,14 +30,17 @@ public class HeliumPackage { private String className; // entry point private String [][] resources; // resource classnames that requires // [[ .. and .. and .. ] or [ .. and .. and ..] ..] + private String license; private String icon; + /** * Type of package */ public static enum Type { INTERPRETER, NOTEBOOK_REPO, - APPLICATION + APPLICATION, + VISUALIZATION } public HeliumPackage(Type type, @@ -45,13 +48,17 @@ public HeliumPackage(Type type, String description, String artifact, String className, - String[][] resources) { + String[][] resources, + String license, + String icon) { this.type = type; this.name = name; this.description = description; this.artifact = artifact; this.className = className; this.resources = resources; + this.license = license; + this.icon = icon; } @Override @@ -93,6 +100,9 @@ public String[][] getResources() { return resources; } + public String getLicense() { + return license; + } public String getIcon() { return icon; } 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 c92699c9ee7..3924e28241d 100644 --- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java +++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java @@ -79,7 +79,9 @@ public HeliumPackage createPackageInfo(String className, String artifact) { "desc1", artifact, className, - new String[][]{{}}); + new String[][]{{}}, + "license", + "icon"); return app1; } diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index ea51fa79c74..6503e66fe2c 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -279,7 +279,10 @@ com.jcraft jsch - + + org.apache.commons + commons-compress + 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 062f5b93978..953188bee6c 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java @@ -17,9 +17,11 @@ package org.apache.zeppelin.rest; +import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.apache.commons.io.FileUtils; import org.apache.zeppelin.helium.Helium; -import org.apache.zeppelin.helium.HeliumApplicationFactory; import org.apache.zeppelin.helium.HeliumPackage; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Notebook; @@ -30,6 +32,9 @@ import javax.ws.rs.*; import javax.ws.rs.core.Response; +import java.io.File; +import java.io.IOException; +import java.util.List; /** * Helium Rest Api @@ -40,18 +45,14 @@ public class HeliumRestApi { Logger logger = LoggerFactory.getLogger(HeliumRestApi.class); private Helium helium; - private HeliumApplicationFactory applicationFactory; private Notebook notebook; private Gson gson = new Gson(); public HeliumRestApi() { } - public HeliumRestApi(Helium helium, - HeliumApplicationFactory heliumApplicationFactory, - Notebook notebook) { + public HeliumRestApi(Helium helium, Notebook notebook) { this.helium = helium; - this.applicationFactory = heliumApplicationFactory; this.notebook = notebook; } @@ -101,8 +102,88 @@ public Response suggest(@PathParam("noteId") String noteId, } HeliumPackage pkg = gson.fromJson(heliumPackage, HeliumPackage.class); - String appId = applicationFactory.loadAndRun(pkg, paragraph); + String appId = helium.getApplicationFactory().loadAndRun(pkg, paragraph); return new JsonResponse(Response.Status.OK, "", appId).build(); } + @GET + @Path("visualizations/load") + @Produces("text/javascript") + public Response visualizationLoad(@QueryParam("refresh") String refresh) { + try { + File bundle; + if (refresh != null && refresh.equals("true")) { + bundle = helium.recreateVisualizationBundle(); + } else { + bundle = helium.getVisualizationFactory().getCurrentBundle(); + } + + if (bundle == null) { + return Response.ok().build(); + } else { + String visBundle = FileUtils.readFileToString(bundle); + return Response.ok(visBundle).build(); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + + // returning error will prevent zeppelin front-end render any notebook. + // visualization load fail doesn't need to block notebook rendering work. + // so it's better return ok instead of any error. + return Response.ok("ERROR: " + e.getMessage()).build(); + } + } + + @POST + @Path("enable/{packageName}") + public Response enablePackage(@PathParam("packageName") String packageName, + String artifact) { + try { + helium.enable(packageName, artifact); + return new JsonResponse(Response.Status.OK).build(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); + } catch (TaskRunnerException e) { + logger.error(e.getMessage(), e); + return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); + } + } + + @POST + @Path("disable/{packageName}") + public Response enablePackage(@PathParam("packageName") String packageName) { + try { + helium.disable(packageName); + return new JsonResponse(Response.Status.OK).build(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); + } catch (TaskRunnerException e) { + logger.error(e.getMessage(), e); + return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); + } + } + + @GET + @Path("visualizationOrder") + public Response getVisualizationPackageOrder() { + List order = helium.getVisualizationPackageOrder(); + return new JsonResponse(Response.Status.OK, order).build(); + } + + @POST + @Path("visualizationOrder") + public Response setVisualizationPackageOrder(String orderedPackageNameList) { + List orderedList = gson.fromJson( + orderedPackageNameList, new TypeToken>(){}.getType()); + + try { + helium.setVisualizationPackageOrder(orderedList); + } catch (IOException | TaskRunnerException e) { + logger.error(e.getMessage(), e); + return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); + } + return new JsonResponse(Response.Status.OK).build(); + } } 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 342d5f99c17..b173d0477c0 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java @@ -35,6 +35,7 @@ import org.apache.zeppelin.dep.DependencyResolver; import org.apache.zeppelin.helium.Helium; import org.apache.zeppelin.helium.HeliumApplicationFactory; +import org.apache.zeppelin.helium.HeliumVisualizationFactory; import org.apache.zeppelin.interpreter.InterpreterFactory; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; @@ -82,7 +83,6 @@ public class ZeppelinServer extends Application { public static Server jettyWebServer; public static NotebookServer notebookWsServer; public static Helium helium; - public static HeliumApplicationFactory heliumApplicationFactory; private SchedulerFactory schedulerFactory; private InterpreterFactory replFactory; @@ -98,8 +98,39 @@ public ZeppelinServer() throws Exception { this.depResolver = new DependencyResolver( conf.getString(ConfVars.ZEPPELIN_INTERPRETER_LOCALREPO)); - this.helium = new Helium(conf.getHeliumConfPath(), conf.getHeliumDefaultLocalRegistryPath()); - this.heliumApplicationFactory = new HeliumApplicationFactory(); + HeliumApplicationFactory heliumApplicationFactory = new HeliumApplicationFactory(); + HeliumVisualizationFactory heliumVisualizationFactory; + + if (isBinaryPackage(conf)) { + /* In binary package, zeppelin-web/src/app/visualization and zeppelin-web/src/app/tabledata + * are copied to lib/node_modules/zeppelin-vis, lib/node_modules/zeppelin-tabledata directory. + * Check zeppelin/zeppelin-distribution/src/assemble/distribution.xml to see how they're + * packaged into binary package. + */ + heliumVisualizationFactory = new HeliumVisualizationFactory( + new File(conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO)), + new File(conf.getRelativeDir("lib/node_modules/zeppelin-tabledata")), + new File(conf.getRelativeDir("lib/node_modules/zeppelin-vis"))); + } else { + heliumVisualizationFactory = new HeliumVisualizationFactory( + new File(conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO)), + new File(conf.getRelativeDir("zeppelin-web/src/app/tabledata")), + new File(conf.getRelativeDir("zeppelin-web/src/app/visualization"))); + } + + this.helium = new Helium( + conf.getHeliumConfPath(), + conf.getHeliumDefaultLocalRegistryPath(), + heliumVisualizationFactory, + heliumApplicationFactory); + + // create visualization bundle + try { + heliumVisualizationFactory.bundle(helium.getVisualizationPackagesToBundle()); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + this.schedulerFactory = new SchedulerFactory(); this.replFactory = new InterpreterFactory(conf, notebookWsServer, notebookWsServer, heliumApplicationFactory, depResolver, SecurityUtils.isAuthenticated()); @@ -333,7 +364,7 @@ public Set getSingletons() { NotebookRepoRestApi notebookRepoApi = new NotebookRepoRestApi(notebookRepo, notebookWsServer); singletons.add(notebookRepoApi); - HeliumRestApi heliumApi = new HeliumRestApi(helium, heliumApplicationFactory, notebook); + HeliumRestApi heliumApi = new HeliumRestApi(helium, notebook); singletons.add(heliumApi); InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory); @@ -353,5 +384,13 @@ public Set getSingletons() { return singletons; } + + /** + * Check if it is source build or binary package + * @return + */ + private static boolean isBinaryPackage(ZeppelinConfiguration conf) { + return !new File(conf.getRelativeDir("zeppelin-web")).isDirectory(); + } } diff --git a/zeppelin-web/.eslintrc b/zeppelin-web/.eslintrc index ca8cd072ef6..b4d39096993 100644 --- a/zeppelin-web/.eslintrc +++ b/zeppelin-web/.eslintrc @@ -26,7 +26,8 @@ "BootstrapDialog": false, "Handsontable": false, "moment": false, - "zeppelin" : false + "zeppelin" : false, + "process": false }, "rules": { "no-bitwise": 2, diff --git a/zeppelin-web/package.json b/zeppelin-web/package.json index 49bc0467690..e25f0c8ff67 100644 --- a/zeppelin-web/package.json +++ b/zeppelin-web/package.json @@ -12,8 +12,10 @@ "build": "grunt pre-webpack-dist && webpack && grunt post-webpack-dist", "predev": "grunt pre-webpack-dev", "dev:server": "webpack-dev-server --hot", + "visdev:server": "HELIUM_VIS_DEV=true webpack-dev-server --hot", "dev:watch": "grunt watch-webpack-dev", "dev": "npm-run-all --parallel dev:server dev:watch", + "visdev": "npm-run-all --parallel visdev:server dev:watch", "pretest": "npm install karma-phantomjs-launcher", "test": "karma start test/karma.conf.js" }, diff --git a/zeppelin-web/pom.xml b/zeppelin-web/pom.xml index fc13e53af36..f5fd73ec8f5 100644 --- a/zeppelin-web/pom.xml +++ b/zeppelin-web/pom.xml @@ -92,7 +92,8 @@ src/fonts/source-code-pro* src/fonts/google-fonts.css bower.json - package.json + **/package.json + **/.npmignore yarn.lock *.md diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index fcfed28ba0b..f68b3dafc8b 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -46,25 +46,35 @@ var zeppelinWebApp = angular.module('zeppelinWebApp', [ // withCredentials when running locally via grunt $httpProvider.defaults.withCredentials = true; + var visBundleLoad = { + load: ['heliumService', function(heliumService) { + return heliumService.load; + }] + }; + $routeProvider .when('/', { templateUrl: 'app/home/home.html' }) .when('/notebook/:noteId', { templateUrl: 'app/notebook/notebook.html', - controller: 'NotebookCtrl' + controller: 'NotebookCtrl', + resolve: visBundleLoad }) .when('/notebook/:noteId/paragraph?=:paragraphId', { templateUrl: 'app/notebook/notebook.html', - controller: 'NotebookCtrl' + controller: 'NotebookCtrl', + resolve: visBundleLoad }) .when('/notebook/:noteId/paragraph/:paragraphId?', { templateUrl: 'app/notebook/notebook.html', - controller: 'NotebookCtrl' + controller: 'NotebookCtrl', + resolve: visBundleLoad }) .when('/notebook/:noteId/revision/:revisionId', { templateUrl: 'app/notebook/notebook.html', - controller: 'NotebookCtrl' + controller: 'NotebookCtrl', + resolve: visBundleLoad }) .when('/jobmanager', { templateUrl: 'app/jobmanager/jobmanager.html', @@ -83,6 +93,10 @@ var zeppelinWebApp = angular.module('zeppelinWebApp', [ templateUrl: 'app/credential/credential.html', controller: 'CredentialCtrl' }) + .when('/helium', { + templateUrl: 'app/helium/helium.html', + controller: 'HeliumCtrl' + }) .when('/configuration', { templateUrl: 'app/configuration/configuration.html', controller: 'ConfigurationCtrl' diff --git a/zeppelin-web/src/app/helium/helium.controller.js b/zeppelin-web/src/app/helium/helium.controller.js new file mode 100644 index 00000000000..a344e801691 --- /dev/null +++ b/zeppelin-web/src/app/helium/helium.controller.js @@ -0,0 +1,219 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + + angular.module('zeppelinWebApp').controller('HeliumCtrl', HeliumCtrl); + + HeliumCtrl.$inject = ['$scope', '$rootScope', '$sce', 'baseUrlSrv', 'ngToast', 'heliumService']; + + function HeliumCtrl($scope, $rootScope, $sce, baseUrlSrv, ngToast, heliumService) { + $scope.packageInfos = {}; + $scope.defaultVersions = {}; + $scope.showVersions = {}; + $scope.visualizationOrder = []; + $scope.visualizationOrderChanged = false; + + var buildDefaultVersionListToDisplay = function(packageInfos) { + var defaultVersions = {}; + // show enabled version if any version of package is enabled + for (var name in packageInfos) { + var pkgs = packageInfos[name]; + for (var pkgIdx in pkgs) { + var pkg = pkgs[pkgIdx]; + pkg.pkg.icon = $sce.trustAsHtml(pkg.pkg.icon); + if (pkg.enabled) { + defaultVersions[name] = pkg; + pkgs.splice(pkgIdx, 1); + break; + } + } + + // show first available version if package is not enabled + if (!defaultVersions[name]) { + defaultVersions[name] = pkgs[0]; + pkgs.splice(0, 1); + } + } + $scope.defaultVersions = defaultVersions; + }; + + var getAllPackageInfo = function() { + heliumService.getAllPackageInfo(). + success(function(data, status) { + $scope.packageInfos = data.body; + buildDefaultVersionListToDisplay($scope.packageInfos); + }). + error(function(data, status) { + console.log('Can not load package info %o %o', status, data); + }); + }; + + var getVisualizationOrder = function() { + heliumService.getVisualizationOrder(). + success(function(data, status) { + $scope.visualizationOrder = data.body; + }). + error(function(data, status) { + console.log('Can not get visualization order %o %o', status, data); + }); + }; + + $scope.visualizationOrderListeners = { + accept: function(sourceItemHandleScope, destSortableScope) {return true;}, + itemMoved: function(event) {}, + orderChanged: function(event) { + $scope.visualizationOrderChanged = true; + } + }; + + var init = function() { + getAllPackageInfo(); + getVisualizationOrder(); + $scope.visualizationOrderChanged = false; + }; + + init(); + + $scope.saveVisualizationOrder = function() { + var confirm = BootstrapDialog.confirm({ + closable: false, + closeByBackdrop: false, + closeByKeyboard: false, + title: '', + message: 'Save changes?', + callback: function(result) { + if (result) { + confirm.$modalFooter.find('button').addClass('disabled'); + confirm.$modalFooter.find('button:contains("OK")') + .html(' Enabling'); + heliumService.setVisualizationOrder($scope.visualizationOrder). + success(function(data, status) { + init(); + confirm.close(); + }). + error(function(data, status) { + confirm.close(); + console.log('Failed to save order'); + BootstrapDialog.show({ + title: 'Error on saving order ', + message: data.message + }); + }); + return false; + } + } + }); + } + + var getLicense = function(name, artifact) { + var pkg = _.filter($scope.defaultVersions[name], function(p) { + return p.artifact === artifact; + }); + + var license; + if (pkg.length === 0) { + pkg = _.filter($scope.packageInfos[name], function(p) { + return p.pkg.artifact === artifact; + }); + + if (pkg.length > 0) { + license = pkg[0].pkg.license; + } + } else { + license = pkg[0].license; + } + + if (!license) { + license = 'Unknown'; + } + return license; + } + + $scope.enable = function(name, artifact) { + var license = getLicense(name, artifact); + + var confirm = BootstrapDialog.confirm({ + closable: false, + closeByBackdrop: false, + closeByKeyboard: false, + title: '', + message: 'Do you want to enable ' + name + '?' + + '
    ' + artifact + '
    ' + + '
    License
    ' + + '
    ' + license + '
    ', + callback: function(result) { + if (result) { + confirm.$modalFooter.find('button').addClass('disabled'); + confirm.$modalFooter.find('button:contains("OK")') + .html(' 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(' 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; + } + }; + } +})(); diff --git a/zeppelin-web/src/app/helium/helium.css b/zeppelin-web/src/app/helium/helium.css new file mode 100644 index 00000000000..f17d6bd31e0 --- /dev/null +++ b/zeppelin-web/src/app/helium/helium.css @@ -0,0 +1,107 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.heliumPackageContainer { + padding-bottom: 0px; + margin-bottom: 0px; +} + +.heliumPackageList { + min-height: 25px; + margin-bottom: 15px; + border-bottom: 1px solid #EFEFEF; + padding-bottom: 13px; +} + +.heliumPackageList:last-child { + border-bottom: none; +} + +.heliumPackageList .heliumPackageHead { + height: 30px; +} + +.heliumPackageList .heliumPackageHead .btn { + margin-top: 5px; +} + +.heliumPackageList .heliumPackageIcon { + float: left; + width: 28px; + height: 22px; + padding: 5px 2px 0px 2px; +} + +.heliumPackageList .heliumPackageName { + font-size: 20px; + font-weight: bold; + color: #3071a9; + float: left; + margin-top: 0; +} + +.heliumPackageList .heliumPackageName span { + font-size: 10px; + color: #AAAAAA; +} + + +.heliumPackageList .heliumPackageDisabledArtifact { + color:gray; +} + +.heliumPackageList .heliumPackageEnabledArtifact { + color:#444444; +} + +.heliumPackageList .heliumPackageEnabledArtifact span, +.heliumPackageList .heliumPackageDisabledArtifact span { + margin-left:3px; + cursor:pointer; + text-decoration: + underline;color:#3071a9 +} + +.heliumPackageList .heliumPackageDescription { + margin-top: 10px; +} + +.heliumVisualizationOrder { + display: inline-block; +} + +.heliumVisualizationOrder .as-sortable-item, +.heliumVisualizationOrder .as-sortable-placeholder { + display: inline-block; + float: left; +} + +.sortable-row .as-sortable-item-handle { + width: 35px; + height: 30px; +} + +.sortable-row .as-sortable-item svg { + width: 100%; + height: 100%; +} + +.heliumVisualizationOrder .saveLink { + margin-left:10px; + margin-top:5px; + cursor:pointer; + text-decoration: + underline;color:#3071a9; + display: inline-block; +} diff --git a/zeppelin-web/src/app/helium/helium.html b/zeppelin-web/src/app/helium/helium.html new file mode 100644 index 00000000000..546995cb44b --- /dev/null +++ b/zeppelin-web/src/app/helium/helium.html @@ -0,0 +1,86 @@ + +
    +
    +
    +
    +

    + Helium +

    +
    +
    +
    +
    Visualization package display order (drag and drop to reorder)
    +
    +
    +
    +
    +
    + + save + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    {{pkgName}} {{pkgInfo.pkg.type}}
    +
    Enable
    +
    Disable
    +
    +
    + {{pkgInfo.pkg.artifact}} + + versions + +
    +
      +
    • + {{pkg.pkg.artifact}} - + + enable + +
    • +
    +
    + {{pkgInfo.pkg.description}} +
    +
    +
    +
    diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html index b48e0d7a042..0d2afb7b530 100644 --- a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html +++ b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html @@ -14,13 +14,14 @@
    + class="result-chart-selector">
    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 e7931997b56..6d56fe40a37 100644 --- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js @@ -34,16 +34,18 @@ ResultCtrl.$inject = [ '$http', '$q', '$templateRequest', + '$sce', 'websocketMsgSrv', 'baseUrlSrv', 'ngToast', 'saveAsService', - 'noteVarShareService' + 'noteVarShareService', + 'heliumService' ]; function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location, - $timeout, $compile, $http, $q, $templateRequest, websocketMsgSrv, - baseUrlSrv, ngToast, saveAsService, noteVarShareService) { + $timeout, $compile, $http, $q, $templateRequest, $sce, websocketMsgSrv, + baseUrlSrv, ngToast, saveAsService, noteVarShareService, heliumService) { /** * Built-in visualizations @@ -52,36 +54,36 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location { id: 'table', // paragraph.config.graph.mode name: 'Table', // human readable name. tooltip - icon: 'fa fa-table' + icon: '' }, { id: 'multiBarChart', name: 'Bar Chart', - icon: 'fa fa-bar-chart', + icon: '', transformation: 'pivot' }, { id: 'pieChart', name: 'Pie Chart', - icon: 'fa fa-pie-chart', + icon: '', transformation: 'pivot' }, { id: 'stackedAreaChart', name: 'Area Chart', - icon: 'fa fa-area-chart', + icon: '', transformation: 'pivot' }, { id: 'lineChart', name: 'Line Chart', - icon: 'fa fa-line-chart', + icon: '', transformation: 'pivot' }, { id: 'scatterChart', name: 'Scatter Chart', - icon: 'cf cf-scatter-chart' + icon: '' } ]; @@ -150,6 +152,21 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location $scope.init = function(result, config, paragraph, index) { console.log('result controller init %o %o %o', result, config, index); + + // register helium plugin vis + var heliumVis = heliumService.get(); + console.log('Helium visualizations %o', heliumVis); + heliumVis.forEach(function(vis) { + $scope.builtInTableDataVisualizationList.push({ + id: vis.id, + name: vis.name, + icon: $sce.trustAsHtml(vis.icon) + }); + builtInVisualizations[vis.id] = { + class: vis.class + }; + }); + updateData(result, config, paragraph, index); renderResult($scope.type); }; @@ -421,7 +438,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location builtInViz.instance.resize(); }); } catch (err) { - console.log('Graph drawing error %o', err); + console.error('Graph drawing error %o', err); } } else { $timeout(retryRenderer, 10); diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.css b/zeppelin-web/src/app/notebook/paragraph/result/result.css index 91338bae34a..905b88c1d4b 100644 --- a/zeppelin-web/src/app/notebook/paragraph/result/result.css +++ b/zeppelin-web/src/app/notebook/paragraph/result/result.css @@ -12,9 +12,31 @@ * limitations under the License. */ -.chart-selector { +.result-chart-selector { margin-bottom: 10px; position: relative; display: inline-block; vertical-align: middle; } + +.result-chart-selector button { + width: 35px; + height: 30px; +} + +.result-chart-selector button svg { + width: 100%; + height: 100%; +} + +.rotate90 { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); +} + +.rotate90flipX { + -webkit-transform: rotate(90deg) scaleX(-1); + -moz-transform: rotate(90deg) scaleX(-1); + -ms-transform: rotate(90deg) scaleX(-1); +} diff --git a/zeppelin-web/src/app/tabledata/.npmignore b/zeppelin-web/src/app/tabledata/.npmignore new file mode 100644 index 00000000000..0b84df0f025 --- /dev/null +++ b/zeppelin-web/src/app/tabledata/.npmignore @@ -0,0 +1 @@ +*.html \ No newline at end of file diff --git a/zeppelin-web/src/app/tabledata/package.json b/zeppelin-web/src/app/tabledata/package.json new file mode 100644 index 00000000000..e92abb2a995 --- /dev/null +++ b/zeppelin-web/src/app/tabledata/package.json @@ -0,0 +1,15 @@ +{ + "name": "zeppelin-tabledata", + "description": "tabledata api", + "version": "0.7.0-SNAPSHOT", + "main": "tabledata", + "dependencies": { + "json3": "~3.3.1", + "lodash": "~3.9.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/apache/zeppelin" + }, + "license": "Apache-2.0" +} diff --git a/zeppelin-web/src/app/visualization/.npmignore b/zeppelin-web/src/app/visualization/.npmignore new file mode 100644 index 00000000000..2d19fc766d9 --- /dev/null +++ b/zeppelin-web/src/app/visualization/.npmignore @@ -0,0 +1 @@ +*.html diff --git a/zeppelin-web/src/app/visualization/package.json b/zeppelin-web/src/app/visualization/package.json new file mode 100644 index 00000000000..c0fb9d327c6 --- /dev/null +++ b/zeppelin-web/src/app/visualization/package.json @@ -0,0 +1,16 @@ +{ + "name": "zeppelin-vis", + "description": "Visualization API", + "version": "0.7.0-SNAPSHOT", + "main": "visualization", + "dependencies": { + "json3": "~3.3.1", + "nvd3": "~1.7.1", + "lodash": "~3.9.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/apache/zeppelin" + }, + "license": "Apache-2.0" +} diff --git a/zeppelin-web/src/components/helium/helium.service.js b/zeppelin-web/src/components/helium/helium.service.js new file mode 100644 index 00000000000..ae44425acae --- /dev/null +++ b/zeppelin-web/src/components/helium/helium.service.js @@ -0,0 +1,62 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + + angular.module('zeppelinWebApp').service('heliumService', heliumService); + + heliumService.$inject = ['$http', 'baseUrlSrv', 'ngToast']; + + function heliumService($http, baseUrlSrv, ngToast) { + + var url = baseUrlSrv.getRestApiBase() + '/helium/visualizations/load'; + if (process.env.HELIUM_VIS_DEV) { + url = url + '?refresh=true'; + } + var visualizations = []; + + // load should be promise + this.load = $http.get(url).success(function(response) { + if (response.substring(0, 'ERROR:'.length) !== 'ERROR:') { + eval(response); + } else { + console.error(response); + } + }); + + this.get = function() { + return visualizations; + }; + + this.getVisualizationOrder = function() { + return $http.get(baseUrlSrv.getRestApiBase() + '/helium/visualizationOrder'); + }; + + this.setVisualizationOrder = function(list) { + return $http.post(baseUrlSrv.getRestApiBase() + '/helium/visualizationOrder', list); + }; + + this.getAllPackageInfo = function() { + return $http.get(baseUrlSrv.getRestApiBase() + '/helium/all'); + }; + + this.enable = function(name, artifact) { + return $http.post(baseUrlSrv.getRestApiBase() + '/helium/enable/' + name, artifact); + }; + + this.disable = function(name) { + return $http.post(baseUrlSrv.getRestApiBase() + '/helium/disable/' + name); + }; + }; +})(); diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index a4770559693..cf8b5b6f65b 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -94,6 +94,7 @@
  • Interpreter
  • Notebook Repos
  • Credential
  • +
  • Helium
  • Configuration
  • Logout
  • diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 3769d657305..0bc51297962 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -61,6 +61,7 @@ + diff --git a/zeppelin-web/src/index.js b/zeppelin-web/src/index.js index 95d799dd7d8..191d2e36694 100644 --- a/zeppelin-web/src/index.js +++ b/zeppelin-web/src/index.js @@ -45,6 +45,7 @@ import './app/notebook/paragraph/paragraph.controller.js'; import './app/notebook/paragraph/result/result.controller.js'; import './app/search/result-list.controller.js'; import './app/notebookRepos/notebookRepos.controller.js'; +import './app/helium/helium.controller.js'; import './components/arrayOrderingSrv/arrayOrdering.service.js'; import './components/clipboard/clipboard.controller.js'; import './components/navbar/navbar.controller.js'; @@ -73,3 +74,4 @@ import './components/noteAction/noteAction.service.js'; import './components/notevarshareService/notevarshare.service.js'; import './components/rename/rename.controller.js'; import './components/rename/rename.service.js'; +import './components/helium/helium.service.js'; diff --git a/zeppelin-web/webpack.config.js b/zeppelin-web/webpack.config.js index 0efc7211bdc..d3a2681dfce 100644 --- a/zeppelin-web/webpack.config.js +++ b/zeppelin-web/webpack.config.js @@ -205,7 +205,14 @@ module.exports = function makeWebpackConfig () { // Reference: https://github.com/webpack/extract-text-webpack-plugin // Extract css files // Disabled when in test mode or not in build mode - new ExtractTextPlugin('[name].[hash].css', {disable: !isProd}) + new ExtractTextPlugin('[name].[hash].css', {disable: !isProd}), + + // Reference: https://webpack.github.io/docs/list-of-plugins.html#defineplugin + new webpack.DefinePlugin({ + 'process.env': { + HELIUM_VIS_DEV: process.env.HELIUM_VIS_DEV + } + }) ) } diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index 07fa2df42de..d620512825f 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -45,6 +45,7 @@ 0.9.8 1.4.01 4.1.1.201511131810-r + 1.3 0.27 @@ -217,6 +218,26 @@ ${eclipse.jgit.version} + + com.github.eirslett + frontend-maven-plugin + ${frontend.maven.plugin.version} + + + org.codehaus.plexus + plexus-utils + + + org.apache.maven + maven-plugin-api + + + org.apache.maven + maven-artifact + + + + junit junit @@ -256,7 +277,6 @@ ${jetty.version} test - 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 79eb3a6aecf..8ef30c871e5 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java @@ -16,6 +16,7 @@ */ package org.apache.zeppelin.helium; +import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.commons.io.FileUtils; @@ -32,10 +33,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Manages helium packages @@ -48,10 +46,19 @@ public class Helium { private final String heliumConfPath; private final String defaultLocalRegistryPath; private final Gson gson; + private final HeliumVisualizationFactory visualizationFactory; + private final HeliumApplicationFactory applicationFactory; - public Helium(String heliumConfPath, String defaultLocalRegistryPath) throws IOException { + public Helium( + String heliumConfPath, + String defaultLocalRegistryPath, + HeliumVisualizationFactory visualizationFactory, + HeliumApplicationFactory applicationFactory) + throws IOException, TaskRunnerException { this.heliumConfPath = heliumConfPath; this.defaultLocalRegistryPath = defaultLocalRegistryPath; + this.visualizationFactory = visualizationFactory; + this.applicationFactory = applicationFactory; GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); @@ -83,6 +90,14 @@ public List getAllRegistry() { } } + public HeliumApplicationFactory getApplicationFactory() { + return applicationFactory; + } + + public HeliumVisualizationFactory getVisualizationFactory() { + return visualizationFactory; + } + private synchronized HeliumConf loadConf(String path) throws IOException { File heliumConfFile = new File(path); if (!heliumConfFile.isFile()) { @@ -104,6 +119,7 @@ private synchronized HeliumConf loadConf(String path) throws IOException { public synchronized void save() throws IOException { String jsonString; synchronized (registry) { + clearNotExistsPackages(); heliumConf.setRegistry(registry); jsonString = gson.toJson(heliumConf); } @@ -116,20 +132,118 @@ public synchronized void save() throws IOException { FileUtils.writeStringToFile(heliumConfFile, jsonString); } - public List getAllPackageInfo() { - List list = new LinkedList<>(); + private void clearNotExistsPackages() { + Map> all = getAllPackageInfo(); + + // clear visualization display order + List packageOrder = heliumConf.getVisualizationDisplayOrder(); + List clearedOrder = new LinkedList<>(); + for (String pkgName : packageOrder) { + if (all.containsKey(pkgName)) { + clearedOrder.add(pkgName); + } + } + heliumConf.setVisualizationDisplayOrder(clearedOrder); + + // clear enabled package + Map enabledPackages = heliumConf.getEnabledPackages(); + for (String pkgName : enabledPackages.keySet()) { + if (!all.containsKey(pkgName)) { + heliumConf.disablePackage(pkgName); + } + } + } + + public Map> getAllPackageInfo() { + Map enabledPackageInfo = heliumConf.getEnabledPackages(); + + Map> map = new HashMap<>(); synchronized (registry) { for (HeliumRegistry r : registry) { try { for (HeliumPackage pkg : r.getAll()) { - list.add(new HeliumPackageSearchResult(r.name(), pkg)); + String name = pkg.getName(); + String artifact = enabledPackageInfo.get(name); + boolean enabled = (artifact != null && artifact.equals(pkg.getArtifact())); + + if (!map.containsKey(name)) { + map.put(name, new LinkedList()); + } + map.get(name).add(new HeliumPackageSearchResult(r.name(), pkg, enabled)); } } catch (IOException e) { logger.error(e.getMessage(), e); } } } - return list; + + // sort version (artifact) + for (String name : map.keySet()) { + List packages = map.get(name); + Collections.sort(packages, new Comparator() { + @Override + public int compare(HeliumPackageSearchResult o1, HeliumPackageSearchResult o2) { + return o2.getPkg().getArtifact().compareTo(o1.getPkg().getArtifact()); + } + }); + } + return map; + } + + public HeliumPackageSearchResult getPackageInfo(String name, String artifact) { + Map> infos = getAllPackageInfo(); + List packages = infos.get(name); + if (artifact == null) { + return packages.get(0); + } else { + for (HeliumPackageSearchResult pkg : packages) { + if (pkg.getPkg().getArtifact().equals(artifact)) { + return pkg; + } + } + } + + return null; + } + + public File recreateVisualizationBundle() throws IOException, TaskRunnerException { + return visualizationFactory.bundle(getVisualizationPackagesToBundle(), true); + } + + public void enable(String name, String artifact) throws IOException, TaskRunnerException { + HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact); + + // no package found. + if (pkgInfo == null) { + return; + } + + // enable package + heliumConf.enablePackage(name, artifact); + + // if package is visualization, rebuild bundle + if (pkgInfo.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) { + visualizationFactory.bundle(getVisualizationPackagesToBundle()); + } + + save(); + } + + public void disable(String name) throws IOException, TaskRunnerException { + String artifact = heliumConf.getEnabledPackages().get(name); + + if (artifact == null) { + return; + } + + heliumConf.disablePackage(name); + + HeliumPackageSearchResult pkg = getPackageInfo(name, artifact); + if (pkg == null || pkg.getPkg().getType() == HeliumPackage.Type.VISUALIZATION) { + visualizationFactory.bundle(getVisualizationPackagesToBundle()); + } + + save(); } public HeliumPackageSuggestion suggestApp(Paragraph paragraph) { @@ -153,20 +267,89 @@ public HeliumPackageSuggestion suggestApp(Paragraph paragraph) { allResources = ResourcePoolUtils.getAllResources(); } - for (HeliumPackageSearchResult pkg : getAllPackageInfo()) { - ResourceSet resources = ApplicationLoader.findRequiredResourceSet( - pkg.getPkg().getResources(), - paragraph.getNote().getId(), - paragraph.getId(), - allResources); - if (resources == null) { - continue; - } else { - suggestion.addAvailablePackage(pkg); + for (List pkgs : getAllPackageInfo().values()) { + for (HeliumPackageSearchResult pkg : pkgs) { + if (pkg.getPkg().getType() == HeliumPackage.Type.APPLICATION && pkg.isEnabled()) { + ResourceSet resources = ApplicationLoader.findRequiredResourceSet( + pkg.getPkg().getResources(), + paragraph.getNote().getId(), + paragraph.getId(), + allResources); + if (resources == null) { + continue; + } else { + suggestion.addAvailablePackage(pkg); + } + break; + } } } suggestion.sort(); return suggestion; } + + /** + * Get enabled visualization packages + * + * @return ordered list of enabled visualization package + */ + public List getVisualizationPackagesToBundle() { + Map> allPackages = getAllPackageInfo(); + List visOrder = heliumConf.getVisualizationDisplayOrder(); + + List orderedVisualizationPackages = new LinkedList<>(); + + // add enabled packages in visOrder + for (String name : visOrder) { + List versions = allPackages.get(name); + if (versions == null) { + continue; + } + for (HeliumPackageSearchResult pkgInfo : versions) { + if (pkgInfo.getPkg().getType() == HeliumPackage.Type.VISUALIZATION && pkgInfo.isEnabled()) { + orderedVisualizationPackages.add(pkgInfo.getPkg()); + allPackages.remove(name); + break; + } + } + } + + // add enabled packages not in visOrder + for (List pkgs : allPackages.values()) { + for (HeliumPackageSearchResult pkg : pkgs) { + if (pkg.getPkg().getType() == HeliumPackage.Type.VISUALIZATION && pkg.isEnabled()) { + orderedVisualizationPackages.add(pkg.getPkg()); + break; + } + } + } + + return orderedVisualizationPackages; + } + + /** + * Get enabled package list in order + * @return + */ + public List getVisualizationPackageOrder() { + List orderedPackageList = new LinkedList<>(); + List packages = getVisualizationPackagesToBundle(); + + for (HeliumPackage pkg : packages) { + orderedPackageList.add(pkg.getName()); + } + + return orderedPackageList; + } + + public void setVisualizationPackageOrder(List orderedPackageList) + throws IOException, TaskRunnerException { + heliumConf.setVisualizationDisplayOrder(orderedPackageList); + + // if package is visualization, rebuild bundle + visualizationFactory.bundle(getVisualizationPackagesToBundle()); + + save(); + } } 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 2c535d4c531..d3b0fb4c818 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumConf.java @@ -16,8 +16,7 @@ */ package org.apache.zeppelin.helium; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * Helium config. This object will be persisted to conf/heliumc.conf @@ -25,6 +24,13 @@ public class HeliumConf { List registry = new LinkedList<>(); + // enabled packages {name, version} + Map enabled = Collections.synchronizedMap(new HashMap()); + + // enabled visualization package display order + List visualizationDisplayOrder = new LinkedList<>(); + + public List getRegistry() { return registry; } @@ -32,4 +38,36 @@ public List getRegistry() { public void setRegistry(List registry) { this.registry = registry; } + + public Map getEnabledPackages() { + return new HashMap<>(enabled); + } + + public void enablePackage(HeliumPackage pkg) { + enablePackage(pkg.getName(), pkg.getArtifact()); + } + + public void enablePackage(String name, String artifact) { + enabled.put(name, artifact); + } + + public void disablePackage(HeliumPackage pkg) { + disablePackage(pkg.getName()); + } + + public void disablePackage(String name) { + enabled.remove(name); + } + + public List getVisualizationDisplayOrder() { + if (visualizationDisplayOrder == null) { + return new LinkedList(); + } else { + return visualizationDisplayOrder; + } + } + + public void setVisualizationDisplayOrder(List orderedPackageList) { + visualizationDisplayOrder = orderedPackageList; + } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java index 57a9d4512f4..36b61154f6c 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumPackageSearchResult.java @@ -22,15 +22,17 @@ public class HeliumPackageSearchResult { private final String registry; private final HeliumPackage pkg; + private final boolean enabled; /** * Create search result item * @param registry registry name * @param pkg package information */ - public HeliumPackageSearchResult(String registry, HeliumPackage pkg) { + public HeliumPackageSearchResult(String registry, HeliumPackage pkg, boolean enabled) { this.registry = registry; this.pkg = pkg; + this.enabled = enabled; } public String getRegistry() { @@ -40,4 +42,8 @@ public String getRegistry() { public HeliumPackage getPkg() { return pkg; } + + public boolean isEnabled() { + return enabled; + } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java new file mode 100644 index 00000000000..a06c18b733f --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.zeppelin.helium; + +import com.github.eirslett.maven.plugins.frontend.lib.*; +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.google.gson.Gson; +import org.apache.commons.io.FileUtils; +import org.apache.log4j.Appender; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.WriterAppender; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.URL; +import java.util.*; + +/** + * Load helium visualization + */ +public class HeliumVisualizationFactory { + Logger logger = LoggerFactory.getLogger(HeliumVisualizationFactory.class); + private final String NODE_VERSION = "v6.9.1"; + private final String NPM_VERSION = "3.10.8"; + private final String DEFAULT_NPM_REGISTRY_URL = "http://registry.npmjs.org/"; + + private final FrontendPluginFactory frontEndPluginFactory; + private final File workingDirectory; + private File tabledataModulePath; + private File visualizationModulePath; + private Gson gson; + + String bundleCacheKey = ""; + File currentBundle; + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + public HeliumVisualizationFactory( + File moduleDownloadPath, + File tabledataModulePath, + File visualizationModulePath) throws TaskRunnerException { + this(moduleDownloadPath); + this.tabledataModulePath = tabledataModulePath; + this.visualizationModulePath = visualizationModulePath; + } + + public HeliumVisualizationFactory(File moduleDownloadPath) throws TaskRunnerException { + this.workingDirectory = new File(moduleDownloadPath, "vis"); + File installDirectory = workingDirectory; + + frontEndPluginFactory = new FrontendPluginFactory( + workingDirectory, installDirectory); + + currentBundle = new File(workingDirectory, "vis.bundle.cache.js"); + gson = new Gson(); + installNodeAndNpm(); + configureLogger(); + } + + private void installNodeAndNpm() { + try { + NPMInstaller npmInstaller = frontEndPluginFactory.getNPMInstaller(getProxyConfig()); + npmInstaller.setNpmVersion(NPM_VERSION); + npmInstaller.install(); + + NodeInstaller nodeInstaller = frontEndPluginFactory.getNodeInstaller(getProxyConfig()); + nodeInstaller.setNodeVersion(NODE_VERSION); + nodeInstaller.install(); + } catch (InstallationException e) { + logger.error(e.getMessage(), e); + } + } + + private ProxyConfig getProxyConfig() { + List proxy = new LinkedList<>(); + return new ProxyConfig(proxy); + } + + public File bundle(List pkgs) throws IOException, TaskRunnerException { + return bundle(pkgs, false); + } + + public synchronized File bundle(List pkgs, boolean forceRefresh) + throws IOException, TaskRunnerException { + // package.json + URL pkgUrl = Resources.getResource("helium/package.json"); + String pkgJson = Resources.toString(pkgUrl, Charsets.UTF_8); + StringBuilder dependencies = new StringBuilder(); + + FileFilter npmPackageCopyFilter = new FileFilter() { + @Override + public boolean accept(File pathname) { + String fileName = pathname.getName(); + if (fileName.startsWith(".") || fileName.startsWith("#") || fileName.startsWith("~")) { + return false; + } else { + return true; + } + } + }; + + for (HeliumPackage pkg : pkgs) { + String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg); + if (moduleNameVersion == null) { + logger.error("Can't get module name and version of package " + pkg.getName()); + continue; + } + if (dependencies.length() > 0) { + dependencies.append(",\n"); + } + dependencies.append("\"" + moduleNameVersion[0] + "\": \"" + moduleNameVersion[1] + "\""); + + if (isLocalPackage(pkg)) { + FileUtils.copyDirectory( + new File(pkg.getArtifact()), + new File(workingDirectory, "node_modules/" + pkg.getName()), + npmPackageCopyFilter); + } + } + pkgJson = pkgJson.replaceFirst("DEPENDENCIES", dependencies.toString()); + + // check if we can use previous bundle or not + if (dependencies.toString().equals(bundleCacheKey) && currentBundle.isFile() && !forceRefresh) { + return currentBundle; + } + + // webpack.config.js + URL webpackConfigUrl = Resources.getResource("helium/webpack.config.js"); + String webpackConfig = Resources.toString(webpackConfigUrl, Charsets.UTF_8); + + // generate load.js + StringBuilder loadJsImport = new StringBuilder(); + StringBuilder loadJsRegister = new StringBuilder(); + + long idx = 0; + for (HeliumPackage pkg : pkgs) { + String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg); + if (moduleNameVersion == null) { + continue; + } + + String className = "vis" + idx++; + loadJsImport.append( + "import " + className + " from \"" + moduleNameVersion[0] + "\"\n"); + + loadJsRegister.append("visualizations.push({\n"); + loadJsRegister.append("id: \"" + moduleNameVersion[0] + "\",\n"); + loadJsRegister.append("name: \"" + pkg.getName() + "\",\n"); + loadJsRegister.append("icon: " + gson.toJson(pkg.getIcon()) + ",\n"); + loadJsRegister.append("class: " + className + "\n"); + loadJsRegister.append("})\n"); + } + + FileUtils.write(new File(workingDirectory, "package.json"), pkgJson); + FileUtils.write(new File(workingDirectory, "webpack.config.js"), webpackConfig); + FileUtils.write(new File(workingDirectory, "load.js"), + loadJsImport.append(loadJsRegister).toString()); + + // install tabledata module + File tabledataModuleInstallPath = new File(workingDirectory, + "node_modules/zeppelin-tabledata"); + if (tabledataModulePath != null && !tabledataModuleInstallPath.exists()) { + FileUtils.copyDirectory( + tabledataModulePath, + tabledataModuleInstallPath, + npmPackageCopyFilter); + } + + // install visualization module + File visModuleInstallPath = new File(workingDirectory, + "node_modules/zeppelin-vis"); + if (visualizationModulePath != null && !visModuleInstallPath.exists()) { + FileUtils.copyDirectory(visualizationModulePath, visModuleInstallPath, npmPackageCopyFilter); + } + + out.reset(); + npmCommand("install"); + npmCommand("run bundle"); + + File visBundleJs = new File(workingDirectory, "vis.bundle.js"); + if (!visBundleJs.isFile()) { + throw new IOException( + "Can't create visualization bundle : \n" + new String(out.toByteArray())); + } + + WebpackResult result = getWebpackResultFromOutput(new String(out.toByteArray())); + if (result.errors.length > 0) { + visBundleJs.delete(); + throw new IOException(result.errors[0]); + } + + synchronized (this) { + currentBundle.delete(); + FileUtils.moveFile(visBundleJs, currentBundle); + bundleCacheKey = dependencies.toString(); + } + return currentBundle; + } + + private WebpackResult getWebpackResultFromOutput(String output) { + BufferedReader reader = new BufferedReader(new StringReader(output)); + + String line; + boolean webpackRunDetected = false; + boolean resultJsonDetected = false; + StringBuffer sb = new StringBuffer(); + try { + while ((line = reader.readLine()) != null) { + if (!webpackRunDetected) { + if (line.contains("webpack.js") && line.endsWith("--json")) { + webpackRunDetected = true; + } + continue; + } + + if (!resultJsonDetected) { + if (line.equals("{")) { + sb.append(line); + resultJsonDetected = true; + } + continue; + } + + if (resultJsonDetected && webpackRunDetected) { + sb.append(line); + } + } + + Gson gson = new Gson(); + return gson.fromJson(sb.toString(), WebpackResult.class); + } catch (IOException e) { + logger.error(e.getMessage(), e); + return new WebpackResult(); + } + } + + public File getCurrentBundle() { + synchronized (this) { + if (currentBundle.isFile()) { + return currentBundle; + } else { + return null; + } + } + } + + private boolean isLocalPackage(HeliumPackage pkg) { + return (pkg.getArtifact().startsWith(".") || pkg.getArtifact().startsWith("/")); + } + + private String[] getNpmModuleNameAndVersion(HeliumPackage pkg) { + String artifact = pkg.getArtifact(); + + if (isLocalPackage(pkg)) { + File packageJson = new File(artifact, "package.json"); + if (!packageJson.isFile()) { + return null; + } + Gson gson = new Gson(); + try { + NpmPackage npmPackage = gson.fromJson( + FileUtils.readFileToString(packageJson), + NpmPackage.class); + + String[] nameVersion = new String[2]; + nameVersion[0] = npmPackage.name; + nameVersion[1] = npmPackage.version; + return nameVersion; + } catch (IOException e) { + logger.error(e.getMessage(), e); + return null; + } + } else { + String[] nameVersion = new String[2]; + + int pos; + if ((pos = artifact.indexOf('@')) > 0) { + nameVersion[0] = artifact.substring(0, pos); + nameVersion[1] = artifact.substring(pos + 1); + } else if ( + (pos = artifact.indexOf('^')) > 0 || + (pos = artifact.indexOf('~')) > 0) { + nameVersion[0] = artifact.substring(0, pos); + nameVersion[1] = artifact.substring(pos); + } else { + nameVersion[0] = artifact; + nameVersion[1] = ""; + } + return nameVersion; + } + } + + public synchronized void install(HeliumPackage pkg) throws TaskRunnerException { + npmCommand("install " + pkg.getArtifact()); + } + + private void npmCommand(String args) throws TaskRunnerException { + npmCommand(args, new HashMap()); + } + + private void npmCommand(String args, Map 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 + )); + } +} diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java new file mode 100644 index 00000000000..271f73f3b1d --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/NpmPackage.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.zeppelin.helium; + +import java.util.Map; + +/** + * To read package.json + */ +public class NpmPackage { + public String name; + public String version; + public Map dependencies; +} diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java new file mode 100644 index 00000000000..34142092782 --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/WebpackResult.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.zeppelin.helium; + +/** + * Represetns webpack json format result + */ +public class WebpackResult { + public final String [] errors = new String[0]; + public final String [] warnings = new String[0]; +} diff --git a/zeppelin-zengine/src/main/resources/helium/package.json b/zeppelin-zengine/src/main/resources/helium/package.json new file mode 100644 index 00000000000..e6ec612aa36 --- /dev/null +++ b/zeppelin-zengine/src/main/resources/helium/package.json @@ -0,0 +1,15 @@ +{ + "name": "zeppelin-vis-bundle", + "main": "load", + "scripts": { + "bundle": "node/node node_modules/webpack/bin/webpack.js --display-error-details --json" + }, + "dependencies": { + DEPENDENCIES + }, + "devDependencies": { + "webpack": "^1.12.2", + "babel": "^5.8.23", + "babel-loader": "^5.3.2" + } +} diff --git a/zeppelin-zengine/src/main/resources/helium/webpack.config.js b/zeppelin-zengine/src/main/resources/helium/webpack.config.js new file mode 100644 index 00000000000..80b8c6a2155 --- /dev/null +++ b/zeppelin-zengine/src/main/resources/helium/webpack.config.js @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +module.exports = { + entry: ['./'], + output: { + path: './', + filename: 'vis.bundle.js', + }, + resolve: { + root: __dirname + "/node_modules", + extensions: [".js"] + }, + module: { + loaders: [{ + test: /\.js$/, + //exclude: /node_modules/, + loader: 'babel-loader' + }] + } +} 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 73c78d33fc0..2588c4c1d4a 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java @@ -134,7 +134,8 @@ public void testLoadRunUnloadApplication() "desc1", "", HeliumTestApplication.class.getName(), - new String[][]{}); + new String[][]{}, + "", ""); Note note1 = notebook.createNote(anonymous); factory.setInterpreters("user", note1.getId(),factory.getDefaultInterpreterSettingList()); @@ -179,7 +180,8 @@ public void testUnloadOnParagraphRemove() throws IOException { "desc1", "", HeliumTestApplication.class.getName(), - new String[][]{}); + new String[][]{}, + "", ""); Note note1 = notebook.createNote(anonymous); factory.setInterpreters("user", note1.getId(), factory.getDefaultInterpreterSettingList()); @@ -218,7 +220,8 @@ public void testUnloadOnInterpreterUnbind() throws IOException { "desc1", "", HeliumTestApplication.class.getName(), - new String[][]{}); + new String[][]{}, + "", ""); Note note1 = notebook.createNote(anonymous); notebook.bindInterpretersToNote("user", note1.getId(), factory.getDefaultInterpreterSettingList()); @@ -278,7 +281,8 @@ public void testUnloadOnInterpreterRestart() throws IOException { "desc1", "", HeliumTestApplication.class.getName(), - new String[][]{}); + new String[][]{}, + "", ""); Note note1 = notebook.createNote(anonymous); notebook.bindInterpretersToNote("user", note1.getId(), factory.getDefaultInterpreterSettingList()); 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 e90cbc30a73..03d77b7ff50 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumLocalRegistryTest.java @@ -55,7 +55,9 @@ public void testGetAllPackage() throws IOException { "desc1", "artifact1", "classname1", - new String[][]{}); + new String[][]{}, + "license", + ""); FileUtils.writeStringToFile(new File(r1Path, "pkg1.json"), gson.toJson(pkg1)); // then 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 1ed31c5bb95..7c168d04b30 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumTest.java @@ -16,6 +16,7 @@ */ package org.apache.zeppelin.helium; +import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; @@ -48,10 +49,11 @@ public void tearDown() throws IOException { } @Test - public void testSaveLoadConf() throws IOException, URISyntaxException { + public void testSaveLoadConf() throws IOException, URISyntaxException, TaskRunnerException { // given File heliumConf = new File(tmpDir, "helium.conf"); - Helium helium = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath()); + Helium helium = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath(), + null, null); assertFalse(heliumConf.exists()); HeliumTestRegistry registry1 = new HeliumTestRegistry("r1", "r1"); helium.addRegistry(registry1); @@ -65,14 +67,16 @@ public void testSaveLoadConf() throws IOException, URISyntaxException { assertTrue(heliumConf.exists()); // then - Helium heliumRestored = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath()); + Helium heliumRestored = new Helium( + heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath(), null, null); assertEquals(2, heliumRestored.getAllRegistry().size()); } @Test - public void testRestoreRegistryInstances() throws IOException, URISyntaxException { + public void testRestoreRegistryInstances() throws IOException, URISyntaxException, TaskRunnerException { File heliumConf = new File(tmpDir, "helium.conf"); - Helium helium = new Helium(heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath()); + Helium helium = new Helium( + heliumConf.getAbsolutePath(), localRegistryPath.getAbsolutePath(), null, null); HeliumTestRegistry registry1 = new HeliumTestRegistry("r1", "r1"); HeliumTestRegistry registry2 = new HeliumTestRegistry("r2", "r2"); helium.addRegistry(registry1); @@ -85,7 +89,9 @@ public void testRestoreRegistryInstances() throws IOException, URISyntaxExceptio "desc1", "artifact1", "className1", - new String[][]{})); + new String[][]{}, + "", + "")); registry2.add(new HeliumPackage( HeliumPackage.Type.APPLICATION, @@ -93,7 +99,9 @@ public void testRestoreRegistryInstances() throws IOException, URISyntaxExceptio "desc2", "artifact2", "className2", - new String[][]{})); + new String[][]{}, + "", + "")); // then assertEquals(2, helium.getAllPackageInfo().size()); diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java new file mode 100644 index 00000000000..47af409d32d --- /dev/null +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.zeppelin.helium; + +import com.github.eirslett.maven.plugins.frontend.lib.InstallationException; +import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; +import com.google.common.io.Resources; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class HeliumVisualizationFactoryTest { + private File tmpDir; + private HeliumVisualizationFactory hvf; + + @Before + public void setUp() throws InstallationException, TaskRunnerException { + tmpDir = new File(System.getProperty("java.io.tmpdir") + "/ZeppelinLTest_" + System.currentTimeMillis()); + tmpDir.mkdirs(); + + // get module dir + URL res = Resources.getResource("helium/webpack.config.js"); + String resDir = new File(res.getFile()).getParent(); + File moduleDir = new File(resDir + "/../../../../zeppelin-web/src/app/"); + + hvf = new HeliumVisualizationFactory(tmpDir, + new File(moduleDir, "tabledata"), + new File(moduleDir, "visualization")); + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(tmpDir); + } + + @Test + public void testInstallNpm() throws InstallationException { + assertTrue(new File(tmpDir, "vis/node/npm").isFile()); + assertTrue(new File(tmpDir, "vis/node/node").isFile()); + } + + @Test + public void downloadPackage() throws TaskRunnerException { + HeliumPackage pkg = new HeliumPackage( + HeliumPackage.Type.VISUALIZATION, + "lodash", + "lodash", + "lodash@3.9.3", + "", + null, + "license", + "icon" + ); + hvf.install(pkg); + assertTrue(new File(tmpDir, "vis/node_modules/lodash").isDirectory()); + } + + @Test + public void bundlePackage() throws IOException, TaskRunnerException { + HeliumPackage pkg = new HeliumPackage( + HeliumPackage.Type.VISUALIZATION, + "zeppelin-bubblechart", + "zeppelin-bubblechart", + "zeppelin-bubblechart@0.0.3", + "", + null, + "license", + "icon" + ); + List 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 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 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); + } +} diff --git a/zeppelin-zengine/src/test/resources/helium/vis1/package.json b/zeppelin-zengine/src/test/resources/helium/vis1/package.json new file mode 100644 index 00000000000..481ad58a3c3 --- /dev/null +++ b/zeppelin-zengine/src/test/resources/helium/vis1/package.json @@ -0,0 +1,12 @@ +{ + "name": "vis1", + "version": "1.0.0", + "description": "", + "main": "vis1", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "zeppelin-tabledata": "*", + "zeppelin-vis": "*" + } +} \ No newline at end of file diff --git a/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js b/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js new file mode 100644 index 00000000000..98bd552cd4f --- /dev/null +++ b/zeppelin-zengine/src/test/resources/helium/vis1/vis1.js @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Visualization from 'zeppelin-vis' +import PassthroughTransformation from 'zeppelin-tabledata/passthrough' + +/** + * Base class for visualization + */ +export default class vis1 extends Visualization { + constructor(targetEl, config) { + super(targetEl, config) + this.passthrough = new PassthroughTransformation(config); + console.log('passthrough %o', this.passthrough); + } + + render(tableData) { + this.targetEl.html('Vis1') + } + + getTransformation() { + return this.passthrough + } +} diff --git a/zeppelin-zengine/src/test/resources/helium/vis2/package.json b/zeppelin-zengine/src/test/resources/helium/vis2/package.json new file mode 100644 index 00000000000..b45ee6b4a5f --- /dev/null +++ b/zeppelin-zengine/src/test/resources/helium/vis2/package.json @@ -0,0 +1,12 @@ +{ + "name": "vis2", + "version": "1.0.0", + "description": "", + "main": "vis2", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "zeppelin-tabledata": "*", + "zeppelin-vis": "*" + } +} \ No newline at end of file diff --git a/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js b/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js new file mode 100644 index 00000000000..e9cd324bfb0 --- /dev/null +++ b/zeppelin-zengine/src/test/resources/helium/vis2/vis2.js @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Visualization from 'zeppelin-vis' +import PassthroughTransformation from 'zeppelin-tabledata/passthrough' + +/** + * Base class for visualization + */ +export default class vis2 extends Visualization { + constructor(targetEl, config) { + super(targetEl, config) + this.passthrough = new PassthroughTransformation(config); + + } + + render(tableData) { + this.targetEl.html('Vis2') + error in the package + } + + getTransformation() { + return this.passthrough + } +} From 8956d682f591062cd129b93be23b3a10e91dcc83 Mon Sep 17 00:00:00 2001 From: AhyoungRyu Date: Sun, 15 Jan 2017 15:14:42 +0900 Subject: [PATCH 018/234] [HOTFIX][ZEPPELIN-1970] Use relative path for broken screenshot imgs ### What is this PR for? Two screenshot imgs in [Writing a new visualization](https://zeppelin.apache.org/docs/0.7.0-SNAPSHOT/development/writingzeppelinvisualization.html) page are broken after deployed. It can be fixed by using relative path like other images. (e.g. [shiroauthentication.md](https://github.com/apache/zeppelin/blob/master/docs/security/shiroauthentication.md#4-login)) ### What type of PR is it? Hot Fix ### What is the Jira issue? [ZEPPELIN-1970](https://issues.apache.org/jira/browse/ZEPPELIN-1970) ### How should this be tested? It can't be reproduced using docs dev mode. Needs to be tested with below steps. ``` 1) build gh-pages (website) branch JEKYLL_ENV=production bundle exec jekyll build cp -r _site/ /tmp/zeppelin_website/ mkdir -p /tmp/zeppelin_website/docs/0.7.0-SNAPSHOT 2) build this patch (docs) and copy it under docs/0.7.0-SNAPSHOT of website cd docs bundle exec jekyll build --safe cp -r _site/ /tmp/zeppelin_website/0.7.0-SNAPSHOT/ 3) start httpserver and browse http://localhost:8000/docs/0.7.0-SNAPSHOT/ cd /tmp/zeppelin_website python -m SimpleHTTPServer ``` ### Screenshots (if appropriate) - before screen shot 2017-01-15 at 3 10 53 pm - after screen shot 2017-01-15 at 3 10 13 pm ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: AhyoungRyu Closes #1901 from AhyoungRyu/ZEPPELIN-1970 and squashes the following commits: d64e13f [AhyoungRyu] Use relative path for screenshot imgs (cherry picked from commit f86bb858b8032408b542f15ccb8794df02224a33) Signed-off-by: ahyoungryu --- docs/development/writingzeppelinvisualization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development/writingzeppelinvisualization.md b/docs/development/writingzeppelinvisualization.md index d7c2268f4a9..364bb08dba5 100644 --- a/docs/development/writingzeppelinvisualization.md +++ b/docs/development/writingzeppelinvisualization.md @@ -56,7 +56,7 @@ Once Zeppelin loads _Helium package files_ from local registry, available packag Click 'enable' button. - + #### 3. Create and load visualization bundle on the fly @@ -69,7 +69,7 @@ Once a Visualization package is enabled, [HeliumVisualizationFactory](https://gi Zeppelin shows additional button for loaded Visualizations. User can use just like any other built-in visualizations. - + From 835e9e21f7a94066313af98c44de9b793b1ba54d Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Thu, 12 Jan 2017 15:39:17 +0800 Subject: [PATCH 019/234] ZEPPELIN-1933. Set pig job name and allow to set pig property in pig interpreter setting ### What is this PR for? Two improvements for pig interpreter. * Set job name via paragraph title if it exists, otherwise use the last line of pig script * Allow to set any pig property in interpreter setting ### What type of PR is it? [ Improvement] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1933 ### How should this be tested? Unit tested and manually tested. ### Screenshots (if appropriate) ![image](https://cloud.githubusercontent.com/assets/164491/21840291/a6af18b4-d817-11e6-9778-02e12ec02be1.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1885 from zjffdu/ZEPPELIN-1933 and squashes the following commits: d2e1cd4 [Jeff Zhang] address comments 9cee380 [Jeff Zhang] ZEPPELIN-1933. Set pig job name and allow to set pig property in pig interpreter setting (cherry picked from commit 41f9fd921d77f5112107ba76c8794213cf3af929) Signed-off-by: Felix Cheung --- docs/interpreter/pig.md | 12 ++++++++++ .../zeppelin/pig/BasePigInterpreter.java | 24 +++++++++++++++++++ .../apache/zeppelin/pig/PigInterpreter.java | 8 +++++++ .../zeppelin/pig/PigQueryInterpreter.java | 1 + .../zeppelin/pig/PigInterpreterTezTest.java | 5 ++++ 5 files changed, 50 insertions(+) diff --git a/docs/interpreter/pig.md b/docs/interpreter/pig.md index a7781692e98..ad2e80ad0bf 100644 --- a/docs/interpreter/pig.md +++ b/docs/interpreter/pig.md @@ -52,6 +52,8 @@ group: manual ### How to configure interpreter At the Interpreters menu, you have to create a new Pig interpreter. Pig interpreter has below properties by default. +And you can set any pig properties here which will be passed to pig engine. (like tez.queue.name & mapred.job.queue.name). +Besides, we use paragraph title as job name if it exists, else use the last line of pig script. So you can use that to find app running in YARN RM UI. @@ -74,6 +76,16 @@ At the Interpreters menu, you have to create a new Pig interpreter. Pig interpre + + + + + + + + + +
    1000 max row number displayed in %pig.query
    tez.queue.namedefaultqueue name for tez engine
    mapred.job.queue.namedefaultqueue name for mapreduce engine
    ### Example diff --git a/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java b/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java index 0aa8a20f7ce..a9bb2ce2864 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java @@ -17,6 +17,7 @@ package org.apache.zeppelin.pig; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.pig.PigServer; import org.apache.pig.backend.BackendException; @@ -97,4 +98,27 @@ public Scheduler getScheduler() { } public abstract PigServer getPigServer(); + + /** + * Use paragraph title if it exists, else use the last line of pig script. + * @param cmd + * @param context + * @return + */ + protected String createJobName(String cmd, InterpreterContext context) { + String pTitle = context.getParagraphTitle(); + if (!StringUtils.isBlank(pTitle)) { + return pTitle; + } else { + // use the last non-empty line of pig script as the job name. + String[] lines = cmd.split("\n"); + for (int i = lines.length - 1; i >= 0; --i) { + if (!StringUtils.isBlank(lines[i])) { + return lines[i]; + } + } + // in case all the lines are empty, but usually it is almost impossible + return "empty_job"; + } + } } diff --git a/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java b/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java index 8cd1efc929e..0b50c670d85 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java @@ -18,6 +18,7 @@ package org.apache.zeppelin.pig; import org.apache.commons.io.output.ByteArrayOutputStream; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.pig.PigServer; import org.apache.pig.impl.logicalLayer.FrontendException; @@ -58,6 +59,12 @@ public void open() { } try { pigServer = new PigServer(execType); + for (Map.Entry entry : getProperty().entrySet()) { + if (!entry.getKey().toString().startsWith("zeppelin.")) { + pigServer.getPigContext().getProperties().setProperty(entry.getKey().toString(), + entry.getValue().toString()); + } + } } catch (IOException e) { LOGGER.error("Fail to initialize PigServer", e); throw new RuntimeException("Fail to initialize PigServer", e); @@ -78,6 +85,7 @@ public InterpreterResult interpret(String cmd, InterpreterContext contextInterpr ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream(); File tmpFile = null; try { + pigServer.setJobName(createJobName(cmd, contextInterpreter)); tmpFile = PigUtils.createTempPigScript(cmd); System.setOut(new PrintStream(bytesOutput)); // each thread should its own ScriptState & PigStats diff --git a/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java b/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java index 77632a74618..5e968d6929a 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java @@ -78,6 +78,7 @@ public InterpreterResult interpret(String st, InterpreterContext context) { StringBuilder resultBuilder = new StringBuilder("%table "); try { + pigServer.setJobName(createJobName(st, context)); File tmpScriptFile = PigUtils.createTempPigScript(queries); // each thread should its own ScriptState & PigStats ScriptState.start(pigServer.getPigContext().getExecutionEngine().instantiateScriptState()); diff --git a/pig/src/test/java/org/apache/zeppelin/pig/PigInterpreterTezTest.java b/pig/src/test/java/org/apache/zeppelin/pig/PigInterpreterTezTest.java index e742fd800f2..964b31c9ba3 100644 --- a/pig/src/test/java/org/apache/zeppelin/pig/PigInterpreterTezTest.java +++ b/pig/src/test/java/org/apache/zeppelin/pig/PigInterpreterTezTest.java @@ -45,6 +45,7 @@ public void setUpTez(boolean includeJobStats) { Properties properties = new Properties(); properties.put("zeppelin.pig.execType", "tez_local"); properties.put("zeppelin.pig.includeJobStats", includeJobStats + ""); + properties.put("tez.queue.name", "test"); pigInterpreter = new PigInterpreter(properties); pigInterpreter.open(); context = new InterpreterContext(null, "paragraph_id", null, null, null, null, null, null, null, null, @@ -60,6 +61,10 @@ public void tearDown() { public void testBasics() throws IOException { setUpTez(false); + assertEquals("test", + pigInterpreter.getPigServer().getPigContext().getProperties() + .getProperty("tez.queue.name")); + String content = "1\tandy\n" + "2\tpeter\n"; File tmpFile = File.createTempFile("zeppelin", "test"); From a3d7316190f487ef5a5f041c2eede752b9b38df8 Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Fri, 13 Jan 2017 11:39:42 +0900 Subject: [PATCH 020/234] [BUILD] Update release script ### What is this PR for? * Change hadoop profile from `hadoop-2.4` to `hadoop-2.6` * Use `SCALA_VERSION` variable instead of hardcoding * Specify projects to be built for netinst package instead of excluding unnecessary projects. ### What type of PR is it? Build ### What is the Jira issue? ### How should this be tested? Outline the steps to test the PR here. ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Mina Lee Closes #1890 from minahlee/update/release_script and squashes the following commits: a97b5fe [Mina Lee] Remove one r interpreter profile as only one will take effect 1479d26 [Mina Lee] Use scala variable instead of hardcoding Specify project to build for net-inst package instead of adding exluding projects everytime zeppelin have new interpreter da9080a [Mina Lee] Update hadoop profile from 2.4 to 2.6 (cherry picked from commit f8c11d15f4378523922c76a892a755a60e4a187b) Signed-off-by: Mina Lee --- dev/create_release.sh | 7 ++++--- dev/publish_release.sh | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dev/create_release.sh b/dev/create_release.sh index ece58638319..9b0b231c3e0 100755 --- a/dev/create_release.sh +++ b/dev/create_release.sh @@ -42,6 +42,7 @@ done RELEASE_VERSION="$1" GIT_TAG="$2" +SCALA_VERSION="2.11" function build_docker_base() { # build base image @@ -80,7 +81,7 @@ function make_binary_release() { cp -r "${WORKING_DIR}/zeppelin" "${WORKING_DIR}/zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}" cd "${WORKING_DIR}/zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}" - ./dev/change_scala_version.sh 2.11 + ./dev/change_scala_version.sh "${SCALA_VERSION}" echo "mvn clean package -Pbuild-distr -DskipTests ${BUILD_FLAGS}" mvn clean package -Pbuild-distr -DskipTests ${BUILD_FLAGS} if [[ $? -ne 0 ]]; then @@ -123,8 +124,8 @@ function make_binary_release() { build_docker_base git_clone make_source_package -make_binary_release all "-Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr -Pscala-2.11" -make_binary_release netinst "-Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr -Pscala-2.11 -pl !alluxio,!angular,!cassandra,!elasticsearch,!file,!flink,!hbase,!ignite,!jdbc,!kylin,!lens,!livy,!markdown,!postgresql,!python,!shell,!bigquery" +make_binary_release all "-Pspark-2.0 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION}" +make_binary_release netinst "-Pspark-2.0 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION} -pl zeppelin-interpreter,zeppelin-zengine,:zeppelin-display_${SCALA_VERSION},:zeppelin-spark-dependencies_${SCALA_VERSION},:zeppelin-spark_${SCALA_VERSION},zeppelin-web,zeppelin-server,zeppelin-distribution -am" # remove non release files and dirs rm -rf "${WORKING_DIR}/zeppelin" diff --git a/dev/publish_release.sh b/dev/publish_release.sh index f945f6e0c3f..7b99f398b7b 100755 --- a/dev/publish_release.sh +++ b/dev/publish_release.sh @@ -44,7 +44,7 @@ NC='\033[0m' # No Color RELEASE_VERSION="$1" GIT_TAG="$2" -PUBLISH_PROFILES="-Ppublish-distr -Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr" +PUBLISH_PROFILES="-Ppublish-distr -Pspark-2.0 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pr" PROJECT_OPTIONS="-pl !zeppelin-distribution" NEXUS_STAGING="https://repository.apache.org/service/local/staging" NEXUS_PROFILE="153446d1ac37c4" From 20c3cbccfd0ac4e58637bd4324774b7e3ab536c9 Mon Sep 17 00:00:00 2001 From: Khalid Huseynov Date: Thu, 12 Jan 2017 15:12:29 -0800 Subject: [PATCH 021/234] [ZEPPELIN-1961] Improve stability of sync when get fails ### What is this PR for? This is to improve the stability of sync mechanism when `get` from some backend storage fails (e.g. corrupt file, network issues). ### What type of PR is it? Bug Fix | Hot Fix ### Todos * [x] - handle exception ### What is the Jira issue? [ZEPPELIN-1961](https://issues.apache.org/jira/browse/ZEPPELIN-1961) ### How should this be tested? CI green ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Khalid Huseynov Closes #1895 from khalidhuseynov/fix-stability/sync-fail and squashes the following commits: aa1e199 [Khalid Huseynov] catch failed get command (cherry picked from commit e94d5c0fb66ee0954a74450002c5b81b8a7331a7) Signed-off-by: Mina Lee --- .../zeppelin/notebook/repo/NotebookRepoSync.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java index 73b25e98ac4..8553349ed6a 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java @@ -330,8 +330,7 @@ NotebookRepo getRepo(int repoIndex) throws IOException { private Map> notesCheckDiff(List sourceNotes, NotebookRepo sourceRepo, List destNotes, NotebookRepo destRepo, - AuthenticationInfo subject) - throws IOException { + AuthenticationInfo subject) { List pushIDs = new ArrayList<>(); List pullIDs = new ArrayList<>(); List delDstIDs = new ArrayList<>(); @@ -341,9 +340,14 @@ private Map> notesCheckDiff(List sourceNotes, for (NoteInfo snote : sourceNotes) { dnote = containsID(destNotes, snote.getId()); if (dnote != null) { - /* note exists in source and destination storage systems */ - sdate = lastModificationDate(sourceRepo.get(snote.getId(), subject)); - ddate = lastModificationDate(destRepo.get(dnote.getId(), subject)); + try { + /* note exists in source and destination storage systems */ + sdate = lastModificationDate(sourceRepo.get(snote.getId(), subject)); + ddate = lastModificationDate(destRepo.get(dnote.getId(), subject)); + } catch (IOException e) { + LOG.error("Cannot access previously listed note {} from storage ", dnote.getId(), e); + continue; + } if (sdate.compareTo(ddate) != 0) { if (sdate.after(ddate) || oneWaySync) { From 7b123fb8e5b593c6839613f4f851e692529543a8 Mon Sep 17 00:00:00 2001 From: Jun Kim Date: Sun, 15 Jan 2017 22:22:42 +0900 Subject: [PATCH 022/234] [DOCS] Reflect changed default storage to doc ### What is this PR for? Reflect effects caused by changing the default notebook storage VFSNotebookRepo to GitNotebookRepo. ### What type of PR is it? [Documentation] ### Questions: * Does the licenses files need update? NO * Is there breaking changes for older versions? NO * Does this needs documentation? NO Author: Jun Kim Closes #1903 from tae-jun/patch-3 and squashes the following commits: 60448da [Jun Kim] [DOCS] Reflect changed default storage to doc (cherry picked from commit 982cc0d17e6482844068dbb29ad5a11325a43446) Signed-off-by: ahyoungryu --- docs/storage/storage.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/storage/storage.md b/docs/storage/storage.md index 20c631292f6..0ab01daf805 100644 --- a/docs/storage/storage.md +++ b/docs/storage/storage.md @@ -28,8 +28,8 @@ limitations under the License. Apache Zeppelin has a pluggable notebook storage mechanism controlled by `zeppelin.notebook.storage` configuration option with multiple implementations. There are few notebook storage systems available for a use out of the box: - * (default) all notes are saved in the notebook folder in your local File System - `VFSNotebookRepo` - * use local file system and version it using local Git repository - `GitNotebookRepo` + * (default) use local file system and version it using local Git repository - `GitNotebookRepo` + * all notes are saved in the notebook folder in your local File System - `VFSNotebookRepo` * storage using Amazon S3 service - `S3NotebookRepo` * storage using Azure service - `AzureNotebookRepo` @@ -100,13 +100,13 @@ Uncomment the next property for use S3NotebookRepo class: ``` -Comment out the next property to disable local notebook storage (the default): +Comment out the next property to disable local git notebook storage (the default): ``` zeppelin.notebook.storage - org.apache.zeppelin.notebook.repo.VFSNotebookRepo - notebook persistence layer implementation + org.apache.zeppelin.notebook.repo.GitNotebookRepo + versioned notebook persistence layer implementation ``` @@ -191,8 +191,8 @@ Secondly, you can initialize `AzureNotebookRepo` class in the file **zeppelin-si ``` zeppelin.notebook.storage - org.apache.zeppelin.notebook.repo.VFSNotebookRepo - notebook persistence layer implementation + org.apache.zeppelin.notebook.repo.GitNotebookRepo + versioned notebook persistence layer implementation ``` @@ -206,12 +206,12 @@ and commenting out: ``` -In case you want to use simultaneously your local storage with Azure storage use the following property instead: +In case you want to use simultaneously your local git storage with Azure storage use the following property instead: ``` zeppelin.notebook.storage - org.apache.zeppelin.notebook.repo.VFSNotebookRepo, apache.zeppelin.notebook.repo.AzureNotebookRepo + org.apache.zeppelin.notebook.repo.GitNotebookRepo, apache.zeppelin.notebook.repo.AzureNotebookRepo notebook persistence layer implementation ``` @@ -236,7 +236,7 @@ ZeppelinHub storage layer allows out of the box connection of Zeppelin instance @@ -245,7 +245,7 @@ ZeppelinHub storage layer allows out of the box connection of Zeppelin instance or set the environment variable in the file **zeppelin-env.sh**: ``` -export ZEPPELIN_NOTEBOOK_STORAGE="org.apache.zeppelin.notebook.repo.VFSNotebookRepo, org.apache.zeppelin.notebook.repo.zeppelinhub.ZeppelinHubRepo" +export ZEPPELIN_NOTEBOOK_STORAGE="org.apache.zeppelin.notebook.repo.GitNotebookRepo, org.apache.zeppelin.notebook.repo.zeppelinhub.ZeppelinHubRepo" ``` Secondly, you need to set the environment variables in the file **zeppelin-env.sh**: From 57f03df9e5bd5fbe5e715a78c0f7d809ea31a180 Mon Sep 17 00:00:00 2001 From: "victor.sheng" Date: Thu, 12 Jan 2017 14:54:26 +0800 Subject: [PATCH 023/234] [ZEPPELIN-1941] Fix cron job with release resource option dead lock ### What is this PR for? There is a deadlock in concurrent cron job execution with release resource option. `Scenario`: Two notebook run with cron job that release resource after job finished. In `Notebook.CronJob.execute()` method: `T1. note.runAll(); // locked paragraphs(lock) and wait to interpreterSettings(lock)` `T2. notebook.getInterpreterFactory().restart() //locked(interpreterSettings) and wait for paragraphs(lock) during jobAbort.` This will trigger a deadlock that cause zeppelin hang. ### What type of PR is it? [Bug Fix ] ### Todos * [x] - Fix this by avoid acquire lock in job abort method. ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1941 ### How should this be tested? Outline the steps to test the PR here. ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? * Is there breaking changes for older versions? * Does this needs documentation? Author: victor.sheng Closes #1891 from OopsOutOfMemory/fix_dead_lock_cronjob and squashes the following commits: 517fdfa [victor.sheng] fix cron job with release resource option dead lock (cherry picked from commit 215599cb39420a3564f8fcc7ac64a8da748aa526) Signed-off-by: Jongyoul Lee --- .../apache/zeppelin/notebook/Paragraph.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java index a69c0e7f1e9..27a707137a1 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java @@ -454,7 +454,7 @@ protected boolean jobAbort() { if (job != null) { job.setStatus(Status.ABORT); } else { - repl.cancel(getInterpreterContext(null)); + repl.cancel(getInterpreterContextWithoutRunner(null)); } return true; } @@ -498,6 +498,34 @@ private void updateParagraphResult(List msgs) { })); } + private InterpreterContext getInterpreterContextWithoutRunner(InterpreterOutput output) { + AngularObjectRegistry registry = null; + ResourcePool resourcePool = null; + + if (!factory.getInterpreterSettings(note.getId()).isEmpty()) { + InterpreterSetting intpGroup = factory.getInterpreterSettings(note.getId()).get(0); + registry = intpGroup.getInterpreterGroup(getUser(), note.getId()).getAngularObjectRegistry(); + resourcePool = intpGroup.getInterpreterGroup(getUser(), note.getId()).getResourcePool(); + } + + List runners = new LinkedList<>(); + + final Paragraph self = this; + + Credentials credentials = note.getCredentials(); + if (authenticationInfo != null) { + UserCredentials userCredentials = + credentials.getUserCredentials(authenticationInfo.getUser()); + authenticationInfo.setUserCredentials(userCredentials); + } + + InterpreterContext interpreterContext = + new InterpreterContext(note.getId(), getId(), getRequiredReplName(), this.getTitle(), + this.getText(), this.getAuthenticationInfo(), this.getConfig(), this.settings, registry, + resourcePool, runners, output); + return interpreterContext; + } + private InterpreterContext getInterpreterContext(InterpreterOutput output) { AngularObjectRegistry registry = null; ResourcePool resourcePool = null; From 2a869c80edcaa8ebd2141fe97ddc0d036e66ccc8 Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Tue, 10 Jan 2017 14:07:33 +0800 Subject: [PATCH 024/234] ZEPPELIN-1770. Restart only the client user's interpreter when restarting interpreter setting ### What is this PR for? This PR would only restart the trigger user's interpreter rather than all the interpreter. So that restarting won't affect other users. ### What type of PR is it? [Improvement] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1770 ### How should this be tested? Tested manually. ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1846 from zjffdu/ZEPPELIN-1770 and squashes the following commits: 5ee076d [Jeff Zhang] fix scoped mode and add unit test 8cb28a3 [Jeff Zhang] ZEPPELIN-1770. Restart only the client user's interpreter when restarting interpreter setting (cherry picked from commit ae1cb0527bc223b25761e1370618929e228183f8) Signed-off-by: Jongyoul Lee --- .../zeppelin/rest/InterpreterRestApi.java | 4 +- .../interpreter/InterpreterFactory.java | 26 +++++-- .../interpreter/InterpreterSetting.java | 44 ++++++++++- .../interpreter/InterpreterFactoryTest.java | 78 +++++++++++++++++-- 4 files changed, 136 insertions(+), 16 deletions(-) diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java index 90a58ac0d62..06d475295c5 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java @@ -38,6 +38,7 @@ import com.google.gson.Gson; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.zeppelin.rest.message.RestartInterpreterRequest; +import org.apache.zeppelin.utils.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonatype.aether.repository.RemoteRepository; @@ -178,12 +179,11 @@ public Response removeSetting(@PathParam("settingId") String settingId) throws I @ZeppelinApi public Response restartSetting(String message, @PathParam("settingId") String settingId) { logger.info("Restart interpreterSetting {}, msg={}", settingId, message); - try { RestartInterpreterRequest request = gson.fromJson(message, RestartInterpreterRequest.class); String noteId = request == null ? null : request.getNoteId(); - interpreterFactory.restart(settingId, noteId); + interpreterFactory.restart(settingId, noteId, SecurityUtils.getPrincipal()); } catch (InterpreterException e) { logger.error("Exception in InterpreterRestApi while restartSetting ", e); diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java index 8a891707e6f..e8b6868f76b 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java @@ -737,7 +737,7 @@ public void removeInterpretersForNote(InterpreterSetting interpreterSetting, Str String noteId) { InterpreterOption option = interpreterSetting.getOption(); if (option.isProcess()) { - interpreterSetting.closeAndRemoveInterpreterGroup(noteId); + interpreterSetting.closeAndRemoveInterpreterGroupByNoteId(noteId); } else if (option.isSession()) { InterpreterGroup interpreterGroup = interpreterSetting.getInterpreterGroup(user, noteId); String key = getInterpreterSessionKey(user, noteId, interpreterSetting); @@ -971,18 +971,23 @@ private boolean noteIdIsExist(String noteId) { return noteId == null ? false : true; } - public void restart(String settingId, String noteId) { + public void restart(String settingId, String noteId, String user) { InterpreterSetting intpSetting = interpreterSettings.get(settingId); Preconditions.checkNotNull(intpSetting); + // restart interpreter setting in note page if (noteIdIsExist(noteId) && intpSetting.getOption().isProcess()) { - intpSetting.closeAndRemoveInterpreterGroup(noteId); + intpSetting.closeAndRemoveInterpreterGroupByNoteId(noteId); return; + } else { + // restart interpreter setting in interpreter setting page + restart(settingId, user); } - restart(settingId); + + } - public void restart(String id) { + public void restart(String id, String user) { synchronized (interpreterSettings) { InterpreterSetting intpSetting = interpreterSettings.get(id); // Check if dependency in specified path is changed @@ -993,8 +998,11 @@ public void restart(String id) { copyDependenciesFromLocalPath(intpSetting); stopJobAllInterpreter(intpSetting); - - intpSetting.closeAndRemoveAllInterpreterGroups(); + if (user.equals("anonymous")) { + intpSetting.closeAndRemoveAllInterpreterGroups(); + } else { + intpSetting.closeAndRemoveInterpreterGroupByUser(user); + } } else { throw new InterpreterException("Interpreter setting id " + id + " not found"); @@ -1002,6 +1010,10 @@ public void restart(String id) { } } + public void restart(String id) { + restart(id, "anonymous"); + } + private void stopJobAllInterpreter(InterpreterSetting intpSetting) { if (intpSetting != null) { for (InterpreterGroup intpGroup : intpSetting.getAllInterpreterGroups()) { diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java index 828938c5d4a..9176ddff639 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java @@ -144,6 +144,26 @@ private String getInterpreterProcessKey(String user, String noteId) { return key; } + private String getInterpreterSessionKey(String user, String noteId) { + InterpreterOption option = getOption(); + String key; + if (option.isExistingProcess()) { + key = Constants.EXISTING_PROCESS; + } else if (option.perNoteScoped() && option.perUserScoped()) { + key = user + ":" + noteId; + } else if (option.perUserScoped()) { + key = user; + } else if (option.perNoteScoped()) { + key = noteId; + } else { + key = "shared_session"; + } + + logger.debug("Interpreter session key: {}, for note: {}, user: {}, InterpreterSetting Name: " + + "{}", key, noteId, user, getName()); + return key; + } + public InterpreterGroup getInterpreterGroup(String user, String noteId) { String key = getInterpreterProcessKey(user, noteId); if (!interpreterGroupRef.containsKey(key)) { @@ -173,7 +193,7 @@ public Collection getAllInterpreterGroups() { } } - void closeAndRemoveInterpreterGroup(String noteId) { + void closeAndRemoveInterpreterGroupByNoteId(String noteId) { String key = getInterpreterProcessKey("", noteId); InterpreterGroup groupToRemove = null; @@ -190,10 +210,30 @@ void closeAndRemoveInterpreterGroup(String noteId) { } } + void closeAndRemoveInterpreterGroupByUser(String user) { + if (user.equals("anonymous")) { + user = ""; + } + String processKey = getInterpreterProcessKey(user, ""); + String sessionKey = getInterpreterSessionKey(user, ""); + InterpreterGroup groupToRemove = null; + for (String intpKey : new HashSet<>(interpreterGroupRef.keySet())) { + if (intpKey.contains(processKey)) { + interpreterGroupWriteLock.lock(); + groupToRemove = interpreterGroupRef.remove(intpKey); + interpreterGroupWriteLock.unlock(); + } + } + + if (groupToRemove != null) { + groupToRemove.close(sessionKey); + } + } + void closeAndRemoveAllInterpreterGroups() { HashSet groupsToRemove = new HashSet<>(interpreterGroupRef.keySet()); for (String key : groupsToRemove) { - closeAndRemoveInterpreterGroup(key); + closeAndRemoveInterpreterGroupByNoteId(key); } } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java index 661459b9245..7522366b474 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java @@ -167,6 +167,79 @@ public void testRemoteRepl() throws Exception { assertEquals("value_2", remoteInterpreter.getProperty("property_2")); } + /** + * 2 users' interpreters in scoped mode. Each user has one session. Restarting user1's interpreter + * won't affect user2's interpreter + * @throws Exception + */ + @Test + public void testRestartInterpreterInScopedMode() throws Exception { + factory = new InterpreterFactory(conf, new InterpreterOption(true), null, null, null, depResolver, false); + List all = factory.get(); + InterpreterSetting mock1Setting = null; + for (InterpreterSetting setting : all) { + if (setting.getName().equals("mock1")) { + mock1Setting = setting; + break; + } + } + mock1Setting.getOption().setPerUser("scoped"); + mock1Setting.getOption().setPerNote("shared"); + // set remote as false so that we won't create new remote interpreter process + mock1Setting.getOption().setRemote(false); + mock1Setting.getOption().setHost("localhost"); + mock1Setting.getOption().setPort(2222); + InterpreterGroup interpreterGroup = mock1Setting.getInterpreterGroup("user1", "sharedProcess"); + factory.createInterpretersForNote(mock1Setting, "user1", "sharedProcess", "user1"); + factory.createInterpretersForNote(mock1Setting, "user2", "sharedProcess", "user2"); + + LazyOpenInterpreter interpreter1 = (LazyOpenInterpreter)interpreterGroup.get("user1").get(0); + interpreter1.open(); + LazyOpenInterpreter interpreter2 = (LazyOpenInterpreter)interpreterGroup.get("user2").get(0); + interpreter2.open(); + + mock1Setting.closeAndRemoveInterpreterGroupByUser("user1"); + assertFalse(interpreter1.isOpen()); + assertTrue(interpreter2.isOpen()); + } + + /** + * 2 users' interpreters in isolated mode. Each user has one interpreterGroup. Restarting user1's interpreter + * won't affect user2's interpreter + * @throws Exception + */ + @Test + public void testRestartInterpreterInIsolatedMode() throws Exception { + factory = new InterpreterFactory(conf, new InterpreterOption(true), null, null, null, depResolver, false); + List all = factory.get(); + InterpreterSetting mock1Setting = null; + for (InterpreterSetting setting : all) { + if (setting.getName().equals("mock1")) { + mock1Setting = setting; + break; + } + } + mock1Setting.getOption().setPerUser("isolated"); + mock1Setting.getOption().setPerNote("shared"); + // set remote as false so that we won't create new remote interpreter process + mock1Setting.getOption().setRemote(false); + mock1Setting.getOption().setHost("localhost"); + mock1Setting.getOption().setPort(2222); + InterpreterGroup interpreterGroup1 = mock1Setting.getInterpreterGroup("user1", "note1"); + InterpreterGroup interpreterGroup2 = mock1Setting.getInterpreterGroup("user2", "note2"); + factory.createInterpretersForNote(mock1Setting, "user1", "note1", "shared_session"); + factory.createInterpretersForNote(mock1Setting, "user2", "note2", "shared_session"); + + LazyOpenInterpreter interpreter1 = (LazyOpenInterpreter)interpreterGroup1.get("shared_session").get(0); + interpreter1.open(); + LazyOpenInterpreter interpreter2 = (LazyOpenInterpreter)interpreterGroup2.get("shared_session").get(0); + interpreter2.open(); + + mock1Setting.closeAndRemoveInterpreterGroupByUser("user1"); + assertFalse(interpreter1.isOpen()); + assertTrue(interpreter2.isOpen()); + } + @Test public void testFactoryDefaultList() throws IOException, RepositoryException { // get default settings @@ -365,9 +438,4 @@ public void interpreterRunnerTest() { interpreterRunner = ((RemoteInterpreter) ((LazyOpenInterpreter) i).getInnerInterpreter()).getInterpreterRunner(); assertEquals(interpreterRunner, testInterpreterRunner); } - - @Test - public void interpreterRunnerAsAbsolutePathTest() { - - } } From 20ebb6cc1e8246f72e74af36fa286761a79c53f4 Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Wed, 11 Jan 2017 10:56:53 +0800 Subject: [PATCH 025/234] ZEPPELIN-1918. Fix build with Spark 2.1.0 ### What is this PR for? It's my misunderstanding of `SPARK_VER` in travis. It is only used for downloading spark distribution. We need to specify spark profile explicitly for building with specific version of spark. This PR add new profile for spark 2.1 and fix the build issue with spark 2.1.0 because `SecurityManager` changes its constructor signature in spark 2.1 ### What type of PR is it? [Bug Fix ] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1918 ### How should this be tested? Build with spark 2.1.0 and tested it manually as below screenshot. ### Screenshots (if appropriate) ![image](https://cloud.githubusercontent.com/assets/164491/21797414/d586aa04-d749-11e6-8c6f-3b12e9e2ae2d.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1880 from zjffdu/ZEPPELIN-1918 and squashes the following commits: 8f87459 [Jeff Zhang] update release script 34772e0 [Jeff Zhang] update build doc for spark 2.1 5efdb11 [Jeff Zhang] fix unit test and address comments def502f [Jeff Zhang] ZEPPELIN-1918. Fix build with Spark 2.1.0 (cherry picked from commit 9bc4dce9884ec4011f677560f708286968549374) Signed-off-by: Felix Cheung --- .travis.yml | 8 ++-- dev/create_release.sh | 4 +- dev/publish_release.sh | 2 +- docs/install/build.md | 5 +++ spark-dependencies/pom.xml | 14 ++++++- spark/pom.xml | 14 ++++++- .../zeppelin/spark/SparkInterpreter.java | 41 +++++++++++++++++-- 7 files changed, 73 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 48e8aa7ed3a..5b3371ddf37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,13 +40,13 @@ matrix: - jdk: "oraclejdk7" env: SCALA_VER="2.11" SPARK_VER="2.0.2" HADOOP_VER="2.6" PROFILE="-Prat" BUILD_FLAG="clean" TEST_FLAG="org.apache.rat:apache-rat-plugin:check" TEST_PROJECTS="" - # Test all modules with spark 2.0.2 and scala 2.11 + # Test all modules with spark 2.1.0 and scala 2.11 - jdk: "oraclejdk7" - env: SCALA_VER="2.11" SPARK_VER="2.0.2" HADOOP_VER="2.6" PROFILE="-Pspark-2.0 -Phadoop-2.6 -Ppyspark -Psparkr -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" + env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pspark-2.1 -Phadoop-2.6 -Ppyspark -Psparkr -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" - # Test all modules with spark 2.1.0 and scala 2.11 + # Test all modules with spark 2.0.2 and scala 2.11 - jdk: "oraclejdk7" - env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pspark-2.0 -Phadoop-2.6 -Ppyspark -Psparkr -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" + env: SCALA_VER="2.11" SPARK_VER="2.0.2" HADOOP_VER="2.6" PROFILE="-Pspark-2.0 -Phadoop-2.6 -Ppyspark -Psparkr -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" # Test all modules with scala 2.10 - jdk: "oraclejdk7" diff --git a/dev/create_release.sh b/dev/create_release.sh index 9b0b231c3e0..873bb10236e 100755 --- a/dev/create_release.sh +++ b/dev/create_release.sh @@ -124,8 +124,8 @@ function make_binary_release() { build_docker_base git_clone make_source_package -make_binary_release all "-Pspark-2.0 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION}" -make_binary_release netinst "-Pspark-2.0 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION} -pl zeppelin-interpreter,zeppelin-zengine,:zeppelin-display_${SCALA_VERSION},:zeppelin-spark-dependencies_${SCALA_VERSION},:zeppelin-spark_${SCALA_VERSION},zeppelin-web,zeppelin-server,zeppelin-distribution -am" +make_binary_release all "-Pspark-2.1 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION}" +make_binary_release netinst "-Pspark-2.1 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION} -pl zeppelin-interpreter,zeppelin-zengine,:zeppelin-display_${SCALA_VERSION},:zeppelin-spark-dependencies_${SCALA_VERSION},:zeppelin-spark_${SCALA_VERSION},zeppelin-web,zeppelin-server,zeppelin-distribution -am" # remove non release files and dirs rm -rf "${WORKING_DIR}/zeppelin" diff --git a/dev/publish_release.sh b/dev/publish_release.sh index 7b99f398b7b..a2acc983972 100755 --- a/dev/publish_release.sh +++ b/dev/publish_release.sh @@ -44,7 +44,7 @@ NC='\033[0m' # No Color RELEASE_VERSION="$1" GIT_TAG="$2" -PUBLISH_PROFILES="-Ppublish-distr -Pspark-2.0 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pr" +PUBLISH_PROFILES="-Ppublish-distr -Pspark-2.1 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pr" PROJECT_OPTIONS="-pl !zeppelin-distribution" NEXUS_STAGING="https://repository.apache.org/service/local/staging" NEXUS_PROFILE="153446d1ac37c4" diff --git a/docs/install/build.md b/docs/install/build.md index f3012a56abc..e4525599ab5 100644 --- a/docs/install/build.md +++ b/docs/install/build.md @@ -97,6 +97,7 @@ Set spark major version Available profiles are ``` +-Pspark-2.1 -Pspark-2.0 -Pspark-1.6 -Pspark-1.5 @@ -185,6 +186,10 @@ Bulid examples under zeppelin-examples directory Here are some examples with several options: ```bash +# build with spark-2.1, scala-2.11 +./dev/change_scala_version.sh 2.11 +mvn clean package -Pspark-2.1 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pscala-2.11 -DskipTests + # build with spark-2.0, scala-2.11 ./dev/change_scala_version.sh 2.11 mvn clean package -Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pscala-2.11 -DskipTests diff --git a/spark-dependencies/pom.xml b/spark-dependencies/pom.xml index 198f2bfb20e..887964db819 100644 --- a/spark-dependencies/pom.xml +++ b/spark-dependencies/pom.xml @@ -523,13 +523,23 @@ spark-2.0 + + 2.0.2 + 2.5.0 + 0.10.3 + 2.11.8 + + + + + spark-2.1 true - 2.0.2 + 2.1.0 2.5.0 - 0.10.3 + 0.10.4 2.11.8 diff --git a/spark/pom.xml b/spark/pom.xml index 54690ead99e..4989af9ac29 100644 --- a/spark/pom.xml +++ b/spark/pom.xml @@ -515,13 +515,23 @@ spark-2.0 + + 2.0.2 + 2.5.0 + 0.10.3 + 2.11.8 + + + + + spark-2.1 true - 2.0.2 + 2.1.0 2.5.0 - 0.10.3 + 0.10.4 2.11.8 diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java index 788230336cd..0584a302c55 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java @@ -488,8 +488,9 @@ private void setupConfForPySpark(SparkConf conf) { } //Only one of py4j-0.9-src.zip and py4j-0.8.2.1-src.zip should exist + //TODO(zjffdu), this is not maintainable when new version is added. String[] pythonLibs = new String[]{"pyspark.zip", "py4j-0.9-src.zip", "py4j-0.8.2.1-src.zip", - "py4j-0.10.1-src.zip", "py4j-0.10.3-src.zip"}; + "py4j-0.10.1-src.zip", "py4j-0.10.3-src.zip", "py4j-0.10.4-src.zip"}; ArrayList pythonLibUris = new ArrayList<>(); for (String lib : pythonLibs) { File libFile = new File(pysparkPath, lib); @@ -1452,8 +1453,10 @@ private Object createHttpServer(File outputDir) { .getConstructor(new Class[]{ SparkConf.class, File.class, SecurityManager.class, int.class, String.class}); - return constructor.newInstance(new Object[] { - conf, outputDir, new SecurityManager(conf), 0, "HTTP Server"}); + Object securityManager = createSecurityManager(conf); + return constructor.newInstance(new Object[]{ + conf, outputDir, securityManager, 0, "HTTP Server"}); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { // fallback to old constructor @@ -1464,7 +1467,7 @@ private Object createHttpServer(File outputDir) { .getConstructor(new Class[]{ File.class, SecurityManager.class, int.class, String.class}); return constructor.newInstance(new Object[] { - outputDir, new SecurityManager(conf), 0, "HTTP Server"}); + outputDir, createSecurityManager(conf), 0, "HTTP Server"}); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e1) { logger.error(e1.getMessage(), e1); @@ -1472,4 +1475,34 @@ private Object createHttpServer(File outputDir) { } } } + + /** + * Constructor signature of SecurityManager changes in spark 2.1.0, so we use this method to + * create SecurityManager properly for different versions of spark + * + * @param conf + * @return + * @throws ClassNotFoundException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws InstantiationException + */ + private Object createSecurityManager(SparkConf conf) throws ClassNotFoundException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + Object securityManager = null; + try { + Constructor smConstructor = getClass().getClassLoader() + .loadClass("org.apache.spark.SecurityManager") + .getConstructor(new Class[]{ SparkConf.class, scala.Option.class }); + securityManager = smConstructor.newInstance(conf, null); + } catch (NoSuchMethodException e) { + Constructor smConstructor = getClass().getClassLoader() + .loadClass("org.apache.spark.SecurityManager") + .getConstructor(new Class[]{ SparkConf.class }); + securityManager = smConstructor.newInstance(conf); + } + return securityManager; + } } From 98f55c099b4baa814ec4cf7d680b8cbd7e667f1f Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Mon, 16 Jan 2017 07:40:44 -0800 Subject: [PATCH 026/234] [ZEPPELIN-1969] Can not change visualization package version. ### What is this PR for? Changing visualization package version from helium menu, sometimes fail. This PR fixes the problem and providing a unittest. ### What type of PR is it? Bug Fix ### Todos * [x] - remove package from node_module and let npm download again before bundle the package. * [x] - add unittest. ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1969 ### How should this be tested? Unittest HeliumVisualizationFactoryTest.switchVersion() ensure the fix. ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo Closes #1900 from Leemoonsoo/fix_helium_version_switch and squashes the following commits: 540497f [Lee moon soo] fix style e9f2811 [Lee moon soo] Make download package everytime bundle to workaround inconsistent behavior of npm install (cherry picked from commit d61cd99dbb5c63078b3e20e94183355e2c401d5f) Signed-off-by: Lee moon soo --- .../helium/HeliumVisualizationFactory.java | 31 +++++++++++--- .../HeliumVisualizationFactoryTest.java | 42 +++++++++++++++++-- 2 files changed, 65 insertions(+), 8 deletions(-) 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 index a06c18b733f..1c1d25a0967 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java @@ -104,6 +104,7 @@ public synchronized File bundle(List pkgs, boolean forceRefresh) 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 @@ -127,18 +128,25 @@ public boolean accept(File pathname) { 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()), - new File(workingDirectory, "node_modules/" + pkg.getName()), + pkgInstallDir, npmPackageCopyFilter); } } pkgJson = pkgJson.replaceFirst("DEPENDENCIES", dependencies.toString()); // check if we can use previous bundle or not - if (dependencies.toString().equals(bundleCacheKey) && currentBundle.isFile() && !forceRefresh) { + if (cacheKeyBuilder.toString().equals(bundleCacheKey) + && currentBundle.isFile() && !forceRefresh) { return currentBundle; } @@ -177,7 +185,10 @@ public boolean accept(File pathname) { // install tabledata module File tabledataModuleInstallPath = new File(workingDirectory, "node_modules/zeppelin-tabledata"); - if (tabledataModulePath != null && !tabledataModuleInstallPath.exists()) { + if (tabledataModulePath != null) { + if (tabledataModuleInstallPath.exists()) { + FileUtils.deleteDirectory(tabledataModuleInstallPath); + } FileUtils.copyDirectory( tabledataModulePath, tabledataModuleInstallPath, @@ -187,7 +198,17 @@ public boolean accept(File pathname) { // install visualization module File visModuleInstallPath = new File(workingDirectory, "node_modules/zeppelin-vis"); - if (visualizationModulePath != null && !visModuleInstallPath.exists()) { + 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); } @@ -210,7 +231,7 @@ public boolean accept(File pathname) { synchronized (this) { currentBundle.delete(); FileUtils.moveFile(visBundleJs, currentBundle); - bundleCacheKey = dependencies.toString(); + bundleCacheKey = cacheKeyBuilder.toString(); } return currentBundle; } 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 index 47af409d32d..e5a61edb381 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumVisualizationFactoryTest.java @@ -30,9 +30,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; +import static org.junit.Assert.*; public class HeliumVisualizationFactoryTest { private File tmpDir; @@ -154,4 +152,42 @@ public void bundleErrorPropagation() throws IOException, TaskRunnerException { } 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 pkgsV1 = new LinkedList<>(); + pkgsV1.add(pkgV1); + + List pkgsV2 = new LinkedList<>(); + pkgsV2.add(pkgV2); + + File bundle1 = hvf.bundle(pkgsV1); + File bundle2 = hvf.bundle(pkgsV2); + + assertNotSame(bundle1.lastModified(), bundle2.lastModified()); + } } From a2d6a8cf05104443b7a30c84fd4d71838136caaf Mon Sep 17 00:00:00 2001 From: Renjith Kamath Date: Wed, 11 Jan 2017 22:43:32 +0530 Subject: [PATCH 027/234] ZEPPELIN-1935 Add jceks stored password support for jdbc interpreter ### What is this PR for? Add support for jceks stored password instead of using password in clear text. This security enhancement prevents any user from reading clear passwords from interpreter json using shell/py/spark etc. #1315 is the parent PR which fixed similar a issue in Active Directory (shiro.ini config). ### What type of PR is it? Improvement ### Todos ### What is the Jira issue? ZEPPELIN-1935 ### How should this be tested? Create a keystore file using the hadoop credential commandline, for this the hadoop commons should be in the classpath. `hadoop credential create jdbc.password -provider jceks://file/user/zeppelin/conf/zeppelin.jceks` Use the jceks file and the key to configure jdbc interpreter. Example interpreter setting: #### Following are the supported settings for backward compatibility - Settings with username and jceks ``` default.driver org.postgresql.Driver default.jceks.credentialKey jdbc.password default.jceks.file jceks://file/tmp/zeppelin.jceks default.url jdbc:postgresql://rkamath-local-1:5432/ default.user rk-user ``` - Settings with user name and clear text password ``` default.driver org.postgresql.Driver default.url jdbc:postgresql://rkamath-local-1:5432/ default.user rk-user default.password password1 ``` ### Screenshots (if appropriate) screen shot 2017-01-10 at 7 02 12 pm ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? yes Author: Renjith Kamath Closes #1881 from r-kamath/ZEPPELIN-1935 and squashes the following commits: 5cc4db1 [Renjith Kamath] ZEPPELIN-1935 revert wildcard imports dfabe3a [Renjith Kamath] Merge branch 'master' of https://github.com/apache/zeppelin into ZEPPELIN-1935 e62088a [Renjith Kamath] ZEPPELIN-1935 fix log message d41d56c [Renjith Kamath] ZEPPELIN-1935 Add jceks stored password support for jdbc interpreter (cherry picked from commit 04d34547a1453e16ea4cb306cfcfa0d79f22acfe) Signed-off-by: Prabhjyot Singh --- docs/interpreter/jdbc.md | 8 +++ jdbc/pom.xml | 54 ++++++++++++++++ .../apache/zeppelin/jdbc/JDBCInterpreter.java | 64 +++++++++++++++---- 3 files changed, 112 insertions(+), 14 deletions(-) diff --git a/docs/interpreter/jdbc.md b/docs/interpreter/jdbc.md index 74ac1127a68..32adcba94be 100644 --- a/docs/interpreter/jdbc.md +++ b/docs/interpreter/jdbc.md @@ -159,6 +159,14 @@ There are more JDBC interpreter properties you can specify like below. zeppelin.jdbc.keytab.location The path to the keytab file + + default.jceks.file + jceks store path (e.g: jceks://file/tmp/zeppelin.jceks) + + + default.jceks.credentialKey + jceks credential key + You can also add more properties by using this [method](http://docs.oracle.com/javase/7/docs/api/java/sql/DriverManager.html#getConnection%28java.lang.String,%20java.util.Properties%29). diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 1786b715d7a..9a6e97aaca1 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -39,6 +39,7 @@ 2.7.2 1.4.190 2.0.1 + 2.6.0 1.0.8 @@ -117,6 +118,59 @@ ${mockrunner.jdbc.version} test + + + org.apache.hadoop + hadoop-common + ${hadoop-common.version} + + + com.sun.jersey + jersey-core + + + com.sun.jersey + jersey-json + + + com.sun.jersey + jersey-server + + + + javax.servlet + servlet-api + + + org.apache.avro + avro + + + org.apache.jackrabbit + jackrabbit-webdav + + + io.netty + netty + + + commons-httpclient + commons-httpclient + + + org.apache.zookeeper + zookeeper + + + org.eclipse.jgit + org.eclipse.jgit + + + com.jcraft + jsch + + + diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java index 778dcf28229..8d6577e303e 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -15,12 +15,12 @@ package org.apache.zeppelin.jdbc; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; - +import static org.apache.commons.lang.StringUtils.isEmpty; +import static org.apache.commons.lang.StringUtils.isNotEmpty; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.IOException; import java.nio.charset.StandardCharsets; - import java.security.PrivilegedExceptionAction; import java.sql.Connection; import java.sql.DriverManager; @@ -40,10 +40,12 @@ import org.apache.commons.dbcp2.DriverManagerConnectionFactory; import org.apache.commons.dbcp2.PoolableConnectionFactory; import org.apache.commons.dbcp2.PoolingDriver; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; @@ -87,8 +89,6 @@ public class JDBCInterpreter extends Interpreter { private Logger logger = LoggerFactory.getLogger(JDBCInterpreter.class); static final String INTERPRETER_NAME = "jdbc"; - static final String JDBC_DEFAULT_USER_KEY = "default.user"; - static final String JDBC_DEFAULT_PASSWORD_KEY = "default.password"; static final String COMMON_KEY = "common"; static final String MAX_LINE_KEY = "max_count"; static final int MAX_LINE_DEFAULT = 1000; @@ -98,6 +98,8 @@ public class JDBCInterpreter extends Interpreter { static final String URL_KEY = "url"; static final String USER_KEY = "user"; static final String PASSWORD_KEY = "password"; + static final String JDBC_JCEKS_FILE = "jceks.file"; + static final String JDBC_JCEKS_CREDENTIAL_KEY = "jceks.credentialKey"; static final String DOT = "."; private static final char WHITESPACE = ' '; @@ -105,7 +107,6 @@ public class JDBCInterpreter extends Interpreter { private static final char TAB = '\t'; private static final String TABLE_MAGIC_TAG = "%table "; private static final String EXPLAIN_PREDICATE = "EXPLAIN "; - private static final String UPDATE_COUNT_HEADER = "Update Count"; static final String COMMON_MAX_LINE = COMMON_KEY + DOT + MAX_LINE_KEY; @@ -182,7 +183,7 @@ public void open() { } logger.debug("JDBC PropretiesMap: {}", basePropretiesMap); - if (!StringUtils.isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) { + if (!isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) { JDBCSecurityImpl.createSecureConfiguration(property); } for (String propertyKey : basePropretiesMap.keySet()) { @@ -261,9 +262,9 @@ private String getJDBCDriverName(String user, String propertyKey) { return driverName.toString(); } - private boolean existAccountInBaseProperty() { - return property.containsKey(JDBC_DEFAULT_USER_KEY) && - property.containsKey(JDBC_DEFAULT_PASSWORD_KEY); + private boolean existAccountInBaseProperty(String propertyKey) { + return basePropretiesMap.get(propertyKey).containsKey(USER_KEY) && + basePropretiesMap.get(propertyKey).containsKey(PASSWORD_KEY); } private UsernamePassword getUsernamePassword(InterpreterContext interpreterContext, @@ -295,15 +296,22 @@ private void closeDBPool(String user, String propertyKey) throws SQLException { } private void setUserProperty(String propertyKey, InterpreterContext interpreterContext) - throws SQLException { + throws SQLException, IOException { String user = interpreterContext.getAuthenticationInfo().getUser(); JDBCUserConfigurations jdbcUserConfigurations = getJDBCConfiguration(user); + if (basePropretiesMap.get(propertyKey).containsKey(USER_KEY) && + !basePropretiesMap.get(propertyKey).getProperty(USER_KEY).isEmpty()) { + String password = getPassword(basePropretiesMap.get(propertyKey)); + if (!isEmpty(password)) { + basePropretiesMap.get(propertyKey).setProperty(PASSWORD_KEY, password); + } + } jdbcUserConfigurations.setPropertyMap(propertyKey, basePropretiesMap.get(propertyKey)); - if (existAccountInBaseProperty()) { + if (existAccountInBaseProperty(propertyKey)) { return; } jdbcUserConfigurations.cleanUserProperty(propertyKey); @@ -344,7 +352,7 @@ private Connection getConnectionFromPool(String url, String user, String propert } public Connection getConnection(String propertyKey, InterpreterContext interpreterContext) - throws ClassNotFoundException, SQLException, InterpreterException { + throws ClassNotFoundException, SQLException, InterpreterException, IOException { final String user = interpreterContext.getAuthenticationInfo().getUser(); Connection connection; if (propertyKey == null || basePropretiesMap.get(propertyKey) == null) { @@ -357,7 +365,7 @@ public Connection getConnection(String propertyKey, InterpreterContext interpret final Properties properties = jdbcUserConfigurations.getPropertyMap(propertyKey); final String url = properties.getProperty(URL_KEY); - if (StringUtils.isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) { + if (isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) { connection = getConnectionFromPool(url, user, propertyKey, properties); } else { UserGroupInformation.AuthenticationMethod authType = JDBCSecurityImpl.getAuthtype(property); @@ -410,6 +418,34 @@ public Connection run() throws Exception { return connection; } + private String getPassword(Properties properties) throws IOException { + if (isNotEmpty(properties.getProperty(PASSWORD_KEY))) { + return properties.getProperty(PASSWORD_KEY); + } else if (isNotEmpty(properties.getProperty(JDBC_JCEKS_FILE)) + && isNotEmpty(properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY))) { + try { + Configuration configuration = new Configuration(); + configuration.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, + properties.getProperty(JDBC_JCEKS_FILE)); + CredentialProvider provider = CredentialProviderFactory.getProviders(configuration).get(0); + CredentialProvider.CredentialEntry credEntry = + provider.getCredentialEntry(properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY)); + if (credEntry != null) { + return new String(credEntry.getCredential()); + } else { + throw new InterpreterException("Failed to retrieve password from JCEKS from key: " + + properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY)); + } + } catch (Exception e) { + logger.error("Failed to retrieve password from JCEKS \n" + + "For file: " + properties.getProperty(JDBC_JCEKS_FILE) + + "\nFor key: " + properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY), e); + throw e; + } + } + return null; + } + private String getResults(ResultSet resultSet, boolean isTableType) throws SQLException { ResultSetMetaData md = resultSet.getMetaData(); From f7edda1c4633329ed522dea088a12edb00013386 Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Mon, 16 Jan 2017 09:01:54 +0800 Subject: [PATCH 028/234] ZEPPELIN-1432. Support cancellation of paragraph execution ### What is this PR for? Livy 0.3 support cancel operation, this PR is to support cancel in livy interpreter. First we would check the livy version, then based on the livy version, we would call the livy rest api to cancel the statement. ### What type of PR is it? Improvement | Feature ] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1432 ### How should this be tested? Tested manually, because cancel is only avaible in livy 0.3 which is not released yet. ### Screenshots (if appropriate) ![image](https://cloud.githubusercontent.com/assets/164491/21712520/3ed292ec-d430-11e6-8829-581e1bba1a9c.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1859 from zjffdu/ZEPPELIN-1432 and squashes the following commits: 83eaf83 [Jeff Zhang] minor update 200ca71 [Jeff Zhang] address comments 1cbeb26 [Jeff Zhang] add zeppelin.livy.pull_status.interval.millis 070fea0 [Jeff Zhang] ZEPPELIN-1432. Support cancellation of paragraph execution (cherry picked from commit 2fcfaa8c74cad5adf9adcdf76987e4ffbe5983c7) Signed-off-by: Felix Cheung --- docs/interpreter/livy.md | 5 + .../zeppelin/livy/APINotFoundException.java | 43 +++++ .../zeppelin/livy/BaseLivyInterprereter.java | 156 ++++++++++++------ .../livy/LivySparkSQLInterpreter.java | 9 +- .../org/apache/zeppelin/livy/LivyVersion.java | 96 +++++++++++ .../main/resources/interpreter-setting.json | 5 + 6 files changed, 264 insertions(+), 50 deletions(-) create mode 100644 livy/src/main/java/org/apache/zeppelin/livy/APINotFoundException.java create mode 100644 livy/src/main/java/org/apache/zeppelin/livy/LivyVersion.java diff --git a/docs/interpreter/livy.md b/docs/interpreter/livy.md index 067a317ecde..47ebc466522 100644 --- a/docs/interpreter/livy.md +++ b/docs/interpreter/livy.md @@ -65,6 +65,11 @@ Example: `spark.driver.memory` to `livy.spark.driver.memory` false Whether to display app info + + zeppelin.livy.pull_status.interval.millis + 1000 + The interval for checking paragraph execution status + livy.spark.driver.cores diff --git a/livy/src/main/java/org/apache/zeppelin/livy/APINotFoundException.java b/livy/src/main/java/org/apache/zeppelin/livy/APINotFoundException.java new file mode 100644 index 00000000000..3c4b714a3d0 --- /dev/null +++ b/livy/src/main/java/org/apache/zeppelin/livy/APINotFoundException.java @@ -0,0 +1,43 @@ +/* + * 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.livy; + +/** + * APINotFoundException happens because we may introduce new apis in new livy version. + */ +public class APINotFoundException extends LivyException { + public APINotFoundException() { + } + + public APINotFoundException(String message) { + super(message); + } + + public APINotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public APINotFoundException(Throwable cause) { + super(cause); + } + + public APINotFoundException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java index 8ed4622b0b4..3d843633808 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java @@ -37,6 +37,7 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.ConcurrentHashMap; /** * Base class for livy interpreters. @@ -48,15 +49,23 @@ public abstract class BaseLivyInterprereter extends Interpreter { protected volatile SessionInfo sessionInfo; private String livyURL; - private long sessionCreationTimeout; + private int sessionCreationTimeout; + private int pullStatusInterval; protected boolean displayAppInfo; private AtomicBoolean sessionExpired = new AtomicBoolean(false); + private LivyVersion livyVersion; + + // keep tracking the mapping between paragraphId and statementId, so that we can cancel the + // statement after we execute it. + private ConcurrentHashMap paragraphId2StmtIdMapping = new ConcurrentHashMap<>(); public BaseLivyInterprereter(Properties property) { super(property); this.livyURL = property.getProperty("zeppelin.livy.url"); - this.sessionCreationTimeout = Long.parseLong( + this.sessionCreationTimeout = Integer.parseInt( property.getProperty("zeppelin.livy.create.session.timeout", 120 + "")); + this.pullStatusInterval = Integer.parseInt( + property.getProperty("zeppelin.livy.pull_status.interval.millis", 1000 + "")); } public abstract String getSessionKind(); @@ -89,23 +98,33 @@ protected void initLivySession() throws LivyException { // livy 0.2 don't return appId and sparkUiUrl in response so that we need to get it // explicitly by ourselves. sessionInfo.appId = extractStatementResult( - interpret("sc.applicationId", false, false).message() + interpret("sc.applicationId", null, false, false).message() .get(0).getData()); } interpret( "val webui=sc.getClass.getMethod(\"ui\").invoke(sc).asInstanceOf[Some[_]].get", - false, false); + null, false, false); if (StringUtils.isEmpty(sessionInfo.appInfo.get("sparkUiUrl"))) { sessionInfo.webUIAddress = extractStatementResult( interpret( - "webui.getClass.getMethod(\"appUIAddress\").invoke(webui)", false, false) + "webui.getClass.getMethod(\"appUIAddress\").invoke(webui)", null, false, false) .message().get(0).getData()); } else { sessionInfo.webUIAddress = sessionInfo.appInfo.get("sparkUiUrl"); } LOGGER.info("Create livy session successfully with sessionId: {}, appId: {}, webUI: {}", sessionInfo.id, sessionInfo.appId, sessionInfo.webUIAddress); + } else { + LOGGER.info("Create livy session successfully with sessionId: {}", this.sessionInfo.id); + } + // check livy version + try { + this.livyVersion = getLivyVersion(); + LOGGER.info("Use livy " + livyVersion); + } catch (APINotFoundException e) { + this.livyVersion = new LivyVersion("0.2.0"); + LOGGER.info("Use livy 0.2.0"); } } @@ -120,7 +139,7 @@ public InterpreterResult interpret(String st, InterpreterContext context) { } try { - return interpret(st, this.displayAppInfo, true); + return interpret(st, context.getParagraphId(), this.displayAppInfo, true); } catch (LivyException e) { LOGGER.error("Fail to interpret:" + st, e); return new InterpreterResult(InterpreterResult.Code.ERROR, @@ -148,7 +167,21 @@ private String extractStatementResult(String result) { @Override public void cancel(InterpreterContext context) { - //TODO(zjffdu). Use livy cancel api which is available in livy 0.3 + if (livyVersion.isCancelSupported()) { + String paraId = context.getParagraphId(); + Integer stmtId = paragraphId2StmtIdMapping.get(paraId); + try { + if (stmtId != null) { + cancelStatement(stmtId); + } + } catch (LivyException e) { + LOGGER.error("Fail to cancel statement " + stmtId + " for paragraph " + paraId, e); + } finally { + paragraphId2StmtIdMapping.remove(paraId); + } + } else { + LOGGER.warn("cancel is not supported for this version of livy: " + livyVersion); + } } @Override @@ -192,7 +225,7 @@ private SessionInfo createSession(String user, String kind) LOGGER.error(msg); throw new LivyException(msg); } - Thread.sleep(1000); + Thread.sleep(pullStatusInterval); sessionInfo = getSessionInfo(sessionInfo.id); } return sessionInfo; @@ -206,44 +239,58 @@ private SessionInfo getSessionInfo(int sessionId) throws LivyException { return SessionInfo.fromJson(callRestAPI("/sessions/" + sessionId, "GET")); } - public InterpreterResult interpret(String code, boolean displayAppInfo, - boolean appendSessionExpired) - throws LivyException { + public InterpreterResult interpret(String code, + String paragraphId, + boolean displayAppInfo, + boolean appendSessionExpired) throws LivyException { StatementInfo stmtInfo = null; boolean sessionExpired = false; try { - stmtInfo = executeStatement(new ExecuteRequest(code)); - } catch (SessionNotFoundException e) { - LOGGER.warn("Livy session {} is expired, new session will be created.", sessionInfo.id); - sessionExpired = true; - // we don't want to create multiple sessions because it is possible to have multiple thread - // to call this method, like LivySparkSQLInterpreter which use ParallelScheduler. So we need - // to check session status again in this sync block - synchronized (this) { - if (isSessionExpired()) { - initLivySession(); + try { + stmtInfo = executeStatement(new ExecuteRequest(code)); + } catch (SessionNotFoundException e) { + LOGGER.warn("Livy session {} is expired, new session will be created.", sessionInfo.id); + sessionExpired = true; + // we don't want to create multiple sessions because it is possible to have multiple thread + // to call this method, like LivySparkSQLInterpreter which use ParallelScheduler. So we need + // to check session status again in this sync block + synchronized (this) { + if (isSessionExpired()) { + initLivySession(); + } } + stmtInfo = executeStatement(new ExecuteRequest(code)); } - stmtInfo = executeStatement(new ExecuteRequest(code)); - } - // pull the statement status - while (!stmtInfo.isAvailable()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - LOGGER.error("InterruptedException when pulling statement status.", e); - throw new LivyException(e); + if (paragraphId != null) { + paragraphId2StmtIdMapping.put(paragraphId, stmtInfo.id); + } + // pull the statement status + while (!stmtInfo.isAvailable()) { + try { + Thread.sleep(pullStatusInterval); + } catch (InterruptedException e) { + LOGGER.error("InterruptedException when pulling statement status.", e); + throw new LivyException(e); + } + stmtInfo = getStatementInfo(stmtInfo.id); + } + if (appendSessionExpired) { + return appendSessionExpire(getResultFromStatementInfo(stmtInfo, displayAppInfo), + sessionExpired); + } else { + return getResultFromStatementInfo(stmtInfo, displayAppInfo); + } + } finally { + if (paragraphId != null) { + paragraphId2StmtIdMapping.remove(paragraphId); } - stmtInfo = getStatementInfo(stmtInfo.id); - } - if (appendSessionExpired) { - return appendSessionExpire(getResultFromStatementInfo(stmtInfo, displayAppInfo), - sessionExpired); - } else { - return getResultFromStatementInfo(stmtInfo, displayAppInfo); } } + private LivyVersion getLivyVersion() throws LivyException { + return new LivyVersion((LivyVersionResponse.fromJson(callRestAPI("/version", "GET")).version)); + } + private boolean isSessionExpired() throws LivyException { try { getSessionInfo(sessionInfo.id); @@ -270,6 +317,7 @@ private InterpreterResult appendSessionExpire(InterpreterResult result, boolean } } + private InterpreterResult getResultFromStatementInfo(StatementInfo stmtInfo, boolean displayAppInfo) { if (stmtInfo.output.isError()) { @@ -341,6 +389,10 @@ private StatementInfo getStatementInfo(int statementId) callRestAPI("/sessions/" + sessionInfo.id + "/statements/" + statementId, "GET")); } + private void cancelStatement(int statementId) throws LivyException { + callRestAPI("/sessions/" + sessionInfo.id + "/statements/" + statementId + "/cancel", "POST"); + } + private RestTemplate getRestTemplate() { String keytabLocation = property.getProperty("zeppelin.livy.keytab"); String principal = property.getProperty("zeppelin.livy.principal"); @@ -385,21 +437,20 @@ private String callRestAPI(String targetURL, String method, String jsonData) LOGGER.debug("Get response, StatusCode: {}, responseBody: {}", response.getStatusCode(), response.getBody()); if (response.getStatusCode().value() == 200 - || response.getStatusCode().value() == 201 - || response.getStatusCode().value() == 404) { - String responseBody = response.getBody(); - if (responseBody.matches("\"Session '\\d+' not found.\"")) { - throw new SessionNotFoundException(responseBody); + || response.getStatusCode().value() == 201) { + return response.getBody(); + } else if (response.getStatusCode().value() == 404) { + if (response.getBody().matches("Session '\\d+' not found.")) { + throw new SessionNotFoundException(response.getBody()); } else { - return responseBody; + throw new APINotFoundException("No rest api found for " + targetURL + + ", " + response.getStatusCode()); } } else { String responseString = response.getBody(); if (responseString.contains("CreateInteractiveRequest[\\\"master\\\"]")) { return responseString; } - LOGGER.error(String.format("Error with %s StatusCode: %s", - response.getStatusCode().value(), responseString)); throw new LivyException(String.format("Error with %s StatusCode: %s", response.getStatusCode().value(), responseString)); } @@ -502,7 +553,7 @@ public static StatementInfo fromJson(String json) { } public boolean isAvailable() { - return state.equals("available"); + return state.equals("available") || state.equals("cancelled"); } private static class StatementOutput { @@ -543,4 +594,17 @@ private static class TableMagic { } } + private static class LivyVersionResponse { + public String url; + public String branch; + public String revision; + public String version; + public String date; + public String user; + + public static LivyVersionResponse fromJson(String json) { + return gson.fromJson(json, LivyVersionResponse.class); + } + } + } diff --git a/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java b/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java index 0e78860ceab..471d199315c 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java @@ -51,7 +51,7 @@ public void open() { // As we don't know whether livyserver use spark2 or spark1, so we will detect SparkSession // to judge whether it is using spark2. try { - InterpreterResult result = sparkInterpreter.interpret("spark", false, false); + InterpreterResult result = sparkInterpreter.interpret("spark", null, false, false); if (result.code() == InterpreterResult.Code.SUCCESS && result.message().get(0).getData().contains("org.apache.spark.sql.SparkSession")) { LOGGER.info("SparkSession is detected so we are using spark 2.x for session {}", @@ -59,7 +59,7 @@ public void open() { isSpark2 = true; } else { // spark 1.x - result = sparkInterpreter.interpret("sqlContext", false, false); + result = sparkInterpreter.interpret("sqlContext", null, false, false); if (result.code() == InterpreterResult.Code.SUCCESS) { LOGGER.info("sqlContext is detected."); } else if (result.code() == InterpreterResult.Code.ERROR) { @@ -68,7 +68,7 @@ public void open() { LOGGER.info("sqlContext is not detected, try to create SQLContext by ourselves"); result = sparkInterpreter.interpret( "val sqlContext = new org.apache.spark.sql.SQLContext(sc)\n" - + "import sqlContext.implicits._", false, false); + + "import sqlContext.implicits._", null, false, false); if (result.code() == InterpreterResult.Code.ERROR) { throw new LivyException("Fail to create SQLContext," + result.message().get(0).getData()); @@ -113,7 +113,8 @@ public InterpreterResult interpret(String line, InterpreterContext context) { } else { sqlQuery = "sqlContext.sql(\"\"\"" + line + "\"\"\").show(" + maxResult + ")"; } - InterpreterResult result = sparkInterpreter.interpret(sqlQuery, this.displayAppInfo, true); + InterpreterResult result = sparkInterpreter.interpret(sqlQuery, context.getParagraphId(), + this.displayAppInfo, true); if (result.code() == InterpreterResult.Code.SUCCESS) { InterpreterResult result2 = new InterpreterResult(InterpreterResult.Code.SUCCESS); diff --git a/livy/src/main/java/org/apache/zeppelin/livy/LivyVersion.java b/livy/src/main/java/org/apache/zeppelin/livy/LivyVersion.java new file mode 100644 index 00000000000..1b7fe3067fd --- /dev/null +++ b/livy/src/main/java/org/apache/zeppelin/livy/LivyVersion.java @@ -0,0 +1,96 @@ +package org.apache.zeppelin.livy; + +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provide reading comparing capability of livy version + */ +public class LivyVersion { + private static final Logger logger = LoggerFactory.getLogger(LivyVersion.class); + + private static final LivyVersion LIVY_0_2_0 = LivyVersion.fromVersionString("0.2.0"); + private static final LivyVersion LIVY_0_3_0 = LivyVersion.fromVersionString("0.3.0"); + + private int version; + private String versionString; + + LivyVersion(String versionString) { + this.versionString = versionString; + + try { + int pos = versionString.indexOf('-'); + + String numberPart = versionString; + if (pos > 0) { + numberPart = versionString.substring(0, pos); + } + + String versions[] = numberPart.split("\\."); + int major = Integer.parseInt(versions[0]); + int minor = Integer.parseInt(versions[1]); + int patch = Integer.parseInt(versions[2]); + // version is always 5 digits. (e.g. 2.0.0 -> 20000, 1.6.2 -> 10602) + version = Integer.parseInt(String.format("%d%02d%02d", major, minor, patch)); + } catch (Exception e) { + logger.error("Can not recognize Livy version " + versionString + + ". Assume it's a future release", e); + + // assume it is future release + version = 99999; + } + } + + public int toNumber() { + return version; + } + + public String toString() { + return versionString; + } + + public static LivyVersion fromVersionString(String versionString) { + return new LivyVersion(versionString); + } + + public boolean isCancelSupported() { + return this.newerThanEquals(LIVY_0_3_0); + } + + public boolean equals(Object versionToCompare) { + return version == ((LivyVersion) versionToCompare).version; + } + + public boolean newerThan(LivyVersion versionToCompare) { + return version > versionToCompare.version; + } + + public boolean newerThanEquals(LivyVersion versionToCompare) { + return version >= versionToCompare.version; + } + + public boolean olderThan(LivyVersion versionToCompare) { + return version < versionToCompare.version; + } + + public boolean olderThanEquals(LivyVersion versionToCompare) { + return version <= versionToCompare.version; + } +} diff --git a/livy/src/main/resources/interpreter-setting.json b/livy/src/main/resources/interpreter-setting.json index 4be537a361f..a2b97586ba6 100644 --- a/livy/src/main/resources/interpreter-setting.json +++ b/livy/src/main/resources/interpreter-setting.json @@ -77,6 +77,11 @@ "defaultValue": "", "description": "Kerberos keytab to authenticate livy" }, + "zeppelin.livy.pull_status.interval.millis": { + "propertyName": "zeppelin.livy.pull_status.interval.millis", + "defaultValue": "1000", + "description": "The interval for checking paragraph execution status" + }, "livy.spark.jars.packages": { "propertyName": "livy.spark.jars.packages", "defaultValue": "", From 2dc5c8806c76136af5de7cba032f629118e73208 Mon Sep 17 00:00:00 2001 From: AhyoungRyu Date: Mon, 16 Jan 2017 22:30:21 +0900 Subject: [PATCH 029/234] [Hot Fix] Fix deadlink in writingzeppelinvisualization.md ### What is this PR for? It should point [HeliumVisualizationFactory.java](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java) not [HeliumVisualizationPackage.java](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationPackage.java) ### What type of PR is it? Documentation | HotFix ### What is the Jira issue? N/A ### How should this be tested? click `HeliumVisualizationFactory` link in [this section](https://zeppelin.apache.org/docs/0.7.0-SNAPSHOT/development/writingzeppelinvisualization.html#3-create-and-load-visualization-bundle-on-the-fly) :) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: AhyoungRyu Closes #1904 from AhyoungRyu/fix/deadlink and squashes the following commits: 60ac6c9 [AhyoungRyu] Fix deadlink in writingzeppelinvisualization.md (cherry picked from commit 425abe302374522278e7e76d4c78b71ce5dcdd9d) Signed-off-by: ahyoungryu --- docs/development/writingzeppelinvisualization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/writingzeppelinvisualization.md b/docs/development/writingzeppelinvisualization.md index 364bb08dba5..ab46958823e 100644 --- a/docs/development/writingzeppelinvisualization.md +++ b/docs/development/writingzeppelinvisualization.md @@ -61,7 +61,7 @@ Click 'enable' button. #### 3. Create and load visualization bundle on the fly -Once a Visualization package is enabled, [HeliumVisualizationFactory](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationPackage.java) creates a js bundle. The js bundle is served by `helium/visualization/load` rest api endpoint. +Once a Visualization package is enabled, [HeliumVisualizationFactory](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java) creates a js bundle. The js bundle is served by `helium/visualization/load` rest api endpoint. #### 4. Run visualization From 350cf9c294114cd40ceef00c570d8f53caeea922 Mon Sep 17 00:00:00 2001 From: Khalid Huseynov Date: Thu, 12 Jan 2017 21:32:02 -0800 Subject: [PATCH 030/234] [ZEPPELIN-1963] set cron job executor to current user by default ### What is this PR for? currently when executing cron job, backend doesn't know who initiated cron job (with subsequent save) unless user puts his name. This PR adds user name by default by keeping compatibility with current workflow. ### What type of PR is it? Improvement ### Todos * [x] - add user on front ### What is the Jira issue? [ZEPPELIN-1963](https://issues.apache.org/jira/browse/ZEPPELIN-1963) ### How should this be tested? go and set cron job ### Screenshots (if appropriate) ![cron_user](https://cloud.githubusercontent.com/assets/1642088/21919566/25330daa-d910-11e6-9373-aa3c44064f39.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Khalid Huseynov Closes #1898 from khalidhuseynov/cron-user and squashes the following commits: 30dc440 [Khalid Huseynov] add logged cron executing user (cherry picked from commit 8daf32563960b16db739e30c8dcd91d60f854917) Signed-off-by: Mina Lee --- zeppelin-web/src/app/notebook/notebook.controller.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index 248ef8a7719..ccf64b7b907 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -401,6 +401,13 @@ function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope, /** Set cron expression for this note **/ $scope.setCronScheduler = function(cronExpr) { + if (cronExpr) { + if (!$scope.note.config.cronExecutingUser) { + $scope.note.config.cronExecutingUser = $rootScope.ticket.principal; + } + } else { + $scope.note.config.cronExecutingUser = ''; + } $scope.note.config.cron = cronExpr; $scope.setConfig(); }; @@ -1011,4 +1018,3 @@ function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope, angular.element(document.getElementById('content')).css('padding-top', actionbarHeight - 20); }); } - From 2665d709a9f7888a644e3eed4df6ae115044268f Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Wed, 28 Dec 2016 19:08:22 +0800 Subject: [PATCH 031/234] ZEPPELIN-1852. Use multiple InterpreterResult for displaying appInfo ### What is this PR for? Refactor the livy interpreter to use multiple `InterpreterResult` for displaying appInfo. ### What type of PR is it? [Refactoring] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1852 ### How should this be tested? Add integration test and also test it manually ### Screenshots (if appropriate) ![image](https://cloud.githubusercontent.com/assets/164491/21446774/691b84fe-c905-11e6-91a1-9dcc57c75f3c.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1796 from zjffdu/ZEPPELIN-1852 and squashes the following commits: 724f22e [Jeff Zhang] update doc e1c2eb9 [Jeff Zhang] ZEPPELIN-1852. Use multiple InterpreterResult for displaying appInfo (cherry picked from commit 69b866adca5ecac00b26dfd3573013eaae133102) Signed-off-by: Felix Cheung --- docs/interpreter/livy.md | 5 +++ .../zeppelin/livy/BaseLivyInterprereter.java | 34 +++++++++---------- .../main/resources/interpreter-setting.json | 8 ++--- .../zeppelin/livy/LivyInterpreterIT.java | 34 ++++++++++++++++++- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/docs/interpreter/livy.md b/docs/interpreter/livy.md index 47ebc466522..6f042448dae 100644 --- a/docs/interpreter/livy.md +++ b/docs/interpreter/livy.md @@ -60,6 +60,11 @@ Example: `spark.driver.memory` to `livy.spark.driver.memory` 1000 Max number of Spark SQL result to display. + + zeppelin.livy.session.create_timeout + 120 + Timeout in seconds for session creation + zeppelin.livy.displayAppInfo false diff --git a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java index 3d843633808..f0591fd424c 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java @@ -62,8 +62,10 @@ public abstract class BaseLivyInterprereter extends Interpreter { public BaseLivyInterprereter(Properties property) { super(property); this.livyURL = property.getProperty("zeppelin.livy.url"); + this.displayAppInfo = Boolean.parseBoolean( + property.getProperty("zeppelin.livy.displayAppInfo", "false")); this.sessionCreationTimeout = Integer.parseInt( - property.getProperty("zeppelin.livy.create.session.timeout", 120 + "")); + property.getProperty("zeppelin.livy.session.create_timeout", 120 + "")); this.pullStatusInterval = Integer.parseInt( property.getProperty("zeppelin.livy.pull_status.interval.millis", 1000 + "")); } @@ -77,7 +79,6 @@ public void open() { } catch (LivyException e) { String msg = "Fail to create session, please check livy interpreter log and " + "livy server log"; - LOGGER.error(msg); throw new RuntimeException(msg, e); } } @@ -102,10 +103,11 @@ protected void initLivySession() throws LivyException { .get(0).getData()); } - interpret( - "val webui=sc.getClass.getMethod(\"ui\").invoke(sc).asInstanceOf[Some[_]].get", - null, false, false); - if (StringUtils.isEmpty(sessionInfo.appInfo.get("sparkUiUrl"))) { + if (sessionInfo.appInfo == null || + StringUtils.isEmpty(sessionInfo.appInfo.get("sparkUiUrl"))) { + interpret( + "val webui=sc.getClass.getMethod(\"ui\").invoke(sc).asInstanceOf[Some[_]].get", + null, false, false); sessionInfo.webUIAddress = extractStatementResult( interpret( "webui.getClass.getMethod(\"appUIAddress\").invoke(webui)", null, false, false) @@ -215,14 +217,12 @@ private SessionInfo createSession(String user, String kind) if (sessionInfo.isFinished()) { String msg = "Session " + sessionInfo.id + " is finished, appId: " + sessionInfo.appId + ", log: " + sessionInfo.log; - LOGGER.error(msg); throw new LivyException(msg); } if ((System.currentTimeMillis() - start) / 1000 > sessionCreationTimeout) { String msg = "The creation of session " + sessionInfo.id + " is timeout within " + sessionCreationTimeout + " seconds, appId: " + sessionInfo.appId + ", log: " + sessionInfo.log; - LOGGER.error(msg); throw new LivyException(msg); } Thread.sleep(pullStatusInterval); @@ -361,16 +361,14 @@ private InterpreterResult getResultFromStatementInfo(StatementInfo stmtInfo, if (displayAppInfo) { //TODO(zjffdu), use multiple InterpreterResult to display appInfo - StringBuilder outputBuilder = new StringBuilder(); - outputBuilder.append("%angular "); - outputBuilder.append("
    ");
    -        outputBuilder.append(result);
    -        outputBuilder.append("
    "); - outputBuilder.append("
    "); - outputBuilder.append("Spark Application Id:" + sessionInfo.appId + "
    "); - outputBuilder.append("Spark WebUI: " - + sessionInfo.webUIAddress + ""); - return new InterpreterResult(InterpreterResult.Code.SUCCESS, outputBuilder.toString()); + InterpreterResult interpreterResult = new InterpreterResult(InterpreterResult.Code.SUCCESS); + interpreterResult.add(InterpreterResult.Type.TEXT, result); + String appInfoHtml = "
    Spark Application Id: " + sessionInfo.appId + "
    " + + "Spark WebUI: " + + sessionInfo.webUIAddress + ""; + LOGGER.info("appInfoHtml:" + appInfoHtml); + interpreterResult.add(InterpreterResult.Type.HTML, appInfoHtml); + return interpreterResult; } else { return new InterpreterResult(InterpreterResult.Code.SUCCESS, result); } diff --git a/livy/src/main/resources/interpreter-setting.json b/livy/src/main/resources/interpreter-setting.json index a2b97586ba6..42f64cf00f0 100644 --- a/livy/src/main/resources/interpreter-setting.json +++ b/livy/src/main/resources/interpreter-setting.json @@ -11,9 +11,9 @@ "defaultValue": "http://localhost:8998", "description": "The URL for Livy Server." }, - "zeppelin.livy.create.session.retries": { - "envName": "ZEPPELIN_LIVY_CREATE_SESSION_RETRIES", - "propertyName": "zeppelin.livy.create.session.timeout", + "zeppelin.livy.session.create_timeout": { + "envName": "ZEPPELIN_LIVY_SESSION_CREATE_TIMEOUT", + "propertyName": "zeppelin.livy.session.create_timeout", "defaultValue": "120", "description": "Livy Server create session timeout (seconds)." }, @@ -87,7 +87,7 @@ "defaultValue": "", "description": "Adding extra libraries to livy interpreter" }, - "livy.spark.displayAppInfo": { + "zeppelin.livy.displayAppInfo": { "propertyName": "zeppelin.livy.displayAppInfo", "defaultValue": "false", "description": "Whether display app info" diff --git a/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java b/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java index 8ca88421e0e..ada91ed2c30 100644 --- a/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java +++ b/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java @@ -51,7 +51,7 @@ public static void setUp() { LOGGER.info("Starting livy at {}", cluster.livyEndpoint()); properties = new Properties(); properties.setProperty("zeppelin.livy.url", cluster.livyEndpoint()); - properties.setProperty("zeppelin.livy.create.session.timeout", "120"); + properties.setProperty("zeppelin.livy.session.create_timeout", "120"); properties.setProperty("zeppelin.livy.spark.sql.maxResult", "100"); } @@ -313,6 +313,38 @@ public void testPySparkInterpreter() { } } + @Test + public void testSparkInterpreterWithDisplayAppInfo() { + if (!checkPreCondition()) { + return; + } + InterpreterGroup interpreterGroup = new InterpreterGroup("group_1"); + interpreterGroup.put("session_1", new ArrayList()); + Properties properties2 = new Properties(properties); + properties2.put("zeppelin.livy.displayAppInfo", "true"); + // enable spark ui because it is disabled by livy integration test + properties2.put("livy.spark.ui.enabled", "true"); + LivySparkInterpreter sparkInterpreter = new LivySparkInterpreter(properties2); + sparkInterpreter.setInterpreterGroup(interpreterGroup); + interpreterGroup.get("session_1").add(sparkInterpreter); + AuthenticationInfo authInfo = new AuthenticationInfo("user1"); + MyInterpreterOutputListener outputListener = new MyInterpreterOutputListener(); + InterpreterOutput output = new InterpreterOutput(outputListener); + InterpreterContext context = new InterpreterContext("noteId", "paragraphId", "livy.spark", + "title", "text", authInfo, null, null, null, null, null, output); + sparkInterpreter.open(); + + try { + InterpreterResult result = sparkInterpreter.interpret("sc.version", context); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + assertEquals(2, result.message().size()); + assertTrue(result.message().get(0).getData().contains("1.5.2")); + assertTrue(result.message().get(1).getData().contains("Spark Application Id")); + } finally { + sparkInterpreter.close(); + } + } + @Test public void testSparkRInterpreter() { if (!checkPreCondition()) { From 759f3fa6edde88312a55384cc8bfcf32364f3d9f Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Tue, 17 Jan 2017 19:42:43 +0900 Subject: [PATCH 032/234] [DOCS] Update interpter installation guide ### What is this PR for? * Update Scio interpreter's artifact name from `scio` to `scio_2.11`, which will be used for installing scio interpreter from netinst binary package. * Fix typos ### What type of PR is it? Documentation | Hotfix ### What is the Jira issue? ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Mina Lee Closes #1906 from minahlee/docs/interpreterInstall and squashes the following commits: 56eeab3 [Mina Lee] Update interpter installation guide (cherry picked from commit 26808e3a07d43b2533785bd08ed9d47948f3e778) Signed-off-by: Mina Lee --- conf/interpreter-list | 40 ++++++------- docs/manual/interpreterinstallation.md | 82 ++++++++++++++++---------- 2 files changed, 71 insertions(+), 51 deletions(-) diff --git a/conf/interpreter-list b/conf/interpreter-list index c22afef8a65..e0687dfbf69 100644 --- a/conf/interpreter-list +++ b/conf/interpreter-list @@ -17,23 +17,23 @@ # # [name] [maven artifact] [description] -alluxio org.apache.zeppelin:zeppelin-alluxio:0.6.1 Alluxio interpreter -angular org.apache.zeppelin:zeppelin-angular:0.6.1 HTML and AngularJS view rendering -beam org.apache.zeppelin:zeppelin-beam:0.6.1 Beam interpreter -bigquery org.apache.zeppelin:zeppelin-bigquery:0.6.1 BigQuery interpreter -cassandra org.apache.zeppelin:zeppelin-cassandra_2.11:0.6.1 Cassandra interpreter built with Scala 2.11 -elasticsearch org.apache.zeppelin:zeppelin-elasticsearch:0.6.1 Elasticsearch interpreter -file org.apache.zeppelin:zeppelin-file:0.6.1 HDFS file interpreter -flink org.apache.zeppelin:zeppelin-flink_2.11:0.6.1 Flink interpreter built with Scala 2.11 -hbase org.apache.zeppelin:zeppelin-hbase:0.6.1 Hbase interpreter -ignite org.apache.zeppelin:zeppelin-ignite_2.11:0.6.1 Ignite interpreter built with Scala 2.11 -jdbc org.apache.zeppelin:zeppelin-jdbc:0.6.1 Jdbc interpreter -kylin org.apache.zeppelin:zeppelin-kylin:0.6.1 Kylin interpreter -lens org.apache.zeppelin:zeppelin-lens:0.6.1 Lens interpreter -livy org.apache.zeppelin:zeppelin-livy:0.6.1 Livy interpreter -md org.apache.zeppelin:zeppelin-markdown:0.6.1 Markdown support -pig org.apache.zeppelin:zeppelin-pig:0.6.1 Pig interpreter -postgresql org.apache.zeppelin:zeppelin-postgresql:0.6.1 Postgresql interpreter -python org.apache.zeppelin:zeppelin-python:0.6.1 Python interpreter -scio org.apache.zeppelin:zeppelin-scio:0.6.1 Scio interpreter -shell org.apache.zeppelin:zeppelin-shell:0.6.1 Shell command +alluxio org.apache.zeppelin:zeppelin-alluxio:0.7.0 Alluxio interpreter +angular org.apache.zeppelin:zeppelin-angular:0.7.0 HTML and AngularJS view rendering +beam org.apache.zeppelin:zeppelin-beam:0.7.0 Beam interpreter +bigquery org.apache.zeppelin:zeppelin-bigquery:0.7.0 BigQuery interpreter +cassandra org.apache.zeppelin:zeppelin-cassandra_2.11:0.7.0 Cassandra interpreter built with Scala 2.11 +elasticsearch org.apache.zeppelin:zeppelin-elasticsearch:0.7.0 Elasticsearch interpreter +file org.apache.zeppelin:zeppelin-file:0.7.0 HDFS file interpreter +flink org.apache.zeppelin:zeppelin-flink_2.11:0.7.0 Flink interpreter built with Scala 2.11 +hbase org.apache.zeppelin:zeppelin-hbase:0.7.0 Hbase interpreter +ignite org.apache.zeppelin:zeppelin-ignite_2.11:0.7.0 Ignite interpreter built with Scala 2.11 +jdbc org.apache.zeppelin:zeppelin-jdbc:0.7.0 Jdbc interpreter +kylin org.apache.zeppelin:zeppelin-kylin:0.7.0 Kylin interpreter +lens org.apache.zeppelin:zeppelin-lens:0.7.0 Lens interpreter +livy org.apache.zeppelin:zeppelin-livy:0.7.0 Livy interpreter +md org.apache.zeppelin:zeppelin-markdown:0.7.0 Markdown support +pig org.apache.zeppelin:zeppelin-pig:0.7.0 Pig interpreter +postgresql org.apache.zeppelin:zeppelin-postgresql:0.7.0 Postgresql interpreter +python org.apache.zeppelin:zeppelin-python:0.7.0 Python interpreter +scio org.apache.zeppelin:zeppelin-scio_2.11:0.7.0 Scio interpreter +shell org.apache.zeppelin:zeppelin-shell:0.7.0 Shell command diff --git a/docs/manual/interpreterinstallation.md b/docs/manual/interpreterinstallation.md index 1c2f7f4558a..8a08366f0f3 100644 --- a/docs/manual/interpreterinstallation.md +++ b/docs/manual/interpreterinstallation.md @@ -23,10 +23,10 @@ limitations under the License.
    -Apache Zeppelin provides **Interpreter Installation** mechanism for whom downloaded Zeppelin `netinst` binary package, or just want to install another 3rd party interpreters. +Apache Zeppelin provides **Interpreter Installation** mechanism for whom downloaded Zeppelin `netinst` binary package, or just want to install another 3rd party interpreters. ## Community managed interpreters -Apache Zeppelin provides several interpreters as [community managed interpreters](#available-community-managed-interpreters). +Apache Zeppelin provides several interpreters as [community managed interpreters](#available-community-managed-interpreters). If you downloaded `netinst` binary package, you need to install by using below commands. #### Install all community managed interpreters @@ -48,7 +48,7 @@ You can get full list of community managed interpreters by running ``` #### Install interpreter built with Scala 2.10 -From version 0.6.1, Zeppelin support both Scala 2.10 and 2.11 for several interpreters as below: +Zeppelin support both Scala 2.10 and 2.11 for several interpreters as below: @@ -58,30 +58,35 @@ From version 0.6.1, Zeppelin support both Scala 2.10 and 2.11 for several interp - - + + - - + + - - + + - - - + + + + + + + +
    cassandraorg.apache.zeppelin:zeppelin-cassandra_2.10:0.6.1org.apache.zeppelin:zeppelin-cassandra_2.11:0.6.1org.apache.zeppelin:zeppelin-cassandra_2.10:0.7.0org.apache.zeppelin:zeppelin-cassandra_2.11:0.7.0
    flinkorg.apache.zeppelin:zeppelin-flink_2.10:0.6.1org.apache.zeppelin:zeppelin-flink_2.11:0.6.1org.apache.zeppelin:zeppelin-flink_2.10:0.7.0org.apache.zeppelin:zeppelin-flink_2.11:0.7.0
    igniteorg.apache.zeppelin:zeppelin-ignite_2.10:0.6.1org.apache.zeppelin:zeppelin-ignite_2.11:0.6.1org.apache.zeppelin:zeppelin-ignite_2.10:0.7.0org.apache.zeppelin:zeppelin-ignite_2.11:0.7.0
    flinkorg.apache.zeppelin:zeppelin-spark_2.10:0.6.1org.apache.zeppelin:zeppelin-spark_2.11:0.6.1scioorg.apache.zeppelin:zeppelin-scio_2.10:0.7.0org.apache.zeppelin:zeppelin-scio_2.11:0.7.0
    sparkorg.apache.zeppelin:zeppelin-spark_2.10:0.7.0org.apache.zeppelin:zeppelin-spark_2.11:0.7.0
    If you install one of these interpreters only with `--name` option, installer will download interpreter built with Scala 2.11 by default. If you want to specify Scala version, you will need to add `--artifact` option. Here is the example of installing flink interpreter built with Scala 2.10. ``` -./bin/install-interpreter.sh --name flink --artifact org.apache.zeppelin:zeppelin-flink_2.10:0.6.1 +./bin/install-interpreter.sh --name flink --artifact org.apache.zeppelin:zeppelin-flink_2.10:0.7.0 ``` #### Install Spark interpreter built with Scala 2.10 @@ -89,7 +94,7 @@ Spark distribution package has been built with Scala 2.10 until 1.6.2. If you ha ``` rm -rf ./interpreter/spark -./bin/install-interpreter.sh --name spark --artifact org.apache.zeppelin:zeppelin-spark_2.10:0.6.1 +./bin/install-interpreter.sh --name spark --artifact org.apache.zeppelin:zeppelin-spark_2.10:0.7.0 ```
    @@ -131,87 +136,102 @@ You can also find the below community managed interpreter list in `conf/interpre alluxio - org.apache.zeppelin:zeppelin-alluxio:0.6.1 + org.apache.zeppelin:zeppelin-alluxio:0.7.0 Alluxio interpreter angular - org.apache.zeppelin:zeppelin-angular:0.6.1 + org.apache.zeppelin:zeppelin-angular:0.7.0 HTML and AngularJS view rendering + + beam + org.apache.zeppelin:zeppelin-beam:0.7.0 + Beam interpreter + bigquery - org.apache.zeppelin:zeppelin-bigquery:0.6.1 + org.apache.zeppelin:zeppelin-bigquery:0.7.0 BigQuery interpreter cassandra - org.apache.zeppelin:zeppelin-cassandra\_2.11:0.6.1 + org.apache.zeppelin:zeppelin-cassandra\_2.11:0.7.0 Cassandra interpreter built with Scala 2.11 elasticsearch - org.apache.zeppelin:zeppelin-elasticsearch:0.6.1 + org.apache.zeppelin:zeppelin-elasticsearch:0.7.0 Elasticsearch interpreter file - org.apache.zeppelin:zeppelin-file:0.6.1 + org.apache.zeppelin:zeppelin-file:0.7.0 HDFS file interpreter flink - org.apache.zeppelin:zeppelin-flink\_2.11:0.6.1 + org.apache.zeppelin:zeppelin-flink\_2.11:0.7.0 Flink interpreter built with Scala 2.11 hbase - org.apache.zeppelin:zeppelin-hbase:0.6.1 + org.apache.zeppelin:zeppelin-hbase:0.7.0 Hbase interpreter ignite - org.apache.zeppelin:zeppelin-ignite\_2.11:0.6.1 + org.apache.zeppelin:zeppelin-ignite\_2.11:0.7.0 Ignite interpreter built with Scala 2.11 jdbc - org.apache.zeppelin:zeppelin-jdbc:0.6.1 + org.apache.zeppelin:zeppelin-jdbc:0.7.0 Jdbc interpreter kylin - org.apache.zeppelin:zeppelin-kylin:0.6.1 + org.apache.zeppelin:zeppelin-kylin:0.7.0 Kylin interpreter lens - org.apache.zeppelin:zeppelin-lens:0.6.1 + org.apache.zeppelin:zeppelin-lens:0.7.0 Lens interpreter livy - org.apache.zeppelin:zeppelin-livy:0.6.1 + org.apache.zeppelin:zeppelin-livy:0.7.0 Livy interpreter md - org.apache.zeppelin:zeppelin-markdown:0.6.1 + org.apache.zeppelin:zeppelin-markdown:0.7.0 Markdown support + + pig + org.apache.zeppelin:zeppelin-pig:0.7.0 + Pig interpreter + postgresql - org.apache.zeppelin:zeppelin-postgresql:0.6.1 + org.apache.zeppelin:zeppelin-postgresql:0.7.0 Postgresql interpreter python - org.apache.zeppelin:zeppelin-python:0.6.1 + org.apache.zeppelin:zeppelin-python:0.7.0 Python interpreter + + scio + org.apache.zeppelin:zeppelin-scio\_2.11:0.7.0 + Scio interpreter built with Scala 2.11 + shell - org.apache.zeppelin:zeppelin-shell:0.6.1 + org.apache.zeppelin:zeppelin-shell:0.7.0 Shell command From 2256f31e81de13ebf43475c897eac548f638083b Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Wed, 18 Jan 2017 15:10:07 +0800 Subject: [PATCH 033/234] ZEPPELIN-1977. spark 2.1 uses a more recent commons-lang3 ### What is this PR for? The issue is that spark 2.1 use `commons-lang3` 3.5 while `zeppelin-interpreter` use 3.4. We can not just upgrade `commons-lang3` to 3.5, as it just make spark 2.1 work, but would fail other versions of spark. This PR remove `commons-lang3` from zeppelin-interpreter. We should keep zeppelin-interpreter's dependencies as minimum as possible. We can remove other dependencies (like `commons-lang`) from `zeppelin-interpreter` in a followup PR. ### What type of PR is it? [Bug Fix] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-1977 ### How should this be tested? Tested manually ![2017-01-18_1448](https://cloud.githubusercontent.com/assets/164491/22054522/f125e836-dd90-11e6-9acf-d73541046d95.png) on 3 versions of spark (2.1, 2.0.2, 1.6.2) ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1910 from zjffdu/ZEPPELIN-1977 and squashes the following commits: 8bac97f [Jeff Zhang] ZEPPELIN-1977. spark 2.1 uses a more recent commons-lang3 (cherry picked from commit 88a905caf2043d12a85462940c451b424d218dba) Signed-off-by: Lee moon soo --- .../org/apache/zeppelin/livy/BaseLivyInterprereter.java | 2 +- .../org/apache/zeppelin/livy/LivySparkSQLInterpreter.java | 2 +- markdown/pom.xml | 7 +++++++ .../java/org/apache/zeppelin/pig/BasePigInterpreter.java | 2 +- .../main/java/org/apache/zeppelin/pig/PigInterpreter.java | 4 ++-- .../java/org/apache/zeppelin/pig/PigQueryInterpreter.java | 6 +++--- pig/src/main/java/org/apache/zeppelin/pig/PigUtils.java | 4 ++-- shell/pom.xml | 7 +++++++ zeppelin-interpreter/pom.xml | 7 ------- .../src/main/java/org/apache/zeppelin/dep/Booter.java | 2 +- .../src/main/java/org/apache/zeppelin/dep/Repository.java | 2 +- zeppelin-server/pom.xml | 4 ++++ zeppelin-zengine/pom.xml | 7 +++++++ 13 files changed, 37 insertions(+), 19 deletions(-) diff --git a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java index f0591fd424c..56b068d1c57 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/BaseLivyInterprereter.java @@ -20,7 +20,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.interpreter.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java b/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java index 471d199315c..3d1a606b79a 100644 --- a/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java +++ b/livy/src/main/java/org/apache/zeppelin/livy/LivySparkSQLInterpreter.java @@ -17,7 +17,7 @@ package org.apache.zeppelin.livy; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.scheduler.Scheduler; import org.apache.zeppelin.scheduler.SchedulerFactory; diff --git a/markdown/pom.xml b/markdown/pom.xml index 73629d7733f..179d0bde4b3 100644 --- a/markdown/pom.xml +++ b/markdown/pom.xml @@ -33,6 +33,7 @@ Zeppelin: Markdown interpreter + 3.4 2.2-cj-1.0 1.6.0 @@ -67,6 +68,12 @@ ${pegdown.version} + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + + junit junit diff --git a/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java b/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java index a9bb2ce2864..1fb2a69c37e 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/BasePigInterpreter.java @@ -17,7 +17,7 @@ package org.apache.zeppelin.pig; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.pig.PigServer; import org.apache.pig.backend.BackendException; diff --git a/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java b/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java index 0b50c670d85..973397cead1 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/PigInterpreter.java @@ -18,8 +18,8 @@ package org.apache.zeppelin.pig; import org.apache.commons.io.output.ByteArrayOutputStream; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.pig.PigServer; import org.apache.pig.impl.logicalLayer.FrontendException; import org.apache.pig.tools.pigstats.*; diff --git a/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java b/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java index 5e968d6929a..566b5368991 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/PigQueryInterpreter.java @@ -19,8 +19,8 @@ package org.apache.zeppelin.pig; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.pig.PigServer; import org.apache.pig.data.Tuple; import org.apache.pig.impl.logicalLayer.FrontendException; @@ -114,7 +114,7 @@ public InterpreterResult interpret(String st, InterpreterContext context) { resultBuilder.append("\n"); firstRow = false; } - resultBuilder.append(StringUtils.join(tuple, "\t")); + resultBuilder.append(StringUtils.join(tuple.iterator(), "\t")); resultBuilder.append("\n"); } if (index >= maxResult && iter.hasNext()) { diff --git a/pig/src/main/java/org/apache/zeppelin/pig/PigUtils.java b/pig/src/main/java/org/apache/zeppelin/pig/PigUtils.java index 3398281a4e4..43687a5e84a 100644 --- a/pig/src/main/java/org/apache/zeppelin/pig/PigUtils.java +++ b/pig/src/main/java/org/apache/zeppelin/pig/PigUtils.java @@ -19,8 +19,8 @@ import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.pig.PigRunner; import org.apache.pig.backend.hadoop.executionengine.tez.TezExecType; import org.apache.pig.tools.pigstats.InputStats; diff --git a/shell/pom.xml b/shell/pom.xml index 79cfd92996f..418cf5c830b 100644 --- a/shell/pom.xml +++ b/shell/pom.xml @@ -34,6 +34,7 @@ + 3.4 1.3 @@ -61,6 +62,12 @@ ${commons.exec.version} + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + + junit junit diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml index 9fbf7a1f401..34cf182a0f6 100644 --- a/zeppelin-interpreter/pom.xml +++ b/zeppelin-interpreter/pom.xml @@ -37,7 +37,6 @@ - 3.4 2.3 1.3 3.0 @@ -88,12 +87,6 @@ slf4j-log4j12 - - org.apache.commons - commons-lang3 - ${commons.lang3.version} - - org.apache.maven diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Booter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Booter.java index f96963b3a3e..0fd0ea205e3 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Booter.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Booter.java @@ -17,7 +17,7 @@ package org.apache.zeppelin.dep; -import org.apache.commons.lang3.Validate; +import org.apache.commons.lang.Validate; import org.apache.maven.repository.internal.MavenRepositorySystemSession; import org.sonatype.aether.RepositorySystem; import org.sonatype.aether.RepositorySystemSession; diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Repository.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Repository.java index 34fe4f05478..86f39bd44fa 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Repository.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/dep/Repository.java @@ -16,7 +16,7 @@ */ package org.apache.zeppelin.dep; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang.StringUtils.isNotBlank; import org.sonatype.aether.repository.Authentication; import org.sonatype.aether.repository.Proxy; diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index 6503e66fe2c..bacbca3743b 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -380,6 +380,10 @@ net.java.dev.jna jna + + org.apache.commons + commons-lang3 + diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index d620512825f..87d2f13648c 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -36,6 +36,7 @@ + 3.4 2.0 1.10.62 4.0.0 @@ -277,6 +278,12 @@ ${jetty.version} test + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + From b311ee719bf99da25f7a5226b6663309d4ddb274 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Mon, 16 Jan 2017 10:13:45 -0800 Subject: [PATCH 034/234] [ZEPPELIN-1974] Remove extension from webpack config for visualization bundle ### What is this PR for? webpack.config.js for creating visualization bundle has unnecessary extension configuration, which makes unable to import some libraries. This PR removes the unnecessary configuration and propagate 'npm install' error message to front-end. ### What type of PR is it? Bug Fix ### Todos * [x] - exclude 'extensions' from webpack.config.js for visualization bundle * [x] - propagate 'npm install' error to front-end ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1974 ### Screenshots (if appropriate) Before propagate error message to front-end ![image](https://cloud.githubusercontent.com/assets/1540981/21994155/2095e554-dbd3-11e6-8923-8deafecd350b.png) After propagate error message to front-end ![image](https://cloud.githubusercontent.com/assets/1540981/21994317/f8ffdcec-dbd3-11e6-8bec-156aa2d5bdf7.png) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo Closes #1905 from Leemoonsoo/npm_install_error and squashes the following commits: b665588 [Lee moon soo] Propagate npm install error to front-end 7635c55 [Lee moon soo] Remove extension from webpack config for visualization bundle (cherry picked from commit 28a8be46835a0ef1808e6336f3e020ad06a5db46) Signed-off-by: Lee moon soo --- .../org/apache/zeppelin/rest/HeliumRestApi.java | 8 +------- .../java/org/apache/zeppelin/helium/Helium.java | 13 +++++-------- .../zeppelin/helium/HeliumVisualizationFactory.java | 12 ++++++++---- .../src/main/resources/helium/webpack.config.js | 3 +-- 4 files changed, 15 insertions(+), 21 deletions(-) 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 953188bee6c..e5cf70d395e 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 @@ -144,9 +144,6 @@ public Response enablePackage(@PathParam("packageName") String packageName, } catch (IOException e) { logger.error(e.getMessage(), e); return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); - } catch (TaskRunnerException e) { - logger.error(e.getMessage(), e); - return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); } } @@ -159,9 +156,6 @@ public Response enablePackage(@PathParam("packageName") String packageName) { } catch (IOException e) { logger.error(e.getMessage(), e); return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); - } catch (TaskRunnerException e) { - logger.error(e.getMessage(), e); - return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); } } @@ -180,7 +174,7 @@ public Response setVisualizationPackageOrder(String orderedPackageNameList) { try { helium.setVisualizationPackageOrder(orderedList); - } catch (IOException | TaskRunnerException e) { + } catch (IOException e) { logger.error(e.getMessage(), e); return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build(); } 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 8ef30c871e5..b8584efce4d 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java @@ -16,7 +16,6 @@ */ package org.apache.zeppelin.helium; -import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.commons.io.FileUtils; @@ -31,8 +30,6 @@ import java.io.File; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; import java.util.*; /** @@ -54,7 +51,7 @@ public Helium( String defaultLocalRegistryPath, HeliumVisualizationFactory visualizationFactory, HeliumApplicationFactory applicationFactory) - throws IOException, TaskRunnerException { + throws IOException { this.heliumConfPath = heliumConfPath; this.defaultLocalRegistryPath = defaultLocalRegistryPath; this.visualizationFactory = visualizationFactory; @@ -206,11 +203,11 @@ public HeliumPackageSearchResult getPackageInfo(String name, String artifact) { return null; } - public File recreateVisualizationBundle() throws IOException, TaskRunnerException { + public File recreateVisualizationBundle() throws IOException { return visualizationFactory.bundle(getVisualizationPackagesToBundle(), true); } - public void enable(String name, String artifact) throws IOException, TaskRunnerException { + public void enable(String name, String artifact) throws IOException { HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact); // no package found. @@ -229,7 +226,7 @@ public void enable(String name, String artifact) throws IOException, TaskRunnerE save(); } - public void disable(String name) throws IOException, TaskRunnerException { + public void disable(String name) throws IOException { String artifact = heliumConf.getEnabledPackages().get(name); if (artifact == null) { @@ -344,7 +341,7 @@ public List getVisualizationPackageOrder() { } public void setVisualizationPackageOrder(List orderedPackageList) - throws IOException, TaskRunnerException { + throws IOException { heliumConf.setVisualizationDisplayOrder(orderedPackageList); // if package is visualization, rebuild bundle 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 index 1c1d25a0967..624f12aafda 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumVisualizationFactory.java @@ -94,12 +94,12 @@ private ProxyConfig getProxyConfig() { return new ProxyConfig(proxy); } - public File bundle(List pkgs) throws IOException, TaskRunnerException { + public File bundle(List pkgs) throws IOException { return bundle(pkgs, false); } public synchronized File bundle(List pkgs, boolean forceRefresh) - throws IOException, TaskRunnerException { + throws IOException { // package.json URL pkgUrl = Resources.getResource("helium/package.json"); String pkgJson = Resources.toString(pkgUrl, Charsets.UTF_8); @@ -213,8 +213,12 @@ public boolean accept(File pathname) { } out.reset(); - npmCommand("install"); - npmCommand("run bundle"); + 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()) { diff --git a/zeppelin-zengine/src/main/resources/helium/webpack.config.js b/zeppelin-zengine/src/main/resources/helium/webpack.config.js index 80b8c6a2155..2b5015e47a4 100644 --- a/zeppelin-zengine/src/main/resources/helium/webpack.config.js +++ b/zeppelin-zengine/src/main/resources/helium/webpack.config.js @@ -21,8 +21,7 @@ module.exports = { filename: 'vis.bundle.js', }, resolve: { - root: __dirname + "/node_modules", - extensions: [".js"] + root: __dirname + "/node_modules" }, module: { loaders: [{ From 392a0bad2fa0947b8b5f311599c6b0f63cc04a95 Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Thu, 19 Jan 2017 16:28:30 +0900 Subject: [PATCH 035/234] [BUILD] Remove docker build from release script ### What is this PR for? Removing docker build and push from release script. Docker managed by community will be supported sooner or later through https://hub.docker.com/r/apache/ or official docker hub repository, and either way doesn't need push from release script. ### What type of PR is it? Build ### What is the Jira issue? ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Mina Lee Closes #1926 from minahlee/remove_docker_build and squashes the following commits: 6ea789e [Mina Lee] Publish beam interpreter in maven central repo 3a9b64e [Mina Lee] Remove docker build (cherry picked from commit 53f8fa167b7ef95495588940ae87cabd8a377321) Signed-off-by: Mina Lee --- dev/create_release.sh | 20 +------------------- dev/publish_release.sh | 15 +++------------ 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/dev/create_release.sh b/dev/create_release.sh index 873bb10236e..cac6a75d128 100755 --- a/dev/create_release.sh +++ b/dev/create_release.sh @@ -33,7 +33,7 @@ if [[ $# -ne 2 ]]; then usage fi -for var in GPG_PASSPHRASE DOCKER_USERNAME; do +for var in GPG_PASSPHRASE; do if [[ -z "${!var}" ]]; then echo "You need ${var} variable set" exit 1 @@ -44,18 +44,6 @@ RELEASE_VERSION="$1" GIT_TAG="$2" SCALA_VERSION="2.11" -function build_docker_base() { - # build base image - docker build -t ${DOCKER_USERNAME}/zeppelin-base:latest "${BASEDIR}/../scripts/docker/zeppelin-base" -} -function build_docker_image() { - # build release image - echo "FROM ${DOCKER_USERNAME}/zeppelin-base:latest - RUN mkdir /usr/local/zeppelin/ - ADD zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME} /usr/local/zeppelin/" > "Dockerfile" - docker build -t ${DOCKER_USERNAME}/zeppelin-release:"${RELEASE_VERSION}" . -} - function make_source_package() { # create source package cd ${WORKING_DIR} @@ -112,16 +100,10 @@ function make_binary_release() { mv "zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}.tgz.md5" "${WORKING_DIR}/" mv "zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}.tgz.sha512" "${WORKING_DIR}/" - # build docker image if binary_release_name 'all' - if [[ $1 = "all" ]]; then - build_docker_image - fi - # clean up build dir rm -rf "${WORKING_DIR}/zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}" } -build_docker_base git_clone make_source_package make_binary_release all "-Pspark-2.1 -Phadoop-2.6 -Pyarn -Ppyspark -Psparkr -Pscala-${SCALA_VERSION}" diff --git a/dev/publish_release.sh b/dev/publish_release.sh index a2acc983972..cf3de107c79 100755 --- a/dev/publish_release.sh +++ b/dev/publish_release.sh @@ -30,7 +30,7 @@ if [[ $# -ne 2 ]]; then usage fi -for var in GPG_PASSPHRASE ASF_USERID ASF_PASSWORD DOCKER_USERNAME DOCKER_PASSWORD DOCKER_EMAIL; do +for var in GPG_PASSPHRASE ASF_USERID ASF_PASSWORD; do if [[ -z "${!var}" ]]; then echo "You need ${var} variable set" exit 1 @@ -67,14 +67,6 @@ function curl_error() { fi } -function publish_to_dockerhub() { - # publish images - docker login --username="${DOCKER_USERNAME}" --password="${DOCKER_PASSWORD}" --email="${DOCKER_EMAIL}" - docker push ${DOCKER_USERNAME}/zeppelin-base:latest - docker push ${DOCKER_USERNAME}/zeppelin-release:"${RELEASE_VERSION}" - -} - function publish_to_maven() { cd "${WORKING_DIR}/zeppelin" @@ -102,9 +94,9 @@ function publish_to_maven() { # build with scala-2.10 echo "mvn clean install -DskipTests \ - -Dmaven.repo.local=${tmp_repo} -Pscala-2.10 \ + -Dmaven.repo.local=${tmp_repo} -Pscala-2.10 -Pbeam \ ${PUBLISH_PROFILES} ${PROJECT_OPTIONS}" - mvn clean install -DskipTests -Dmaven.repo.local="${tmp_repo}" -Pscala-2.10 \ + mvn clean install -DskipTests -Dmaven.repo.local="${tmp_repo}" -Pscala-2.10 -Pbeam \ ${PUBLISH_PROFILES} ${PROJECT_OPTIONS} if [[ $? -ne 0 ]]; then echo "Build with scala 2.10 failed." @@ -161,6 +153,5 @@ function publish_to_maven() { } git_clone -publish_to_dockerhub publish_to_maven cleanup From bcbe0855175f55eddd10b918303806eb5e5db091 Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Sat, 21 Jan 2017 17:18:41 +0900 Subject: [PATCH 036/234] Preparing Apache Zeppelin release 0.7.0 --- alluxio/pom.xml | 4 ++-- angular/pom.xml | 4 ++-- beam/pom.xml | 4 ++-- bigquery/pom.xml | 4 ++-- cassandra/pom.xml | 4 ++-- docs/_config.yml | 4 ++-- elasticsearch/pom.xml | 4 ++-- file/pom.xml | 4 ++-- flink/pom.xml | 4 ++-- geode/pom.xml | 4 ++-- hbase/pom.xml | 4 ++-- helium-dev/pom.xml | 4 ++-- ignite/pom.xml | 4 ++-- jdbc/pom.xml | 4 ++-- kylin/pom.xml | 4 ++-- lens/pom.xml | 4 ++-- livy/pom.xml | 4 ++-- markdown/pom.xml | 4 ++-- pig/pom.xml | 4 ++-- pom.xml | 2 +- postgresql/pom.xml | 4 ++-- python/pom.xml | 4 ++-- r/pom.xml | 2 +- scalding/pom.xml | 4 ++-- scio/pom.xml | 4 ++-- shell/pom.xml | 4 ++-- spark-dependencies/pom.xml | 4 ++-- spark/pom.xml | 4 ++-- zeppelin-display/pom.xml | 4 ++-- zeppelin-distribution/pom.xml | 2 +- zeppelin-examples/pom.xml | 4 ++-- zeppelin-examples/zeppelin-example-clock/pom.xml | 4 ++-- .../zeppelin-example-clock/zeppelin-example-clock.json | 2 +- zeppelin-examples/zeppelin-example-horizontalbar/pom.xml | 4 ++-- zeppelin-interpreter/pom.xml | 4 ++-- zeppelin-server/pom.xml | 4 ++-- zeppelin-web/pom.xml | 4 ++-- zeppelin-web/src/app/tabledata/package.json | 2 +- zeppelin-web/src/app/visualization/package.json | 2 +- zeppelin-zengine/pom.xml | 4 ++-- 40 files changed, 74 insertions(+), 74 deletions(-) diff --git a/alluxio/pom.xml b/alluxio/pom.xml index 55c964a7f3a..8b4be3c87f8 100644 --- a/alluxio/pom.xml +++ b/alluxio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-alluxio jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Alluxio interpreter diff --git a/angular/pom.xml b/angular/pom.xml index afc20d18562..a3d78629db3 100644 --- a/angular/pom.xml +++ b/angular/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-angular jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Angular interpreter diff --git a/beam/pom.xml b/beam/pom.xml index 02a7ab30343..b21eb23d5b3 100644 --- a/beam/pom.xml +++ b/beam/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-beam jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Beam interpreter diff --git a/bigquery/pom.xml b/bigquery/pom.xml index 47db5d96c47..f6f89f7d3cc 100644 --- a/bigquery/pom.xml +++ b/bigquery/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-bigquery jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: BigQuery interpreter diff --git a/cassandra/pom.xml b/cassandra/pom.xml index 16c002f5632..ec0b085f6ff 100644 --- a/cassandra/pom.xml +++ b/cassandra/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-cassandra_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Apache Cassandra interpreter Zeppelin cassandra support diff --git a/docs/_config.yml b/docs/_config.yml index 63b12300aff..e20e3971b89 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -21,7 +21,7 @@ author : twitter : ASF feedburner : feedname -ZEPPELIN_VERSION : 0.7.0-SNAPSHOT +ZEPPELIN_VERSION : 0.7.0 # The production_url is only used when full-domain names are needed # such as sitemap.txt @@ -59,7 +59,7 @@ JB : # - Only the following values are falsy: ["", null, false] # - When setting BASE_PATH it must be a valid url. # This means always setting the protocol (http|https) or prefixing with "/" - BASE_PATH : /docs/0.7.0-SNAPSHOT + BASE_PATH : /docs/0.7.0 # By default, the asset_path is automatically defined relative to BASE_PATH plus the enabled theme. # ex: [BASE_PATH]/assets/themes/[THEME-NAME] diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml index 97abd3fcf14..5e621d4dbd3 100644 --- a/elasticsearch/pom.xml +++ b/elasticsearch/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-elasticsearch jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Elasticsearch interpreter diff --git a/file/pom.xml b/file/pom.xml index 0f7089b31f9..8ce5d3ee588 100644 --- a/file/pom.xml +++ b/file/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-file jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: File System Interpreters diff --git a/flink/pom.xml b/flink/pom.xml index 7ed559f687a..f20d30471b1 100644 --- a/flink/pom.xml +++ b/flink/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-flink_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Flink Zeppelin flink support diff --git a/geode/pom.xml b/geode/pom.xml index 9f5601832fe..c92cdd98535 100644 --- a/geode/pom.xml +++ b/geode/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-geode jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Apache Geode interpreter diff --git a/hbase/pom.xml b/hbase/pom.xml index 481221c03e8..cb14389d4af 100644 --- a/hbase/pom.xml +++ b/hbase/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-hbase jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: HBase interpreter diff --git a/helium-dev/pom.xml b/helium-dev/pom.xml index 938007d2a45..188ce7a1183 100644 --- a/helium-dev/pom.xml +++ b/helium-dev/pom.xml @@ -24,12 +24,12 @@ org.apache.zeppelin zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin helium-dev - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Helium development interpreter diff --git a/ignite/pom.xml b/ignite/pom.xml index 635036f1383..812649b4b05 100644 --- a/ignite/pom.xml +++ b/ignite/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. zeppelin-ignite_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Apache Ignite interpreter diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 9a6e97aaca1..903a095f815 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-jdbc jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: JDBC interpreter diff --git a/kylin/pom.xml b/kylin/pom.xml index 0d7c7ce32c5..b859d06d48d 100644 --- a/kylin/pom.xml +++ b/kylin/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 4.0.0 org.apache.zeppelin zeppelin-kylin jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Kylin interpreter diff --git a/lens/pom.xml b/lens/pom.xml index 991456028a6..63b1837ccbe 100644 --- a/lens/pom.xml +++ b/lens/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-lens jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Lens interpreter diff --git a/livy/pom.xml b/livy/pom.xml index c173e9e5db9..b85aa58d722 100644 --- a/livy/pom.xml +++ b/livy/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-livy jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Livy interpreter diff --git a/markdown/pom.xml b/markdown/pom.xml index 179d0bde4b3..32023383238 100644 --- a/markdown/pom.xml +++ b/markdown/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-markdown jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Markdown interpreter diff --git a/pig/pom.xml b/pig/pom.xml index 1dbfa1468ee..e60c88cff11 100644 --- a/pig/pom.xml +++ b/pig/pom.xml @@ -24,13 +24,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-pig jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Apache Pig Interpreter Zeppelin interpreter for Apache Pig http://zeppelin.apache.org diff --git a/pom.xml b/pom.xml index 481015ccb67..41b61f29d64 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.zeppelin zeppelin pom - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin Zeppelin project http://zeppelin.apache.org diff --git a/postgresql/pom.xml b/postgresql/pom.xml index 3580889e30c..0159d5fa0fc 100644 --- a/postgresql/pom.xml +++ b/postgresql/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-postgresql jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: PostgreSQL interpreter diff --git a/python/pom.xml b/python/pom.xml index aa9e4b1285b..fb856ed3ecd 100644 --- a/python/pom.xml +++ b/python/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-python jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Python interpreter diff --git a/r/pom.xml b/r/pom.xml index eeb9994346a..9f26a141d3e 100644 --- a/r/pom.xml +++ b/r/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. diff --git a/scalding/pom.xml b/scalding/pom.xml index 83485b01a9a..e301a87be22 100644 --- a/scalding/pom.xml +++ b/scalding/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-scalding_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Scalding interpreter diff --git a/scio/pom.xml b/scio/pom.xml index 5d385ac3d52..90ab6e2c69d 100644 --- a/scio/pom.xml +++ b/scio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-scio_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Scio Zeppelin Scio support diff --git a/shell/pom.xml b/shell/pom.xml index 418cf5c830b..0c03f5cc40a 100644 --- a/shell/pom.xml +++ b/shell/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-shell jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Shell interpreter diff --git a/spark-dependencies/pom.xml b/spark-dependencies/pom.xml index 887964db819..c41876d1d14 100644 --- a/spark-dependencies/pom.xml +++ b/spark-dependencies/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-spark-dependencies_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Spark dependencies Zeppelin spark support diff --git a/spark/pom.xml b/spark/pom.xml index 4989af9ac29..5ffad2a3a0e 100644 --- a/spark/pom.xml +++ b/spark/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-spark_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Spark Zeppelin spark support diff --git a/zeppelin-display/pom.xml b/zeppelin-display/pom.xml index 1127228cfc9..e25618da1b0 100644 --- a/zeppelin-display/pom.xml +++ b/zeppelin-display/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-display_2.10 jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Display system apis diff --git a/zeppelin-distribution/pom.xml b/zeppelin-distribution/pom.xml index 378c980909a..a45bbf64504 100644 --- a/zeppelin-distribution/pom.xml +++ b/zeppelin-distribution/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. diff --git a/zeppelin-examples/pom.xml b/zeppelin-examples/pom.xml index 0109a41fa34..ebfdb50c4c8 100644 --- a/zeppelin-examples/pom.xml +++ b/zeppelin-examples/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-examples pom - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Examples Zeppelin examples diff --git a/zeppelin-examples/zeppelin-example-clock/pom.xml b/zeppelin-examples/zeppelin-example-clock/pom.xml index 82f744c27eb..9f915fc4cdd 100644 --- a/zeppelin-examples/zeppelin-example-clock/pom.xml +++ b/zeppelin-examples/zeppelin-example-clock/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-example-clock jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Example application - Clock diff --git a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json index 3e48697c5a7..a1c16ec3321 100644 --- a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json +++ b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json @@ -18,7 +18,7 @@ "type" : "APPLICATION", "name" : "zeppelin.clock", "description" : "Clock (example)", - "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNAPSHOT.jar", + "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0.jar", "className" : "org.apache.zeppelin.example.app.clock.Clock", "resources" : [[":java.util.Date"]], "license" : "Apache-2.0", diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml index 8784d79b385..aa7bf7b78ff 100644 --- a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml +++ b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-example-horizontalbar jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Example application - Horizontal Bar chart diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml index 34cf182a0f6..192de0d1c93 100644 --- a/zeppelin-interpreter/pom.xml +++ b/zeppelin-interpreter/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-interpreter jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Interpreter Zeppelin Interpreter diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index bacbca3743b..f1fc6771e9f 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-server jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Server diff --git a/zeppelin-web/pom.xml b/zeppelin-web/pom.xml index f5fd73ec8f5..8aa60bc0329 100644 --- a/zeppelin-web/pom.xml +++ b/zeppelin-web/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-web war - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: web Application diff --git a/zeppelin-web/src/app/tabledata/package.json b/zeppelin-web/src/app/tabledata/package.json index e92abb2a995..ef0a47b9d20 100644 --- a/zeppelin-web/src/app/tabledata/package.json +++ b/zeppelin-web/src/app/tabledata/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-tabledata", "description": "tabledata api", - "version": "0.7.0-SNAPSHOT", + "version": "0.7.0", "main": "tabledata", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-web/src/app/visualization/package.json b/zeppelin-web/src/app/visualization/package.json index c0fb9d327c6..55d204dcb67 100644 --- a/zeppelin-web/src/app/visualization/package.json +++ b/zeppelin-web/src/app/visualization/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-vis", "description": "Visualization API", - "version": "0.7.0-SNAPSHOT", + "version": "0.7.0", "main": "visualization", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index 87d2f13648c..3e3d88d806d 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-zengine jar - 0.7.0-SNAPSHOT + 0.7.0 Zeppelin: Zengine Zeppelin Zengine From e713c32e99cdd66a6a5823a5d0f156506c5069fd Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Sat, 21 Jan 2017 17:23:22 +0900 Subject: [PATCH 037/234] Preparing development version 0.7.1-SNAPSHOT --- alluxio/pom.xml | 4 ++-- angular/pom.xml | 4 ++-- beam/pom.xml | 4 ++-- bigquery/pom.xml | 4 ++-- cassandra/pom.xml | 4 ++-- elasticsearch/pom.xml | 4 ++-- file/pom.xml | 4 ++-- flink/pom.xml | 4 ++-- geode/pom.xml | 4 ++-- hbase/pom.xml | 4 ++-- helium-dev/pom.xml | 4 ++-- ignite/pom.xml | 4 ++-- jdbc/pom.xml | 4 ++-- kylin/pom.xml | 4 ++-- lens/pom.xml | 4 ++-- livy/pom.xml | 4 ++-- markdown/pom.xml | 4 ++-- pig/pom.xml | 4 ++-- pom.xml | 2 +- postgresql/pom.xml | 4 ++-- python/pom.xml | 4 ++-- r/pom.xml | 2 +- scalding/pom.xml | 4 ++-- scio/pom.xml | 4 ++-- shell/pom.xml | 4 ++-- spark-dependencies/pom.xml | 4 ++-- spark/pom.xml | 4 ++-- zeppelin-display/pom.xml | 4 ++-- zeppelin-distribution/pom.xml | 2 +- zeppelin-examples/pom.xml | 4 ++-- zeppelin-examples/zeppelin-example-clock/pom.xml | 4 ++-- .../zeppelin-example-clock/zeppelin-example-clock.json | 2 +- zeppelin-examples/zeppelin-example-horizontalbar/pom.xml | 4 ++-- zeppelin-interpreter/pom.xml | 4 ++-- zeppelin-server/pom.xml | 4 ++-- zeppelin-web/pom.xml | 4 ++-- zeppelin-web/src/app/tabledata/package.json | 2 +- zeppelin-web/src/app/visualization/package.json | 2 +- zeppelin-zengine/pom.xml | 4 ++-- 39 files changed, 72 insertions(+), 72 deletions(-) diff --git a/alluxio/pom.xml b/alluxio/pom.xml index 8b4be3c87f8..0c839ccf4cb 100644 --- a/alluxio/pom.xml +++ b/alluxio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-alluxio jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Alluxio interpreter diff --git a/angular/pom.xml b/angular/pom.xml index a3d78629db3..f40e9183216 100644 --- a/angular/pom.xml +++ b/angular/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-angular jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Angular interpreter diff --git a/beam/pom.xml b/beam/pom.xml index b21eb23d5b3..fab559dc3c4 100644 --- a/beam/pom.xml +++ b/beam/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-beam jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Beam interpreter diff --git a/bigquery/pom.xml b/bigquery/pom.xml index f6f89f7d3cc..032178a02cb 100644 --- a/bigquery/pom.xml +++ b/bigquery/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-bigquery jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: BigQuery interpreter diff --git a/cassandra/pom.xml b/cassandra/pom.xml index ec0b085f6ff..fee8a1b24fe 100644 --- a/cassandra/pom.xml +++ b/cassandra/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-cassandra_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Cassandra interpreter Zeppelin cassandra support diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml index 5e621d4dbd3..eb06a8494b2 100644 --- a/elasticsearch/pom.xml +++ b/elasticsearch/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-elasticsearch jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Elasticsearch interpreter diff --git a/file/pom.xml b/file/pom.xml index 8ce5d3ee588..8aadfd6918c 100644 --- a/file/pom.xml +++ b/file/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-file jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: File System Interpreters diff --git a/flink/pom.xml b/flink/pom.xml index f20d30471b1..54eeaf576e5 100644 --- a/flink/pom.xml +++ b/flink/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-flink_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Flink Zeppelin flink support diff --git a/geode/pom.xml b/geode/pom.xml index c92cdd98535..ba793a05a5c 100644 --- a/geode/pom.xml +++ b/geode/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-geode jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Geode interpreter diff --git a/hbase/pom.xml b/hbase/pom.xml index cb14389d4af..da0a9745177 100644 --- a/hbase/pom.xml +++ b/hbase/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-hbase jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: HBase interpreter diff --git a/helium-dev/pom.xml b/helium-dev/pom.xml index 188ce7a1183..62d20f40e62 100644 --- a/helium-dev/pom.xml +++ b/helium-dev/pom.xml @@ -24,12 +24,12 @@ org.apache.zeppelin zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin helium-dev - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Helium development interpreter diff --git a/ignite/pom.xml b/ignite/pom.xml index 812649b4b05..2dd544201c8 100644 --- a/ignite/pom.xml +++ b/ignite/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. zeppelin-ignite_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Ignite interpreter diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 903a095f815..dfbb8f78b22 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-jdbc jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: JDBC interpreter diff --git a/kylin/pom.xml b/kylin/pom.xml index b859d06d48d..6db44a3ab65 100644 --- a/kylin/pom.xml +++ b/kylin/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT 4.0.0 org.apache.zeppelin zeppelin-kylin jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Kylin interpreter diff --git a/lens/pom.xml b/lens/pom.xml index 63b1837ccbe..13056608d34 100644 --- a/lens/pom.xml +++ b/lens/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-lens jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Lens interpreter diff --git a/livy/pom.xml b/livy/pom.xml index b85aa58d722..367369117f7 100644 --- a/livy/pom.xml +++ b/livy/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-livy jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Livy interpreter diff --git a/markdown/pom.xml b/markdown/pom.xml index 32023383238..d12085dc238 100644 --- a/markdown/pom.xml +++ b/markdown/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-markdown jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Markdown interpreter diff --git a/pig/pom.xml b/pig/pom.xml index e60c88cff11..4e2ec7b2e0a 100644 --- a/pig/pom.xml +++ b/pig/pom.xml @@ -24,13 +24,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-pig jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Pig Interpreter Zeppelin interpreter for Apache Pig http://zeppelin.apache.org diff --git a/pom.xml b/pom.xml index 41b61f29d64..3342727bd72 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.zeppelin zeppelin pom - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin Zeppelin project http://zeppelin.apache.org diff --git a/postgresql/pom.xml b/postgresql/pom.xml index 0159d5fa0fc..7b716c9ffb2 100644 --- a/postgresql/pom.xml +++ b/postgresql/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-postgresql jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: PostgreSQL interpreter diff --git a/python/pom.xml b/python/pom.xml index fb856ed3ecd..cd481c1785a 100644 --- a/python/pom.xml +++ b/python/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-python jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Python interpreter diff --git a/r/pom.xml b/r/pom.xml index 9f26a141d3e..c0176c0a72c 100644 --- a/r/pom.xml +++ b/r/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. diff --git a/scalding/pom.xml b/scalding/pom.xml index e301a87be22..fc8b7e3ea4d 100644 --- a/scalding/pom.xml +++ b/scalding/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-scalding_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Scalding interpreter diff --git a/scio/pom.xml b/scio/pom.xml index 90ab6e2c69d..233d934cae0 100644 --- a/scio/pom.xml +++ b/scio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-scio_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Scio Zeppelin Scio support diff --git a/shell/pom.xml b/shell/pom.xml index 0c03f5cc40a..48df49b92a7 100644 --- a/shell/pom.xml +++ b/shell/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-shell jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Shell interpreter diff --git a/spark-dependencies/pom.xml b/spark-dependencies/pom.xml index c41876d1d14..5dac2cbfb77 100644 --- a/spark-dependencies/pom.xml +++ b/spark-dependencies/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-spark-dependencies_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Spark dependencies Zeppelin spark support diff --git a/spark/pom.xml b/spark/pom.xml index 5ffad2a3a0e..c414d3d13f2 100644 --- a/spark/pom.xml +++ b/spark/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-spark_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Spark Zeppelin spark support diff --git a/zeppelin-display/pom.xml b/zeppelin-display/pom.xml index e25618da1b0..a1d06e1e7d3 100644 --- a/zeppelin-display/pom.xml +++ b/zeppelin-display/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-display_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Display system apis diff --git a/zeppelin-distribution/pom.xml b/zeppelin-distribution/pom.xml index a45bbf64504..b83da5b9555 100644 --- a/zeppelin-distribution/pom.xml +++ b/zeppelin-distribution/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. diff --git a/zeppelin-examples/pom.xml b/zeppelin-examples/pom.xml index ebfdb50c4c8..8361be470fa 100644 --- a/zeppelin-examples/pom.xml +++ b/zeppelin-examples/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-examples pom - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Examples Zeppelin examples diff --git a/zeppelin-examples/zeppelin-example-clock/pom.xml b/zeppelin-examples/zeppelin-example-clock/pom.xml index 9f915fc4cdd..b14cf33043a 100644 --- a/zeppelin-examples/zeppelin-example-clock/pom.xml +++ b/zeppelin-examples/zeppelin-example-clock/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-example-clock jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Example application - Clock diff --git a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json index a1c16ec3321..20a2289996a 100644 --- a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json +++ b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json @@ -18,7 +18,7 @@ "type" : "APPLICATION", "name" : "zeppelin.clock", "description" : "Clock (example)", - "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0.jar", + "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.1-SNAPSHOT.jar", "className" : "org.apache.zeppelin.example.app.clock.Clock", "resources" : [[":java.util.Date"]], "license" : "Apache-2.0", diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml index aa7bf7b78ff..235fd460684 100644 --- a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml +++ b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-example-horizontalbar jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Example application - Horizontal Bar chart diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml index 192de0d1c93..7d066dacd5b 100644 --- a/zeppelin-interpreter/pom.xml +++ b/zeppelin-interpreter/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-interpreter jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Interpreter Zeppelin Interpreter diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index f1fc6771e9f..2b26f16998e 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-server jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Server diff --git a/zeppelin-web/pom.xml b/zeppelin-web/pom.xml index 8aa60bc0329..5f7c2db1d99 100644 --- a/zeppelin-web/pom.xml +++ b/zeppelin-web/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-web war - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: web Application diff --git a/zeppelin-web/src/app/tabledata/package.json b/zeppelin-web/src/app/tabledata/package.json index ef0a47b9d20..d2424158da7 100644 --- a/zeppelin-web/src/app/tabledata/package.json +++ b/zeppelin-web/src/app/tabledata/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-tabledata", "description": "tabledata api", - "version": "0.7.0", + "version": "0.7.1-SNAPSHOT", "main": "tabledata", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-web/src/app/visualization/package.json b/zeppelin-web/src/app/visualization/package.json index 55d204dcb67..01e0c37a16f 100644 --- a/zeppelin-web/src/app/visualization/package.json +++ b/zeppelin-web/src/app/visualization/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-vis", "description": "Visualization API", - "version": "0.7.0", + "version": "0.7.1-SNAPSHOT", "main": "visualization", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index 3e3d88d806d..bf0cfce7dab 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-zengine jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Zengine Zeppelin Zengine From ca62d386a5c4f14c3bc69b4ddaf124efb6498a78 Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Sun, 22 Jan 2017 12:52:55 +0800 Subject: [PATCH 038/234] ZEPPELIN-1985 Remove user from pig tutorial note ### What is this PR for? Should remove the user from pig tutorial note, otherwise it can not be seen in anonymous mode. ### What type of PR is it? [Bug Fix] ### Todos * [ ] - Task ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1985 ### How should this be tested? Tested it in anonymous mode ### Screenshots (if appropriate) ![image](https://cloud.githubusercontent.com/assets/164491/22135013/5f34ad78-df06-11e6-9043-fffce363bb9e.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang Closes #1915 from zjffdu/ZEPPELIN-1985 and squashes the following commits: 4432f89 [Jeff Zhang] add downloading bank.csv c92b8f3 [Jeff Zhang] ZEPPELIN-1985. Remove user from pig tutorial note (cherry picked from commit 3c5d1283b58fbb54057526bd85fd0b95c6a4da90) Signed-off-by: Mina Lee --- notebook/2C57UKYWR/note.json | 112 ++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 47 deletions(-) diff --git a/notebook/2C57UKYWR/note.json b/notebook/2C57UKYWR/note.json index 8292592655f..21d1231ff82 100644 --- a/notebook/2C57UKYWR/note.json +++ b/notebook/2C57UKYWR/note.json @@ -2,8 +2,8 @@ "paragraphs": [ { "text": "%md\n\n\n### [Apache Pig](http://pig.apache.org/) is a platform for analyzing large data sets that consists of a high-level language for expressing data analysis programs, coupled with infrastructure for evaluating these programs. The salient property of Pig programs is that their structure is amenable to substantial parallelization, which in turns enables them to handle very large data sets.\n\nPig\u0027s language layer currently consists of a textual language called Pig Latin, which has the following key properties:\n\n* Ease of programming. It is trivial to achieve parallel execution of simple, \"embarrassingly parallel\" data analysis tasks. Complex tasks comprised of multiple interrelated data transformations are explicitly encoded as data flow sequences, making them easy to write, understand, and maintain.\n* Optimization opportunities. The way in which tasks are encoded permits the system to optimize their execution automatically, allowing the user to focus on semantics rather than efficiency.\n* Extensibility. Users can create their own functions to do special-purpose processing.\n", - "user": "user1", - "dateUpdated": "Jan 6, 2017 3:55:03 PM", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:48:50 PM", "config": { "colWidth": 12.0, "enabled": true, @@ -33,15 +33,15 @@ "jobName": "paragraph_1483277502513_1156234051", "id": "20170101-213142_1565013608", "dateCreated": "Jan 1, 2017 9:31:42 PM", - "dateStarted": "Jan 6, 2017 3:55:03 PM", - "dateFinished": "Jan 6, 2017 3:55:04 PM", + "dateStarted": "Jan 22, 2017 12:48:50 PM", + "dateFinished": "Jan 22, 2017 12:48:51 PM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%md\n\nThis pig tutorial use pig to do the same thing as spark tutorial. The default mode is mapreduce, you can also use other modes like local/tez_local/tez. For mapreduce mode, you need to have hadoop installed and export `HADOOP_CONF_DIR` in `zeppelin-env.sh`\n\nThe tutorial consists of 3 steps.\n\n* Use shell interpreter to download bank.csv and upload it to hdfs\n* use `%pig` to process the data\n* use `%pig.query` to query the data", - "user": "user1", - "dateUpdated": "Jan 6, 2017 3:55:18 PM", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:48:55 PM", "config": { "colWidth": 12.0, "enabled": true, @@ -71,15 +71,51 @@ "jobName": "paragraph_1483689316217_-629483391", "id": "20170106-155516_1050601059", "dateCreated": "Jan 6, 2017 3:55:16 PM", - "dateStarted": "Jan 6, 2017 3:55:18 PM", - "dateFinished": "Jan 6, 2017 3:55:18 PM", + "dateStarted": "Jan 22, 2017 12:48:55 PM", + "dateFinished": "Jan 22, 2017 12:48:55 PM", + "status": "FINISHED", + "progressUpdateIntervalMs": 500 + }, + { + "text": "%sh\n\nwget https://s3.amazonaws.com/apache-zeppelin/tutorial/bank/bank.csv\nhadoop fs -put bank.csv .\n", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:51:48 PM", + "config": { + "colWidth": 12.0, + "enabled": true, + "results": {}, + "editorSetting": { + "language": "text", + "editOnDblClick": false + }, + "editorMode": "ace/mode/text" + }, + "settings": { + "params": {}, + "forms": {} + }, + "results": { + "code": "SUCCESS", + "msg": [ + { + "type": "TEXT", + "data": "--2017-01-22 12:51:48-- https://s3.amazonaws.com/apache-zeppelin/tutorial/bank/bank.csv\nResolving s3.amazonaws.com... 52.216.80.227\nConnecting to s3.amazonaws.com|52.216.80.227|:443... connected.\nHTTP request sent, awaiting response... 200 OK\nLength: 461474 (451K) [application/octet-stream]\nSaving to: \u0027bank.csv.3\u0027\n\n 0K .......... .......... .......... .......... .......... 11% 141K 3s\n 50K .......... .......... .......... .......... .......... 22% 243K 2s\n 100K .......... .......... .......... .......... .......... 33% 449K 1s\n 150K .......... .......... .......... .......... .......... 44% 413K 1s\n 200K .......... .......... .......... .......... .......... 55% 746K 1s\n 250K .......... .......... .......... .......... .......... 66% 588K 0s\n 300K .......... .......... .......... .......... .......... 77% 840K 0s\n 350K .......... .......... .......... .......... .......... 88% 795K 0s\n 400K .......... .......... .......... .......... .......... 99% 1.35M 0s\n 450K 100% 13.2K\u003d1.1s\n\n2017-01-22 12:51:50 (409 KB/s) - \u0027bank.csv.3\u0027 saved [461474/461474]\n\n17/01/22 12:51:51 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n" + } + ] + }, + "apps": [], + "jobName": "paragraph_1485058437578_-1906301827", + "id": "20170122-121357_640055590", + "dateCreated": "Jan 22, 2017 12:13:57 PM", + "dateStarted": "Jan 22, 2017 12:51:48 PM", + "dateFinished": "Jan 22, 2017 12:51:52 PM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%pig\n\nbankText \u003d load \u0027bank.csv\u0027 using PigStorage(\u0027;\u0027);\nbank \u003d foreach bankText generate $0 as age, $1 as job, $2 as marital, $3 as education, $5 as balance; \nbank \u003d filter bank by age !\u003d \u0027\"age\"\u0027;\nbank \u003d foreach bank generate (int)age, REPLACE(job,\u0027\"\u0027,\u0027\u0027) as job, REPLACE(marital, \u0027\"\u0027, \u0027\u0027) as marital, (int)(REPLACE(balance, \u0027\"\u0027, \u0027\u0027)) as balance;\n\n-- The following statement is optional, it depends on whether your needs.\n-- store bank into \u0027clean_bank.csv\u0027 using PigStorage(\u0027;\u0027);\n\n\n", - "user": "user1", - "dateUpdated": "Jan 6, 2017 3:57:11 PM", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:49:11 PM", "config": { "colWidth": 12.0, "editorMode": "ace/mode/pig", @@ -102,15 +138,15 @@ "jobName": "paragraph_1483277250237_-466604517", "id": "20161228-140640_1560978333", "dateCreated": "Jan 1, 2017 9:27:30 PM", - "dateStarted": "Jan 6, 2017 3:57:11 PM", - "dateFinished": "Jan 6, 2017 3:57:13 PM", + "dateStarted": "Jan 22, 2017 12:49:11 PM", + "dateFinished": "Jan 22, 2017 12:49:13 PM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%pig.query\n\nbank_data \u003d filter bank by age \u003c 30;\nb \u003d group bank_data by age;\nforeach b generate group, COUNT($1);\n\n", - "user": "user1", - "dateUpdated": "Jan 6, 2017 3:57:15 PM", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:49:16 PM", "config": { "colWidth": 4.0, "editorMode": "ace/mode/pig", @@ -139,7 +175,7 @@ "msg": [ { "type": "TABLE", - "data": "group\tnull\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n" + "data": "group\tcol_1\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n" } ] }, @@ -147,15 +183,15 @@ "jobName": "paragraph_1483277250238_-465450270", "id": "20161228-140730_1903342877", "dateCreated": "Jan 1, 2017 9:27:30 PM", - "dateStarted": "Jan 6, 2017 3:57:15 PM", - "dateFinished": "Jan 6, 2017 3:57:16 PM", + "dateStarted": "Jan 22, 2017 12:49:16 PM", + "dateFinished": "Jan 22, 2017 12:49:30 PM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%pig.query\n\nbank_data \u003d filter bank by age \u003c ${maxAge\u003d40};\nb \u003d group bank_data by age;\nforeach b generate group, COUNT($1);", - "user": "user1", - "dateUpdated": "Jan 6, 2017 3:57:18 PM", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:49:18 PM", "config": { "colWidth": 4.0, "editorMode": "ace/mode/pig", @@ -192,7 +228,7 @@ "msg": [ { "type": "TABLE", - "data": "group\tnull\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n30\t150\n31\t199\n32\t224\n33\t186\n34\t231\n35\t180\n" + "data": "group\tcol_1\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n30\t150\n31\t199\n32\t224\n33\t186\n34\t231\n35\t180\n" } ] }, @@ -200,15 +236,15 @@ "jobName": "paragraph_1483277250239_-465835019", "id": "20161228-154918_1551591203", "dateCreated": "Jan 1, 2017 9:27:30 PM", - "dateStarted": "Jan 6, 2017 3:57:18 PM", - "dateFinished": "Jan 6, 2017 3:57:19 PM", + "dateStarted": "Jan 22, 2017 12:49:18 PM", + "dateFinished": "Jan 22, 2017 12:49:32 PM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%pig.query\n\nbank_data \u003d filter bank by marital\u003d\u003d\u0027${marital\u003dsingle,single|divorced|married}\u0027;\nb \u003d group bank_data by age;\nforeach b generate group, COUNT($1) as c;\n\n\n", - "user": "user1", - "dateUpdated": "Jan 6, 2017 3:57:24 PM", + "user": "anonymous", + "dateUpdated": "Jan 22, 2017 12:49:20 PM", "config": { "colWidth": 4.0, "editorMode": "ace/mode/pig", @@ -264,8 +300,8 @@ "jobName": "paragraph_1483277250240_-480070728", "id": "20161228-142259_575675591", "dateCreated": "Jan 1, 2017 9:27:30 PM", - "dateStarted": "Jan 6, 2017 3:57:20 PM", - "dateFinished": "Jan 6, 2017 3:57:20 PM", + "dateStarted": "Jan 22, 2017 12:49:30 PM", + "dateFinished": "Jan 22, 2017 12:49:34 PM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, @@ -289,28 +325,10 @@ "name": "Zeppelin Tutorial/Using Pig for querying data", "id": "2C57UKYWR", "angularObjects": { - "2C3DR183X:shared_process": [], - "2C5VH924X:shared_process": [], - "2C686X8ZH:shared_process": [], - "2C66Z9XPQ:shared_process": [], - "2C3JKFMJU:shared_process": [], - "2C69WE69N:shared_process": [], "2C3RWCVAG:shared_process": [], - "2C4HKDCQW:shared_process": [], - "2C4BJDRRZ:shared_process": [], - "2C6V3D44K:shared_process": [], - "2C3VECEG2:shared_process": [], - "2C5SRRXHM:shared_process": [], - "2C5DCRVGM:shared_process": [], - "2C66GE1VB:shared_process": [], - "2C3PTPMUH:shared_process": [], - "2C48Y7FSJ:shared_process": [], - "2C4ZD49PF:shared_process": [], - "2C63XW4XE:shared_process": [], - "2C4UB1UZA:shared_process": [], - "2C5S1R21W:shared_process": [], - "2C3SQSB7V:shared_process": [] + "2C9KGCHDE:shared_process": [], + "2C8X2BS16:shared_process": [] }, "config": {}, "info": {} -} +} \ No newline at end of file From a45ef6bdc6a36d00385f99442a74070f9fd444b1 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 18 Jan 2017 16:29:20 -0800 Subject: [PATCH 039/234] [HOTFIX][ZEPPELIN-1980] - Test and update CI for matplotlib 2.0.0 ### What is this PR for? Matplotlib 2.0.0 was just released. It has introduced some major changes including some to the `rcParams` which makes it incompatible with Zeppelin's built-in plotting backend. This PR updates the backend config for 2.0.0 as well as the CI tests. ### What type of PR is it? Hot Fix ### What is the Jira issue? [ZEPPELIN-1980](https://issues.apache.org/jira/browse/ZEPPELIN-1980) ### How should this be tested? Run the matplotlib tutorial notebook with matplotlib 2.0.0 installed. ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No If possible, this should be included as a hotfix for 0.7.0. Author: Alex Goodman Closes #1912 from agoodm/ZEPPELIN-1980 and squashes the following commits: 202548c [Alex Goodman] Use figure.dpi instead of savefig.dpi 2678359 [Alex Goodman] Make CI install latest matplotlib (cherry picked from commit 7941d37518a9393435680833c0828b13c77dd863) Signed-off-by: Mina Lee --- interpreter/lib/python/mpl_config.py | 8 ++++++-- testing/install_external_dependencies.sh | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/interpreter/lib/python/mpl_config.py b/interpreter/lib/python/mpl_config.py index 14aa60d4c22..e48678f6359 100644 --- a/interpreter/lib/python/mpl_config.py +++ b/interpreter/lib/python/mpl_config.py @@ -50,8 +50,12 @@ def get(key): def _on_config_change(): # dpi dpi = _config['dpi'] - matplotlib.rcParams['savefig.dpi'] = dpi + + # For older versions of matplotlib, savefig.dpi is not synced with + # figure.dpi by default matplotlib.rcParams['figure.dpi'] = dpi + if matplotlib.__version__ < '2.0.0': + matplotlib.rcParams['savefig.dpi'] = dpi # Width and height width = float(_config['width']) / dpi @@ -75,7 +79,7 @@ def _on_config_change(): def _init_config(): - dpi = matplotlib.rcParams['savefig.dpi'] + dpi = matplotlib.rcParams['figure.dpi'] fmt = matplotlib.rcParams['savefig.format'] width, height = matplotlib.rcParams['figure.figsize'] fontsize = matplotlib.rcParams['font.size'] diff --git a/testing/install_external_dependencies.sh b/testing/install_external_dependencies.sh index ebf7d0f5f14..93823072f0b 100755 --- a/testing/install_external_dependencies.sh +++ b/testing/install_external_dependencies.sh @@ -44,5 +44,5 @@ if [[ -n "$PYTHON" ]] ; then conda update -q conda conda info -a conda config --add channels conda-forge - conda install -q matplotlib=1.5.3 pandasql + conda install -q matplotlib pandasql fi From d0ddf8fdff75b4229543f7ebfd5eb001f685382d Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Sun, 22 Jan 2017 15:05:36 +0900 Subject: [PATCH 040/234] Preparing Apache Zeppelin release 0.7.0 --- alluxio/pom.xml | 4 ++-- angular/pom.xml | 4 ++-- beam/pom.xml | 4 ++-- bigquery/pom.xml | 4 ++-- cassandra/pom.xml | 4 ++-- elasticsearch/pom.xml | 4 ++-- file/pom.xml | 4 ++-- flink/pom.xml | 4 ++-- geode/pom.xml | 4 ++-- hbase/pom.xml | 4 ++-- helium-dev/pom.xml | 4 ++-- ignite/pom.xml | 4 ++-- jdbc/pom.xml | 4 ++-- kylin/pom.xml | 4 ++-- lens/pom.xml | 4 ++-- livy/pom.xml | 4 ++-- markdown/pom.xml | 4 ++-- pig/pom.xml | 4 ++-- pom.xml | 2 +- postgresql/pom.xml | 4 ++-- python/pom.xml | 4 ++-- r/pom.xml | 2 +- scalding/pom.xml | 4 ++-- scio/pom.xml | 4 ++-- shell/pom.xml | 4 ++-- spark-dependencies/pom.xml | 4 ++-- spark/pom.xml | 4 ++-- zeppelin-display/pom.xml | 4 ++-- zeppelin-distribution/pom.xml | 2 +- zeppelin-examples/pom.xml | 4 ++-- zeppelin-examples/zeppelin-example-clock/pom.xml | 4 ++-- .../zeppelin-example-clock/zeppelin-example-clock.json | 2 +- zeppelin-examples/zeppelin-example-horizontalbar/pom.xml | 4 ++-- zeppelin-interpreter/pom.xml | 4 ++-- zeppelin-server/pom.xml | 4 ++-- zeppelin-web/pom.xml | 4 ++-- zeppelin-web/src/app/tabledata/package.json | 2 +- zeppelin-web/src/app/visualization/package.json | 2 +- zeppelin-zengine/pom.xml | 4 ++-- 39 files changed, 72 insertions(+), 72 deletions(-) diff --git a/alluxio/pom.xml b/alluxio/pom.xml index 0c839ccf4cb..8b4be3c87f8 100644 --- a/alluxio/pom.xml +++ b/alluxio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-alluxio jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Alluxio interpreter diff --git a/angular/pom.xml b/angular/pom.xml index f40e9183216..a3d78629db3 100644 --- a/angular/pom.xml +++ b/angular/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-angular jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Angular interpreter diff --git a/beam/pom.xml b/beam/pom.xml index fab559dc3c4..b21eb23d5b3 100644 --- a/beam/pom.xml +++ b/beam/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-beam jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Beam interpreter diff --git a/bigquery/pom.xml b/bigquery/pom.xml index 032178a02cb..f6f89f7d3cc 100644 --- a/bigquery/pom.xml +++ b/bigquery/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-bigquery jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: BigQuery interpreter diff --git a/cassandra/pom.xml b/cassandra/pom.xml index fee8a1b24fe..ec0b085f6ff 100644 --- a/cassandra/pom.xml +++ b/cassandra/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-cassandra_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Apache Cassandra interpreter Zeppelin cassandra support diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml index eb06a8494b2..5e621d4dbd3 100644 --- a/elasticsearch/pom.xml +++ b/elasticsearch/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-elasticsearch jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Elasticsearch interpreter diff --git a/file/pom.xml b/file/pom.xml index 8aadfd6918c..8ce5d3ee588 100644 --- a/file/pom.xml +++ b/file/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-file jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: File System Interpreters diff --git a/flink/pom.xml b/flink/pom.xml index 54eeaf576e5..f20d30471b1 100644 --- a/flink/pom.xml +++ b/flink/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-flink_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Flink Zeppelin flink support diff --git a/geode/pom.xml b/geode/pom.xml index ba793a05a5c..c92cdd98535 100644 --- a/geode/pom.xml +++ b/geode/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-geode jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Apache Geode interpreter diff --git a/hbase/pom.xml b/hbase/pom.xml index da0a9745177..cb14389d4af 100644 --- a/hbase/pom.xml +++ b/hbase/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-hbase jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: HBase interpreter diff --git a/helium-dev/pom.xml b/helium-dev/pom.xml index 62d20f40e62..188ce7a1183 100644 --- a/helium-dev/pom.xml +++ b/helium-dev/pom.xml @@ -24,12 +24,12 @@ org.apache.zeppelin zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin helium-dev - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Helium development interpreter diff --git a/ignite/pom.xml b/ignite/pom.xml index 2dd544201c8..812649b4b05 100644 --- a/ignite/pom.xml +++ b/ignite/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. zeppelin-ignite_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Apache Ignite interpreter diff --git a/jdbc/pom.xml b/jdbc/pom.xml index dfbb8f78b22..903a095f815 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-jdbc jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: JDBC interpreter diff --git a/kylin/pom.xml b/kylin/pom.xml index 6db44a3ab65..b859d06d48d 100644 --- a/kylin/pom.xml +++ b/kylin/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 4.0.0 org.apache.zeppelin zeppelin-kylin jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Kylin interpreter diff --git a/lens/pom.xml b/lens/pom.xml index 13056608d34..63b1837ccbe 100644 --- a/lens/pom.xml +++ b/lens/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-lens jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Lens interpreter diff --git a/livy/pom.xml b/livy/pom.xml index 367369117f7..b85aa58d722 100644 --- a/livy/pom.xml +++ b/livy/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-livy jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Livy interpreter diff --git a/markdown/pom.xml b/markdown/pom.xml index d12085dc238..32023383238 100644 --- a/markdown/pom.xml +++ b/markdown/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-markdown jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Markdown interpreter diff --git a/pig/pom.xml b/pig/pom.xml index 4e2ec7b2e0a..e60c88cff11 100644 --- a/pig/pom.xml +++ b/pig/pom.xml @@ -24,13 +24,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-pig jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Apache Pig Interpreter Zeppelin interpreter for Apache Pig http://zeppelin.apache.org diff --git a/pom.xml b/pom.xml index 3342727bd72..41b61f29d64 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.zeppelin zeppelin pom - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin Zeppelin project http://zeppelin.apache.org diff --git a/postgresql/pom.xml b/postgresql/pom.xml index 7b716c9ffb2..0159d5fa0fc 100644 --- a/postgresql/pom.xml +++ b/postgresql/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 org.apache.zeppelin zeppelin-postgresql jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: PostgreSQL interpreter diff --git a/python/pom.xml b/python/pom.xml index cd481c1785a..fb856ed3ecd 100644 --- a/python/pom.xml +++ b/python/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-python jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Python interpreter diff --git a/r/pom.xml b/r/pom.xml index c0176c0a72c..9f26a141d3e 100644 --- a/r/pom.xml +++ b/r/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. diff --git a/scalding/pom.xml b/scalding/pom.xml index fc8b7e3ea4d..e301a87be22 100644 --- a/scalding/pom.xml +++ b/scalding/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-scalding_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Scalding interpreter diff --git a/scio/pom.xml b/scio/pom.xml index 233d934cae0..90ab6e2c69d 100644 --- a/scio/pom.xml +++ b/scio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-scio_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Scio Zeppelin Scio support diff --git a/shell/pom.xml b/shell/pom.xml index 48df49b92a7..0c03f5cc40a 100644 --- a/shell/pom.xml +++ b/shell/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-shell jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Shell interpreter diff --git a/spark-dependencies/pom.xml b/spark-dependencies/pom.xml index 5dac2cbfb77..c41876d1d14 100644 --- a/spark-dependencies/pom.xml +++ b/spark-dependencies/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-spark-dependencies_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Spark dependencies Zeppelin spark support diff --git a/spark/pom.xml b/spark/pom.xml index c414d3d13f2..5ffad2a3a0e 100644 --- a/spark/pom.xml +++ b/spark/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-spark_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Spark Zeppelin spark support diff --git a/zeppelin-display/pom.xml b/zeppelin-display/pom.xml index a1d06e1e7d3..e25618da1b0 100644 --- a/zeppelin-display/pom.xml +++ b/zeppelin-display/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-display_2.10 jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Display system apis diff --git a/zeppelin-distribution/pom.xml b/zeppelin-distribution/pom.xml index b83da5b9555..a45bbf64504 100644 --- a/zeppelin-distribution/pom.xml +++ b/zeppelin-distribution/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. diff --git a/zeppelin-examples/pom.xml b/zeppelin-examples/pom.xml index 8361be470fa..ebfdb50c4c8 100644 --- a/zeppelin-examples/pom.xml +++ b/zeppelin-examples/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-examples pom - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Examples Zeppelin examples diff --git a/zeppelin-examples/zeppelin-example-clock/pom.xml b/zeppelin-examples/zeppelin-example-clock/pom.xml index b14cf33043a..9f915fc4cdd 100644 --- a/zeppelin-examples/zeppelin-example-clock/pom.xml +++ b/zeppelin-examples/zeppelin-example-clock/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-example-clock jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Example application - Clock diff --git a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json index 20a2289996a..a1c16ec3321 100644 --- a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json +++ b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json @@ -18,7 +18,7 @@ "type" : "APPLICATION", "name" : "zeppelin.clock", "description" : "Clock (example)", - "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.1-SNAPSHOT.jar", + "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0.jar", "className" : "org.apache.zeppelin.example.app.clock.Clock", "resources" : [[":java.util.Date"]], "license" : "Apache-2.0", diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml index 235fd460684..aa7bf7b78ff 100644 --- a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml +++ b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-example-horizontalbar jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Example application - Horizontal Bar chart diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml index 7d066dacd5b..192de0d1c93 100644 --- a/zeppelin-interpreter/pom.xml +++ b/zeppelin-interpreter/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-interpreter jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Interpreter Zeppelin Interpreter diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index 2b26f16998e..f1fc6771e9f 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-server jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Server diff --git a/zeppelin-web/pom.xml b/zeppelin-web/pom.xml index 5f7c2db1d99..8aa60bc0329 100644 --- a/zeppelin-web/pom.xml +++ b/zeppelin-web/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-web war - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: web Application diff --git a/zeppelin-web/src/app/tabledata/package.json b/zeppelin-web/src/app/tabledata/package.json index d2424158da7..ef0a47b9d20 100644 --- a/zeppelin-web/src/app/tabledata/package.json +++ b/zeppelin-web/src/app/tabledata/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-tabledata", "description": "tabledata api", - "version": "0.7.1-SNAPSHOT", + "version": "0.7.0", "main": "tabledata", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-web/src/app/visualization/package.json b/zeppelin-web/src/app/visualization/package.json index 01e0c37a16f..55d204dcb67 100644 --- a/zeppelin-web/src/app/visualization/package.json +++ b/zeppelin-web/src/app/visualization/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-vis", "description": "Visualization API", - "version": "0.7.1-SNAPSHOT", + "version": "0.7.0", "main": "visualization", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index bf0cfce7dab..3e3d88d806d 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.1-SNAPSHOT + 0.7.0 .. org.apache.zeppelin zeppelin-zengine jar - 0.7.1-SNAPSHOT + 0.7.0 Zeppelin: Zengine Zeppelin Zengine From 9ed215e2b60e7b4ce6da158191d4b7c640753b57 Mon Sep 17 00:00:00 2001 From: Mina Lee Date: Sun, 22 Jan 2017 15:07:37 +0900 Subject: [PATCH 041/234] Preparing development version 0.7.1-SNAPSHOT --- alluxio/pom.xml | 4 ++-- angular/pom.xml | 4 ++-- beam/pom.xml | 4 ++-- bigquery/pom.xml | 4 ++-- cassandra/pom.xml | 4 ++-- elasticsearch/pom.xml | 4 ++-- file/pom.xml | 4 ++-- flink/pom.xml | 4 ++-- geode/pom.xml | 4 ++-- hbase/pom.xml | 4 ++-- helium-dev/pom.xml | 4 ++-- ignite/pom.xml | 4 ++-- jdbc/pom.xml | 4 ++-- kylin/pom.xml | 4 ++-- lens/pom.xml | 4 ++-- livy/pom.xml | 4 ++-- markdown/pom.xml | 4 ++-- pig/pom.xml | 4 ++-- pom.xml | 2 +- postgresql/pom.xml | 4 ++-- python/pom.xml | 4 ++-- r/pom.xml | 2 +- scalding/pom.xml | 4 ++-- scio/pom.xml | 4 ++-- shell/pom.xml | 4 ++-- spark-dependencies/pom.xml | 4 ++-- spark/pom.xml | 4 ++-- zeppelin-display/pom.xml | 4 ++-- zeppelin-distribution/pom.xml | 2 +- zeppelin-examples/pom.xml | 4 ++-- zeppelin-examples/zeppelin-example-clock/pom.xml | 4 ++-- .../zeppelin-example-clock/zeppelin-example-clock.json | 2 +- zeppelin-examples/zeppelin-example-horizontalbar/pom.xml | 4 ++-- zeppelin-interpreter/pom.xml | 4 ++-- zeppelin-server/pom.xml | 4 ++-- zeppelin-web/pom.xml | 4 ++-- zeppelin-web/src/app/tabledata/package.json | 2 +- zeppelin-web/src/app/visualization/package.json | 2 +- zeppelin-zengine/pom.xml | 4 ++-- 39 files changed, 72 insertions(+), 72 deletions(-) diff --git a/alluxio/pom.xml b/alluxio/pom.xml index 8b4be3c87f8..0c839ccf4cb 100644 --- a/alluxio/pom.xml +++ b/alluxio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-alluxio jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Alluxio interpreter diff --git a/angular/pom.xml b/angular/pom.xml index a3d78629db3..f40e9183216 100644 --- a/angular/pom.xml +++ b/angular/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-angular jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Angular interpreter diff --git a/beam/pom.xml b/beam/pom.xml index b21eb23d5b3..fab559dc3c4 100644 --- a/beam/pom.xml +++ b/beam/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-beam jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Beam interpreter diff --git a/bigquery/pom.xml b/bigquery/pom.xml index f6f89f7d3cc..032178a02cb 100644 --- a/bigquery/pom.xml +++ b/bigquery/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-bigquery jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: BigQuery interpreter diff --git a/cassandra/pom.xml b/cassandra/pom.xml index ec0b085f6ff..fee8a1b24fe 100644 --- a/cassandra/pom.xml +++ b/cassandra/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-cassandra_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Cassandra interpreter Zeppelin cassandra support diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml index 5e621d4dbd3..eb06a8494b2 100644 --- a/elasticsearch/pom.xml +++ b/elasticsearch/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-elasticsearch jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Elasticsearch interpreter diff --git a/file/pom.xml b/file/pom.xml index 8ce5d3ee588..8aadfd6918c 100644 --- a/file/pom.xml +++ b/file/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-file jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: File System Interpreters diff --git a/flink/pom.xml b/flink/pom.xml index f20d30471b1..54eeaf576e5 100644 --- a/flink/pom.xml +++ b/flink/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-flink_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Flink Zeppelin flink support diff --git a/geode/pom.xml b/geode/pom.xml index c92cdd98535..ba793a05a5c 100644 --- a/geode/pom.xml +++ b/geode/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-geode jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Geode interpreter diff --git a/hbase/pom.xml b/hbase/pom.xml index cb14389d4af..da0a9745177 100644 --- a/hbase/pom.xml +++ b/hbase/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-hbase jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: HBase interpreter diff --git a/helium-dev/pom.xml b/helium-dev/pom.xml index 188ce7a1183..62d20f40e62 100644 --- a/helium-dev/pom.xml +++ b/helium-dev/pom.xml @@ -24,12 +24,12 @@ org.apache.zeppelin zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin helium-dev - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Helium development interpreter diff --git a/ignite/pom.xml b/ignite/pom.xml index 812649b4b05..2dd544201c8 100644 --- a/ignite/pom.xml +++ b/ignite/pom.xml @@ -22,13 +22,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. zeppelin-ignite_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Ignite interpreter diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 903a095f815..dfbb8f78b22 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-jdbc jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: JDBC interpreter diff --git a/kylin/pom.xml b/kylin/pom.xml index b859d06d48d..6db44a3ab65 100644 --- a/kylin/pom.xml +++ b/kylin/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT 4.0.0 org.apache.zeppelin zeppelin-kylin jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Kylin interpreter diff --git a/lens/pom.xml b/lens/pom.xml index 63b1837ccbe..13056608d34 100644 --- a/lens/pom.xml +++ b/lens/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-lens jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Lens interpreter diff --git a/livy/pom.xml b/livy/pom.xml index b85aa58d722..367369117f7 100644 --- a/livy/pom.xml +++ b/livy/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-livy jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Livy interpreter diff --git a/markdown/pom.xml b/markdown/pom.xml index 32023383238..d12085dc238 100644 --- a/markdown/pom.xml +++ b/markdown/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-markdown jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Markdown interpreter diff --git a/pig/pom.xml b/pig/pom.xml index e60c88cff11..4e2ec7b2e0a 100644 --- a/pig/pom.xml +++ b/pig/pom.xml @@ -24,13 +24,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-pig jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Apache Pig Interpreter Zeppelin interpreter for Apache Pig http://zeppelin.apache.org diff --git a/pom.xml b/pom.xml index 41b61f29d64..3342727bd72 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.zeppelin zeppelin pom - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin Zeppelin project http://zeppelin.apache.org diff --git a/postgresql/pom.xml b/postgresql/pom.xml index 0159d5fa0fc..7b716c9ffb2 100644 --- a/postgresql/pom.xml +++ b/postgresql/pom.xml @@ -23,13 +23,13 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT org.apache.zeppelin zeppelin-postgresql jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: PostgreSQL interpreter diff --git a/python/pom.xml b/python/pom.xml index fb856ed3ecd..cd481c1785a 100644 --- a/python/pom.xml +++ b/python/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-python jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Python interpreter diff --git a/r/pom.xml b/r/pom.xml index 9f26a141d3e..c0176c0a72c 100644 --- a/r/pom.xml +++ b/r/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. diff --git a/scalding/pom.xml b/scalding/pom.xml index e301a87be22..fc8b7e3ea4d 100644 --- a/scalding/pom.xml +++ b/scalding/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-scalding_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Scalding interpreter diff --git a/scio/pom.xml b/scio/pom.xml index 90ab6e2c69d..233d934cae0 100644 --- a/scio/pom.xml +++ b/scio/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-scio_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Scio Zeppelin Scio support diff --git a/shell/pom.xml b/shell/pom.xml index 0c03f5cc40a..48df49b92a7 100644 --- a/shell/pom.xml +++ b/shell/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-shell jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Shell interpreter diff --git a/spark-dependencies/pom.xml b/spark-dependencies/pom.xml index c41876d1d14..5dac2cbfb77 100644 --- a/spark-dependencies/pom.xml +++ b/spark-dependencies/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-spark-dependencies_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Spark dependencies Zeppelin spark support diff --git a/spark/pom.xml b/spark/pom.xml index 5ffad2a3a0e..c414d3d13f2 100644 --- a/spark/pom.xml +++ b/spark/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-spark_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Spark Zeppelin spark support diff --git a/zeppelin-display/pom.xml b/zeppelin-display/pom.xml index e25618da1b0..a1d06e1e7d3 100644 --- a/zeppelin-display/pom.xml +++ b/zeppelin-display/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-display_2.10 jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Display system apis diff --git a/zeppelin-distribution/pom.xml b/zeppelin-distribution/pom.xml index a45bbf64504..b83da5b9555 100644 --- a/zeppelin-distribution/pom.xml +++ b/zeppelin-distribution/pom.xml @@ -23,7 +23,7 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. diff --git a/zeppelin-examples/pom.xml b/zeppelin-examples/pom.xml index ebfdb50c4c8..8361be470fa 100644 --- a/zeppelin-examples/pom.xml +++ b/zeppelin-examples/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-examples pom - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Examples Zeppelin examples diff --git a/zeppelin-examples/zeppelin-example-clock/pom.xml b/zeppelin-examples/zeppelin-example-clock/pom.xml index 9f915fc4cdd..b14cf33043a 100644 --- a/zeppelin-examples/zeppelin-example-clock/pom.xml +++ b/zeppelin-examples/zeppelin-example-clock/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-example-clock jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Example application - Clock diff --git a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json index a1c16ec3321..20a2289996a 100644 --- a/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json +++ b/zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json @@ -18,7 +18,7 @@ "type" : "APPLICATION", "name" : "zeppelin.clock", "description" : "Clock (example)", - "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0.jar", + "artifact" : "zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.1-SNAPSHOT.jar", "className" : "org.apache.zeppelin.example.app.clock.Clock", "resources" : [[":java.util.Date"]], "license" : "Apache-2.0", diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml index aa7bf7b78ff..235fd460684 100644 --- a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml +++ b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml @@ -22,14 +22,14 @@ zeppelin-examples org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-example-horizontalbar jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Example application - Horizontal Bar chart diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml index 192de0d1c93..7d066dacd5b 100644 --- a/zeppelin-interpreter/pom.xml +++ b/zeppelin-interpreter/pom.xml @@ -24,14 +24,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-interpreter jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Interpreter Zeppelin Interpreter diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index f1fc6771e9f..2b26f16998e 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-server jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Server diff --git a/zeppelin-web/pom.xml b/zeppelin-web/pom.xml index 8aa60bc0329..5f7c2db1d99 100644 --- a/zeppelin-web/pom.xml +++ b/zeppelin-web/pom.xml @@ -22,14 +22,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-web war - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: web Application diff --git a/zeppelin-web/src/app/tabledata/package.json b/zeppelin-web/src/app/tabledata/package.json index ef0a47b9d20..d2424158da7 100644 --- a/zeppelin-web/src/app/tabledata/package.json +++ b/zeppelin-web/src/app/tabledata/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-tabledata", "description": "tabledata api", - "version": "0.7.0", + "version": "0.7.1-SNAPSHOT", "main": "tabledata", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-web/src/app/visualization/package.json b/zeppelin-web/src/app/visualization/package.json index 55d204dcb67..01e0c37a16f 100644 --- a/zeppelin-web/src/app/visualization/package.json +++ b/zeppelin-web/src/app/visualization/package.json @@ -1,7 +1,7 @@ { "name": "zeppelin-vis", "description": "Visualization API", - "version": "0.7.0", + "version": "0.7.1-SNAPSHOT", "main": "visualization", "dependencies": { "json3": "~3.3.1", diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index 3e3d88d806d..bf0cfce7dab 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -23,14 +23,14 @@ zeppelin org.apache.zeppelin - 0.7.0 + 0.7.1-SNAPSHOT .. org.apache.zeppelin zeppelin-zengine jar - 0.7.0 + 0.7.1-SNAPSHOT Zeppelin: Zengine Zeppelin Zengine From 7066aff9a21f2fab4f4857494926b9d60488cb58 Mon Sep 17 00:00:00 2001 From: astroshim Date: Sat, 21 Jan 2017 01:06:52 -0800 Subject: [PATCH 042/234] [ZEPPELIN-1994] bugfix of streaming output. ### What is this PR for? If you run the following code, then streaming output doesn't work properly from the second run. ``` %spark.pyspark import time print("1") time.sleep(2) print("2") time.sleep(2) print("3") time.sleep(2) print("4") ``` This problem comes from the order of `paragraph update` event timing and `paragraph update-append` event timing is incorrect. and This PR will fix also https://github.com/apache/zeppelin/pull/1833 too. ### What type of PR is it? Bug Fix ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1994 ### How should this be tested? - run several times pyspark interpreter with above code. ### Screenshots (if appropriate) - before ![2017-01-21 00_55_25](https://cloud.githubusercontent.com/assets/3348133/22173437/bfa48e64-df77-11e6-9625-ab44dedee395.gif) - after ![2017-01-21 00_59_12](https://cloud.githubusercontent.com/assets/3348133/22173438/c21820ac-df77-11e6-87dc-07970fca13ca.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions?no * Does this needs documentation?no Author: astroshim Closes #1927 from astroshim/ZEPPELIN-1994 and squashes the following commits: c7baa59 [astroshim] fix streaming output problem (cherry picked from commit 1a1fbc423bc2631000e0ecad2e792efc377a4fd5) Signed-off-by: ahyoungryu --- .../src/app/notebook/paragraph/paragraph.controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 5dbe1a00583..228bb77799a 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -1040,7 +1040,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat $scope.paragraph.title = data.paragraph.title; $scope.paragraph.lineNumbers = data.paragraph.lineNumbers; $scope.paragraph.status = data.paragraph.status; - $scope.paragraph.results = data.paragraph.results; + 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)); From d5c8d175fb16a156132ec363fdbaa6b09223c288 Mon Sep 17 00:00:00 2001 From: astroshim Date: Fri, 20 Jan 2017 16:18:40 -0800 Subject: [PATCH 043/234] [ZEPPELIN-1991] Can't get the PARAGRAPH_APPEND_OUTPUT from the Interpreter. ### What is this PR for? This PR fixes the problem of streaming events(PARAGRAPH_APPEND_OUTPUT). It's because of the queue was not thread safe. ### What type of PR is it? Bug Fix ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1991 ### How should this be tested? 1. make a spark paragraph 2. run following code on spark paragraph. ``` %spark (1 to 5).foreach{ i=> Thread.sleep(1000) println("Hello " + i) } ``` 3. make a python paragraph 4. run following code on python paragraph. ``` %python.python print("hi1") ``` 5. retry run step 2, and check if streaming working. ### Screenshots (if appropriate) - before ![2017-01-20 13_36_02](https://cloud.githubusercontent.com/assets/3348133/22166640/edb9f610-df16-11e6-926c-c781f618e6cf.gif) - after ![2017-01-20 13_52_53](https://cloud.githubusercontent.com/assets/3348133/22166840/efd855e4-df17-11e6-816c-17e069cd6a04.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: astroshim Closes #1922 from astroshim/ZEPPELIN-1991 and squashes the following commits: e993585 [astroshim] make scheduler not static 66d3d2f [astroshim] fix to thread safe (cherry picked from commit 4ef4e186946f7a0df7da6cd3d0718e60a4975d62) Signed-off-by: ahyoungryu --- .../interpreter/remote/RemoteInterpreterEventPoller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java index bf64d9fa084..e2a8adddfd2 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java @@ -52,7 +52,7 @@ */ public class RemoteInterpreterEventPoller extends Thread { private static final Logger logger = LoggerFactory.getLogger(RemoteInterpreterEventPoller.class); - private static final ScheduledExecutorService appendService = + private final ScheduledExecutorService appendService = Executors.newSingleThreadScheduledExecutor(); private final RemoteInterpreterProcessListener listener; private final ApplicationEventListener appListener; From d90615e4fbbd366005373f155900d0be9ffa1910 Mon Sep 17 00:00:00 2001 From: CloverHearts Date: Sat, 21 Jan 2017 05:46:22 -0800 Subject: [PATCH 044/234] [ZEPPELIN-969] order by note name in job menu ### What is this PR for? we can sort by note name in job menu. Sorting supports ascending and descending order. Sort by note name. ### What type of PR is it? Feature ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-969 ### How should this be tested? click to name order button. ### Screenshots (if appropriate) ![nameorder](https://cloud.githubusercontent.com/assets/10525473/20826460/66a7e7a6-b8ae-11e6-8e03-e2b00698e069.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: CloverHearts Author: cloverhearts Closes #1721 from cloverhearts/jobm/nameOrder and squashes the following commits: 6df5688 [cloverhearts] change tooltip msg 4230a2a [cloverhearts] Merge branch 'master' into jobm/nameOrder d5bca61 [cloverhearts] Merge branch 'master' into jobm/nameOrder 11ffe68 [cloverhearts] Merge branch 'master' into jobm/nameOrder 57347c2 [cloverhearts] change filter icon ead26f4 [CloverHearts] Merge branch 'master' into jobm/nameOrder 5383a2e [CloverHearts] Merge branch 'master' into jobm/nameOrder 88d4708 [CloverHearts] fix code style ff44245 [CloverHearts] Merge branch 'master' into jobm/nameOrder 6142b8e [CloverHearts] Revert "change sortBy to orderby" 3b698fb [CloverHearts] change sortBy to orderby e8a6588 [CloverHearts] Merge branch 'master' into jobm/nameOrder da09527 [CloverHearts] Rename notebook to note. 2d9fc27 [CloverHearts] order by note name in job menu (cherry picked from commit 062b90a5d0c47345963bea00a4d69316276327b1) Signed-off-by: ahyoungryu --- .../src/app/jobmanager/jobmanager.controller.js | 9 +++++++++ .../src/app/jobmanager/jobmanager.filter.js | 7 ++++++- zeppelin-web/src/app/jobmanager/jobmanager.html | 13 +++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.controller.js b/zeppelin-web/src/app/jobmanager/jobmanager.controller.js index 4c437da46e8..37c599b8ba8 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.controller.js +++ b/zeppelin-web/src/app/jobmanager/jobmanager.controller.js @@ -98,10 +98,19 @@ function JobmanagerCtrl($scope, websocketMsgSrv, $interval, ngToast, $q, $timeou filterValueInterpreter: '*', isSortByAsc: true }; + $scope.sortTooltipMsg = 'Switch to sort by desc'; $scope.jobTypeFilter = jobManagerFilter; websocketMsgSrv.getNoteJobsList(); + $scope.$watch('filterConfig.isSortByAsc', function (value) { + if (value) { + $scope.sortTooltipMsg = 'Switch to sort by desc'; + } else { + $scope.sortTooltipMsg = 'Switch to sort by asc'; + } + }); + $scope.$on('$destroy', function() { websocketMsgSrv.unsubscribeJobManager(); }); diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.filter.js b/zeppelin-web/src/app/jobmanager/jobmanager.filter.js index 62e85fd23bd..05a1b6ecc1e 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.filter.js +++ b/zeppelin-web/src/app/jobmanager/jobmanager.filter.js @@ -18,6 +18,7 @@ function jobManagerFilter() { function filterContext(jobItems, filterConfig) { var filterValueInterpreter = filterConfig.filterValueInterpreter; var filterValueNotebookName = filterConfig.filterValueNotebookName; + var isSortByAsc = filterConfig.isSortByAsc; var filterItems = jobItems; if (filterValueInterpreter === undefined) { @@ -36,7 +37,11 @@ function jobManagerFilter() { }); } - return filterItems; + filterItems = _.sortBy(filterItems, function(sortItem) { + return sortItem.noteName.toLowerCase(); + }); + + return isSortByAsc ? filterItems : filterItems.reverse(); } return filterContext; } diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.html b/zeppelin-web/src/app/jobmanager/jobmanager.html index eaf5f079f73..96e05affdcb 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.html +++ b/zeppelin-web/src/app/jobmanager/jobmanager.html @@ -34,7 +34,16 @@

    - + + + +
    Date: Fri, 20 Jan 2017 21:51:36 +0530 Subject: [PATCH 045/234] Remove multple artifactId - hadoop-common ### What is this PR for? Remove multple artifactId - hadoop-common in JDBC ### What type of PR is it? [Refactor | Minor] ### Todos * [ ] - N/A ### What is the Jira issue? * [ZEPPELIN-1989](https://issues.apache.org/jira/browse/ZEPPELIN-1989) ### How should this be tested? Just a small refactor. CI should be green. ### Screenshots (if appropriate) N/A ### Questions: * Does the licenses files need update? N/A * Is there breaking changes for older versions? N/A * Does this needs documentation? N/A Author: Prabhjyot Singh Closes #1918 from prabhjyotsingh/ZEPPELIN-1989 and squashes the following commits: dcdbf74 [Prabhjyot Singh] remove multple artifactId - hadoop-common (cherry picked from commit 584b1d94e22adc7b915c77d8ca71b0b2ed0ea7c2) Signed-off-by: Prabhjyot Singh --- jdbc/pom.xml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/jdbc/pom.xml b/jdbc/pom.xml index dfbb8f78b22..b45e6e8a954 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -39,7 +39,6 @@ 2.7.2 1.4.190 2.0.1 - 2.6.0 1.0.8 @@ -80,13 +79,6 @@ ${jline.version} - - org.apache.hadoop - hadoop-common - ${hadoop.common.version} - provided - - com.h2database h2 @@ -122,7 +114,8 @@ org.apache.hadoop hadoop-common - ${hadoop-common.version} + ${hadoop.common.version} + provided com.sun.jersey From 2005dfe6c4cab70b8cedf62c6fc2cb42c5b6e49f Mon Sep 17 00:00:00 2001 From: Khalid Huseynov Date: Wed, 18 Jan 2017 21:28:56 -0800 Subject: [PATCH 046/234] [ZEPPELIN-1979] fix 'File size limit Exceeded' when importing notes ### What is this PR for? This is to fix the problem with import of note because of size limitations. Actually it seemed to be working in anonymous mode, and I noticed the problem only when authentication is enabled. ### What type of PR is it? Bug Fix ### Todos * [x] - list conf on successful login ### What is the Jira issue? [ZEPPELIN-1979](https://issues.apache.org/jira/browse/ZEPPELIN-1979) ### How should this be tested? 1. set note msg size value in `conf/zeppelin-env.sh` e.g. ``` export ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE="4096000" ``` 2. login to Zeppelin 3. try to import note unde 4MB ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Khalid Huseynov Closes #1913 from khalidhuseynov/fix/note-message-size and squashes the following commits: 3cf02fe [Khalid Huseynov] list conf on login (cherry picked from commit de659a5ed882ffca37626b5f6d5e5f0f014a2e01) Signed-off-by: Lee moon soo --- zeppelin-web/src/components/navbar/navbar.controller.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zeppelin-web/src/components/navbar/navbar.controller.js b/zeppelin-web/src/components/navbar/navbar.controller.js index 23baf5abb78..b226d4d74e4 100644 --- a/zeppelin-web/src/components/navbar/navbar.controller.js +++ b/zeppelin-web/src/components/navbar/navbar.controller.js @@ -85,6 +85,10 @@ function NavCtrl($scope, $rootScope, $http, $routeParams, $location, return ($routeParams.noteId === noteId); } + function listConfigurations() { + websocketMsgSrv.listConfigurations(); + } + function loadNotes() { websocketMsgSrv.getNoteList(); } @@ -135,6 +139,7 @@ function NavCtrl($scope, $rootScope, $http, $routeParams, $location, }); $scope.$on('loginSuccess', function(event, param) { + listConfigurations(); loadNotes(); }); @@ -153,4 +158,3 @@ function NavCtrl($scope, $rootScope, $http, $routeParams, $location, }); } } - From df6b8562723957b658e6afdd143b4a60601aeadc Mon Sep 17 00:00:00 2001 From: Kavin Date: Wed, 28 Dec 2016 13:09:19 +0530 Subject: [PATCH 047/234] [ZEPPELIN-1843] Error on invoking the REST API to run paragraph synchronously ### What is this PR for? This fixes the validation check of paragraph's note id to match with the Note instance id. ### What type of PR is it? Bug Fix ### Todos NA ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1843 ### How should this be tested? The run para synchronous REST API should be successful. ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Kavin Closes #1808 from kavinkumarks/zeppelin-1843-run-para-sync-api-error and squashes the following commits: b5f2927 [Kavin] Throw IAE only when the note id of the instance and paragraph's note id doesn't match. (cherry picked from commit e49fb47de88897427b915ffec3f8936deebb5dc6) Signed-off-by: Lee moon soo --- .../src/main/java/org/apache/zeppelin/notebook/Note.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index 22934d3705e..f0eae73aa90 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -222,7 +222,7 @@ void setInterpreterFactory(InterpreterFactory factory) { public void initializeJobListenerForParagraph(Paragraph paragraph) { final Note paragraphNote = paragraph.getNote(); - if (paragraphNote.getId().equals(this.getId())) { + if (!paragraphNote.getId().equals(this.getId())) { throw new IllegalArgumentException( format("The paragraph %s from note %s " + "does not belong to note %s", paragraph.getId(), paragraphNote.getId(), this.getId())); From bf09c1457b2356602e59bee0999085a36dcc8e54 Mon Sep 17 00:00:00 2001 From: Igor Drozdov Date: Mon, 19 Dec 2016 13:00:39 +0300 Subject: [PATCH 048/234] [ZEPPELIN-1837] Fix possible reason of DepInterpreterTest failure ### What is this PR for? Fix possible reason of DepInterpreterTest failure ### What type of PR is it? [Bug Fix] ### What is the Jira issue? [ZEPPELIN-1837](https://issues.apache.org/jira/browse/ZEPPELIN-1837) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No DepInterpreterTest will use random temporary folder for repo `DefaultRepositorySystem.resolveDependencies` throws NPE at line 352 if `ArtifactResolutionException` is thrown during `collectDependencies`. Artifact is found but it is impossible to download it (for example folder can't be created) I think the reason is that several tests use "local-repo" folder for repository. While one test tries to download artifact, another deletes repository (or some another distructive thing) Author: Igor Drozdov Closes #1782 from DrIgor/ZEPPELIN-1837 and squashes the following commits: a2af135 [Igor Drozdov] Fix possible reason of DepInterpreterTest failure (cherry picked from commit 2cc10d0ba78362b3c4d351a6e3044b73dc5404eb) Signed-off-by: Mina Lee --- .../zeppelin/spark/DepInterpreterTest.java | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/spark/src/test/java/org/apache/zeppelin/spark/DepInterpreterTest.java b/spark/src/test/java/org/apache/zeppelin/spark/DepInterpreterTest.java index 7bb660dd1d5..608807cd032 100644 --- a/spark/src/test/java/org/apache/zeppelin/spark/DepInterpreterTest.java +++ b/spark/src/test/java/org/apache/zeppelin/spark/DepInterpreterTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; -import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.Properties; @@ -31,28 +31,27 @@ import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; public class DepInterpreterTest { + + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + private DepInterpreter dep; private InterpreterContext context; - private File tmpDir; - private SparkInterpreter repl; - private Properties getTestProperties() { + private Properties getTestProperties() throws IOException { Properties p = new Properties(); - p.setProperty("zeppelin.dep.localrepo", "local-repo"); + p.setProperty("zeppelin.dep.localrepo", tmpDir.newFolder().getAbsolutePath()); p.setProperty("zeppelin.dep.additionalRemoteRepository", "spark-packages,http://dl.bintray.com/spark-packages/maven,false;"); return p; } @Before public void setUp() throws Exception { - tmpDir = new File(System.getProperty("java.io.tmpdir") + "/ZeppelinLTest_" + System.currentTimeMillis()); - System.setProperty("zeppelin.dep.localrepo", tmpDir.getAbsolutePath() + "/local-repo"); - - tmpDir.mkdirs(); - Properties p = getTestProperties(); dep = new DepInterpreter(p); @@ -74,20 +73,6 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { dep.close(); - delete(tmpDir); - } - - private void delete(File file) { - if (file.isFile()) file.delete(); - else if (file.isDirectory()) { - File[] files = file.listFiles(); - if (files != null && files.length > 0) { - for (File f : files) { - delete(f); - } - } - file.delete(); - } } @Test From 792687182c9d94d256b79080bf11aef078d67a82 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Fri, 20 Jan 2017 13:39:26 -0800 Subject: [PATCH 049/234] [ZEPPELIN-1986] Fix flaky test: Increase WelcomePageSuite timeout ### What is this PR for? Try fix problem described in https://issues.apache.org/jira/browse/ZEPPELIN-1986. Almost all recent build has this failure on CI build. ### What type of PR is it? Hot Fix ### Todos * [x] - Increase timeout ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1986 ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo Closes #1919 from Leemoonsoo/ZEPPELIN-1986-fix and squashes the following commits: 2a7c1ba [Lee moon soo] Increase timeout from AbstractFunctionalSuite, too 21e6840 [Lee moon soo] Increase WelcomePageSuite timeout (cherry picked from commit f604dffa380e4a6474284ec2ca857d7d89e33ed0) Signed-off-by: Mina Lee --- .../scala/org/apache/zeppelin/AbstractFunctionalSuite.scala | 2 +- .../src/test/scala/org/apache/zeppelin/WelcomePageSuite.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zeppelin-server/src/test/scala/org/apache/zeppelin/AbstractFunctionalSuite.scala b/zeppelin-server/src/test/scala/org/apache/zeppelin/AbstractFunctionalSuite.scala index 694944d95c2..2f773c6fddc 100644 --- a/zeppelin-server/src/test/scala/org/apache/zeppelin/AbstractFunctionalSuite.scala +++ b/zeppelin-server/src/test/scala/org/apache/zeppelin/AbstractFunctionalSuite.scala @@ -41,7 +41,7 @@ class AbstractFunctionalSuite extends FunSuite with WebBrowser with BeforeAndAft override def beforeAll() = { "../bin/zeppelin-daemon.sh start" ! - eventually (timeout(Span(20, Seconds))) { + eventually (timeout(Span(180, Seconds))) { go to SERVER_ADDRESS assert(find("welcome").isDefined) } diff --git a/zeppelin-server/src/test/scala/org/apache/zeppelin/WelcomePageSuite.scala b/zeppelin-server/src/test/scala/org/apache/zeppelin/WelcomePageSuite.scala index 3ce534a849b..1798214aecb 100644 --- a/zeppelin-server/src/test/scala/org/apache/zeppelin/WelcomePageSuite.scala +++ b/zeppelin-server/src/test/scala/org/apache/zeppelin/WelcomePageSuite.scala @@ -28,7 +28,7 @@ import AbstractFunctionalSuite.SERVER_ADDRESS class WelcomePageSuite(implicit driver: WebDriver) extends FunSuite with WebBrowser { test("Welcome sign is correct") { - eventually (timeout(Span(20, Seconds))) { + eventually (timeout(Span(180, Seconds))) { go to SERVER_ADDRESS assert(find("welcome").isDefined) } From 12763f9ca68ad803974df809d0fa7b8e47b54a24 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Fri, 20 Jan 2017 13:26:25 -0800 Subject: [PATCH 050/234] [ZEPPELIN-1455] Fix flaky test: AbstractAngularElemTest ### What is this PR for? This PR fix flaky test [ZEPPELIN-1455](https://issues.apache.org/jira/browse/ZEPPELIN-1455). According to http://doc.scalatest.org/1.8/org/scalatest/concurrent/Eventually.html, default timeout of eventually is 150millisecond. Set enough timeout for the test. ### What type of PR is it? Hot Fix ### Todos * [x] - increase timeout ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1455 ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo Closes #1920 from Leemoonsoo/ZEPPELIN-1455 and squashes the following commits: 13a993d [Lee moon soo] Increase tolerance of eventually (cherry picked from commit 99c21c4edab8f588e57b95c27e957b8bb882560f) Signed-off-by: Mina Lee --- .../zeppelin/display/angular/AbstractAngularElemTest.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala index de8c6d09762..43ad1bd0a23 100644 --- a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala +++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala @@ -23,6 +23,7 @@ import org.apache.zeppelin.display.{AngularObject, AngularObjectRegistry, GUI} import org.apache.zeppelin.interpreter._ import org.apache.zeppelin.user.AuthenticationInfo import org.scalatest.concurrent.Eventually +import org.scalatest.time.{Seconds, Span} import org.scalatest.{BeforeAndAfter, BeforeAndAfterEach, FlatSpec, Matchers} /** @@ -61,12 +62,12 @@ trait AbstractAngularElemTest // click create thread for callback function to run. So it'll may not immediately invoked // after click. therefore eventually should be click(elem) - eventually { + eventually (timeout(Span(5, Seconds))) { a should be(1) } click(elem) - eventually { + eventually (timeout(Span(5, Seconds))) { a should be(2) } @@ -120,7 +121,7 @@ trait AbstractAngularElemTest click(elem) - eventually { modelValue should be("value")} + eventually (timeout(Span(5, Seconds))) { modelValue should be("value")} } From aa3d319c8e4f0f1ac120c6996b6803566b63d227 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Fri, 20 Jan 2017 15:01:32 -0800 Subject: [PATCH 051/234] [ZEPPELIN-1857] Fix flaky test: NotebookTest.testAbortParagraphStatusOnInterpreterRestart() ### What is this PR for? This PR fixes flaky test NotebookTest.testAbortParagraphStatusOnInterpreterRestart() ### What type of PR is it? Hot Fix ### Todos * [x] - re implement NotebookTest.testAbortParagraphStatusOnInterpreterRestart() ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1857 ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo Closes #1923 from Leemoonsoo/ZEPPELIN-1857 and squashes the following commits: 5d60e5b [Lee moon soo] Fix NotebookTest.testAbortParagraphStatusOnInterpreterRestart() (cherry picked from commit 236bf3ad6f8300e61321f64bccc8a7540e02e59f) Signed-off-by: Mina Lee --- .../zeppelin/notebook/NotebookTest.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java index 072503747ac..82d280ef05f 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java @@ -736,35 +736,32 @@ public void testAbortParagraphStatusOnInterpreterRestart() throws InterruptedExc Note note = notebook.createNote(anonymous); factory.setInterpreters(anonymous.getUser(), note.getId(), factory.getDefaultInterpreterSettingList()); - ArrayList paragraphs = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - Paragraph tmp = note.addParagraph(AuthenticationInfo.ANONYMOUS); - tmp.setText("p" + tmp.getId()); - paragraphs.add(tmp); - } + // create three paragraphs + Paragraph p1 = note.addParagraph(anonymous); + p1.setText("sleep 1000"); + Paragraph p2 = note.addParagraph(anonymous); + p2.setText("sleep 1000"); + Paragraph p3 = note.addParagraph(anonymous); + p3.setText("sleep 1000"); - for (Paragraph p : paragraphs) { - assertEquals(Job.Status.READY, p.getStatus()); - } note.runAll(); - while (paragraphs.get(0).getStatus() != Status.FINISHED) Thread.yield(); + // wait until first paragraph finishes and second paragraph starts + while (p1.getStatus() != Status.FINISHED || p2.getStatus() != Status.RUNNING) Thread.yield(); + assertEquals(Status.FINISHED, p1.getStatus()); + assertEquals(Status.RUNNING, p2.getStatus()); + assertEquals(Status.PENDING, p3.getStatus()); + + // restart interpreter factory.restart(factory.getInterpreterSettings(note.getId()).get(0).getId()); - boolean isAborted = false; - for (Paragraph p : paragraphs) { - logger.debug(p.getStatus().name()); - if (isAborted) { - assertEquals(Job.Status.ABORT, p.getStatus()); - } - if (p.getStatus() == Status.ABORT) { - isAborted = true; - } - } + // make sure three differnt status aborted well. + assertEquals(Status.FINISHED, p1.getStatus()); + assertEquals(Status.ABORT, p2.getStatus()); + assertEquals(Status.ABORT, p3.getStatus()); - assertTrue(isAborted); notebook.removeNote(note.getId(), anonymous); } From 4eb29bd7e01c8a75b915f2f73c9b580d223ca9f7 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Fri, 20 Jan 2017 16:41:22 -0800 Subject: [PATCH 052/234] [ZEPPELIN-1749] Fix Flaky test: in Websequence markdown plugin ### What is this PR for? Fix flaky test n Websequence markdown plugin. Test is failing when request to websequence is failed (network issue, etc). This PR increases timeout and remove assert from unittest. (log error and pass) ### What type of PR is it? Hot Fix ### Todos * [x] - Increase timeout * [x] - Log error and pass instead of assert in unittest. ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-1749 ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo Closes #1925 from Leemoonsoo/ZEPPELIN-1749 and squashes the following commits: 93b3a15 [Lee moon soo] Increase websequence request timeout, and log unittest instead of assert (cherry picked from commit 638e4c2f51177ab984dba611e33c7c39b80319ec) Signed-off-by: Mina Lee --- .../zeppelin/markdown/PegdownParser.java | 2 +- .../zeppelin/markdown/PegdownParserTest.java | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/markdown/src/main/java/org/apache/zeppelin/markdown/PegdownParser.java b/markdown/src/main/java/org/apache/zeppelin/markdown/PegdownParser.java index 61237e44a58..baf18f0d79d 100644 --- a/markdown/src/main/java/org/apache/zeppelin/markdown/PegdownParser.java +++ b/markdown/src/main/java/org/apache/zeppelin/markdown/PegdownParser.java @@ -27,7 +27,7 @@ public class PegdownParser implements MarkdownParser { private PegDownProcessor processor; - public static final long PARSING_TIMEOUT_AS_MILLIS = 5000; + public static final long PARSING_TIMEOUT_AS_MILLIS = 10000; public static final int OPTIONS = Extensions.ALL_WITH_OPTIONALS - Extensions.ANCHORLINKS; public PegdownParser() { diff --git a/markdown/src/test/java/org/apache/zeppelin/markdown/PegdownParserTest.java b/markdown/src/test/java/org/apache/zeppelin/markdown/PegdownParserTest.java index 25b8b39bc1b..0c545dc3732 100644 --- a/markdown/src/test/java/org/apache/zeppelin/markdown/PegdownParserTest.java +++ b/markdown/src/test/java/org/apache/zeppelin/markdown/PegdownParserTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertEquals; import java.util.Properties; - import org.apache.zeppelin.interpreter.InterpreterResult; import static org.apache.zeppelin.markdown.PegdownParser.wrapWithMarkdownClassDiv; @@ -31,9 +30,11 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PegdownParserTest { - + Logger logger = LoggerFactory.getLogger(PegdownParserTest.class); Markdown md; @Before @@ -317,7 +318,19 @@ public void testWebsequencePlugin() { .toString(); InterpreterResult result = md.interpret(input, null); - assertThat(result.message().get(0).getData(), CoreMatchers.containsString(" Date: Mon, 23 Jan 2017 14:08:38 +0900 Subject: [PATCH 053/234] [ZEPPELIN-2000] Run paragraph on enter when select dynamic form value changed ### What is this PR for? Run paragraph on enter when select dynamic form value changed to make paragraph runnable in report mode. ### What type of PR is it? Bug Fix | Hot Fix ### What is the Jira issue? [ZEPPELIN-2000](https://issues.apache.org/jira/browse/ZEPPELIN-2000) ### How should this be tested? 1. Go to `Zeppelin Tutorial/Basic Features (Spark)` notebook 2. Change view mode to `report`. 3. Change selected value from `married` to `single` in 5th paragraph and hit enter. 4. See if paragraph runs ### Screenshots (if appropriate) ![jan-23-2017 14-14-05](https://cloud.githubusercontent.com/assets/8503346/22192310/460d713c-e176-11e6-96d4-96d25eb029c0.gif) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? yes. docs update included. Author: Mina Lee Closes #1932 from minahlee/ZEPPELIN-2000 and squashes the following commits: a9f02d2 [Mina Lee] Update docs 66b513c [Mina Lee] Run paragraph onEnter when select value changed (cherry picked from commit 7b7625db10eb6b0c49d58375b09d0dead9f4912d) Signed-off-by: Mina Lee --- docs/manual/dynamicform.md | 2 ++ .../notebook/paragraph/paragraph-parameterizedQueryForm.html | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/manual/dynamicform.md b/docs/manual/dynamicform.md index e0adb4c0e79..12f51a9b338 100644 --- a/docs/manual/dynamicform.md +++ b/docs/manual/dynamicform.md @@ -56,6 +56,8 @@ Also you can separate option's display name and value, using `${formName=default +Hit enter after selecting option to run the paragraph with new value. + ### Checkbox form For multi-selection, you can create a checkbox form using `${checkbox:formName=defaultValue1|defaultValue2...,option1|option2...}`. The variable will be substituted by a comma-separated string based on the selected items. For example: diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html index 61080547bb7..117e11c2ecc 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html @@ -29,6 +29,7 @@ + pu-elastic-input pu-elastic-input-minwidth="180px" /> - + - + From ed023376705f3a1c6f6bebddd5eeb910cfa0ea06 Mon Sep 17 00:00:00 2001 From: Prabhjyot Singh Date: Fri, 10 Feb 2017 12:44:44 +0530 Subject: [PATCH 087/234] [ZEPPELIN-2063] Hive Jdbc interpreter does not relogin if kerberos ticket expired when hive.server2.transport.mode is http ### What is this PR for? Hadoop Client will re-login once the ticket expired in case of RPC and so when hive.server2.transport.mode is binary, Hive Jdbc interpreter does a relogin and works fine. But when Rest API is used i.e when hive.server2.transport.mode is http, it is not doing a re-login and so fails with GSS exception. ### What type of PR is it? [Bug Fix] ### What is the Jira issue? * [ZEPPELIN-2063](https://issues.apache.org/jira/browse/ZEPPELIN-2063) ### How should this be tested? Run hive in http mode i.e. hive.server2.transport.mode is http. Run any query say `show tables` now wait for key to expire (usually its 24hrs), now try to run the same paragraph again without restarting zeppelin-server or jdbc interpreter. It should not fail with `GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt) ` exception ### Screenshots (if appropriate) N/A ### Questions: * Does the licenses files need update? N/A * Is there breaking changes for older versions? N/A * Does this needs documentation? N/A Author: Prabhjyot Singh Closes #1979 from prabhjyotsingh/ZEPPELIN-2063 and squashes the following commits: 4199fd8 [Prabhjyot Singh] Catch all GSS initiate failed instead of GSS 0617ebd [Prabhjyot Singh] - reduce error log sent to user in interpreter exception - log error, if any while closeDBPool b417c37 [Prabhjyot Singh] try to re login from keytab before failing. (cherry picked from commit 090a40a9fc944c58734cee3a294074b117a694f0) Signed-off-by: Prabhjyot Singh --- .../apache/zeppelin/jdbc/JDBCInterpreter.java | 54 +++++++++++++++---- .../zeppelin/jdbc/JDBCUserConfigurations.java | 16 ++++++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java index a8987756993..0f6ebad0d67 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -17,6 +17,7 @@ import static org.apache.commons.lang.StringUtils.containsIgnoreCase; import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.isNotEmpty; +import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.IOException; @@ -36,6 +37,7 @@ import java.util.Properties; import java.util.Set; +import com.google.common.base.Throwables; import org.apache.commons.dbcp2.ConnectionFactory; import org.apache.commons.dbcp2.DriverManagerConnectionFactory; import org.apache.commons.dbcp2.PoolableConnectionFactory; @@ -46,6 +48,7 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.thrift.transport.TTransportException; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; @@ -575,6 +578,7 @@ private InterpreterResult executeSql(String propertyKey, String sql, getJDBCConfiguration(user).saveStatement(paragraphId, statement); boolean isResultSetAvailable = statement.execute(sqlToExecute); + getJDBCConfiguration(user).setConnectionInDBDriverPoolSuccessful(propertyKey); if (isResultSetAvailable) { resultSet = statement.getResultSet(); @@ -618,21 +622,49 @@ private InterpreterResult executeSql(String propertyKey, String sql, } getJDBCConfiguration(user).removeStatement(paragraphId); } catch (Exception e) { - logger.error("Cannot run " + sql, e); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos); - e.printStackTrace(ps); - String errorMsg = new String(baos.toByteArray(), StandardCharsets.UTF_8); + if (e.getCause() instanceof TTransportException && + Throwables.getStackTraceAsString(e).contains("GSS") && + getJDBCConfiguration(user).isConnectionInDBDriverPoolSuccessful(propertyKey)) { + return reLoginFromKeytab(propertyKey, sql, interpreterContext, interpreterResult); + } else { + logger.error("Cannot run " + sql, e); + String errorMsg = Throwables.getStackTraceAsString(e); + try { + closeDBPool(user, propertyKey); + } catch (SQLException e1) { + logger.error("Cannot close DBPool for user, propertyKey: " + user + propertyKey, e1); + } + interpreterResult.add(errorMsg); + return new InterpreterResult(Code.ERROR, interpreterResult.message()); + } + } + return interpreterResult; + } + private InterpreterResult reLoginFromKeytab(String propertyKey, String sql, + InterpreterContext interpreterContext, InterpreterResult interpreterResult) { + String user = interpreterContext.getAuthenticationInfo().getUser(); + try { + closeDBPool(user, propertyKey); + } catch (SQLException e) { + logger.error("Error, could not close DB pool in reLoginFromKeytab ", e); + } + UserGroupInformation.AuthenticationMethod authType = + JDBCSecurityImpl.getAuthtype(property); + if (authType.equals(KERBEROS)) { try { - closeDBPool(user, propertyKey); - } catch (SQLException e1) { - e1.printStackTrace(); + if (UserGroupInformation.isLoginKeytabBased()) { + UserGroupInformation.getLoginUser().reloginFromKeytab(); + } else if (UserGroupInformation.isLoginTicketBased()) { + UserGroupInformation.getLoginUser().reloginFromTicketCache(); + } + } catch (IOException e) { + logger.error("Cannot reloginFromKeytab " + sql, e); + interpreterResult.add(e.getMessage()); + return new InterpreterResult(Code.ERROR, interpreterResult.message()); } - interpreterResult.add(errorMsg); - return new InterpreterResult(Code.ERROR, interpreterResult.message()); } - return interpreterResult; + return executeSql(propertyKey, sql, interpreterContext); } /** diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCUserConfigurations.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCUserConfigurations.java index e23145be440..d00e1e9b6f5 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCUserConfigurations.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCUserConfigurations.java @@ -31,11 +31,13 @@ public class JDBCUserConfigurations { private final Map paragraphIdStatementMap; private final Map poolingDriverMap; private final HashMap propertiesMap; + private HashMap isSuccessful; public JDBCUserConfigurations() { paragraphIdStatementMap = new HashMap<>(); poolingDriverMap = new HashMap<>(); propertiesMap = new HashMap<>(); + isSuccessful = new HashMap<>(); } public void initStatementMap() throws SQLException { @@ -53,6 +55,7 @@ public void initConnectionPoolMap() throws SQLException { it.remove(); } poolingDriverMap.clear(); + isSuccessful.clear(); } public void setPropertyMap(String key, Properties properties) { @@ -88,8 +91,10 @@ public void removeStatement(String key) { public void saveDBDriverPool(String key, PoolingDriver driver) throws SQLException { poolingDriverMap.put(key, driver); + isSuccessful.put(key, false); } public PoolingDriver removeDBDriverPool(String key) throws SQLException { + isSuccessful.remove(key); return poolingDriverMap.remove(key); } @@ -97,4 +102,15 @@ public boolean isConnectionInDBDriverPool(String key) { return poolingDriverMap.containsKey(key); } + public void setConnectionInDBDriverPoolSuccessful(String key) { + isSuccessful.put(key, true); + } + + public boolean isConnectionInDBDriverPoolSuccessful(String key) { + if (isSuccessful.containsKey(key)) { + return isSuccessful.get(key); + } + return false; + } + } From 86d18979ee07c672363eeb3769eeea228a95f428 Mon Sep 17 00:00:00 2001 From: AhyoungRyu Date: Sun, 12 Feb 2017 16:54:09 +0900 Subject: [PATCH 088/234] [MINOR][ZEPPELIN-2100] Enable to go back to zeppelin.apache.org in docs site ### What is this PR for? Currently there is no link to go back to [zeppelin.apache.org](https://zeppelin.apache.org) in each docs site. It's a bit inconvenient. e.g. In [https://zeppelin.apache.org/docs/0.8.0-SNAPSHOT/](https://zeppelin.apache.org/docs/0.8.0-SNAPSHOT/), if I click Zeppelin main logo, it keeps me staying in docs main page not the root: [zeppelin.apache.org](https://zeppelin.apache.org). So I separated main logo in navbar to "Zeppelin" and "version". And linked [production_url](https://github.com/apache/zeppelin/blob/master/docs/_config.yml#L34) and [BASE_PATH](https://github.com/apache/zeppelin/blob/master/docs/_config.yml#L62) to each of them. Please see the below screenshot img. ### What type of PR is it? Improvement ### What is the Jira issue? [ZEPPELIN-2100](https://issues.apache.org/jira/browse/ZEPPELIN-2100) ### How should this be tested? Run docs site locally under `ZEPPELIN_HOME/docs` as described in here: [Run website locally](https://github.com/apache/zeppelin/blob/master/docs/README.md#run-website-locally) ### Screenshots (if appropriate) In [https://zeppelin.apache.org/docs/0.8.0-SNAPSHOT/](https://zeppelin.apache.org/docs/0.8.0-SNAPSHOT/), - Before - After ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: AhyoungRyu Closes #2008 from AhyoungRyu/change/mainLogoUrlToOfficialSite and squashes the following commits: 335e625 [AhyoungRyu] Fix indentation 80f724c [AhyoungRyu] Set main logo url to zeppelin.apache.org not docs site (cherry picked from commit d053e5b3338565eb0b3e8fe505e2363d09dbd926) Signed-off-by: ahyoungryu --- .../themes/zeppelin/_navigation.html | 14 +++++--- docs/assets/themes/zeppelin/css/style.css | 34 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html index b13ef68542d..bc06b856b2c 100644 --- a/docs/_includes/themes/zeppelin/_navigation.html +++ b/docs/_includes/themes/zeppelin/_navigation.html @@ -7,11 +7,15 @@ - - I'm zeppelin - Zeppelin - {{site.ZEPPELIN_VERSION}} - +