mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge remote-tracking branch 'upstream/master' into ZEPPELIN-2403
# Conflicts: # spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java
This commit is contained in:
commit
7c25b6db82
115 changed files with 3133 additions and 1391 deletions
|
|
@ -46,8 +46,13 @@ matrix:
|
|||
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 core modules
|
||||
#
|
||||
# Several tests were excluded from this configuration due to the following issues:
|
||||
# HeliumApplicationFactoryTest - https://issues.apache.org/jira/browse/ZEPPELIN-2470
|
||||
# ZeppelinRestApiTest - https://issues.apache.org/jira/browse/ZEPPELIN-2473
|
||||
# After issues are fixed these tests need to be included back by removing them from the "-Dtests.to.exclude" property
|
||||
- jdk: "oraclejdk7"
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_PROJECTS="-Dtest='!ZeppelinSparkClusterTest,!org.apache.zeppelin.spark.*' -DfailIfNoTests=false"
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_PROJECTS="-Dtests.to.exclude=**/ZeppelinSparkClusterTest.java,**/org.apache.zeppelin.spark.*,**/HeliumApplicationFactoryTest.java,**/ZeppelinRestApiTest.java -DfailIfNoTests=false"
|
||||
|
||||
# Test selenium with spark module for 1.6.3
|
||||
- jdk: "oraclejdk7"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ user3 = password4, role2
|
|||
#ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM
|
||||
#ldapRealm.contextFactory.url = ldap://ldap.test.com:389
|
||||
#ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
|
||||
#ldapRealm.contextFactory.authenticationMechanism = SIMPLE
|
||||
#ldapRealm.contextFactory.authenticationMechanism = simple
|
||||
|
||||
### A sample PAM configuration
|
||||
#pamRealm=org.apache.zeppelin.realm.PamRealm
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ mvn versions:set -DnewVersion="${TO_VERSION}" -DgenerateBackupPoms=false > /dev/
|
|||
sed -i '' 's/-'"${FROM_VERSION}"'.jar",/-'"${TO_VERSION}"'.jar",/g' zeppelin-examples/zeppelin-example-clock/zeppelin-example-clock.json
|
||||
sed -i '' 's/"version": "'"${FROM_VERSION}"'",/"version": "'"${TO_VERSION}"'",/g' zeppelin-web/src/app/tabledata/package.json
|
||||
sed -i '' 's/"version": "'"${FROM_VERSION}"'",/"version": "'"${TO_VERSION}"'",/g' zeppelin-web/src/app/visualization/package.json
|
||||
sed -i '' 's/"version": "'"${FROM_VERSION}"'",/"version": "'"${TO_VERSION}"'",/g' zeppelin-web/src/app/spell/package.json
|
||||
|
||||
# Change version in Dockerfile
|
||||
sed -i '' 's/Z_VERSION="'"${FROM_VERSION}"'"/Z_VERSION="'"${TO_VERSION}"'"/g' scripts/docker/zeppelin/bin/Dockerfile
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@
|
|||
<li><a href="{{BASE_PATH}}/storage/storage.html#notebook-storage-in-mongodb">MongoDB Storage</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="title"><span><b>REST API</b><span></li>
|
||||
<li><a href="{{BASE_PATH}}/rest-api/rest-zeppelin-server.html">Zeppelin Server API</a></li>
|
||||
<li><a href="{{BASE_PATH}}/rest-api/rest-interpreter.html">Interpreter API</a></li>
|
||||
<li><a href="{{BASE_PATH}}/rest-api/rest-notebook.html">Notebook API</a></li>
|
||||
<li><a href="{{BASE_PATH}}/rest-api/rest-notebookRepo.html">Notebook Repository API</a></li>
|
||||
|
|
@ -116,6 +117,7 @@
|
|||
<li><a href="{{BASE_PATH}}/security/shiroauthentication.html">Shiro Authentication</a></li>
|
||||
<li><a href="{{BASE_PATH}}/security/notebook_authorization.html">Notebook Authorization</a></li>
|
||||
<li><a href="{{BASE_PATH}}/security/datasource_authorization.html">Data Source Authorization</a></li>
|
||||
<li><a href="{{BASE_PATH}}/security/helium_authorization.html">Helium Authorization</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="title"><span><b>Helium Framework (Experimental)</b></span></li>
|
||||
<li><a href="{{BASE_PATH}}/development/writingzeppelinapplication.html">Writing Zeppelin Application</a></li>
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ Join to our [Mailing list](https://zeppelin.apache.org/community.html) and repor
|
|||
* [Azure Storage](./storage/storage.html#notebook-storage-in-azure)
|
||||
* [ZeppelinHub Storage](./storage/storage.html#storage-in-zeppelinhub)
|
||||
* REST API: available REST API list in Apache Zeppelin
|
||||
* [Zeppelin server API](./rest-api/rest-zeppelin-server.html)
|
||||
* [Interpreter API](./rest-api/rest-interpreter.html)
|
||||
* [Notebook API](./rest-api/rest-notebook.html)
|
||||
* [Notebook Repository API](./rest-api/rest-notebookRepo.html)
|
||||
|
|
@ -172,6 +173,7 @@ Join to our [Mailing list](https://zeppelin.apache.org/community.html) and repor
|
|||
* [Shiro Authentication](./security/shiroauthentication.html)
|
||||
* [Notebook Authorization](./security/notebook_authorization.html)
|
||||
* [Data Source Authorization](./security/datasource_authorization.html)
|
||||
* [Helium Authorization](./security/helium_authorization.html)
|
||||
* Helium Framework (Experimental)
|
||||
* [Writing Zeppelin Application](./development/writingzeppelinapplication.html)
|
||||
* [Writing Zeppelin Spell](./development/writingzeppelinspell.html)
|
||||
|
|
|
|||
|
|
@ -128,6 +128,16 @@ The JDBC interpreter properties are defined by default like below.
|
|||
<td></td>
|
||||
<td>Сomma separated schema (schema = catalog = database) filters to get metadata for completions. Supports '%' symbol is equivalent to any set of characters. (ex. prod_v_%,public%,info)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default.completer.ttlInSeconds</td>
|
||||
<td>120</td>
|
||||
<td>Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default.splitQueries</td>
|
||||
<td>false</td>
|
||||
<td>Each query is executed apart and returns the result</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
If you want to connect other databases such as `Mysql`, `Redshift` and `Hive`, you need to edit the property values.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ All REST APIs are available starting with the following endpoint `http://[zeppel
|
|||
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).
|
||||
nd a need for an additional REST API, please [file an issue or send us mail](../../community.html).
|
||||
|
||||
|
||||
## Configuration REST API list
|
||||
|
|
|
|||
78
docs/rest-api/rest-zeppelin-server.md
Normal file
78
docs/rest-api/rest-zeppelin-server.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
layout: page
|
||||
title: "Apache Zeppelin Server REST API"
|
||||
description: "This page contains Apache Zeppelin Server REST API information."
|
||||
group: rest-api
|
||||
---
|
||||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
{% include JB/setup %}
|
||||
|
||||
# Apache Zeppelin Server REST API
|
||||
|
||||
<div id="toc"></div>
|
||||
|
||||
## Overview
|
||||
Apache Zeppelin provides several REST APIs for interaction and remote activation of zeppelin functionality.
|
||||
All REST APIs are available starting with the following endpoint `http://[zeppelin-server]:[zeppelin-port]/api`.
|
||||
Note that Apache Zeppelin REST APIs receive or return JSON objects, it is recommended for you to install some JSON viewers such as [JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc).
|
||||
|
||||
If you work with Apache Zeppelin and find a need for an additional REST API, please [file an issue or send us an email](http://zeppelin.apache.org/community.html).
|
||||
|
||||
|
||||
## Zeppelin Server REST API list
|
||||
|
||||
### Change the log level of Zeppelin Server
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```PUT``` method is used to update the root logger's log level of the server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/log/level/<LOG_LEVEL>```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 406 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> sample JSON response
|
||||
</td>
|
||||
<td>
|
||||
<pre>
|
||||
{
|
||||
"status": "OK"
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> sample error JSON response
|
||||
</td>
|
||||
<td>
|
||||
<pre>
|
||||
{
|
||||
"status":"NOT_ACCEPTABLE",
|
||||
"message":"Please check LOG level specified. Valid values: DEBUG, ERROR, FATAL, INFO, TRACE, WARN"
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
28
docs/security/helium_authorization.md
Normal file
28
docs/security/helium_authorization.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
layout: page
|
||||
title: "Helium Authorization in Apache Zeppelin"
|
||||
description: "Apache Zeppelin supports Helium plugins which fetch required installer packages from remote registry/repositories"
|
||||
group: security
|
||||
---
|
||||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
{% include JB/setup %}
|
||||
|
||||
# Helium Authorization in Apache Zeppelin
|
||||
|
||||
<div id="toc"></div>
|
||||
|
||||
## How to configure proxies?
|
||||
|
||||
Set **http_proxy** and **https_proxy** env variables to allow connection to npm registry behind a corporate firewall.
|
||||
|
|
@ -85,7 +85,7 @@ ldapRealm = org.apache.zeppelin.server.LdapGroupRealm
|
|||
ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM
|
||||
ldapRealm.contextFactory.url = ldap://ldap.test.com:389
|
||||
ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
|
||||
ldapRealm.contextFactory.authenticationMechanism = SIMPLE
|
||||
ldapRealm.contextFactory.authenticationMechanism = simple
|
||||
```
|
||||
|
||||
also define roles/groups that you want to have in system, like below;
|
||||
|
|
@ -128,15 +128,59 @@ Change the following values in the Shiro.ini file, and uncomment the line:
|
|||
|
||||
### LDAP
|
||||
|
||||
Two options exist for configuring an LDAP Realm. The simpler to use is the LdapGroupRealm. How ever it has limited
|
||||
flexibility with mapping of ldap groups to users and for authorization for user groups. A sample configuration file for
|
||||
this realm is given below.
|
||||
|
||||
```
|
||||
ldapRealm = org.apache.zeppelin.realm.LdapGroupRealm
|
||||
# search base for ldap groups (only relevant for LdapGroupRealm):
|
||||
ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM
|
||||
ldapRealm.contextFactory.url = ldap://ldap.test.com:389
|
||||
ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
|
||||
ldapRealm.contextFactory.authenticationMechanism = SIMPLE
|
||||
ldapRealm.contextFactory.authenticationMechanism = simple
|
||||
```
|
||||
|
||||
The other more flexible option is to use the LdapRealm. It allows for mapping of ldapgroups to roles and also allows for
|
||||
role/group based authentication into the zeppelin server. Sample configuration for this realm is given below.
|
||||
```
|
||||
[main]
|
||||
ldapRealm=org.apache.zeppelin.realm.LdapRealm
|
||||
|
||||
ldapRealm.contextFactory.authenticationMechanism=simple
|
||||
ldapRealm.contextFactory.url=ldap://localhost:33389
|
||||
ldapRealm.userDnTemplate=uid={0},ou=people,dc=hadoop,dc=apache,dc=org
|
||||
# Ability to set ldap paging Size if needed default is 100
|
||||
ldapRealm.pagingSize = 200
|
||||
ldapRealm.authorizationEnabled=true
|
||||
ldapRealm.contextFactory.systemAuthenticationMechanism=simple
|
||||
ldapRealm.searchBase=dc=hadoop,dc=apache,dc=org
|
||||
ldapRealm.userSearchBase = dc=hadoop,dc=apache,dc=org
|
||||
ldapRealm.groupSearchBase = ou=groups,dc=hadoop,dc=apache,dc=org
|
||||
ldapRealm.groupObjectClass=groupofnames
|
||||
# Allow userSearchAttribute to be customized
|
||||
ldapRealm.userSearchAttributeName = sAMAccountName
|
||||
ldapRealm.memberAttribute=member
|
||||
# force usernames returned from ldap to lowercase useful for AD
|
||||
ldapRealm.userLowerCase = true
|
||||
# ability set searchScopes subtree (default), one, base
|
||||
ldapRealm.userSearchScope = subtree;
|
||||
ldapRealm.groupSearchScope = subtree;
|
||||
ldapRealm.memberAttributeValueTemplate=cn={0},ou=people,dc=hadoop,dc=apache,dc=org
|
||||
ldapRealm.contextFactory.systemUsername=uid=guest,ou=people,dc=hadoop,dc=apache,dc=org
|
||||
ldapRealm.contextFactory.systemPassword=S{ALIAS=ldcSystemPassword}
|
||||
# enable support for nested groups using the LDAP_MATCHING_RULE_IN_CHAIN operator
|
||||
ldapRealm.groupSearchEnableMatchingRuleInChain = true
|
||||
# optional mapping from physical groups to logical application roles
|
||||
ldapRealm.rolesByGroup = LDN_USERS: user_role, NYK_USERS: user_role, HKG_USERS: user_role, GLOBAL_ADMIN: admin_role
|
||||
# optional list of roles that are allowed to authenticate. Incase not present all groups are allowed to authenticate (login).
|
||||
# This changes nothing for url specific permissions that will continue to work as specified in [urls].
|
||||
ldapRealm.allowedRolesForAuthentication = admin_role,user_role
|
||||
ldapRealm.permissionsByRole= user_role = *:ToDoItemsJdo:*:*, *:ToDoItem:*:*; admin_role = *
|
||||
securityManager.sessionManager = $sessionManager
|
||||
securityManager.realms = $ldapRealm
|
||||
```
|
||||
|
||||
### PAM
|
||||
[PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) authentication support allows the reuse of existing authentication
|
||||
moduls on the host where Zeppelin is running. On a typical system modules are configured per service for example sshd, passwd, etc. under `/etc/pam.d/`. You can
|
||||
|
|
|
|||
|
|
@ -23,11 +23,15 @@ import java.sql.ResultSetMetaData;
|
|||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.dbcp2.ConnectionFactory;
|
||||
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
|
||||
|
|
@ -101,6 +105,9 @@ public class JDBCInterpreter extends Interpreter {
|
|||
static final String PASSWORD_KEY = "password";
|
||||
static final String PRECODE_KEY = "precode";
|
||||
static final String COMPLETER_SCHEMA_FILTERS_KEY = "completer.schemaFilters";
|
||||
static final String COMPLETER_TTL_KEY = "completer.ttlInSeconds";
|
||||
static final String DEFAULT_COMPLETER_TTL = "120";
|
||||
static final String SPLIT_QURIES_KEY = "splitQueries";
|
||||
static final String JDBC_JCEKS_FILE = "jceks.file";
|
||||
static final String JDBC_JCEKS_CREDENTIAL_KEY = "jceks.credentialKey";
|
||||
static final String PRECODE_KEY_TEMPLATE = "%s.precode";
|
||||
|
|
@ -128,6 +135,7 @@ public class JDBCInterpreter extends Interpreter {
|
|||
|
||||
private final HashMap<String, Properties> basePropretiesMap;
|
||||
private final HashMap<String, JDBCUserConfigurations> jdbcUserConfigurationsMap;
|
||||
private final HashMap<String, SqlCompleter> sqlCompletersMap;
|
||||
|
||||
private int maxLineResults;
|
||||
|
||||
|
|
@ -135,6 +143,7 @@ public class JDBCInterpreter extends Interpreter {
|
|||
super(property);
|
||||
jdbcUserConfigurationsMap = new HashMap<>();
|
||||
basePropretiesMap = new HashMap<>();
|
||||
sqlCompletersMap = new HashMap<>();
|
||||
maxLineResults = MAX_LINE_DEFAULT;
|
||||
}
|
||||
|
||||
|
|
@ -188,11 +197,43 @@ public class JDBCInterpreter extends Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
private SqlCompleter createSqlCompleter(Connection jdbcConnection, String propertyKey) {
|
||||
private SqlCompleter createOrUpdateSqlCompleter(SqlCompleter sqlCompleter,
|
||||
final Connection connection, String propertyKey, final String buf, final int cursor) {
|
||||
String schemaFiltersKey = String.format("%s.%s", propertyKey, COMPLETER_SCHEMA_FILTERS_KEY);
|
||||
String filters = getProperty(schemaFiltersKey);
|
||||
SqlCompleter completer = new SqlCompleter();
|
||||
completer.initFromConnection(jdbcConnection, filters);
|
||||
String sqlCompleterTtlKey = String.format("%s.%s", propertyKey, COMPLETER_TTL_KEY);
|
||||
final String schemaFiltersString = getProperty(schemaFiltersKey);
|
||||
int ttlInSeconds = Integer.valueOf(
|
||||
StringUtils.defaultIfEmpty(getProperty(sqlCompleterTtlKey), DEFAULT_COMPLETER_TTL)
|
||||
);
|
||||
final SqlCompleter completer;
|
||||
if (sqlCompleter == null) {
|
||||
completer = new SqlCompleter(ttlInSeconds);
|
||||
} else {
|
||||
completer = sqlCompleter;
|
||||
}
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(1);
|
||||
executorService.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
completer.createOrUpdateFromConnection(connection, schemaFiltersString, buf, cursor);
|
||||
}
|
||||
});
|
||||
|
||||
executorService.shutdown();
|
||||
|
||||
try {
|
||||
// protection to release connection
|
||||
executorService.awaitTermination(3, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
logger.warn("Completion timeout", e);
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e1) {
|
||||
logger.warn("Error close connection", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return completer;
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +540,6 @@ public class JDBCInterpreter extends Interpreter {
|
|||
StringBuilder query = new StringBuilder();
|
||||
char character;
|
||||
|
||||
Boolean antiSlash = false;
|
||||
Boolean multiLineComment = false;
|
||||
Boolean singleLineComment = false;
|
||||
Boolean quoteString = false;
|
||||
|
|
@ -522,14 +562,8 @@ public class JDBCInterpreter extends Interpreter {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (character == '\\') {
|
||||
antiSlash = true;
|
||||
}
|
||||
|
||||
if (character == '\'') {
|
||||
if (antiSlash) {
|
||||
antiSlash = false;
|
||||
} else if (quoteString) {
|
||||
if (quoteString) {
|
||||
quoteString = false;
|
||||
} else if (!doubleQuoteString) {
|
||||
quoteString = true;
|
||||
|
|
@ -537,9 +571,7 @@ public class JDBCInterpreter extends Interpreter {
|
|||
}
|
||||
|
||||
if (character == '"') {
|
||||
if (antiSlash) {
|
||||
antiSlash = false;
|
||||
} else if (doubleQuoteString) {
|
||||
if (doubleQuoteString && item > 0) {
|
||||
doubleQuoteString = false;
|
||||
} else if (!quoteString) {
|
||||
doubleQuoteString = true;
|
||||
|
|
@ -559,7 +591,7 @@ public class JDBCInterpreter extends Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
if (character == ';' && !antiSlash && !quoteString && !doubleQuoteString) {
|
||||
if (character == ';' && !quoteString && !doubleQuoteString) {
|
||||
queries.add(StringUtils.trim(query.toString()));
|
||||
query = new StringBuilder();
|
||||
} else if (item == sql.length() - 1) {
|
||||
|
|
@ -596,17 +628,29 @@ public class JDBCInterpreter extends Interpreter {
|
|||
String paragraphId = interpreterContext.getParagraphId();
|
||||
String user = interpreterContext.getAuthenticationInfo().getUser();
|
||||
|
||||
InterpreterResult interpreterResult = new InterpreterResult(InterpreterResult.Code.SUCCESS);
|
||||
boolean splitQuery = false;
|
||||
String splitQueryProperty = getProperty(String.format("%s.%s", propertyKey, SPLIT_QURIES_KEY));
|
||||
if (StringUtils.isNotBlank(splitQueryProperty) && splitQueryProperty.equalsIgnoreCase("true")) {
|
||||
splitQuery = true;
|
||||
}
|
||||
|
||||
InterpreterResult interpreterResult = new InterpreterResult(InterpreterResult.Code.SUCCESS);
|
||||
try {
|
||||
connection = getConnection(propertyKey, interpreterContext);
|
||||
if (connection == null) {
|
||||
return new InterpreterResult(Code.ERROR, "Prefix not found.");
|
||||
}
|
||||
|
||||
ArrayList<String> multipleSqlArray = splitSqlQueries(sql);
|
||||
for (int i = 0; i < multipleSqlArray.size(); i++) {
|
||||
String sqlToExecute = multipleSqlArray.get(i);
|
||||
|
||||
List<String> sqlArray;
|
||||
if (splitQuery) {
|
||||
sqlArray = splitSqlQueries(sql);
|
||||
} else {
|
||||
sqlArray = Arrays.asList(sql);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sqlArray.size(); i++) {
|
||||
String sqlToExecute = sqlArray.get(i);
|
||||
statement = connection.createStatement();
|
||||
if (statement == null) {
|
||||
return new InterpreterResult(Code.ERROR, "Prefix not found.");
|
||||
|
|
@ -787,6 +831,10 @@ public class JDBCInterpreter extends Interpreter {
|
|||
InterpreterContext interpreterContext) {
|
||||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
String propertyKey = getPropertyKey(buf);
|
||||
String sqlCompleterKey =
|
||||
String.format("%s.%s", interpreterContext.getAuthenticationInfo().getUser(), propertyKey);
|
||||
SqlCompleter sqlCompleter = sqlCompletersMap.get(sqlCompleterKey);
|
||||
|
||||
Connection connection = null;
|
||||
try {
|
||||
if (interpreterContext != null) {
|
||||
|
|
@ -796,11 +844,9 @@ public class JDBCInterpreter extends Interpreter {
|
|||
logger.warn("SQLCompleter will created without use connection");
|
||||
}
|
||||
|
||||
SqlCompleter sqlCompleter = createSqlCompleter(connection, propertyKey);
|
||||
|
||||
if (sqlCompleter != null) {
|
||||
sqlCompleter.complete(buf, cursor - 1, candidates);
|
||||
}
|
||||
sqlCompleter = createOrUpdateSqlCompleter(sqlCompleter, connection, propertyKey, buf, cursor);
|
||||
sqlCompletersMap.put(sqlCompleterKey, sqlCompleter);
|
||||
sqlCompleter.complete(buf, cursor, candidates);
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.zeppelin.completer.CachedCompleter;
|
||||
import org.apache.zeppelin.completer.CompletionType;
|
||||
import org.apache.zeppelin.completer.StringsCompleter;
|
||||
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
|
||||
|
|
@ -33,8 +34,6 @@ import org.slf4j.LoggerFactory;
|
|||
import jline.console.completer.ArgumentCompleter.ArgumentList;
|
||||
import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
|
||||
|
||||
import static org.apache.commons.lang.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
* SQL auto complete functionality for the JdbcInterpreter.
|
||||
*/
|
||||
|
|
@ -42,6 +41,7 @@ public class SqlCompleter {
|
|||
|
||||
private static Logger logger = LoggerFactory.getLogger(SqlCompleter.class);
|
||||
|
||||
|
||||
/**
|
||||
* Delimiter that can split SQL statement in keyword list
|
||||
*/
|
||||
|
|
@ -59,23 +59,30 @@ public class SqlCompleter {
|
|||
/**
|
||||
* Schema completer
|
||||
*/
|
||||
private StringsCompleter schemasCompleter = new StringsCompleter();
|
||||
private CachedCompleter schemasCompleter;
|
||||
|
||||
/**
|
||||
* Contain different completer with table list for every schema name
|
||||
*/
|
||||
private Map<String, StringsCompleter> tablesCompleters = new HashMap<>();
|
||||
private Map<String, CachedCompleter> tablesCompleters = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Contains different completer with column list for every table name
|
||||
* Table names store as schema_name.table_name
|
||||
*/
|
||||
private Map<String, StringsCompleter> columnsCompleters = new HashMap<>();
|
||||
private Map<String, CachedCompleter> columnsCompleters = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Completer for sql keywords
|
||||
*/
|
||||
private StringsCompleter keywordCompleter = new StringsCompleter();
|
||||
private CachedCompleter keywordCompleter;
|
||||
|
||||
private int ttlInSeconds;
|
||||
|
||||
|
||||
public SqlCompleter(int ttlInSeconds) {
|
||||
this.ttlInSeconds = ttlInSeconds;
|
||||
}
|
||||
|
||||
public int complete(String buffer, int cursor, List<InterpreterCompletion> candidates) {
|
||||
|
||||
|
|
@ -95,25 +102,9 @@ public class SqlCompleter {
|
|||
argumentPosition = argumentList.getArgumentPosition();
|
||||
}
|
||||
|
||||
boolean isColumnAllowed = true;
|
||||
if (buffer.length() > 0) {
|
||||
String beforeCursorBuffer = buffer.substring(0,
|
||||
Math.min(cursor, buffer.length())).toUpperCase();
|
||||
// check what sql is and where cursor is to allow column completion or not
|
||||
if (beforeCursorBuffer.contains("SELECT ") && beforeCursorBuffer.contains(" FROM ")
|
||||
&& !beforeCursorBuffer.contains(" WHERE "))
|
||||
isColumnAllowed = false;
|
||||
}
|
||||
|
||||
int complete = completeName(cursorArgument, argumentPosition, candidates,
|
||||
findAliasesInSQL(argumentList.getArguments()), isColumnAllowed);
|
||||
findAliasesInSQL(argumentList.getArguments()));
|
||||
|
||||
if (candidates.size() == 1) {
|
||||
InterpreterCompletion interpreterCompletion = candidates.get(0);
|
||||
interpreterCompletion.setName(interpreterCompletion.getName() + " ");
|
||||
interpreterCompletion.setValue(interpreterCompletion.getValue() + " ");
|
||||
candidates.set(0, interpreterCompletion);
|
||||
}
|
||||
logger.debug("complete:" + complete + ", size:" + candidates.size());
|
||||
return complete;
|
||||
}
|
||||
|
|
@ -132,6 +123,7 @@ public class SqlCompleter {
|
|||
Set<String> res = new HashSet<>();
|
||||
try {
|
||||
ResultSet schemas = meta.getSchemas();
|
||||
|
||||
try {
|
||||
while (schemas.next()) {
|
||||
String schemaName = schemas.getString("TABLE_SCHEM");
|
||||
|
|
@ -185,58 +177,40 @@ public class SqlCompleter {
|
|||
return res;
|
||||
}
|
||||
|
||||
|
||||
private static void fillTableNames(String schema, DatabaseMetaData meta, Set<String> tables) {
|
||||
try (ResultSet tbls = meta.getTables(schema, schema, "%", null)) {
|
||||
while (tbls.next()) {
|
||||
String table = tbls.getString("TABLE_NAME");
|
||||
tables.add(table);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to retrieve the table name", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill two map with list of tables and list of columns
|
||||
*
|
||||
* @param catalogName name of a catalog
|
||||
* @param meta metadata from connection to database
|
||||
* @param schemaFilter a schema name pattern; must match the schema name
|
||||
* as it is stored in the database; "" retrieves those without a schema;
|
||||
* <code>null</code> means that the schema name should not be used to narrow
|
||||
* the search; supports '%'; for example "prod_v_%"
|
||||
* @param tables function fills this map, for every schema name adds
|
||||
* set of table names within the schema
|
||||
* @param columns function fills this map, for every table name adds set
|
||||
* @param schema name of a scheme
|
||||
* @param table name of a table
|
||||
* @param meta meta metadata from connection to database
|
||||
* @param columns function fills this set, for every table name adds set
|
||||
* of columns within the table; table name is in format schema_name.table_name
|
||||
*/
|
||||
private static void fillTableAndColumnNames(String catalogName, DatabaseMetaData meta,
|
||||
String schemaFilter,
|
||||
Map<String, Set<String>> tables,
|
||||
Map<String, Set<String>> columns) {
|
||||
try {
|
||||
ResultSet cols = meta.getColumns(catalogName, StringUtils.EMPTY, "%", "%");
|
||||
try {
|
||||
while (cols.next()) {
|
||||
String schema = cols.getString("TABLE_SCHEM");
|
||||
if (schema == null) {
|
||||
schema = cols.getString("TABLE_CAT");
|
||||
}
|
||||
if (!schemaFilter.equals("") && !schema.matches(schemaFilter.replace("%", ".*?"))) {
|
||||
continue;
|
||||
}
|
||||
String table = cols.getString("TABLE_NAME");
|
||||
String column = cols.getString("COLUMN_NAME");
|
||||
if (!isBlank(table)) {
|
||||
String schemaTable = schema + "." + table;
|
||||
if (!columns.containsKey(schemaTable)) {
|
||||
columns.put(schemaTable, new HashSet<String>());
|
||||
}
|
||||
columns.get(schemaTable).add(column);
|
||||
if (!tables.containsKey(schema)) {
|
||||
tables.put(schema, new HashSet<String>());
|
||||
}
|
||||
tables.get(schema).add(table);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cols.close();
|
||||
private static void fillColumnNames(String schema, String table, DatabaseMetaData meta,
|
||||
Set<String> columns) {
|
||||
try (ResultSet cols = meta.getColumns(schema, schema, table, "%")) {
|
||||
while (cols.next()) {
|
||||
String column = cols.getString("COLUMN_NAME");
|
||||
columns.add(column);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to retrieve the column name", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> getSqlKeywordsCompletions(Connection connection) throws IOException,
|
||||
public static Set<String> getSqlKeywordsCompletions(DatabaseMetaData meta) throws IOException,
|
||||
SQLException {
|
||||
|
||||
// Add the default SQL completions
|
||||
|
|
@ -246,12 +220,11 @@ public class SqlCompleter {
|
|||
|
||||
Set<String> completions = new TreeSet<>();
|
||||
|
||||
if (null != connection) {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
if (null != meta) {
|
||||
|
||||
// Add the driver specific SQL completions
|
||||
String driverSpecificKeywords =
|
||||
"/" + metaData.getDriverName().replace(" ", "-").toLowerCase() + "-sql.keywords";
|
||||
"/" + meta.getDriverName().replace(" ", "-").toLowerCase() + "-sql.keywords";
|
||||
logger.info("JDBC DriverName:" + driverSpecificKeywords);
|
||||
try {
|
||||
if (SqlCompleter.class.getResource(driverSpecificKeywords) != null) {
|
||||
|
|
@ -269,27 +242,27 @@ public class SqlCompleter {
|
|||
|
||||
// Add the keywords from the current JDBC connection
|
||||
try {
|
||||
keywords += "," + metaData.getSQLKeywords();
|
||||
keywords += "," + meta.getSQLKeywords();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get SQL key words from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getStringFunctions();
|
||||
keywords += "," + meta.getStringFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get string function names from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getNumericFunctions();
|
||||
keywords += "," + meta.getNumericFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get numeric function names from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getSystemFunctions();
|
||||
keywords += "," + meta.getSystemFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get system function names from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getTimeDateFunctions();
|
||||
keywords += "," + meta.getTimeDateFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get time date function names from database metadata: " + e, e);
|
||||
}
|
||||
|
|
@ -307,95 +280,101 @@ public class SqlCompleter {
|
|||
return completions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes local schema completers from list of schema names
|
||||
*
|
||||
* @param schemas set of schema names
|
||||
*/
|
||||
private void initSchemas(Set<String> schemas) {
|
||||
schemasCompleter = new StringsCompleter(new TreeSet<>(schemas));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes local table completers from list of table name
|
||||
*
|
||||
* @param tables for every schema name there is a set of table names within the schema
|
||||
*/
|
||||
private void initTables(Map<String, Set<String>> tables) {
|
||||
tablesCompleters.clear();
|
||||
for (Map.Entry<String, Set<String>> entry : tables.entrySet()) {
|
||||
tablesCompleters.put(entry.getKey(), new StringsCompleter(new TreeSet<>(entry.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes local column completers from list of column names
|
||||
*
|
||||
* @param columns for every table name there is a set of columns within the table;
|
||||
* table name is in format schema_name.table_name
|
||||
*/
|
||||
private void initColumns(Map<String, Set<String>> columns) {
|
||||
columnsCompleters.clear();
|
||||
for (Map.Entry<String, Set<String>> entry : columns.entrySet()) {
|
||||
columnsCompleters.put(entry.getKey(), new StringsCompleter(new TreeSet<>(entry.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all local completers
|
||||
*
|
||||
* @param schemas set of schema names
|
||||
* @param tables for every schema name there is a set of table names within the schema
|
||||
* @param columns for every table name there is a set of columns within the table;
|
||||
* table name is in format schema_name.table_name
|
||||
* @param keywords set with sql keywords
|
||||
*/
|
||||
public void init(Set<String> schemas, Map<String, Set<String>> tables,
|
||||
Map<String, Set<String>> columns, Set<String> keywords) {
|
||||
initSchemas(schemas);
|
||||
initTables(tables);
|
||||
initColumns(columns);
|
||||
keywordCompleter = new StringsCompleter(keywords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all local completers from database connection
|
||||
*
|
||||
* @param connection database connection
|
||||
* @param schemaFiltersString a comma separated schema name patterns; supports '%' symbol;
|
||||
* for example "prod_v_%,prod_t_%"
|
||||
* @param schemaFiltersString a comma separated schema name patterns, supports '%' symbol;
|
||||
* for example "prod_v_%,prod_t_%"
|
||||
*/
|
||||
public void initFromConnection(Connection connection, String schemaFiltersString) {
|
||||
if (schemaFiltersString == null) {
|
||||
schemaFiltersString = StringUtils.EMPTY;
|
||||
}
|
||||
List<String> schemaFilters = Arrays.asList(schemaFiltersString.split(","));
|
||||
|
||||
public void createOrUpdateFromConnection(Connection connection, String schemaFiltersString,
|
||||
String buffer, int cursor) {
|
||||
try (Connection c = connection) {
|
||||
Map<String, Set<String>> tables = new HashMap<>();
|
||||
Map<String, Set<String>> columns = new HashMap<>();
|
||||
if (schemaFiltersString == null) {
|
||||
schemaFiltersString = StringUtils.EMPTY;
|
||||
}
|
||||
List<String> schemaFilters = Arrays.asList(schemaFiltersString.split(","));
|
||||
CursorArgument cursorArgument = parseCursorArgument(buffer, cursor);
|
||||
|
||||
Set<String> tables = new HashSet<>();
|
||||
Set<String> columns = new HashSet<>();
|
||||
Set<String> schemas = new HashSet<>();
|
||||
Set<String> catalogs = new HashSet<>();
|
||||
Set<String> keywords = getSqlKeywordsCompletions(connection);
|
||||
if (connection != null) {
|
||||
schemas = getSchemaNames(connection.getMetaData(), schemaFilters);
|
||||
catalogs = getCatalogNames(connection.getMetaData(), schemaFilters);
|
||||
if (schemas.size() == 0) {
|
||||
schemas.addAll(catalogs);
|
||||
Set<String> keywords = new HashSet<>();
|
||||
|
||||
if (c != null) {
|
||||
DatabaseMetaData databaseMetaData = c.getMetaData();
|
||||
if (keywordCompleter == null || keywordCompleter.getCompleter() == null
|
||||
|| keywordCompleter.isExpired()) {
|
||||
keywords = getSqlKeywordsCompletions(databaseMetaData);
|
||||
initKeywords(keywords);
|
||||
}
|
||||
for (String schema : schemas) {
|
||||
for (String schemaFilter : schemaFilters) {
|
||||
fillTableAndColumnNames(schema, connection.getMetaData(), schemaFilter, tables,
|
||||
columns);
|
||||
if (cursorArgument.needLoadSchemas() &&
|
||||
(schemasCompleter == null || schemasCompleter.getCompleter() == null
|
||||
|| schemasCompleter.isExpired())) {
|
||||
schemas = getSchemaNames(databaseMetaData, schemaFilters);
|
||||
catalogs = getCatalogNames(databaseMetaData, schemaFilters);
|
||||
|
||||
if (schemas.size() == 0) {
|
||||
schemas.addAll(catalogs);
|
||||
}
|
||||
|
||||
initSchemas(schemas);
|
||||
}
|
||||
|
||||
CachedCompleter tablesCompleter = tablesCompleters.get(cursorArgument.getSchema());
|
||||
if (cursorArgument.needLoadTables() &&
|
||||
(tablesCompleter == null || tablesCompleter.isExpired())) {
|
||||
fillTableNames(cursorArgument.getSchema(), databaseMetaData, tables);
|
||||
initTables(cursorArgument.getSchema(), tables);
|
||||
}
|
||||
|
||||
String schemaTable =
|
||||
String.format("%s.%s", cursorArgument.getSchema(), cursorArgument.getTable());
|
||||
CachedCompleter columnsCompleter = columnsCompleters.get(schemaTable);
|
||||
|
||||
if (cursorArgument.needLoadColumns() &&
|
||||
(columnsCompleter == null || columnsCompleter.isExpired())) {
|
||||
fillColumnNames(cursorArgument.getSchema(), cursorArgument.getTable(), databaseMetaData,
|
||||
columns);
|
||||
initColumns(schemaTable, columns);
|
||||
}
|
||||
|
||||
logger.info("Completer initialized with " + schemas.size() + " schemas, " +
|
||||
columns.size() + " tables and " + keywords.size() + " keywords");
|
||||
}
|
||||
init(schemas, tables, columns, keywords);
|
||||
logger.info("Completer initialized with " + schemas.size() + " schemas, " +
|
||||
columns.size() + " tables and " + keywords.size() + " keywords");
|
||||
|
||||
} catch (SQLException | IOException e) {
|
||||
logger.error("Failed to update the metadata conmpletions", e);
|
||||
logger.error("Failed to update the metadata completions", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void initKeywords(Set<String> keywords) {
|
||||
if (keywords != null && !keywords.isEmpty()) {
|
||||
keywordCompleter = new CachedCompleter(new StringsCompleter(keywords), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void initSchemas(Set<String> schemas) {
|
||||
if (schemas != null && !schemas.isEmpty()) {
|
||||
schemasCompleter = new CachedCompleter(
|
||||
new StringsCompleter(new TreeSet<>(schemas)), ttlInSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void initTables(String schema, Set<String> tables) {
|
||||
if (tables != null && !tables.isEmpty()) {
|
||||
tablesCompleters.put(schema, new CachedCompleter(
|
||||
new StringsCompleter(new TreeSet<>(tables)), ttlInSeconds));
|
||||
}
|
||||
}
|
||||
|
||||
public void initColumns(String schemaTable, Set<String> columns) {
|
||||
if (columns != null && !columns.isEmpty()) {
|
||||
columnsCompleters.put(schemaTable,
|
||||
new CachedCompleter(new StringsCompleter(columns), ttlInSeconds));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -422,7 +401,7 @@ public class SqlCompleter {
|
|||
* @return -1 in case of no candidates found, 0 otherwise
|
||||
*/
|
||||
private int completeKeyword(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
return keywordCompleter.complete(buffer, cursor, candidates);
|
||||
return keywordCompleter.getCompleter().complete(buffer, cursor, candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -431,7 +410,7 @@ public class SqlCompleter {
|
|||
* @return -1 in case of no candidates found, 0 otherwise
|
||||
*/
|
||||
private int completeSchema(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
return schemasCompleter.complete(buffer, cursor, candidates);
|
||||
return schemasCompleter.getCompleter().complete(buffer, cursor, candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -441,21 +420,12 @@ public class SqlCompleter {
|
|||
*/
|
||||
private int completeTable(String schema, String buffer, int cursor,
|
||||
List<CharSequence> candidates) {
|
||||
if (schema == null) {
|
||||
int res = -1;
|
||||
Set<CharSequence> candidatesSet = new HashSet<>();
|
||||
for (StringsCompleter stringsCompleter : tablesCompleters.values()) {
|
||||
int resTable = stringsCompleter.complete(buffer, cursor, candidatesSet);
|
||||
res = Math.max(res, resTable);
|
||||
}
|
||||
candidates.addAll(candidatesSet);
|
||||
return res;
|
||||
}
|
||||
// Wrong schema
|
||||
if (!tablesCompleters.containsKey(schema) && schema != null)
|
||||
if (schema == null || !tablesCompleters.containsKey(schema))
|
||||
return -1;
|
||||
else
|
||||
return tablesCompleters.get(schema).complete(buffer, cursor, candidates);
|
||||
else {
|
||||
return tablesCompleters.get(schema).getCompleter().complete(buffer, cursor, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -465,22 +435,12 @@ public class SqlCompleter {
|
|||
*/
|
||||
private int completeColumn(String schema, String table, String buffer, int cursor,
|
||||
List<CharSequence> candidates) {
|
||||
if (table == null && schema == null) {
|
||||
int res = -1;
|
||||
Set<CharSequence> candidatesSet = new HashSet<>();
|
||||
for (StringsCompleter stringsCompleter : columnsCompleters.values()) {
|
||||
int resColumn = stringsCompleter.complete(buffer, cursor, candidatesSet);
|
||||
res = Math.max(res, resColumn);
|
||||
}
|
||||
candidates.addAll(candidatesSet);
|
||||
return res;
|
||||
}
|
||||
// Wrong schema or wrong table
|
||||
if (!tablesCompleters.containsKey(schema) ||
|
||||
!columnsCompleters.containsKey(schema + "." + table)) {
|
||||
if (schema == null || table == null || !columnsCompleters.containsKey(schema + "." + table)) {
|
||||
return -1;
|
||||
} else {
|
||||
return columnsCompleters.get(schema + "." + table).complete(buffer, cursor, candidates);
|
||||
return columnsCompleters.get(schema + "." + table).getCompleter()
|
||||
.complete(buffer, cursor, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -489,74 +449,55 @@ public class SqlCompleter {
|
|||
* a schema, a table of a column or a keyword
|
||||
*
|
||||
* @param aliases for every alias contains table name in format schema_name.table_name
|
||||
* @param isColumnAllowed if false the function will not search and complete columns
|
||||
* @return -1 in case of no candidates found, 0 otherwise
|
||||
*/
|
||||
public int completeName(String buffer, int cursor, List<InterpreterCompletion> candidates,
|
||||
Map<String, String> aliases, boolean isColumnAllowed) {
|
||||
Map<String, String> aliases) {
|
||||
CursorArgument cursorArgument = parseCursorArgument(buffer, cursor);
|
||||
|
||||
// points divide the name to the schema, table and column - find them
|
||||
int pointPos1 = -1;
|
||||
int pointPos2 = -1;
|
||||
|
||||
if (StringUtils.isNotEmpty(buffer)) {
|
||||
if (buffer.length() > cursor) {
|
||||
buffer = buffer.substring(0, cursor + 1);
|
||||
}
|
||||
pointPos1 = buffer.indexOf('.');
|
||||
pointPos2 = buffer.indexOf('.', pointPos1 + 1);
|
||||
}
|
||||
// find schema and table name if they are
|
||||
String schema;
|
||||
String table;
|
||||
String column;
|
||||
|
||||
if (pointPos1 == -1) { // process all
|
||||
if (cursorArgument.getSchema() == null) { // process all
|
||||
List<CharSequence> keywordsCandidates = new ArrayList();
|
||||
List<CharSequence> schemaCandidates = new ArrayList<>();
|
||||
List<CharSequence> tableCandidates = new ArrayList<>();
|
||||
List<CharSequence> columnCandidates = new ArrayList<>();
|
||||
int keywordsRes = completeKeyword(buffer, cursor, keywordsCandidates);
|
||||
int schemaRes = completeSchema(buffer, cursor, schemaCandidates);
|
||||
int tableRes = completeTable(null, buffer, cursor, tableCandidates);
|
||||
int columnRes = -1;
|
||||
if (isColumnAllowed) {
|
||||
columnRes = completeColumn(null, null, buffer, cursor, columnCandidates);
|
||||
}
|
||||
addCompletions(candidates, keywordsCandidates, CompletionType.keyword.name());
|
||||
addCompletions(candidates, schemaCandidates, CompletionType.schema.name());
|
||||
addCompletions(candidates, tableCandidates, CompletionType.table.name());
|
||||
addCompletions(candidates, columnCandidates, CompletionType.column.name());
|
||||
return NumberUtils.max(new int[]{keywordsRes, schemaRes, tableRes, columnRes});
|
||||
return NumberUtils.max(new int[]{keywordsRes, schemaRes});
|
||||
} else {
|
||||
schema = buffer.substring(0, pointPos1);
|
||||
schema = cursorArgument.getSchema();
|
||||
if (aliases.containsKey(schema)) { // process alias case
|
||||
String alias = aliases.get(schema);
|
||||
int pointPos = alias.indexOf('.');
|
||||
schema = alias.substring(0, pointPos);
|
||||
table = alias.substring(pointPos + 1);
|
||||
column = buffer.substring(pointPos1 + 1);
|
||||
} else if (pointPos2 == -1) { // process schema.table case
|
||||
column = cursorArgument.getColumn();
|
||||
List<CharSequence> columnCandidates = new ArrayList();
|
||||
int columnRes = completeColumn(schema, table, column, cursorArgument.getCursorPosition(),
|
||||
columnCandidates);
|
||||
addCompletions(candidates, columnCandidates, CompletionType.column.name());
|
||||
// process schema.table case
|
||||
} else if (cursorArgument.getTable() != null && cursorArgument.getColumn() == null) {
|
||||
List<CharSequence> tableCandidates = new ArrayList();
|
||||
table = buffer.substring(pointPos1 + 1);
|
||||
int tableRes = completeTable(schema, table, cursor - pointPos1 - 1, tableCandidates);
|
||||
table = cursorArgument.getTable();
|
||||
int tableRes = completeTable(schema, table, cursorArgument.getCursorPosition(),
|
||||
tableCandidates);
|
||||
addCompletions(candidates, tableCandidates, CompletionType.table.name());
|
||||
return tableRes;
|
||||
} else {
|
||||
table = buffer.substring(pointPos1 + 1, pointPos2);
|
||||
column = buffer.substring(pointPos2 + 1);
|
||||
List<CharSequence> columnCandidates = new ArrayList();
|
||||
table = cursorArgument.getTable();
|
||||
column = cursorArgument.getColumn();
|
||||
int columnRes = completeColumn(schema, table, column, cursorArgument.getCursorPosition(),
|
||||
columnCandidates);
|
||||
addCompletions(candidates, columnCandidates, CompletionType.column.name());
|
||||
}
|
||||
}
|
||||
|
||||
// here in case of column
|
||||
if (table != null && isColumnAllowed) {
|
||||
List<CharSequence> columnCandidates = new ArrayList();
|
||||
int columnRes = completeColumn(schema, table, column, cursor - pointPos2 - 1,
|
||||
columnCandidates);
|
||||
addCompletions(candidates, columnCandidates, CompletionType.column.name());
|
||||
return columnRes;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -572,4 +513,92 @@ public class SqlCompleter {
|
|||
candidate.toString(), meta));
|
||||
}
|
||||
}
|
||||
|
||||
private CursorArgument parseCursorArgument(String buffer, int cursor) {
|
||||
CursorArgument result = new CursorArgument();
|
||||
if (buffer != null && buffer.length() >= cursor) {
|
||||
String buf = buffer.substring(0, cursor);
|
||||
if (StringUtils.isNotBlank(buf)) {
|
||||
ArgumentList argumentList = sqlDelimiter.delimit(buf, cursor);
|
||||
String cursorArgument = argumentList.getCursorArgument();
|
||||
if (cursorArgument != null) {
|
||||
int pointPos1 = cursorArgument.indexOf('.');
|
||||
int pointPos2 = cursorArgument.indexOf('.', pointPos1 + 1);
|
||||
if (pointPos1 > -1) {
|
||||
result.setSchema(cursorArgument.substring(0, pointPos1).trim());
|
||||
if (pointPos2 > -1) {
|
||||
result.setTable(cursorArgument.substring(pointPos1 + 1, pointPos2));
|
||||
result.setColumn(cursorArgument.substring(pointPos2 + 1));
|
||||
result.setCursorPosition(cursor - pointPos2 - 1);
|
||||
} else {
|
||||
result.setTable(cursorArgument.substring(pointPos1 + 1));
|
||||
result.setCursorPosition(cursor - pointPos1 - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class CursorArgument {
|
||||
private String schema;
|
||||
private String table;
|
||||
private String column;
|
||||
private int cursorPosition;
|
||||
|
||||
public String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
public void setSchema(String schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setTable(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public String getColumn() {
|
||||
return column;
|
||||
}
|
||||
|
||||
public void setColumn(String column) {
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public int getCursorPosition() {
|
||||
return cursorPosition;
|
||||
}
|
||||
|
||||
public void setCursorPosition(int cursorPosition) {
|
||||
this.cursorPosition = cursorPosition;
|
||||
}
|
||||
|
||||
public boolean needLoadSchemas() {
|
||||
if (table == null && column == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean needLoadTables() {
|
||||
if (schema != null && table != null && column == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean needLoadColumns() {
|
||||
if (schema != null && table != null && column != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@
|
|||
"description": "The JDBC user password",
|
||||
"type": "password"
|
||||
},
|
||||
"default.completer.ttlInSeconds": {
|
||||
"envName": null,
|
||||
"propertyName": "default.completer.ttlInSeconds",
|
||||
"defaultValue": "120",
|
||||
"description": "Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)"
|
||||
},
|
||||
"default.driver": {
|
||||
"envName": null,
|
||||
"propertyName": "default.driver",
|
||||
|
|
@ -46,6 +52,12 @@
|
|||
"description": "SQL which executes while opening connection",
|
||||
"type": "textarea"
|
||||
},
|
||||
"default.splitQueries": {
|
||||
"envName": null,
|
||||
"propertyName": "default.splitQueries",
|
||||
"defaultValue": "false",
|
||||
"description": "Each query is executed apart and returns the result"
|
||||
},
|
||||
"common.max_count": {
|
||||
"envName": null,
|
||||
"propertyName": "common.max_count",
|
||||
|
|
|
|||
|
|
@ -27,8 +27,11 @@ import static org.junit.Assert.*;
|
|||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
|
|
@ -87,7 +90,7 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
PreparedStatement insertStatement = connection.prepareStatement("insert into test_table(id, name) values ('a', 'a_name'),('b', 'b_name'),('c', ?);");
|
||||
insertStatement.setString(1, null);
|
||||
insertStatement.execute();
|
||||
interpreterContext = new InterpreterContext("", "1", null, "", "", new AuthenticationInfo(), null, null, null, null,
|
||||
interpreterContext = new InterpreterContext("", "1", null, "", "", new AuthenticationInfo("testUser"), null, null, null, null,
|
||||
null, null);
|
||||
}
|
||||
|
||||
|
|
@ -175,21 +178,34 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
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 = ';'";
|
||||
"select * from test_table WHERE ID = ';';" +
|
||||
"select '\n', ';';" +
|
||||
"select replace('A\\;B', '\\', 'text');" +
|
||||
"select '\\', ';';" +
|
||||
"select '''', ';'";
|
||||
|
||||
Properties properties = new Properties();
|
||||
JDBCInterpreter t = new JDBCInterpreter(properties);
|
||||
t.open();
|
||||
ArrayList<String> multipleSqlArray = t.splitSqlQueries(sqlQuery);
|
||||
assertEquals(4, multipleSqlArray.size());
|
||||
List<String> multipleSqlArray = t.splitSqlQueries(sqlQuery);
|
||||
assertEquals(8, 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));
|
||||
assertEquals("select '\n', ';'", multipleSqlArray.get(4));
|
||||
assertEquals("select replace('A\\;B', '\\', 'text')", multipleSqlArray.get(5));
|
||||
assertEquals("select '\\', ';'", multipleSqlArray.get(6));
|
||||
assertEquals("select '''', ';'", multipleSqlArray.get(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectMultipleQuries() throws SQLException, IOException {
|
||||
public void testQueryWithEsсapedCharacters() throws SQLException, IOException {
|
||||
String sqlQuery = "select '\\n', ';';" +
|
||||
"select replace('A\\;B', '\\', 'text');" +
|
||||
"select '\\', ';';" +
|
||||
"select '''', ';'";
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("common.max_count", "1000");
|
||||
properties.setProperty("common.max_retry", "3");
|
||||
|
|
@ -197,6 +213,34 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
properties.setProperty("default.url", getJdbcConnection());
|
||||
properties.setProperty("default.user", "");
|
||||
properties.setProperty("default.password", "");
|
||||
properties.setProperty("default.splitQueries", "true");
|
||||
JDBCInterpreter t = new JDBCInterpreter(properties);
|
||||
t.open();
|
||||
|
||||
InterpreterResult interpreterResult = t.interpret(sqlQuery, interpreterContext);
|
||||
|
||||
assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code());
|
||||
assertEquals(InterpreterResult.Type.TABLE, interpreterResult.message().get(0).getType());
|
||||
assertEquals(InterpreterResult.Type.TABLE, interpreterResult.message().get(1).getType());
|
||||
assertEquals(InterpreterResult.Type.TABLE, interpreterResult.message().get(2).getType());
|
||||
assertEquals(InterpreterResult.Type.TABLE, interpreterResult.message().get(3).getType());
|
||||
assertEquals("'\\n'\t';'\n\\n\t;\n", interpreterResult.message().get(0).getData());
|
||||
assertEquals("'Atext;B'\nAtext;B\n", interpreterResult.message().get(1).getData());
|
||||
assertEquals("'\\'\t';'\n\\\t;\n", interpreterResult.message().get(2).getData());
|
||||
assertEquals("''''\t';'\n'\t;\n", interpreterResult.message().get(3).getData());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectMultipleQueries() 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", "");
|
||||
properties.setProperty("default.splitQueries", "true");
|
||||
JDBCInterpreter t = new JDBCInterpreter(properties);
|
||||
t.open();
|
||||
|
||||
|
|
@ -213,6 +257,28 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
assertEquals("ID\tNAME\n", interpreterResult.message().get(1).getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSplitQuries() 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(1, 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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectQueryWithNull() throws SQLException, IOException {
|
||||
Properties properties = new Properties();
|
||||
|
|
@ -296,9 +362,9 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
|
||||
jdbcInterpreter.interpret("", interpreterContext);
|
||||
|
||||
List<InterpreterCompletion> completionList = jdbcInterpreter.completion("sel", 3, null);
|
||||
List<InterpreterCompletion> completionList = jdbcInterpreter.completion("sel", 3, interpreterContext);
|
||||
|
||||
InterpreterCompletion correctCompletionKeyword = new InterpreterCompletion("select ", "select ", CompletionType.keyword.name());
|
||||
InterpreterCompletion correctCompletionKeyword = new InterpreterCompletion("select", "select", CompletionType.keyword.name());
|
||||
|
||||
assertEquals(1, completionList.size());
|
||||
assertEquals(true, completionList.contains(correctCompletionKeyword));
|
||||
|
|
@ -465,6 +531,7 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
properties.setProperty("default.url", getJdbcConnection());
|
||||
properties.setProperty("default.user", "");
|
||||
properties.setProperty("default.password", "");
|
||||
properties.setProperty("default.splitQueries", "true");
|
||||
JDBCInterpreter t = new JDBCInterpreter(properties);
|
||||
t.open();
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ public class SqlCompleterTest {
|
|||
private void expectedCompletions(String buffer, int cursor,
|
||||
Set<InterpreterCompletion> expected) {
|
||||
if (StringUtils.isNotEmpty(buffer) && buffer.length() > cursor) {
|
||||
buffer = buffer.substring(0, cursor + 1);
|
||||
buffer = buffer.substring(0, cursor);
|
||||
}
|
||||
|
||||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
|
|
@ -140,13 +140,10 @@ public class SqlCompleterTest {
|
|||
private ArgumentCompleter.WhitespaceArgumentDelimiter delimiter =
|
||||
new ArgumentCompleter.WhitespaceArgumentDelimiter();
|
||||
|
||||
private SqlCompleter sqlCompleter = new SqlCompleter();
|
||||
private SqlCompleter sqlCompleter = new SqlCompleter(0);
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws IOException, SQLException {
|
||||
|
||||
Map<String, Set<String>> tables = new HashMap<>();
|
||||
Map<String, Set<String>> columns = new HashMap<>();
|
||||
Set<String> schemas = new HashSet<>();
|
||||
Set<String> keywords = new HashSet<>();
|
||||
|
||||
|
|
@ -158,37 +155,42 @@ public class SqlCompleterTest {
|
|||
keywords.add("LIMIT");
|
||||
keywords.add("FROM");
|
||||
|
||||
sqlCompleter.initKeywords(keywords);
|
||||
|
||||
schemas.add("prod_dds");
|
||||
schemas.add("prod_emart");
|
||||
|
||||
sqlCompleter.initSchemas(schemas);
|
||||
|
||||
Set<String> prod_dds_tables = new HashSet<>();
|
||||
prod_dds_tables.add("financial_account");
|
||||
prod_dds_tables.add("customer");
|
||||
|
||||
sqlCompleter.initTables("prod_dds", prod_dds_tables);
|
||||
|
||||
Set<String> prod_emart_tables = new HashSet<>();
|
||||
prod_emart_tables.add("financial_account");
|
||||
|
||||
tables.put("prod_dds", prod_dds_tables);
|
||||
tables.put("prod_emart", prod_emart_tables);
|
||||
sqlCompleter.initTables("prod_emart", prod_emart_tables);
|
||||
|
||||
Set<String> prod_dds_financial_account_columns = new HashSet<>();
|
||||
prod_dds_financial_account_columns.add("account_rk");
|
||||
prod_dds_financial_account_columns.add("account_id");
|
||||
|
||||
sqlCompleter.initColumns("prod_dds.financial_account", prod_dds_financial_account_columns);
|
||||
|
||||
Set<String> prod_dds_customer_columns = new HashSet<>();
|
||||
prod_dds_customer_columns.add("customer_rk");
|
||||
prod_dds_customer_columns.add("name");
|
||||
prod_dds_customer_columns.add("birth_dt");
|
||||
|
||||
sqlCompleter.initColumns("prod_dds.customer", prod_dds_customer_columns);
|
||||
|
||||
Set<String> prod_emart_financial_account_columns = new HashSet<>();
|
||||
prod_emart_financial_account_columns.add("account_rk");
|
||||
prod_emart_financial_account_columns.add("balance_amt");
|
||||
|
||||
columns.put("prod_dds.financial_account", prod_dds_financial_account_columns);
|
||||
columns.put("prod_dds.customer", prod_dds_customer_columns);
|
||||
columns.put("prod_emart.financial_account", prod_emart_financial_account_columns);
|
||||
|
||||
sqlCompleter.init(schemas, tables, columns, keywords);
|
||||
sqlCompleter.initColumns("prod_emart.financial_account", prod_emart_financial_account_columns);
|
||||
|
||||
tester = new CompleterTester(sqlCompleter);
|
||||
}
|
||||
|
|
@ -223,8 +225,8 @@ public class SqlCompleterTest {
|
|||
int cursor = 0;
|
||||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
Map<String, String> aliases = new HashMap<>();
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
|
||||
assertEquals(17, candidates.size());
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases);
|
||||
assertEquals(9, candidates.size());
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("prod_dds", "prod_dds", CompletionType.schema.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("prod_emart", "prod_emart", CompletionType.schema.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("SUM", "SUM", CompletionType.keyword.name())));
|
||||
|
|
@ -234,14 +236,6 @@ public class SqlCompleterTest {
|
|||
assertTrue(candidates.contains(new InterpreterCompletion("ORDER", "ORDER", CompletionType.keyword.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("LIMIT", "LIMIT", CompletionType.keyword.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("customer", "customer", CompletionType.table.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("customer_rk", "customer_rk", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("name", "name", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("birth_dt", "birth_dt", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("balance_amt", "balance_amt", CompletionType.column.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -250,7 +244,7 @@ public class SqlCompleterTest {
|
|||
int cursor = 3;
|
||||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
Map<String, String> aliases = new HashMap<>();
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases, false);
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases);
|
||||
assertEquals(2, candidates.size());
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("prod_dds", "prod_dds", CompletionType.schema.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("prod_emart", "prod_emart", CompletionType.schema.name())));
|
||||
|
|
@ -262,7 +256,7 @@ public class SqlCompleterTest {
|
|||
int cursor = 11;
|
||||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
Map<String, String> aliases = new HashMap<>();
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases, false);
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases);
|
||||
assertEquals(1, candidates.size());
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name())));
|
||||
}
|
||||
|
|
@ -273,7 +267,7 @@ public class SqlCompleterTest {
|
|||
int cursor = 30;
|
||||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
Map<String, String> aliases = new HashMap<>();
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases);
|
||||
assertEquals(2, candidates.size());
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
|
||||
|
|
@ -286,7 +280,7 @@ public class SqlCompleterTest {
|
|||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
Map<String, String> aliases = new HashMap<>();
|
||||
aliases.put("a", "prod_dds.financial_account");
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases);
|
||||
assertEquals(2, candidates.size());
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
|
||||
|
|
@ -299,7 +293,7 @@ public class SqlCompleterTest {
|
|||
List<InterpreterCompletion> candidates = new ArrayList<>();
|
||||
Map<String, String> aliases = new HashMap<>();
|
||||
aliases.put("a", "prod_dds.financial_account");
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
|
||||
sqlCompleter.completeName(buffer, cursor, candidates, aliases);
|
||||
assertEquals(2, candidates.size());
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
|
||||
assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
|
||||
|
|
@ -308,14 +302,14 @@ public class SqlCompleterTest {
|
|||
@Test
|
||||
public void testSchemaAndTable() {
|
||||
String buffer = "select * from prod_emart.fi";
|
||||
tester.buffer(buffer).from(19).to(23).expect(newHashSet(new InterpreterCompletion("prod_emart ", "prod_emart ", CompletionType.schema.name()))).test();
|
||||
tester.buffer(buffer).from(25).to(27).expect(newHashSet(new InterpreterCompletion("financial_account ", "financial_account ", CompletionType.table.name()))).test();
|
||||
tester.buffer(buffer).from(20).to(23).expect(newHashSet(new InterpreterCompletion("prod_emart", "prod_emart", CompletionType.schema.name()))).test();
|
||||
tester.buffer(buffer).from(25).to(27).expect(newHashSet(new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name()))).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEdges() {
|
||||
String buffer = " ORDER ";
|
||||
tester.buffer(buffer).from(2).to(6).expect(newHashSet(new InterpreterCompletion("ORDER ", "ORDER ", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(3).to(7).expect(newHashSet(new InterpreterCompletion("ORDER", "ORDER", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(0).to(1).expect(newHashSet(
|
||||
new InterpreterCompletion("ORDER", "ORDER", CompletionType.keyword.name()),
|
||||
new InterpreterCompletion("SUBCLASS_ORIGIN", "SUBCLASS_ORIGIN", CompletionType.keyword.name()),
|
||||
|
|
@ -325,37 +319,29 @@ public class SqlCompleterTest {
|
|||
new InterpreterCompletion("SUM", "SUM", CompletionType.keyword.name()),
|
||||
new InterpreterCompletion("prod_dds", "prod_dds", CompletionType.schema.name()),
|
||||
new InterpreterCompletion("SELECT", "SELECT", CompletionType.keyword.name()),
|
||||
new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name()),
|
||||
new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name()),
|
||||
new InterpreterCompletion("customer", "customer", CompletionType.table.name()),
|
||||
new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name()),
|
||||
new InterpreterCompletion("account_id", "account_id", CompletionType.column.name()),
|
||||
new InterpreterCompletion("customer_rk", "customer_rk", CompletionType.column.name()),
|
||||
new InterpreterCompletion("name", "name", CompletionType.column.name()),
|
||||
new InterpreterCompletion("birth_dt", "birth_dt", CompletionType.column.name()),
|
||||
new InterpreterCompletion("balance_amt", "balance_amt", CompletionType.column.name())
|
||||
new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name())
|
||||
)).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleWords() {
|
||||
String buffer = "SELE FRO LIM";
|
||||
tester.buffer(buffer).from(1).to(3).expect(newHashSet(new InterpreterCompletion("SELECT ", "SELECT ", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(6).to(7).expect(newHashSet(new InterpreterCompletion("FROM ", "FROM ", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(9).to(12).expect(newHashSet(new InterpreterCompletion("LIMIT ", "LIMIT ", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(2).to(4).expect(newHashSet(new InterpreterCompletion("SELECT", "SELECT", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(6).to(8).expect(newHashSet(new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(10).to(12).expect(newHashSet(new InterpreterCompletion("LIMIT", "LIMIT", CompletionType.keyword.name()))).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiLineBuffer() {
|
||||
String buffer = " \n SELE\nFRO";
|
||||
tester.buffer(buffer).from(4).to(6).expect(newHashSet(new InterpreterCompletion("SELECT ", "SELECT ", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(9).to(11).expect(newHashSet(new InterpreterCompletion("FROM ", "FROM ", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(5).to(7).expect(newHashSet(new InterpreterCompletion("SELECT", "SELECT", CompletionType.keyword.name()))).test();
|
||||
tester.buffer(buffer).from(9).to(11).expect(newHashSet(new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name()))).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleCompletionSuggestions() {
|
||||
String buffer = "SU";
|
||||
tester.buffer(buffer).from(1).to(2).expect(newHashSet(
|
||||
tester.buffer(buffer).from(2).to(2).expect(newHashSet(
|
||||
new InterpreterCompletion("SUBCLASS_ORIGIN", "SUBCLASS_ORIGIN", CompletionType.keyword.name()),
|
||||
new InterpreterCompletion("SUM", "SUM", CompletionType.keyword.name()),
|
||||
new InterpreterCompletion("SUBSTRING", "SUBSTRING", CompletionType.keyword.name()))
|
||||
|
|
|
|||
18
livy/pom.xml
18
livy/pom.xml
|
|
@ -37,14 +37,10 @@
|
|||
<properties>
|
||||
<!--library versions-->
|
||||
<commons.exec.version>1.3</commons.exec.version>
|
||||
<httpcomponents.client.version>4.3.4</httpcomponents.client.version>
|
||||
<spring.web.version>4.3.0.RELEASE</spring.web.version>
|
||||
<spring.security.kerberosclient>1.0.1.RELEASE</spring.security.kerberosclient>
|
||||
|
||||
<!--test library versions-->
|
||||
<achilles.version>3.2.4-Zeppelin</achilles.version>
|
||||
<assertj.version>1.7.0</assertj.version>
|
||||
<mockito.version>1.9.5</mockito.version>
|
||||
<livy.version>0.3.0</livy.version>
|
||||
<spark.version>2.1.0</spark.version>
|
||||
<hadoop.version>2.6.0</hadoop.version>
|
||||
|
|
@ -80,7 +76,6 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpcomponents.client.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
@ -106,19 +101,6 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.cloudera.livy</groupId>
|
||||
<artifactId>livy-integration-test</artifactId>
|
||||
|
|
|
|||
|
|
@ -21,9 +21,20 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.auth.AuthSchemeProvider;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.Credentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.AuthSchemes;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLContexts;
|
||||
import org.apache.http.impl.auth.SPNegoSchemeFactory;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.apache.zeppelin.interpreter.*;
|
||||
|
|
@ -38,11 +49,11 @@ import org.springframework.security.kerberos.client.KerberosRestTemplate;
|
|||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -52,7 +63,6 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for livy interpreters.
|
||||
*/
|
||||
|
|
@ -407,6 +417,11 @@ public abstract class BaseLivyInterpreter extends Interpreter {
|
|||
|
||||
|
||||
private RestTemplate createRestTemplate() {
|
||||
String keytabLocation = property.getProperty("zeppelin.livy.keytab");
|
||||
String principal = property.getProperty("zeppelin.livy.principal");
|
||||
boolean isSpnegoEnabled = StringUtils.isNotEmpty(keytabLocation) &&
|
||||
StringUtils.isNotEmpty(principal);
|
||||
|
||||
HttpClient httpClient = null;
|
||||
if (livyURL.startsWith("https:")) {
|
||||
String keystoreFile = property.getProperty("zeppelin.livy.ssl.trustStore");
|
||||
|
|
@ -427,7 +442,37 @@ public abstract class BaseLivyInterpreter extends Interpreter {
|
|||
.loadTrustMaterial(trustStore)
|
||||
.build();
|
||||
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
|
||||
httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
|
||||
HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(csf);
|
||||
RequestConfig reqConfig = new RequestConfig() {
|
||||
@Override
|
||||
public boolean isAuthenticationEnabled() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
httpClientBuilder.setDefaultRequestConfig(reqConfig);
|
||||
Credentials credentials = new Credentials() {
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
||||
credsProvider.setCredentials(AuthScope.ANY, credentials);
|
||||
httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
|
||||
if (isSpnegoEnabled) {
|
||||
Registry<AuthSchemeProvider> authSchemeProviderRegistry =
|
||||
RegistryBuilder.<AuthSchemeProvider>create()
|
||||
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
|
||||
.build();
|
||||
httpClientBuilder.setDefaultAuthSchemeRegistry(authSchemeProviderRegistry);
|
||||
}
|
||||
|
||||
httpClient = httpClientBuilder.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create SSL HttpClient", e);
|
||||
} finally {
|
||||
|
|
@ -441,9 +486,8 @@ public abstract class BaseLivyInterpreter extends Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
String keytabLocation = property.getProperty("zeppelin.livy.keytab");
|
||||
String principal = property.getProperty("zeppelin.livy.principal");
|
||||
if (StringUtils.isNotEmpty(keytabLocation) && StringUtils.isNotEmpty(principal)) {
|
||||
|
||||
if (isSpnegoEnabled) {
|
||||
if (httpClient == null) {
|
||||
return new KerberosRestTemplate(keytabLocation, principal);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -239,6 +239,15 @@ public class LivySparkSQLInterpreter extends BaseLivyInterpreter {
|
|||
this.sparkInterpreter.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgress(InterpreterContext context) {
|
||||
if (this.sparkInterpreter != null) {
|
||||
return this.sparkInterpreter.getProgress(context);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String extractAppId() throws LivyException {
|
||||
// it wont' be called because it would delegate to LivySparkInterpreter
|
||||
|
|
|
|||
|
|
@ -308,6 +308,8 @@ public class LivyInterpreterIT {
|
|||
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
|
||||
assertEquals(InterpreterResult.Type.TABLE, result.message().get(0).getType());
|
||||
assertTrue(result.message().get(0).getData().contains("tableName"));
|
||||
int r = sqlInterpreter.getProgress(context);
|
||||
assertTrue(r == 0);
|
||||
} finally {
|
||||
sqlInterpreter.close();
|
||||
}
|
||||
|
|
|
|||
10
pom.xml
10
pom.xml
|
|
@ -96,8 +96,8 @@
|
|||
<gson-extras.version>0.2.1</gson-extras.version>
|
||||
<guava.version>15.0</guava.version>
|
||||
<jetty.version>9.2.15.v20160210</jetty.version>
|
||||
<httpcomponents.core.version>4.3.3</httpcomponents.core.version>
|
||||
<httpcomponents.client.version>4.3.6</httpcomponents.client.version>
|
||||
<httpcomponents.core.version>4.4.1</httpcomponents.core.version>
|
||||
<httpcomponents.client.version>4.5.1</httpcomponents.client.version>
|
||||
<httpcomponents.asyncclient.version>4.0.2</httpcomponents.asyncclient.version>
|
||||
<commons.lang.version>2.5</commons.lang.version>
|
||||
<commons.configuration.version>1.9</commons.configuration.version>
|
||||
|
|
@ -134,6 +134,9 @@
|
|||
|
||||
<PermGen>64m</PermGen>
|
||||
<MaxPermGen>512m</MaxPermGen>
|
||||
|
||||
<!-- to be able to exclude some tests using command line -->
|
||||
<tests.to.exclude/>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
|
@ -555,6 +558,9 @@
|
|||
<version>${plugin.surefire.version}</version>
|
||||
<configuration combine.children="append">
|
||||
<argLine>-Xmx2g -Xms1g -Dfile.encoding=UTF-8</argLine>
|
||||
<excludes>
|
||||
<exclude>${tests.to.exclude}</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<!-- <excludes> <exclude>**/itest/**</exclude> </excludes> <executions>
|
||||
<execution> <id>surefire-itest</id> <phase>integration-test</phase> <goals>
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@
|
|||
# limitations under the License.
|
||||
FROM centos:centos6
|
||||
|
||||
ENV SPARK_PROFILE 2.0
|
||||
ENV SPARK_VERSION 2.0.0
|
||||
ENV HADOOP_PROFILE 2.3
|
||||
ENV HADOOP_VERSION 2.3.0
|
||||
ENV SPARK_PROFILE 2.1
|
||||
ENV SPARK_VERSION 2.1.1
|
||||
ENV HADOOP_PROFILE 2.7
|
||||
ENV HADOOP_VERSION 2.7.0
|
||||
|
||||
# Update the image with the latest packages
|
||||
RUN yum update -y; yum clean all
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
# limitations under the License.
|
||||
FROM centos:centos6
|
||||
|
||||
ENV SPARK_PROFILE 1.6
|
||||
ENV SPARK_VERSION 1.6.2
|
||||
ENV HADOOP_PROFILE 2.3
|
||||
ENV SPARK_PROFILE 2.1
|
||||
ENV SPARK_VERSION 2.1.1
|
||||
ENV HADOOP_PROFILE 2.7
|
||||
ENV SPARK_HOME /usr/local/spark
|
||||
|
||||
# Update the image with the latest packages
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
# limitations under the License.
|
||||
FROM centos:centos6
|
||||
|
||||
ENV SPARK_PROFILE 2.0
|
||||
ENV SPARK_VERSION 2.0.0
|
||||
ENV SPARK_PROFILE 2.1
|
||||
ENV SPARK_VERSION 2.1.1
|
||||
ENV HADOOP_PROFILE 2.7
|
||||
ENV HADOOP_VERSION 2.7.0
|
||||
|
||||
|
|
|
|||
|
|
@ -276,10 +276,13 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
public class PythonInterpretRequest {
|
||||
public String statements;
|
||||
public String jobGroup;
|
||||
public String jobDescription;
|
||||
|
||||
public PythonInterpretRequest(String statements, String jobGroup) {
|
||||
public PythonInterpretRequest(String statements, String jobGroup,
|
||||
String jobDescription) {
|
||||
this.statements = statements;
|
||||
this.jobGroup = jobGroup;
|
||||
this.jobDescription = jobDescription;
|
||||
}
|
||||
|
||||
public String statements() {
|
||||
|
|
@ -289,6 +292,10 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
public String jobGroup() {
|
||||
return jobGroup;
|
||||
}
|
||||
|
||||
public String jobDescription() {
|
||||
return jobDescription;
|
||||
}
|
||||
}
|
||||
|
||||
Integer statementSetNotifier = new Integer(0);
|
||||
|
|
@ -395,10 +402,11 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
return new InterpreterResult(Code.ERROR, errorMessage);
|
||||
}
|
||||
String jobGroup = Utils.buildJobGroupId(context);
|
||||
String jobDesc = "Started by: " + Utils.getUserName(context.getAuthenticationInfo());
|
||||
SparkZeppelinContext __zeppelin__ = sparkInterpreter.getZeppelinContext();
|
||||
__zeppelin__.setInterpreterContext(context);
|
||||
__zeppelin__.setGui(context.getGui());
|
||||
pythonInterpretRequest = new PythonInterpretRequest(st, jobGroup);
|
||||
pythonInterpretRequest = new PythonInterpretRequest(st, jobGroup, jobDesc);
|
||||
statementOutput = null;
|
||||
|
||||
synchronized (statementSetNotifier) {
|
||||
|
|
@ -476,7 +484,7 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
return new LinkedList<>();
|
||||
}
|
||||
|
||||
pythonInterpretRequest = new PythonInterpretRequest(completionCommand, "");
|
||||
pythonInterpretRequest = new PythonInterpretRequest(completionCommand, "", "");
|
||||
statementOutput = null;
|
||||
|
||||
synchronized (statementSetNotifier) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import org.apache.spark.SecurityManager;
|
|||
import org.apache.spark.SparkConf;
|
||||
import org.apache.spark.SparkContext;
|
||||
import org.apache.spark.SparkEnv;
|
||||
import org.apache.spark.SecurityManager;
|
||||
import org.apache.spark.api.java.JavaSparkContext;
|
||||
import org.apache.spark.repl.SparkILoop;
|
||||
import org.apache.spark.scheduler.ActiveJob;
|
||||
|
|
@ -69,6 +70,7 @@ import org.apache.zeppelin.scheduler.Scheduler;
|
|||
import org.apache.zeppelin.scheduler.SchedulerFactory;
|
||||
import org.apache.zeppelin.spark.dep.SparkDependencyContext;
|
||||
import org.apache.zeppelin.spark.dep.SparkDependencyResolver;
|
||||
import org.apache.zeppelin.user.AuthenticationInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -187,6 +189,7 @@ public class SparkInterpreter extends Interpreter {
|
|||
String jobUrl = getJobUrl(jobId);
|
||||
String noteId = Utils.getNoteId(jobGroupId);
|
||||
String paragraphId = Utils.getParagraphId(jobGroupId);
|
||||
|
||||
if (jobUrl != null && noteId != null && paragraphId != null) {
|
||||
RemoteEventClientWrapper eventClient = BaseZeppelinContext.getEventClient();
|
||||
Map<String, String> infos = new java.util.HashMap<>();
|
||||
|
|
@ -1204,7 +1207,8 @@ public class SparkInterpreter extends Interpreter {
|
|||
public InterpreterResult interpret(String[] lines, InterpreterContext context) {
|
||||
synchronized (this) {
|
||||
z.setGui(context.getGui());
|
||||
sc.setJobGroup(Utils.buildJobGroupId(context), "Zeppelin", false);
|
||||
String jobDesc = "Started by: " + Utils.getUserName(context.getAuthenticationInfo());
|
||||
sc.setJobGroup(Utils.buildJobGroupId(context), jobDesc, false);
|
||||
InterpreterResult r = interpretInput(lines, context);
|
||||
sc.clearJobGroup();
|
||||
return r;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static org.apache.zeppelin.spark.ZeppelinRDisplay.render;
|
|||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.apache.spark.SparkContext;
|
||||
import org.apache.spark.SparkRBackend;
|
||||
import org.apache.spark.api.java.JavaSparkContext;
|
||||
|
|
@ -114,7 +115,9 @@ public class SparkRInterpreter extends Interpreter {
|
|||
}
|
||||
|
||||
String jobGroup = Utils.buildJobGroupId(interpreterContext);
|
||||
sparkInterpreter.getSparkContext().setJobGroup(jobGroup, "Zeppelin", false);
|
||||
String jobDesc = "Started by: " +
|
||||
Utils.getUserName(interpreterContext.getAuthenticationInfo());
|
||||
sparkInterpreter.getSparkContext().setJobGroup(jobGroup, jobDesc, false);
|
||||
|
||||
String imageWidth = getProperty("zeppelin.R.image.width");
|
||||
|
||||
|
|
@ -139,10 +142,10 @@ public class SparkRInterpreter extends Interpreter {
|
|||
// assign setJobGroup to dummy__, otherwise it would print NULL for this statement
|
||||
if (Utils.isSpark2()) {
|
||||
setJobGroup = "dummy__ <- setJobGroup(\"" + jobGroup +
|
||||
"\", \"zeppelin sparkR job group description\", TRUE)";
|
||||
"\", \" +" + jobDesc + "\", TRUE)";
|
||||
} else if (getSparkInterpreter().getSparkVersion().newerThanEquals(SparkVersion.SPARK_1_5_0)) {
|
||||
setJobGroup = "dummy__ <- setJobGroup(sc, \"" + jobGroup +
|
||||
"\", \"zeppelin sparkR job group description\", TRUE)";
|
||||
"\", \"" + jobDesc + "\", TRUE)";
|
||||
}
|
||||
logger.debug("set JobGroup:" + setJobGroup);
|
||||
lines = setJobGroup + "\n" + lines;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ public class SparkSqlInterpreter extends Interpreter {
|
|||
sc.setLocalProperty("spark.scheduler.pool", null);
|
||||
}
|
||||
|
||||
sc.setJobGroup(Utils.buildJobGroupId(context), "Zeppelin", false);
|
||||
String jobDesc = "Started by: " + Utils.getUserName(context.getAuthenticationInfo());
|
||||
sc.setJobGroup(Utils.buildJobGroupId(context), jobDesc, false);
|
||||
Object rdd = null;
|
||||
try {
|
||||
// method signature of sqlc.sql() is changed
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package org.apache.zeppelin.spark;
|
||||
|
||||
import org.apache.zeppelin.interpreter.InterpreterContext;
|
||||
import org.apache.zeppelin.user.AuthenticationInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -123,4 +124,15 @@ class Utils {
|
|||
int secondIndex = jobgroupId.indexOf("-", indexOf + 1);
|
||||
return jobgroupId.substring(secondIndex + 1, jobgroupId.length());
|
||||
}
|
||||
|
||||
public static String getUserName(AuthenticationInfo info) {
|
||||
String uName = "";
|
||||
if (info != null) {
|
||||
uName = info.getUser();
|
||||
}
|
||||
if (uName == null || uName.isEmpty()) {
|
||||
uName = "anonymous";
|
||||
}
|
||||
return uName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ while True :
|
|||
try:
|
||||
stmts = req.statements().split("\n")
|
||||
jobGroup = req.jobGroup()
|
||||
jobDesc = req.jobDescription()
|
||||
|
||||
# Get post-execute hooks
|
||||
try:
|
||||
|
|
@ -318,7 +319,7 @@ while True :
|
|||
if stmts:
|
||||
# use exec mode to compile the statements except the last statement,
|
||||
# so that the last statement's evaluation will be printed to stdout
|
||||
sc.setJobGroup(jobGroup, "Zeppelin")
|
||||
sc.setJobGroup(jobGroup, jobDesc)
|
||||
code = compile('\n'.join(stmts), '<stdin>', 'exec', ast.PyCF_ONLY_AST, 1)
|
||||
to_run_hooks = []
|
||||
if (nhooks > 0):
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ The following components are provided under Apache License.
|
|||
(Apache 2.0) Apache Avro (org.apache.avro:avro:1.7.7 - http://avro.apache.org)
|
||||
(Apache 2.0) Apache Curator (org.apache.curator:curator:2.4.0 - http://curator.apache.org/)
|
||||
(Apache 2.0) Apache Cassandra (http://cassandra.apache.org/)
|
||||
(Apache 2.0) Apache CXF (http://cxf.apache.org/)
|
||||
(Apache 2.0) Apache HBase (http://hbase.apache.org/)
|
||||
(Apache 2.0) Apache Ignite (http://ignite.apache.org/)
|
||||
(Apache 2.0) Apache Kylin (http://kylin.apache.org/)
|
||||
|
|
@ -118,6 +117,7 @@ The following components are provided under Apache License.
|
|||
(Apache 2.0) Utility classes for Jetty (org.mortbay.jetty:jetty-util:6.1.26 - http://javadox.com/org.mortbay.jetty/jetty/6.1.26/overview-tree.html)
|
||||
(Apache 2.0) Servlet API (org.mortbay.jetty:servlet-api:2.5-20081211 - https://en.wikipedia.org/wiki/Jetty_(web_server))
|
||||
(Apache 2.0) Google HTTP Client Library for Java (com.google.http-client:google-http-client-jackson2:1.21.0 - https://github.com/google/google-http-java-client/tree/dev/google-http-client-jackson2)
|
||||
(Apache 2.0) validation-api (javax.validation - http://beanvalidation.org/)
|
||||
(Apache 2.0) pegdown (org.pegdown:pegdown:1.6.0 - https://github.com/sirthias/pegdown)
|
||||
(Apache 2.0) parboiled-java (org.parboiled:parboiled-java:1.1.7 - https://github.com/sirthias/parboiled)
|
||||
(Apache 2.0) parboiled-core (org.parboiled:parboiled-core:1.1.7 - https://github.com/sirthias/parboiled)
|
||||
|
|
@ -244,9 +244,9 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
|
|||
(The MIT License) lodash v3.9.3 (https://lodash.com/) - https://github.com/lodash/lodash/blob/3.9.3/LICENSE.txt
|
||||
(The MIT License) angular-filter v0.5.4 (https://github.com/a8m/angular-filter) - https://github.com/a8m/angular-filter/blob/v0.5.4/license.md
|
||||
(The MIT License) ngToast v2.0.0 (http://tamerayd.in/ngToast/) - http://tameraydin.mit-license.org/
|
||||
(The MIT License) Handsontable v0.24.2 (https://github.com/handsontable/handsontable) - https://github.com/handsontable/handsontable/blob/master/LICENSE
|
||||
(The MIT License) Zeroclipboard v2.2.0 (https://github.com/zeroclipboard/zeroclipboard) - https://github.com/zeroclipboard/zeroclipboard/blob/v2.2.0/LICENSE
|
||||
(The MIT License) Moment v2.9.0 (https://github.com/moment/moment) - https://github.com/moment/moment/blob/2.9.0/LICENSE
|
||||
(The MIT License) moment v2.18.1 (https://github.com/moment/moment) - https://github.com/moment/moment/blob/2.18.1/LICENSE
|
||||
(The MIT License) moment-duration-format v1.3.0 (https://github.com/jsmreese/moment-duration-format) - https://github.com/jsmreese/moment-duration-format/blob/1.3.0/LICENSE
|
||||
(The MIT License) angular-ui-grid v4.0.4 (https://github.com/angular-ui/ui-grid) - https://github.com/angular-ui/ui-grid/blob/v4.0.4/LICENSE.md
|
||||
(The MIT License) Pikaday v1.3.2 (https://github.com/dbushell/Pikaday) - https://github.com/dbushell/Pikaday/blob/1.3.2/LICENSE
|
||||
(The MIT License) slf4j v1.7.10 (org.slf4j:slf4j-api:jar:1.7.10 - http://www.slf4j.org) - http://www.slf4j.org/license.html
|
||||
(The MIT License) slf4j v1.7.21 (org.slf4j:slf4j-simple:1.7.21 - http://www.slf4j.org) - http://www.slf4j.org/license.html
|
||||
|
|
@ -271,6 +271,7 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
|
|||
(The MIT License) Unirest 1.4.9 (com.mashape.unirest:unirest-java:1.4.9 - https://github.com/Mashape/unirest-java)
|
||||
(The MIT License) ngclipboard v1.1.1 (https://github.com/sachinchoolur/ngclipboard) - https://github.com/sachinchoolur/ngclipboard/blob/1.1.1/LICENSE
|
||||
(The MIT License) headroom.js 0.9.3 (https://github.com/WickyNilliams/headroom.js) - https://github.com/WickyNilliams/headroom.js/blob/master/LICENSE
|
||||
(The MIT License) angular-viewport-watch 0.135 (https://github.com/wix/angular-viewport-watch) - https://github.com/wix/angular-viewport-watch/blob/master/LICENSE
|
||||
|
||||
========================================================================
|
||||
BSD-style licenses
|
||||
|
|
@ -348,9 +349,10 @@ The following components are provided under the CDDL License.
|
|||
|
||||
(CDDL 1.0) javax.activation (javax.activation:activation:jar:1.1.1 - http://java.sun.com/javase/technologies/desktop/javabeans/jaf/index.jsp)
|
||||
(CDDL 1.0) java.annotation (javax.annotation:javax.annotation-api:jar:1.2:compile - http://jcp.org/en/jsr/detail?id=250)
|
||||
(CDDL 1.1) Jersey (com.sun.jersey:jersey:jar:1.9 - https://jersey.java.net/)
|
||||
(CDDL 1.1) jersey-core (org.glassfish.jersey.core:jersey-core:2.22.2 - https://jersey.java.net/)
|
||||
(CDDL 1.1) hk2 (org.glassfish.hk2 - https://hk2.java.net/2.5.0-b03/)
|
||||
(CDDL 1.0) javax.ws.rs-api (javax.ws.rs - https://jax-rs-spec.java.net/)
|
||||
(CDDL 1.1) jersey-client (org.glassfish.jersey.client:jersey-core:2.22.2 - https://jersey.java.net/)
|
||||
(CDDL 1.1) jersey-media (org.glassfish.jersey.media:jersey-core:2.22.2 - https://jersey.java.net/)
|
||||
(CDDL 1.1) jersey-core (com.sun.jersey:jersey-core:1.9 - https://jersey.java.net/jersey-core/)
|
||||
(CDDL 1.1) jersey-json (com.sun.jersey:jersey-json:1.9 - https://jersey.java.net/jersey-json/)
|
||||
(CDDL 1.1) jersey-server (com.sun.jersey:jersey-server:1.9 - https://jersey.java.net/jersey-server/)
|
||||
|
|
|
|||
130
zeppelin-distribution/src/bin_license/licenses/LICENSE-jersey-2
Normal file
130
zeppelin-distribution/src/bin_license/licenses/LICENSE-jersey-2
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL - Version 1.1)
|
||||
|
||||
1. Definitions.
|
||||
|
||||
1.1. “Contributor” means each individual or entity that creates or contributes to the creation of Modifications.
|
||||
|
||||
1.2. “Contributor Version” means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor.
|
||||
|
||||
1.3. “Covered Software” means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof.
|
||||
|
||||
1.4. “Executable” means the Covered Software in any form other than Source Code.
|
||||
|
||||
1.5. “Initial Developer” means the individual or entity that first makes Original Software available under this License.
|
||||
|
||||
1.6. “Larger Work” means a work which combines Covered Software or portions thereof with code not governed by the terms of this License.
|
||||
|
||||
1.7. “License” means this document.
|
||||
|
||||
1.8. “Licensable” means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein.
|
||||
|
||||
1.9. “Modifications” means the Source Code and Executable form of any of the following:
|
||||
|
||||
A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications;
|
||||
|
||||
B. Any new file that contains any part of the Original Software or previous Modification; or
|
||||
|
||||
C. Any new file that is contributed or otherwise made available under the terms of this License.
|
||||
|
||||
1.10. “Original Software” means the Source Code and Executable form of computer software code that is originally released under this License.
|
||||
|
||||
1.11. “Patent Claims” means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor.
|
||||
|
||||
1.12. “Source Code” means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code.
|
||||
|
||||
1.13. “You” (or “Your”) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, “You” includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
2. License Grants.
|
||||
|
||||
2.1. The Initial Developer Grant.
|
||||
|
||||
Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof).
|
||||
|
||||
(c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License.
|
||||
|
||||
(d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices.
|
||||
|
||||
2.2. Contributor Grant.
|
||||
|
||||
Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination).
|
||||
|
||||
(c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party.
|
||||
|
||||
(d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor.
|
||||
|
||||
3. Distribution Obligations.
|
||||
|
||||
3.1. Availability of Source Code.
|
||||
|
||||
Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange.
|
||||
|
||||
3.2. Modifications.
|
||||
|
||||
The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License.
|
||||
|
||||
3.3. Required Notices.
|
||||
|
||||
You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer.
|
||||
|
||||
3.4. Application of Additional Terms.
|
||||
|
||||
You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients’ rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer.
|
||||
|
||||
3.5. Distribution of Executable Versions.
|
||||
|
||||
You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipient’s rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer.
|
||||
|
||||
3.6. Larger Works.
|
||||
|
||||
You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software.
|
||||
|
||||
4. Versions of the License.
|
||||
|
||||
4.1. New Versions.
|
||||
|
||||
Oracle is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License.
|
||||
|
||||
4.2. Effect of New Versions.
|
||||
|
||||
You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward.
|
||||
|
||||
4.3. Modified Versions.
|
||||
|
||||
When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License.
|
||||
|
||||
5. DISCLAIMER OF WARRANTY.
|
||||
|
||||
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN “AS IS” BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
|
||||
6. TERMINATION.
|
||||
|
||||
6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive.
|
||||
|
||||
6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as “Participant”) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant.
|
||||
|
||||
6.3. If You assert a patent infringement claim against Participant alleging that the Participant Software directly or indirectly infringes any patent where such claim is resolved (such as by license or settlement) prior to the initiation of patent infringement litigation, then the reasonable value of the licenses granted by such Participant under Sections 2.1 or 2.2 shall be taken into account in determining the amount or value of any payment or license.
|
||||
|
||||
6.4. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination.
|
||||
|
||||
7. LIMITATION OF LIABILITY.
|
||||
|
||||
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY’S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
|
||||
8. U.S. GOVERNMENT END USERS.
|
||||
|
||||
The Covered Software is a “commercial item,” as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of “commercial computer software” (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1)) and “commercial computer software documentation” as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License.
|
||||
9. MISCELLANEOUS.
|
||||
|
||||
This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdiction’s conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys’ fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software.
|
||||
10. RESPONSIBILITY FOR CLAIMS.
|
||||
|
||||
As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability.
|
||||
NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)
|
||||
|
||||
The code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* 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.completer;
|
||||
|
||||
import jline.console.completer.Completer;
|
||||
|
||||
/**
|
||||
* Completer with time to live
|
||||
*/
|
||||
public class CachedCompleter {
|
||||
private Completer completer;
|
||||
private int ttlInSeconds;
|
||||
private long createdAt;
|
||||
|
||||
public CachedCompleter(Completer completer, int ttlInSeconds) {
|
||||
this.completer = completer;
|
||||
this.ttlInSeconds = ttlInSeconds;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
if (ttlInSeconds == -1 || (ttlInSeconds > 0 &&
|
||||
(System.currentTimeMillis() - createdAt) / 1000 > ttlInSeconds)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Completer getCompleter() {
|
||||
return completer;
|
||||
}
|
||||
}
|
||||
|
|
@ -61,8 +61,9 @@ public class StringsCompleter implements Completer {
|
|||
if (buffer == null) {
|
||||
candidates.addAll(strings);
|
||||
} else {
|
||||
String bufferTmp = buffer.toUpperCase();
|
||||
for (String match : strings.tailSet(buffer)) {
|
||||
String part = buffer.substring(0, cursor);
|
||||
String bufferTmp = part.toUpperCase();
|
||||
for (String match : strings.tailSet(part)) {
|
||||
String matchTmp = match.toUpperCase();
|
||||
if (!matchTmp.startsWith(bufferTmp)) {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -477,15 +477,18 @@ public class RemoteInterpreterEventClient implements ResourcePoolConnector {
|
|||
/**
|
||||
* Wait for eventQueue becomes empty
|
||||
*/
|
||||
public void waitForEventQueueBecomesEmpty() {
|
||||
public void waitForEventQueueBecomesEmpty(long atMost) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
synchronized (eventQueue) {
|
||||
while (!eventQueue.isEmpty()) {
|
||||
while (!eventQueue.isEmpty() && (System.currentTimeMillis() - startTime) < atMost) {
|
||||
try {
|
||||
eventQueue.wait(100);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore exception
|
||||
}
|
||||
}
|
||||
if (!eventQueue.isEmpty())
|
||||
eventQueue.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ public class RemoteInterpreterServer
|
|||
private Map<String, Object> remoteWorksResponsePool;
|
||||
private ZeppelinRemoteWorksController remoteWorksController;
|
||||
|
||||
private final long DEFAULT_SHUTDOWN_TIMEOUT = 2000;
|
||||
|
||||
public RemoteInterpreterServer(int port) throws TTransportException {
|
||||
this.port = port;
|
||||
|
||||
|
|
@ -99,7 +101,7 @@ public class RemoteInterpreterServer
|
|||
|
||||
@Override
|
||||
public void shutdown() throws TException {
|
||||
eventClient.waitForEventQueueBecomesEmpty();
|
||||
eventClient.waitForEventQueueBecomesEmpty(DEFAULT_SHUTDOWN_TIMEOUT);
|
||||
if (interpreterGroup != null) {
|
||||
interpreterGroup.close();
|
||||
}
|
||||
|
|
@ -111,7 +113,8 @@ public class RemoteInterpreterServer
|
|||
// this case, need to force kill the process
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - startTime < 2000 && server.isServing()) {
|
||||
while (System.currentTimeMillis() - startTime < DEFAULT_SHUTDOWN_TIMEOUT &&
|
||||
server.isServing()) {
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
|||
|
|
@ -259,10 +259,18 @@ public abstract class Job {
|
|||
return dateStarted;
|
||||
}
|
||||
|
||||
public synchronized void setDateStarted(Date startedAt) {
|
||||
dateStarted = startedAt;
|
||||
}
|
||||
|
||||
public synchronized Date getDateFinished() {
|
||||
return dateFinished;
|
||||
}
|
||||
|
||||
public synchronized void setDateFinished(Date finishedAt) {
|
||||
dateFinished = finishedAt;
|
||||
}
|
||||
|
||||
public abstract void setResult(Object results);
|
||||
|
||||
public synchronized String getErrorMessage() {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ package org.apache.zeppelin.interpreter.remote;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer;
|
||||
|
|
@ -72,5 +75,65 @@ public class RemoteInterpreterServerTest {
|
|||
assertEquals(false, running);
|
||||
}
|
||||
|
||||
class ShutdownRun implements Runnable {
|
||||
private RemoteInterpreterServer serv = null;
|
||||
public ShutdownRun(RemoteInterpreterServer serv) {
|
||||
this.serv = serv;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
serv.shutdown();
|
||||
} catch (Exception ex) {};
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testStartStopWithQueuedEvents() throws InterruptedException, IOException, TException {
|
||||
RemoteInterpreterServer server = new RemoteInterpreterServer(
|
||||
RemoteInterpreterUtils.findRandomAvailablePortOnAllLocalInterfaces());
|
||||
assertEquals(false, server.isRunning());
|
||||
|
||||
server.start();
|
||||
long startTime = System.currentTimeMillis();
|
||||
boolean running = false;
|
||||
|
||||
while (System.currentTimeMillis() - startTime < 10 * 1000) {
|
||||
if (server.isRunning()) {
|
||||
running = true;
|
||||
break;
|
||||
} else {
|
||||
Thread.sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(true, running);
|
||||
assertEquals(true, RemoteInterpreterUtils.checkIfRemoteEndpointAccessible("localhost", server.getPort()));
|
||||
|
||||
//just send an event on the client queue
|
||||
server.eventClient.onAppStatusUpdate("","","","");
|
||||
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
Runnable task = new ShutdownRun(server);
|
||||
|
||||
executor.schedule(task, 0, TimeUnit.MILLISECONDS);
|
||||
|
||||
while (System.currentTimeMillis() - startTime < 10 * 1000) {
|
||||
if (server.isRunning()) {
|
||||
Thread.sleep(200);
|
||||
} else {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
|
||||
//cleanup environment for next tests
|
||||
server.shutdown();
|
||||
|
||||
assertEquals(false, running);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,12 @@
|
|||
|
||||
<properties>
|
||||
<!--library versions-->
|
||||
<cxf.version>2.7.8</cxf.version>
|
||||
<commons.httpclient.version>4.3.6</commons.httpclient.version>
|
||||
<jersey.version>2.22.2</jersey.version>
|
||||
<hadoop-common.version>2.6.0</hadoop-common.version>
|
||||
<quartz.scheduler.version>2.2.1</quartz.scheduler.version>
|
||||
<jersey.servlet.version>1.13</jersey.servlet.version>
|
||||
<javax.ws.rsapi.version>2.0-m10</javax.ws.rsapi.version>
|
||||
<javax.ws.rsapi.version>2.0.1</javax.ws.rsapi.version>
|
||||
<libpam4j.version>1.8</libpam4j.version>
|
||||
<jna.version>4.1.0</jna.version>
|
||||
|
||||
|
|
@ -86,6 +87,12 @@
|
|||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>zeppelin-zengine</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
@ -98,6 +105,45 @@
|
|||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.containers</groupId>
|
||||
<artifactId>jersey-container-servlet-core</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-json-jackson</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>${javax.ws.rsapi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
|
|
@ -131,54 +177,11 @@
|
|||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Needed for dependency conergence -->
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-transports-http</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-transports-http-jetty</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-api</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>2.5.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
@ -292,18 +295,6 @@
|
|||
<version>${quartz.scheduler.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-servlet</artifactId>
|
||||
<version>${jersey.servlet.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>${javax.ws.rsapi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.scala-lang</groupId>
|
||||
<artifactId>scala-library</artifactId>
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ public class ActiveDirectoryGroupRealm extends AbstractLdapRealm {
|
|||
LdapContext ctx = null;
|
||||
try {
|
||||
String userPrincipalName = upToken.getUsername();
|
||||
if (userPrincipalName == null) {
|
||||
if (!isValidPrincipalName(userPrincipalName)) {
|
||||
return null;
|
||||
}
|
||||
if (this.principalSuffix != null && userPrincipalName.indexOf('@') < 0) {
|
||||
|
|
@ -201,7 +201,24 @@ public class ActiveDirectoryGroupRealm extends AbstractLdapRealm {
|
|||
return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword());
|
||||
}
|
||||
|
||||
private Boolean isValidPrincipalName(String userPrincipalName) {
|
||||
if (userPrincipalName != null) {
|
||||
if (StringUtils.isNotEmpty(userPrincipalName) && userPrincipalName.contains("@")) {
|
||||
String userPrincipalWithoutDomain = userPrincipalName.split("@")[0].trim();
|
||||
if (StringUtils.isNotEmpty(userPrincipalWithoutDomain)) {
|
||||
return true;
|
||||
}
|
||||
} else if (StringUtils.isNotEmpty(userPrincipalName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
|
||||
if (this.principalSuffix != null && username.indexOf('@') > 1) {
|
||||
username = username.split("@")[0];
|
||||
}
|
||||
return new SimpleAuthenticationInfo(username, password, getName());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ import javax.naming.ldap.PagedResultsControl;
|
|||
* <p># optional mapping from physical groups to logical application roles
|
||||
* ldapRealm.rolesByGroup = \ LDN_USERS: user_role,\ NYK_USERS: user_role,\
|
||||
* HKG_USERS: user_role,\ GLOBAL_ADMIN: admin_role,\ DEMOS: self-install_role
|
||||
*
|
||||
* <p># optional list of roles that are allowed to authenticate
|
||||
* ldapRealm.allowedRolesForAuthentication = admin_role,user_role
|
||||
*
|
||||
* <p>ldapRealm.permissionsByRole=\ user_role = *:ToDoItemsJdo:*:*,\
|
||||
* *:ToDoItem:*:*; \ self-install_role = *:ToDoItemsFixturesService:install:* ;
|
||||
|
|
@ -176,6 +179,7 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
private String memberAttributeValueSuffix = "";
|
||||
|
||||
private final Map<String, String> rolesByGroup = new LinkedHashMap<String, String>();
|
||||
private final List<String> allowedRolesForAuthentication = new ArrayList<String>();
|
||||
private final Map<String, List<String>> permissionsByRole =
|
||||
new LinkedHashMap<String, List<String>>();
|
||||
|
||||
|
|
@ -202,6 +206,29 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This overrides the implementation of queryForAuthenticationInfo inside JndiLdapRealm.
|
||||
* In addition to calling the super method for authentication it also tries to validate
|
||||
* if this user has atleast one of the allowed roles for authentication. In case the property
|
||||
* allowedRolesForAuthentication is empty this check always returns true.
|
||||
*
|
||||
* @param token the submitted authentication token that triggered the authentication attempt.
|
||||
* @param ldapContextFactory factory used to retrieve LDAP connections.
|
||||
* @return AuthenticationInfo instance representing the authenticated user's information.
|
||||
* @throws NamingException if any LDAP errors occur.
|
||||
*/
|
||||
@Override
|
||||
protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
|
||||
LdapContextFactory ldapContextFactory)
|
||||
throws NamingException {
|
||||
AuthenticationInfo info = super.queryForAuthenticationInfo(token, ldapContextFactory);
|
||||
// Credentials were verified. Verify that the principal has all allowedRulesForAuthentication
|
||||
if (!hasAllowedAuthenticationRules(info.getPrincipals(), ldapContextFactory)) {
|
||||
throw new NamingException("Principal does not have any of the allowedRolesForAuthentication");
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups from LDAP.
|
||||
*
|
||||
|
|
@ -231,6 +258,23 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
return simpleAuthorizationInfo;
|
||||
}
|
||||
|
||||
private boolean hasAllowedAuthenticationRules(PrincipalCollection principals,
|
||||
final LdapContextFactory ldapContextFactory)
|
||||
throws NamingException {
|
||||
boolean allowed = allowedRolesForAuthentication.isEmpty();
|
||||
if (!allowed) {
|
||||
Set<String> roles = getRoles(principals, ldapContextFactory);
|
||||
for (String allowedRole: allowedRolesForAuthentication) {
|
||||
if (roles.contains(allowedRole)) {
|
||||
log.debug("Allowed role for user [" + allowedRole + "] found.");
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
|
||||
private Set<String> getRoles(PrincipalCollection principals,
|
||||
final LdapContextFactory ldapContextFactory)
|
||||
throws NamingException {
|
||||
|
|
@ -524,6 +568,10 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
this.memberAttributeValueSuffix = suffix;
|
||||
}
|
||||
|
||||
public void setAllowedRolesForAuthentication(List<String> allowedRolesForAuthencation) {
|
||||
this.allowedRolesForAuthentication.addAll(allowedRolesForAuthencation);
|
||||
}
|
||||
|
||||
public void setRolesByGroup(Map<String, String> rolesByGroup) {
|
||||
this.rolesByGroup.putAll(rolesByGroup);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -588,17 +588,17 @@ public class NotebookRestApi {
|
|||
throws IOException, IllegalArgumentException {
|
||||
LOG.info("run note jobs {} ", noteId);
|
||||
Note note = notebook.getNote(noteId);
|
||||
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
|
||||
checkIfNoteIsNotNull(note);
|
||||
checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run job for this note");
|
||||
|
||||
try {
|
||||
note.runAll();
|
||||
note.runAll(subject);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Exception from run", ex);
|
||||
return new JsonResponse<>(Status.PRECONDITION_FAILED,
|
||||
ex.getMessage() + "- Not selected or Invalid Interpreter bind").build();
|
||||
}
|
||||
|
||||
return new JsonResponse<>(Status.OK).build();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,18 @@
|
|||
|
||||
package org.apache.zeppelin.rest;
|
||||
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.zeppelin.annotation.ZeppelinApi;
|
||||
import org.apache.zeppelin.server.JsonResponse;
|
||||
import org.apache.zeppelin.util.Util;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
|
|
@ -52,4 +58,25 @@ public class ZeppelinRestApi {
|
|||
public Response getVersion() {
|
||||
return new JsonResponse<>(Response.Status.OK, "Zeppelin version", Util.getVersion()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log level for root logger
|
||||
* @param request
|
||||
* @param logLevel new log level for Rootlogger
|
||||
* @return
|
||||
*/
|
||||
@PUT
|
||||
@Path("log/level/{logLevel}")
|
||||
public Response changeRootLogLevel(@Context HttpServletRequest request,
|
||||
@PathParam("logLevel") String logLevel) {
|
||||
Level level = Level.toLevel(logLevel);
|
||||
if (logLevel.toLowerCase().equalsIgnoreCase(level.toString().toLowerCase())) {
|
||||
Logger.getRootLogger().setLevel(level);
|
||||
return new JsonResponse<>(Response.Status.OK).build();
|
||||
} else {
|
||||
return new JsonResponse<>(Response.Status.NOT_ACCEPTABLE,
|
||||
"Please check LOG level specified. Valid values: DEBUG, ERROR, FATAL, "
|
||||
+ "INFO, TRACE, WARN").build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import javax.servlet.DispatcherType;
|
|||
import javax.ws.rs.core.Application;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
|
||||
import org.apache.shiro.web.env.EnvironmentLoaderListener;
|
||||
import org.apache.shiro.web.servlet.ShiroFilter;
|
||||
import org.apache.zeppelin.conf.ZeppelinConfiguration;
|
||||
|
|
@ -286,7 +285,7 @@ public class ZeppelinServer extends Application {
|
|||
final ServletHolder servletHolder = new ServletHolder(notebookWsServer);
|
||||
servletHolder.setInitParameter("maxTextMessageSize", maxTextMessageSize);
|
||||
|
||||
final ServletContextHandler cxfContext = new ServletContextHandler(
|
||||
final ServletContextHandler context = new ServletContextHandler(
|
||||
ServletContextHandler.SESSIONS);
|
||||
|
||||
webapp.addServlet(servletHolder, "/ws/*");
|
||||
|
|
@ -316,13 +315,15 @@ public class ZeppelinServer extends Application {
|
|||
private static void setupRestApiContextHandler(WebAppContext webapp,
|
||||
ZeppelinConfiguration conf) {
|
||||
|
||||
final ServletHolder cxfServletHolder = new ServletHolder(new CXFNonSpringJaxrsServlet());
|
||||
cxfServletHolder.setInitParameter("javax.ws.rs.Application", ZeppelinServer.class.getName());
|
||||
cxfServletHolder.setName("rest");
|
||||
cxfServletHolder.setForcedPath("rest");
|
||||
final ServletHolder servletHolder = new ServletHolder(
|
||||
new org.glassfish.jersey.servlet.ServletContainer());
|
||||
|
||||
servletHolder.setInitParameter("javax.ws.rs.Application", ZeppelinServer.class.getName());
|
||||
servletHolder.setName("rest");
|
||||
servletHolder.setForcedPath("rest");
|
||||
|
||||
webapp.setSessionHandler(new SessionHandler());
|
||||
webapp.addServlet(cxfServletHolder, "/api/*");
|
||||
webapp.addServlet(servletHolder, "/api/*");
|
||||
|
||||
String shiroIniPath = conf.getShiroPath();
|
||||
if (!StringUtils.isBlank(shiroIniPath)) {
|
||||
|
|
|
|||
|
|
@ -19,15 +19,9 @@ package org.apache.zeppelin.socket;
|
|||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.regex.Matcher;
|
||||
|
|
@ -1696,6 +1690,23 @@ public class NotebookServer extends WebSocketServlet
|
|||
p.setErrorMessage((String) fromMessage.get("errorMessage"));
|
||||
p.setStatusWithoutNotification(status);
|
||||
|
||||
// Spell uses ISO 8601 formatted string generated from moment
|
||||
String dateStarted = (String) fromMessage.get("dateStarted");
|
||||
String dateFinished = (String) fromMessage.get("dateFinished");
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
|
||||
|
||||
try {
|
||||
p.setDateStarted(df.parse(dateStarted));
|
||||
} catch (ParseException e) {
|
||||
LOG.error("Failed parse dateStarted", e);
|
||||
}
|
||||
|
||||
try {
|
||||
p.setDateFinished(df.parse(dateFinished));
|
||||
} catch (ParseException e) {
|
||||
LOG.error("Failed parse dateFinished", e);
|
||||
}
|
||||
|
||||
addNewParagraphIfLastParagraphIsExecuted(note, p);
|
||||
if (!persistNoteWithAuthInfo(conn, note, p)) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -253,7 +253,6 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
} catch (Exception e) {
|
||||
handleException("Exception in ParagraphActionsIT while testDisableParagraphRunButton ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -263,7 +262,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
}
|
||||
try {
|
||||
String xpathToRunOnSelectionChangeCheckbox = getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]";
|
||||
String xpathToDropdownMenu = getParagraphXPath(1) + "//select";
|
||||
String xpathToDropdownMenu = "(" + (getParagraphXPath(1) + "//select)[2]");
|
||||
String xpathToResultText = getParagraphXPath(1) + "//div[contains(@id,\"_html\")]";
|
||||
|
||||
createNewNote();
|
||||
|
|
@ -593,7 +592,8 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
CoreMatchers.equalTo("Howdy "));
|
||||
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[1]"))));
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
|
||||
dropDownMenu.selectByVisibleText("Alice");
|
||||
collector.checkThat("After selection in drop down menu, output should display the newly selected option",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -602,7 +602,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]"));
|
||||
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[1]"))));
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
sameDropDownMenu.selectByVisibleText("Bob");
|
||||
collector.checkThat("After 'Run on selection change' checkbox is unchecked, the paragraph should not run if selecting a different option",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -632,7 +632,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
CoreMatchers.containsString("Greetings han and leia and luke"));
|
||||
|
||||
WebElement firstCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[1]"));
|
||||
WebElement firstCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[2]"));
|
||||
firstCheckbox.click();
|
||||
collector.checkThat("After unchecking one of the boxes, we can see the newly updated output without the option we unchecked",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -641,7 +641,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]"));
|
||||
|
||||
WebElement secondCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[2]"));
|
||||
WebElement secondCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[3]"));
|
||||
secondCheckbox.click();
|
||||
collector.checkThat("After 'Run on selection change' checkbox is unchecked, the paragraph should not run if check box state is modified",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -676,7 +676,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
CoreMatchers.equalTo("Howdy \nHowdy "));
|
||||
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[1]"))));
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
dropDownMenu.selectByVisibleText("Apple");
|
||||
collector.checkThat("After selection in drop down menu, output should display the new option we selected",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -685,7 +685,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]"));
|
||||
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[3]"))));
|
||||
sameDropDownMenu.selectByVisibleText("Earth");
|
||||
waitForParagraph(1, "FINISHED");
|
||||
collector.checkThat("After 'Run on selection change' checkbox is unchecked, the paragraph should not run if selecting a different option",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import org.openqa.selenium.WebElement;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SparkParagraphIT extends AbstractZeppelinIT {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SparkParagraphIT.class);
|
||||
|
||||
|
|
@ -182,10 +184,16 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
|
|||
);
|
||||
}
|
||||
|
||||
WebElement paragraph1Result = driver.findElement(By.xpath(
|
||||
getParagraphXPath(1) + "//div[contains(@id,\"_graph\")]/div/div/div/div/div[1]"));
|
||||
// Age, Job, Marital, Education, Balance
|
||||
List<WebElement> tableHeaders = driver.findElements(By.cssSelector("span.ui-grid-header-cell-label"));
|
||||
String headerNames = "";
|
||||
|
||||
for(WebElement header : tableHeaders) {
|
||||
headerNames += header.getText().toString() + "|";
|
||||
}
|
||||
|
||||
collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark result: ",
|
||||
paragraph1Result.getText().toString(), CoreMatchers.equalTo("age\n▼\njob\n▼\nmarital\n▼\neducation\n▼\nbalance\n▼\n30 unemployed married primary 1787"));
|
||||
headerNames, CoreMatchers.equalTo("Age|Job|Marital|Education|Balance|"));
|
||||
} catch (Exception e) {
|
||||
handleException("Exception in SparkParagraphIT while testSqlSpark", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,13 +441,19 @@ public abstract class AbstractTestRestApi {
|
|||
LOG.info("{} - {}", postMethod.getStatusCode(), postMethod.getStatusText());
|
||||
Pattern pattern = Pattern.compile("JSESSIONID=([a-zA-Z0-9-]*)");
|
||||
Header[] setCookieHeaders = postMethod.getResponseHeaders("Set-Cookie");
|
||||
String jsessionId = null;
|
||||
for (Header setCookie : setCookieHeaders) {
|
||||
java.util.regex.Matcher matcher = pattern.matcher(setCookie.toString());
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
jsessionId = matcher.group(1);
|
||||
}
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
|
||||
if (jsessionId != null) {
|
||||
return jsessionId;
|
||||
} else {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean userAndPasswordAreNotBlank(String user, String pwd) {
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@
|
|||
"ngtoast": "~2.0.0",
|
||||
"ng-focus-if": "~1.0.2",
|
||||
"bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
|
||||
"handsontable": "~0.24.2",
|
||||
"moment-duration-format": "^1.3.0",
|
||||
"select2": "^4.0.3",
|
||||
"MathJax": "2.7.0",
|
||||
"ngclipboard": "^1.1.1"
|
||||
|
|
|
|||
|
|
@ -81,11 +81,6 @@ module.exports = function(config) {
|
|||
'bower_components/ngtoast/dist/ngToast.js',
|
||||
'bower_components/ng-focus-if/focusIf.js',
|
||||
'bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js',
|
||||
'bower_components/zeroclipboard/dist/ZeroClipboard.js',
|
||||
'bower_components/moment/moment.js',
|
||||
'bower_components/pikaday/pikaday.js',
|
||||
'bower_components/handsontable/dist/handsontable.js',
|
||||
'bower_components/moment-duration-format/lib/moment-duration-format.js',
|
||||
'bower_components/select2/dist/js/select2.js',
|
||||
'bower_components/MathJax/MathJax.js',
|
||||
'bower_components/clipboard/dist/clipboard.js',
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
"clean": "rimraf dist && rimraf .tmp",
|
||||
"postinstall": "bower install --silent",
|
||||
"prebuild": "npm-run-all clean lint:once",
|
||||
"build:dist": "grunt pre-webpack-dist && webpack && grunt post-webpack-dist",
|
||||
"build:ci": "grunt pre-webpack-ci && webpack && grunt post-webpack-dist",
|
||||
"build:dist": "npm-run-all prebuild && grunt pre-webpack-dist && webpack && grunt post-webpack-dist",
|
||||
"build:ci": "npm-run-all prebuild && grunt pre-webpack-ci && webpack && grunt post-webpack-dist",
|
||||
"lint:watch": "esw --watch src",
|
||||
"lint:once": "eslint src",
|
||||
"predev": "grunt pre-webpack-dev",
|
||||
|
|
@ -18,14 +18,18 @@
|
|||
"dev:helium": "HELIUM_BUNDLE_DEV=true webpack-dev-server --hot",
|
||||
"dev:watch": "grunt watch-webpack-dev",
|
||||
"dev": "npm-run-all --parallel dev:server lint:watch dev:watch",
|
||||
"pretest": "npm install karma-phantomjs-launcher babel-polyfill",
|
||||
"test": "karma start karma.conf.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"angular-viewport-watch": "github:shahata/angular-viewport-watch",
|
||||
"github-markdown-css": "2.6.0",
|
||||
"angular-ui-grid": "^4.0.4",
|
||||
"grunt-angular-templates": "^0.5.7",
|
||||
"grunt-dom-munger": "^3.4.0",
|
||||
"headroom.js": "^0.9.3"
|
||||
"headroom.js": "^0.9.3",
|
||||
"moment": "^2.18.1",
|
||||
"moment-duration-format": "^1.3.0",
|
||||
"scrollmonitor": "^1.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.5.4",
|
||||
|
|
@ -73,6 +77,7 @@
|
|||
"karma-coverage": "^1.1.1",
|
||||
"karma-jasmine": "~1.0.2",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"karma-webpack": "^1.8.1",
|
||||
"load-grunt-tasks": "^0.4.0",
|
||||
"ng-annotate-loader": "^0.2.0",
|
||||
|
|
|
|||
|
|
@ -23,11 +23,12 @@
|
|||
<display-name>zeppelin-web</display-name>
|
||||
<servlet>
|
||||
<servlet-name>default</servlet-name>
|
||||
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
|
||||
<init-param>
|
||||
<param-name>com.sun.jersey.config.property.packages</param-name>
|
||||
<param-value>org.apache.zeppelin.rest</param-value>
|
||||
</init-param>
|
||||
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
|
||||
<init-param>
|
||||
<param-name>jersey.config.server.provider.packages</param-name>
|
||||
<param-value>org.apache.zeppelin.rest</param-value>
|
||||
</init-param>
|
||||
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@
|
|||
import 'headroom.js'
|
||||
import 'headroom.js/dist/angular.headroom'
|
||||
|
||||
import 'scrollmonitor/scrollMonitor.js'
|
||||
import 'angular-viewport-watch/angular-viewport-watch.js'
|
||||
|
||||
import 'angular-ui-grid/ui-grid.css'
|
||||
import 'angular-ui-grid'
|
||||
|
||||
const requiredModules = [
|
||||
'ngCookies',
|
||||
'ngAnimate',
|
||||
|
|
@ -37,6 +43,17 @@ const requiredModules = [
|
|||
'focus-if',
|
||||
'ngResource',
|
||||
'ngclipboard',
|
||||
'angularViewportWatch',
|
||||
'ui.grid',
|
||||
'ui.grid.exporter',
|
||||
'ui.grid.edit', 'ui.grid.rowEdit',
|
||||
'ui.grid.selection',
|
||||
'ui.grid.cellNav', 'ui.grid.pinning',
|
||||
'ui.grid.grouping',
|
||||
'ui.grid.emptyBaseLayer',
|
||||
'ui.grid.resizeColumns', 'ui.grid.moveColumns',
|
||||
'ui.grid.pagination',
|
||||
'ui.grid.saveState',
|
||||
]
|
||||
|
||||
// headroom should not be used for CI, since we have to execute some integration tests.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ function ConfigurationCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
|||
timeout: '3000'
|
||||
})
|
||||
setTimeout(function () {
|
||||
window.location.replace('/')
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 3000)
|
||||
}
|
||||
console.log('Error %o %o', status, data.message)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
|||
timeout: '3000'
|
||||
})
|
||||
setTimeout(function () {
|
||||
window.location.replace('/')
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 3000)
|
||||
}
|
||||
console.log('Error %o %o', status, data.message)
|
||||
|
|
|
|||
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HandsonHelper class
|
||||
*/
|
||||
export default class HandsonHelper {
|
||||
constructor (columns, rows, comment) {
|
||||
this.columns = columns || []
|
||||
this.rows = rows || []
|
||||
this.comment = comment || ''
|
||||
this._numericValidator = this._numericValidator.bind(this)
|
||||
}
|
||||
|
||||
getHandsonTableConfig (columns, columnNames, resultRows) {
|
||||
let self = this
|
||||
return {
|
||||
colHeaders: columnNames,
|
||||
data: resultRows,
|
||||
rowHeaders: false,
|
||||
stretchH: 'all',
|
||||
sortIndicator: true,
|
||||
columns: columns,
|
||||
columnSorting: true,
|
||||
contextMenu: false,
|
||||
manualColumnResize: true,
|
||||
manualRowResize: true,
|
||||
readOnly: true,
|
||||
readOnlyCellClassName: '',
|
||||
fillHandle: false,
|
||||
fragmentSelection: true,
|
||||
disableVisualSelection: true,
|
||||
cells: function (ro, co, pro) {
|
||||
let cellProperties = {}
|
||||
let colType = columns[co].type
|
||||
cellProperties.renderer = function (instance, td, row, col, prop, value, cellProperties) {
|
||||
self._cellRenderer(instance, td, row, col, prop, value, cellProperties, colType)
|
||||
}
|
||||
return cellProperties
|
||||
},
|
||||
afterGetColHeader: function (col, TH) {
|
||||
let instance = this
|
||||
let menu = self._buildDropDownMenu(columns[col].type)
|
||||
let button = self._buildTypeSwitchButton()
|
||||
|
||||
self._addButtonMenuEvent(button, menu)
|
||||
|
||||
Handsontable.Dom.addEvent(menu, 'click', function (event) {
|
||||
if (event.target.nodeName === 'LI') {
|
||||
self._setColumnType(columns, event.target.data.colType, instance, col)
|
||||
}
|
||||
})
|
||||
if (TH.firstChild.lastChild.nodeName === 'BUTTON') {
|
||||
TH.firstChild.removeChild(TH.firstChild.lastChild)
|
||||
}
|
||||
TH.firstChild.appendChild(button)
|
||||
TH.style['white-space'] = 'normal'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Private Service Functions
|
||||
*/
|
||||
|
||||
_addButtonMenuEvent (button, menu) {
|
||||
Handsontable.Dom.addEvent(button, 'click', function (event) {
|
||||
let changeTypeMenu
|
||||
let position
|
||||
let removeMenu
|
||||
|
||||
document.body.appendChild(menu)
|
||||
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
changeTypeMenu = document.querySelectorAll('.changeTypeMenu')
|
||||
|
||||
for (let i = 0, len = changeTypeMenu.length; i < len; i++) {
|
||||
changeTypeMenu[i].style.display = 'none'
|
||||
}
|
||||
menu.style.display = 'block'
|
||||
position = button.getBoundingClientRect()
|
||||
|
||||
menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px'
|
||||
menu.style.left = (position.left) + 'px'
|
||||
|
||||
removeMenu = function (event) {
|
||||
if (menu.parentNode) {
|
||||
menu.parentNode.removeChild(menu)
|
||||
}
|
||||
}
|
||||
Handsontable.Dom.removeEvent(document, 'click', removeMenu)
|
||||
Handsontable.Dom.addEvent(document, 'click', removeMenu)
|
||||
})
|
||||
}
|
||||
|
||||
_buildDropDownMenu (activeCellType) {
|
||||
let menu = document.createElement('UL')
|
||||
let types = ['text', 'numeric', 'date']
|
||||
let item
|
||||
|
||||
menu.className = 'changeTypeMenu'
|
||||
|
||||
for (let i = 0, len = types.length; i < len; i++) {
|
||||
item = document.createElement('LI')
|
||||
if ('innerText' in item) {
|
||||
item.innerText = types[i]
|
||||
} else {
|
||||
item.textContent = types[i]
|
||||
}
|
||||
|
||||
item.data = {'colType': types[i]}
|
||||
|
||||
if (activeCellType === types[i]) {
|
||||
item.className = 'active'
|
||||
}
|
||||
menu.appendChild(item)
|
||||
}
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
_buildTypeSwitchButton () {
|
||||
let button = document.createElement('BUTTON')
|
||||
|
||||
button.innerHTML = '\u25BC'
|
||||
button.className = 'changeType'
|
||||
|
||||
return button
|
||||
}
|
||||
|
||||
_isNumeric (value) {
|
||||
if (!isNaN(value)) {
|
||||
if (value.length !== 0) {
|
||||
if (Number(value) <= Number.MAX_SAFE_INTEGER && Number(value) >= Number.MIN_SAFE_INTEGER) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_cellRenderer (instance, td, row, col, prop, value, cellProperties, colType) {
|
||||
if (colType === 'numeric' && this._isNumeric(value)) {
|
||||
cellProperties.format = '0,0.[00000]'
|
||||
td.style.textAlign = 'left'
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
Handsontable.renderers.NumericRenderer.apply(this, arguments)
|
||||
} else if (value.length > '%html'.length && value.substring(0, '%html '.length) === '%html ') {
|
||||
td.innerHTML = value.substring('%html'.length)
|
||||
} else {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
Handsontable.renderers.TextRenderer.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
_dateValidator (value, callback) {
|
||||
let d = moment(value)
|
||||
return callback(d.isValid())
|
||||
}
|
||||
|
||||
_numericValidator (value, callback) {
|
||||
return callback(this._isNumeric(value))
|
||||
}
|
||||
|
||||
_setColumnType (columns, type, instance, col) {
|
||||
columns[col].type = type
|
||||
this._setColumnValidator(columns, col)
|
||||
instance.updateSettings({columns: columns})
|
||||
instance.validateCells(null)
|
||||
if (this._isColumnSorted(instance, col)) {
|
||||
instance.sort(col, instance.sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
_isColumnSorted (instance, col) {
|
||||
return instance.sortingEnabled && instance.sortColumn === col
|
||||
}
|
||||
|
||||
_setColumnValidator (columns, col) {
|
||||
if (columns[col].type === 'numeric') {
|
||||
columns[col].validator = this._numericValidator
|
||||
} else if (columns[col].type === 'date') {
|
||||
columns[col].validator = this._dateValidator
|
||||
} else {
|
||||
columns[col].validator = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,14 +32,14 @@ body {
|
|||
background: white;
|
||||
}
|
||||
|
||||
.displayNavBar {
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
body.asIframe {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.displayNavBar {
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
body .navbar {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -59,58 +59,6 @@ body .navbar {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
background: #3071a9;
|
||||
color: #fff;
|
||||
border-color: #3071a9;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > .open > a,
|
||||
.navbar-inverse .navbar-nav > .open > a:hover,
|
||||
.navbar-inverse .navbar-nav > .open > a:focus {
|
||||
color: #fff;
|
||||
background: #3071a9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-toggle:hover,
|
||||
.navbar-inverse .navbar-toggle:focus {
|
||||
background: #3071a9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a:hover,
|
||||
.navbar-inverse .navbar-nav > li > a:focus {
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-toggle {
|
||||
border-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > .active > a,
|
||||
.navbar-inverse .navbar-nav > .active > a:hover,
|
||||
.navbar-inverse .navbar-nav > .active > a:focus {
|
||||
color: #fff;
|
||||
background: #080808;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-brand {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-family: 'Patua One', cursive;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
a.navbar-brand:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Css for the Notebook Dropdown */
|
||||
|
||||
.expandable ul {
|
||||
|
|
@ -248,45 +196,6 @@ a.navbar-brand:hover {
|
|||
border-radius:6px 0 6px 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a,
|
||||
#notebook-list li a {
|
||||
color: #D3D3D3;
|
||||
}
|
||||
|
||||
.navbar-nav .open .dropdown-menu > .scrollbar-container > li > a,
|
||||
#notebook-list li a {
|
||||
padding: 5px 15px 5px 25px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a {
|
||||
color: #D3D3D3;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a:hover,
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a:focus,
|
||||
#notebook-list li a:hover {
|
||||
color: #fff;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a,
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a:hover,
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a:focus {
|
||||
color: #fff;
|
||||
background: #080808;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu .divider {
|
||||
background: #3071A9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
|
||||
border-color: #3071A9;
|
||||
}
|
||||
}
|
||||
|
||||
#main {
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
|
|
@ -335,14 +244,6 @@ a.navbar-brand:hover {
|
|||
border-color: #357ebd;
|
||||
}
|
||||
|
||||
.username {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 120px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.server-connected {
|
||||
padding-top: 12px;
|
||||
color: #00CC00;
|
||||
|
|
@ -455,40 +356,6 @@ This part should be removed when new version of bootstrap handles this issue.
|
|||
z-index: 10003 !important;
|
||||
}
|
||||
|
||||
#noteImportModal .modal-body {
|
||||
min-height: 420px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#noteImportModal .modal-footer {
|
||||
min-height: 65px;
|
||||
}
|
||||
|
||||
#noteImportModal .display-inline a {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 98px;
|
||||
height: 240px;
|
||||
margin: 0 10px 16px;
|
||||
padding-top: 60px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 264px;
|
||||
}
|
||||
|
||||
#noteImportModal .display-inline a:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#noteImportModal .display-inline a p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------- */
|
||||
/* Slide Top
|
||||
/* ------------------------------------------- */
|
||||
|
|
@ -824,28 +691,3 @@ This part should be removed when new version of bootstrap handles this issue.
|
|||
.bootstrap-dialog.type-primary .modal-header {
|
||||
background: #3071a9;
|
||||
}
|
||||
|
||||
/* About Zeppelin */
|
||||
.about {
|
||||
height: 200px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.about .logo {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.about .logo img {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.about .content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about .content h3 {
|
||||
font-family: 'Patua One';
|
||||
color: #3071A9;
|
||||
font-size: 30px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ limitations under the License.
|
|||
<div ng-mouseenter="showFolderButton=true"
|
||||
ng-mouseleave="showFolderButton=false">
|
||||
<a style="text-decoration: none; cursor: pointer;" ng-click="toggleFolderNode(node)">
|
||||
<i style="font-size: 10px;" ng-class="node.hidden ? 'icon-folder' : 'icon-folder-alt'"></i> {{getNoteName(node)}}
|
||||
<i style="font-size: 10px;" ng-class="node.hidden ? 'fa fa-folder' : 'fa fa-folder-open'"></i> {{getNoteName(node)}}
|
||||
</a>
|
||||
<a ng-if="!node.isTrash" href="" data-toggle="modal" data-target="#noteNameModal" style="text-decoration: none;"
|
||||
ng-controller="NotenameCtrl as notenamectrl" ng-click="notenamectrl.getInterpreterSettings()" data-path="{{node.id}}">
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
timeout: '3000'
|
||||
})
|
||||
setTimeout(function () {
|
||||
window.location.replace('/')
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 3000)
|
||||
}
|
||||
console.log('Error %o %o', status, data.message)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,10 @@ limitations under the License.
|
|||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group" style="margin-top: 10px">
|
||||
<input type="text" ng-model="searchInterpreter" class="form-control ng-pristine ng-untouched ng-valid ng-empty" placeholder="Search interpreters"/>
|
||||
<input type="text" ng-model="searchInterpreter"
|
||||
class="form-control ng-pristine ng-untouched ng-valid ng-empty"
|
||||
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }"
|
||||
placeholder="Search interpreters"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default" ng-disabled="!navbar.connected">
|
||||
<i class="glyphicon glyphicon-search"></i>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
import { ParagraphStatus, } from '../../notebook/paragraph/paragraph.status'
|
||||
|
||||
angular.module('zeppelinWebApp').controller('JobCtrl', JobCtrl)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ 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.
|
||||
-->
|
||||
<headroom class="noteAction"
|
||||
<headroom tolerance="10" offset="30" class="noteAction"
|
||||
ng-show="note.id && !paragraphUrl">
|
||||
<h3>
|
||||
<div style="float: left; width: auto; max-width: 40%"
|
||||
|
|
@ -156,7 +156,7 @@ limitations under the License.
|
|||
<strong>{{revision.message}}</strong>
|
||||
</span>
|
||||
<span class="revisionDate">
|
||||
<em>{{moment.unix(revision.time).format('MMMM Do YYYY, h:mm a')}}</em>
|
||||
<em>{{formatRevisionDate(revision.time)}}</em>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
import { isParagraphRunning, } from './paragraph/paragraph.status'
|
||||
|
||||
angular.module('zeppelinWebApp').controller('NotebookCtrl', NotebookCtrl)
|
||||
|
|
@ -25,7 +27,6 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
ngToast.dismiss()
|
||||
|
||||
$scope.note = null
|
||||
$scope.moment = moment
|
||||
$scope.editorToggled = false
|
||||
$scope.tableToggled = false
|
||||
$scope.viewOnly = false
|
||||
|
|
@ -42,6 +43,10 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
{name: '1d', value: '0 0 0 * * ?'}
|
||||
]
|
||||
|
||||
$scope.formatRevisionDate = function(date) {
|
||||
return moment.unix(date).format('MMMM Do YYYY, h:mm a')
|
||||
}
|
||||
|
||||
$scope.interpreterSettings = []
|
||||
$scope.interpreterBindings = []
|
||||
$scope.isNoteDirty = null
|
||||
|
|
@ -223,7 +228,7 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
}
|
||||
|
||||
$scope.$on('listRevisionHistory', function (event, data) {
|
||||
console.log('received list of revisions %o', data)
|
||||
console.debug('received list of revisions %o', data)
|
||||
$scope.noteRevisions = data.revisionList
|
||||
$scope.noteRevisions.splice(0, 0, {
|
||||
id: 'Head',
|
||||
|
|
@ -464,10 +469,12 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
|
||||
let addPara = function (paragraph, index) {
|
||||
$scope.note.paragraphs.splice(index, 0, paragraph)
|
||||
_.each($scope.note.paragraphs, function (para) {
|
||||
$scope.note.paragraphs.map(para => {
|
||||
if (para.id === paragraph.id) {
|
||||
para.focus = true
|
||||
$scope.$broadcast('focusParagraph', para.id, 0, false)
|
||||
|
||||
// we need `$timeout` since angular DOM might not be initialized
|
||||
$timeout(() => { $scope.$broadcast('focusParagraph', para.id, 0, false) })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ limitations under the License.
|
|||
ng-controller="ParagraphCtrl"
|
||||
ng-init="init(currentParagraph, note)"
|
||||
ng-class="columnWidthClass(currentParagraph.config.colWidth)"
|
||||
style="margin: 0; padding: 0;">
|
||||
style="margin: 0; padding: 0;"
|
||||
viewport-watch>
|
||||
<div class="new-paragraph" ng-click="insertNew('above')" ng-hide="viewOnly || asIframe || revisionView">
|
||||
<h4 class="plus-sign">+</h4>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ limitations under the License.
|
|||
|
||||
<!-- Run / Cancel button -->
|
||||
<span ng-if="!revisionView">
|
||||
<span class="icon-control-play" style="cursor:pointer;color:#3071A9" tooltip-placement="top" uib-tooltip="Run this paragraph (Shift+Enter)"
|
||||
<span class="icon-control-play" style="cursor:pointer;color:#3071A9"
|
||||
tooltip-placement="top" uib-tooltip="Run this paragraph (Shift+Enter)"
|
||||
ng-click="runParagraphFromButton(getEditorValue())"
|
||||
ng-show="paragraph.status!='RUNNING' && paragraph.status!='PENDING' && paragraph.config.enabled"></span>
|
||||
<span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C" tooltip-placement="top"
|
||||
uib-tooltip="Cancel (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+C)"
|
||||
<span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C"
|
||||
tooltip-placement="top" uib-tooltip="Cancel (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+C)"
|
||||
ng-click="cancelParagraph(paragraph)"
|
||||
ng-show="paragraph.status=='RUNNING' || paragraph.status=='PENDING'"></span>
|
||||
<span ng-show="paragraph.runtimeInfos.jobUrl.length == 1">
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import {
|
|||
ParagraphStatus, isParagraphRunning,
|
||||
} from './paragraph.status'
|
||||
|
||||
import moment from 'moment'
|
||||
require('moment-duration-format')
|
||||
|
||||
const ParagraphExecutor = {
|
||||
SPELL: 'SPELL',
|
||||
INTERPRETER: 'INTERPRETER',
|
||||
|
|
@ -251,12 +254,15 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
$scope.propagateSpellResult = function (paragraphId, paragraphTitle,
|
||||
paragraphText, paragraphResults,
|
||||
paragraphStatus, paragraphErrorMessage,
|
||||
paragraphConfig, paragraphSettingsParam) {
|
||||
paragraphConfig, paragraphSettingsParam,
|
||||
paragraphDateStarted, paragraphDateFinished) {
|
||||
websocketMsgSrv.paragraphExecutedBySpell(
|
||||
paragraphId, paragraphTitle,
|
||||
paragraphText, paragraphResults,
|
||||
paragraphStatus, paragraphErrorMessage,
|
||||
paragraphConfig, paragraphSettingsParam)
|
||||
paragraphConfig, paragraphSettingsParam,
|
||||
paragraphDateStarted, paragraphDateFinished
|
||||
)
|
||||
}
|
||||
|
||||
$scope.handleSpellError = function (paragraphText, error,
|
||||
|
|
@ -266,11 +272,16 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
$scope.paragraph.errorMessage = errorMessage
|
||||
console.error('Failed to execute interpret() in spell\n', error)
|
||||
|
||||
if (!propagated) {
|
||||
$scope.paragraph.dateFinished = $scope.getFormattedParagraphTime()
|
||||
}
|
||||
|
||||
if (!propagated) {
|
||||
$scope.propagateSpellResult(
|
||||
$scope.paragraph.id, $scope.paragraph.title,
|
||||
paragraphText, [], $scope.paragraph.status, errorMessage,
|
||||
$scope.paragraph.config, $scope.paragraph.settings.params)
|
||||
$scope.paragraph.config, $scope.paragraph.settings.params,
|
||||
$scope.paragraph.dateStarted, $scope.paragraph.dateFinished)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -305,12 +316,17 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
const resultsMsg = $scope.spellTransaction.resultsMsg
|
||||
const paragraphText = $scope.spellTransaction.paragraphText
|
||||
|
||||
if (!propagated) {
|
||||
$scope.paragraph.dateFinished = $scope.getFormattedParagraphTime()
|
||||
}
|
||||
|
||||
if (!propagated) {
|
||||
const propagable = SpellResult.createPropagable(resultsMsg)
|
||||
$scope.propagateSpellResult(
|
||||
$scope.paragraph.id, $scope.paragraph.title,
|
||||
paragraphText, propagable, status, '',
|
||||
$scope.paragraph.config, $scope.paragraph.settings.params)
|
||||
$scope.paragraph.config, $scope.paragraph.settings.params,
|
||||
$scope.paragraph.dateStarted, $scope.paragraph.dateFinished)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,6 +344,10 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
// remove leading spaces
|
||||
const textWithoutMagic = splited[1].replace(/^\s+/g, '')
|
||||
|
||||
if (!propagated) {
|
||||
$scope.paragraph.dateStarted = $scope.getFormattedParagraphTime()
|
||||
}
|
||||
|
||||
// handle actual result message in promise
|
||||
heliumService.executeSpell(magic, textWithoutMagic)
|
||||
.then(resultsMsg => {
|
||||
|
|
@ -627,10 +647,10 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
$scope.editor.renderer.setShowGutter($scope.paragraph.config.lineNumbers)
|
||||
$scope.editor.setShowFoldWidgets(false)
|
||||
$scope.editor.setHighlightActiveLine(false)
|
||||
$scope.editor.setHighlightGutterLine(false)
|
||||
$scope.editor.getSession().setUseWrapMode(true)
|
||||
$scope.editor.setTheme('ace/theme/chrome')
|
||||
$scope.editor.setReadOnly($scope.isRunning($scope.paragraph))
|
||||
$scope.editor.setHighlightActiveLine($scope.paragraphFocused)
|
||||
|
||||
if ($scope.paragraphFocused) {
|
||||
let prefix = '%' + getInterpreterName($scope.paragraph.text)
|
||||
|
|
@ -815,20 +835,16 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
|
||||
switch (keyCode) {
|
||||
case 38:
|
||||
keyBindingEditorFocusAction(ROW_UP)
|
||||
if (!e.shiftKey) { keyBindingEditorFocusAction(ROW_UP) }
|
||||
break
|
||||
case 80:
|
||||
if (e.ctrlKey && !e.altKey) {
|
||||
keyBindingEditorFocusAction(ROW_UP)
|
||||
}
|
||||
if (e.ctrlKey && !e.altKey) { keyBindingEditorFocusAction(ROW_UP) }
|
||||
break
|
||||
case 40:
|
||||
keyBindingEditorFocusAction(ROW_DOWN)
|
||||
if (!e.shiftKey) { keyBindingEditorFocusAction(ROW_DOWN) }
|
||||
break
|
||||
case 78:
|
||||
if (e.ctrlKey && !e.altKey) {
|
||||
keyBindingEditorFocusAction(ROW_DOWN)
|
||||
}
|
||||
if (e.ctrlKey && !e.altKey) { keyBindingEditorFocusAction(ROW_DOWN) }
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -837,8 +853,11 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
}
|
||||
}
|
||||
|
||||
const handleFocus = function (value, isDigestPass) {
|
||||
$scope.paragraphFocused = value
|
||||
const handleFocus = function (focused, isDigestPass) {
|
||||
$scope.paragraphFocused = focused
|
||||
|
||||
if ($scope.editor) { $scope.editor.setHighlightActiveLine(focused) }
|
||||
|
||||
if (isDigestPass === false || isDigestPass === undefined) {
|
||||
// Protect against error in case digest is already running
|
||||
$timeout(function () {
|
||||
|
|
@ -982,20 +1001,29 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
return $scope.currentProgress || 0
|
||||
}
|
||||
|
||||
$scope.getFormattedParagraphTime = () => {
|
||||
return moment().toISOString()
|
||||
}
|
||||
|
||||
$scope.getExecutionTime = function (pdata) {
|
||||
let timeMs = Date.parse(pdata.dateFinished) - Date.parse(pdata.dateStarted)
|
||||
const end = pdata.dateFinished
|
||||
const start = pdata.dateStarted
|
||||
let timeMs = Date.parse(end) - Date.parse(start)
|
||||
if (isNaN(timeMs) || timeMs < 0) {
|
||||
if ($scope.isResultOutdated(pdata)) {
|
||||
return 'outdated'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const durationFormat = moment.duration((timeMs / 1000), 'seconds').format('h [hrs] m [min] s [sec]')
|
||||
const endFormat = moment(pdata.dateFinished).format('MMMM DD YYYY, h:mm:ss A')
|
||||
|
||||
let user = (pdata.user === undefined || pdata.user === null) ? 'anonymous' : pdata.user
|
||||
let desc = 'Took ' + moment.duration((timeMs / 1000), 'seconds').format('h [hrs] m [min] s [sec]') +
|
||||
'. Last updated by ' + user + ' at ' + moment(pdata.dateFinished).format('MMMM DD YYYY, h:mm:ss A') + '.'
|
||||
if ($scope.isResultOutdated(pdata)) {
|
||||
desc += ' (outdated)'
|
||||
}
|
||||
let desc = `Took ${durationFormat}. Last updated by ${user} at ${endFormat}.`
|
||||
|
||||
if ($scope.isResultOutdated(pdata)) { desc += ' (outdated)' }
|
||||
|
||||
return desc
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,21 +52,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
table.dataTable {
|
||||
margin-top: 0px !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
table.dataTable.table-condensed > thead > tr > th {
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
table.dataTable.table-condensed .sorting:after,
|
||||
table.dataTable.table-condensed .sorting_asc:after,
|
||||
table.dataTable.table-condensed .sorting_desc:after {
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.plainTextContainer {
|
||||
font-family: "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace;
|
||||
font-size: 12px !important;
|
||||
|
|
@ -128,7 +113,6 @@ table.dataTable.table-condensed .sorting_desc:after {
|
|||
display: none;
|
||||
float: right;
|
||||
color: #999;
|
||||
margin-top: -9px;
|
||||
margin-right: 0;
|
||||
position: absolute;
|
||||
clear: both;
|
||||
|
|
@ -190,18 +174,12 @@ table.dataTable.table-condensed .sorting_desc:after {
|
|||
}
|
||||
|
||||
.paragraph .control {
|
||||
background: rgba(255,255,255,0.85);
|
||||
display: inline-block;
|
||||
float: right;
|
||||
background: rgba(255,255,255,0.85);
|
||||
color: #999;
|
||||
margin-top: 1px;
|
||||
margin-right: 5px;
|
||||
position: absolute;
|
||||
clear: both;
|
||||
right: 15px;
|
||||
top: 16px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
padding: 4px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.paragraph .control li {
|
||||
|
|
@ -290,8 +268,11 @@ table.table-shortcut {
|
|||
*/
|
||||
|
||||
.paragraph .title {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
margin-bottom: 7px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.paragraph .title div {
|
||||
|
|
@ -385,10 +366,22 @@ table.table-shortcut {
|
|||
direction: ltr;
|
||||
}
|
||||
|
||||
/** set highlight line color */
|
||||
#main .ace-chrome .ace_marker-layer .ace_active-line {
|
||||
background: rgba(114, 127, 222, 0.08);
|
||||
}
|
||||
|
||||
/** hide not focused cursors */
|
||||
.ace_hidden-cursors {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/** set cursor color */
|
||||
#main .emacs-mode .ace_cursor {
|
||||
background: #C0C0C0!important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ace_text-input, .ace_gutter, .ace_layer,
|
||||
.emacs-mode, .ace_text-layer, .ace_cursor-layer,
|
||||
.ace_cursor, .ace_scrollbar {
|
||||
|
|
@ -399,11 +392,6 @@ table.table-shortcut {
|
|||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
#main .emacs-mode .ace_cursor {
|
||||
background: #C0C0C0!important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/*
|
||||
Table Display CSS
|
||||
*/
|
||||
|
|
@ -487,46 +475,6 @@ table.table-shortcut {
|
|||
background: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Handsontable
|
||||
*/
|
||||
|
||||
.handsontable th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.handsontable th, .handsontable td {
|
||||
border-right: 0px;
|
||||
border-left: 0px !important;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.handsontable tr:first-child th {
|
||||
text-align: left;
|
||||
border-top: 0px;
|
||||
padding: 4px 0px 0px 0px;
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-bottom: 2px solid #CCC;
|
||||
}
|
||||
|
||||
.handsontable .columnSorting.ascending::after {
|
||||
content: '\f160';
|
||||
margin-left: 3px;
|
||||
font-size: 12px;
|
||||
font-family: FontAwesome;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.handsontable .columnSorting.descending::after {
|
||||
content: '\f161';
|
||||
margin-left: 3px;
|
||||
font-size: 12px;
|
||||
margin-left: 3px;
|
||||
font-family: FontAwesome;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
/*
|
||||
Pivot CSS
|
||||
*/
|
||||
|
|
@ -583,54 +531,6 @@ table.table-striped {
|
|||
width: 20px;
|
||||
}
|
||||
|
||||
|
||||
.changeType {
|
||||
border: 1px solid #bbb;
|
||||
color: #bbb;
|
||||
background: #eee;
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
font-size: 9px;
|
||||
float: right;
|
||||
line-height: 9px;
|
||||
margin: 3px 3px 0 0;
|
||||
}
|
||||
.changeType:hover {
|
||||
border: 1px solid #777;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
}
|
||||
.changeType.pressed {
|
||||
background-color: #999;
|
||||
}
|
||||
.changeTypeMenu {
|
||||
position: absolute;
|
||||
border: 1px solid #ccc;
|
||||
margin-top: 22px;
|
||||
box-shadow: 0 1px 3px -1px #323232;
|
||||
background: white;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
display: none;
|
||||
z-index: 10;
|
||||
}
|
||||
.changeTypeMenu li {
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
padding: 2px 20px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.changeTypeMenu li.active:before {
|
||||
font-size: 12px;
|
||||
content: "\2714";
|
||||
margin-left: -15px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.changeTypeMenu li:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
/*
|
||||
Overwrite github-markdown-css for Markdown interpreter
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,24 +15,29 @@ limitations under the License.
|
|||
<div id="{{paragraph.id}}_container"
|
||||
ng-class="{'paragraph': !asIframe, 'paragraphAsIframe': asIframe}">
|
||||
|
||||
<div ng-if="paragraph.config.title"
|
||||
id="{{paragraph.id}}_title"
|
||||
ng-controller="ElasticInputCtrl as input"
|
||||
class="title">
|
||||
<input type="text"
|
||||
pu-elastic-input
|
||||
style="min-width: 400px; max-width: 80%;"
|
||||
placeholder="Untitled"
|
||||
ng-model="paragraph.title"
|
||||
ng-if="input.showEditor"
|
||||
ng-escape="input.showEditor = false; paragraph.title = oldTitle;"
|
||||
ng-blur="setTitle(paragraph); input.showEditor = false"
|
||||
ng-enter="setTitle(paragraph); input.showEditor = false"
|
||||
focus-if="input.showEditor" />
|
||||
<div ng-click="input.showEditor = !asIframe && !viewOnly && !revisionView; oldTitle = paragraph.title;"
|
||||
ng-show="!input.showEditor"
|
||||
ng-bind-html="paragraph.title || 'Untitled'">
|
||||
<div>
|
||||
<div ng-if="paragraph.config.title"
|
||||
id="{{paragraph.id}}_title"
|
||||
ng-controller="ElasticInputCtrl as input"
|
||||
class="title">
|
||||
<input type="text"
|
||||
pu-elastic-input
|
||||
style="min-width: 400px; max-width: 80%;"
|
||||
placeholder="Untitled"
|
||||
ng-model="paragraph.title"
|
||||
ng-if="input.showEditor"
|
||||
ng-escape="input.showEditor = false; paragraph.title = oldTitle;"
|
||||
ng-blur="setTitle(paragraph); input.showEditor = false"
|
||||
ng-enter="setTitle(paragraph); input.showEditor = false"
|
||||
focus-if="input.showEditor" />
|
||||
<div ng-click="input.showEditor = !asIframe && !viewOnly && !revisionView; oldTitle = paragraph.title;"
|
||||
ng-show="!input.showEditor"
|
||||
ng-bind-html="paragraph.title || 'Untitled'">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include src="'app/notebook/paragraph/paragraph-control.html'"></div>
|
||||
<div style="display: inline-block; clear: both;"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -63,8 +68,6 @@ limitations under the License.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include src="'app/notebook/paragraph/paragraph-control.html'"></div>
|
||||
|
||||
<div ng-if="!asIframe" class="paragraphFooter">
|
||||
<div ng-show="!paragraph.config.tableHide && !viewOnly"
|
||||
id="{{paragraph.id}}_executionTime"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.ui-grid-pager-panel {
|
||||
vertical-align: middle;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
.ui-grid-footer-info {
|
||||
padding: 5px;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
.ui-grid-menu {
|
||||
overflow: auto; /** enable scrollbar in column menu */
|
||||
}
|
||||
|
||||
/** disable alternative color for even lows in a table */
|
||||
.ui-grid-row:nth-child(even) .ui-grid-cell {
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
/** support `height: auto` for cells */
|
||||
.ui-grid-viewport .ui-grid-cell-contents {
|
||||
word-wrap: break-word;
|
||||
white-space: normal !important;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.ui-grid-row, .ui-grid-cell {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.ui-grid-row div[role=row] {
|
||||
display: flex ;
|
||||
align-content: stretch;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ limitations under the License.
|
|||
</div>
|
||||
|
||||
<span
|
||||
ng-if="type=='TABLE' && !config.helium.activeApp && graphMode!='table' && !asIframe && !viewOnly"
|
||||
ng-if="type=='TABLE' && !config.helium.activeApp && !asIframe && !viewOnly"
|
||||
style="margin-left:10px; cursor:pointer; display: inline-block; vertical-align:top; position: relative; line-height:30px;">
|
||||
<a class="btnText" ng-click="toggleGraphSetting()">
|
||||
settings <span ng-class="config.graph.optionOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'"></span>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
import TableData from '../../../tabledata/tabledata'
|
||||
import TableVisualization from '../../../visualization/builtins/visualization-table'
|
||||
import BarchartVisualization from '../../../visualization/builtins/visualization-barchart'
|
||||
|
|
@ -25,11 +27,14 @@ import {
|
|||
} from '../../../spell'
|
||||
import { ParagraphStatus, } from '../paragraph.status'
|
||||
|
||||
const TableGridFilterTemplate = require('../../../visualization/builtins/visualization-table-grid-filter.html')
|
||||
|
||||
angular.module('zeppelinWebApp').controller('ResultCtrl', ResultCtrl)
|
||||
|
||||
function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $location,
|
||||
$timeout, $compile, $http, $q, $templateRequest, $sce, websocketMsgSrv,
|
||||
baseUrlSrv, ngToast, saveAsService, noteVarShareService, heliumService) {
|
||||
$timeout, $compile, $http, $q, $templateCache, $templateRequest, $sce, websocketMsgSrv,
|
||||
baseUrlSrv, ngToast, saveAsService, noteVarShareService, heliumService,
|
||||
uiGridConstants) {
|
||||
'ngInject'
|
||||
|
||||
/**
|
||||
|
|
@ -527,7 +532,14 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
}
|
||||
builtInViz.instance._emitter = emitter
|
||||
builtInViz.instance._compile = $compile
|
||||
|
||||
// ui-grid related
|
||||
$templateCache.put('ui-grid/ui-grid-filter', TableGridFilterTemplate)
|
||||
builtInViz.instance._uiGridConstants = uiGridConstants
|
||||
builtInViz.instance._timeout = $timeout
|
||||
|
||||
builtInViz.instance._createNewScope = createNewScope
|
||||
builtInViz.instance._templateRequest = $templateRequest
|
||||
const transformation = builtInViz.instance.getTransformation()
|
||||
transformation._emitter = emitter
|
||||
transformation._templateRequest = $templateRequest
|
||||
|
|
@ -615,8 +627,8 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
} else {
|
||||
newConfig.graph.optionOpen = true
|
||||
}
|
||||
let newParams = angular.copy(paragraph.settings.params)
|
||||
|
||||
let newParams = angular.copy(paragraph.settings.params)
|
||||
commitParagraphResult(paragraph.title, paragraph.text, newConfig, newParams)
|
||||
}
|
||||
|
||||
|
|
@ -645,7 +657,7 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
}
|
||||
}
|
||||
}
|
||||
console.log('getVizConfig', config)
|
||||
console.debug('getVizConfig', config)
|
||||
return config
|
||||
}
|
||||
|
||||
|
|
@ -674,7 +686,7 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
newConfig.graph.values = newConfig.graph.commonSetting.pivot.values
|
||||
delete newConfig.graph.commonSetting.pivot
|
||||
}
|
||||
console.log('committVizConfig', newConfig)
|
||||
console.debug('committVizConfig', newConfig)
|
||||
let newParams = angular.copy(paragraph.settings.params)
|
||||
commitParagraphResult(paragraph.title, paragraph.text, newConfig, newParams)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
*/
|
||||
|
||||
.result-chart-selector {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.result-chart-selector button {
|
||||
|
|
@ -46,3 +46,14 @@
|
|||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.input-rotate {
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
line-height: 10px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 12px !important;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ limitations under the License.
|
|||
|
||||
<div ng-if="type=='TABLE'"
|
||||
ng-style="getPointerEvent()">
|
||||
<!-- graph setting -->
|
||||
<!-- setting -->
|
||||
<div class="option lightBold" style="overflow: visible;"
|
||||
ng-show="graphMode!='table' && config.graph.optionOpen && !asIframe && !viewOnly">
|
||||
ng-show="config.graph.optionOpen && !asIframe && !viewOnly">
|
||||
<div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
|
||||
id="trsetting{{id}}_{{viz.id}}"
|
||||
ng-show="graphMode == viz.id"></div>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function NotebookReposCtrl ($http, baseUrlSrv, ngToast) {
|
|||
timeout: '3000'
|
||||
})
|
||||
setTimeout(function () {
|
||||
window.location.replace('/')
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 3000)
|
||||
}
|
||||
console.log('Error %o %o', status, data.message)
|
||||
|
|
|
|||
|
|
@ -86,15 +86,11 @@ limitations under the License.
|
|||
<div class="columns lightBold">
|
||||
<!-- axis name -->
|
||||
<span class="label label-default"
|
||||
uib-tooltip="{{axisSpec.description ? axisSpec.description : ''}}"
|
||||
ng-style="getAxisAnnotationColor(axisSpec)"
|
||||
uib-tooltip="{{axisSpec.description ? axisSpec.description + ' ' + getAxisTypeAnnotation(axisSpec) : ''}}"
|
||||
style="font-weight: 300; font-size: 13px; margin-left: 1px;">
|
||||
{{getAxisAnnotation(axisSpec)}}
|
||||
</span>
|
||||
<span class="label label-default"
|
||||
ng-style="getAxisTypeAnnotationColor(axisSpec)"
|
||||
style="font-weight: 300; font-size: 13px; margin-left: 3px;">
|
||||
{{getAxisTypeAnnotation(axisSpec)}}
|
||||
</span>
|
||||
|
||||
<!-- axis box: in case of single dimension -->
|
||||
<ul data-drop="true"
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export default class AdvancedTransformation extends Transformation {
|
|||
},
|
||||
|
||||
getAxisTypeAnnotation: (axisSpec) => {
|
||||
let anno = `${axisSpec.axisType}`
|
||||
let anno = ''
|
||||
|
||||
let minAxisCount = axisSpec.minAxisCount
|
||||
let maxAxisCount = axisSpec.maxAxisCount
|
||||
|
|
@ -121,7 +121,7 @@ export default class AdvancedTransformation extends Transformation {
|
|||
return anno
|
||||
},
|
||||
|
||||
getAxisTypeAnnotationColor: (axisSpec) => {
|
||||
getAxisAnnotationColor: (axisSpec) => {
|
||||
if (isAggregatorAxis(axisSpec)) {
|
||||
return { 'background-color': '#5782bd' }
|
||||
} else if (isGroupAxis(axisSpec)) {
|
||||
|
|
|
|||
|
|
@ -11,41 +11,61 @@ 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.
|
||||
-->
|
||||
All fields:
|
||||
<div class="allFields row">
|
||||
<ul class="noDot">
|
||||
<li class="liVertical" ng-repeat="col in tableDataColumns">
|
||||
<div class="btn btn-default btn-xs"
|
||||
data-drag="true"
|
||||
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
|
||||
ng-model="tableDataColumns"
|
||||
jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
|
||||
{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3"
|
||||
ng-repeat="prop in props">
|
||||
<span class="columns lightBold">
|
||||
{{prop.name}}
|
||||
<a tabindex="0" class="fa fa-info-circle" role="button" popover-placement="top"
|
||||
ng-if="prop.tooltip"
|
||||
popover-trigger="focus"
|
||||
popover-html-unsafe="{{prop.tooltip}}"></a>
|
||||
<ul data-drop="true"
|
||||
ng-model="config[prop.name]"
|
||||
jqyoui-droppable="{onDrop:'save()'}"
|
||||
class="list-unstyled"
|
||||
style="height:36px">
|
||||
<li ng-if="config[prop.name]">
|
||||
<div class="btn btn-primary btn-xs">
|
||||
{{config[prop.name].name}} <span class="fa fa-close" ng-click="remove(prop.name)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<div class="panel panel-default" style="margin-top: 10px; margin-bottom: 11px;">
|
||||
<div class="panel-heading"
|
||||
style="padding: 6px 12px 6px 12px; font-size: 13px;">
|
||||
<span style="vertical-align: middle; display: inline-block; margin-top: 3px;">Available Fields</span>
|
||||
<div style="clear: both;"></div>
|
||||
<!-- to fix previous span which has float: right -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- panel-body: columns -->
|
||||
<div class="panel-body"
|
||||
style="padding: 8px; margin-top: 3px;">
|
||||
<ul class="noDot">
|
||||
<li class="liVertical" ng-repeat="col in tableDataColumns">
|
||||
<div class="btn btn-default btn-xs"
|
||||
style="background-color: #EFEFEF;"
|
||||
data-drag="true"
|
||||
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
|
||||
ng-model="tableDataColumns"
|
||||
jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
|
||||
{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- panel-body: axis -->
|
||||
<hr style="margin: 1px;" />
|
||||
<div class="panel-body"
|
||||
style="margin-top: 7px; padding-top: 9px; padding-bottom: 4px;">
|
||||
<div class="col-sm-3"
|
||||
ng-repeat="prop in props">
|
||||
<span class="columns lightBold">
|
||||
<span class="label label-default"
|
||||
style="font-weight: 300; font-size: 13px; margin-left: 1px;"
|
||||
ng-style="prop.name === 'xAxis' ? { 'background-color': '#906ebd' } : prop.name === 'yAxis' ? { 'background-color': '#cd5c5c' } : prop.name === 'group' ? { 'background-color': '#5782bd' } : ''">
|
||||
{{prop.name}}
|
||||
<a tabindex="0" class="fa fa-info-circle" role="button" popover-placement="top"
|
||||
ng-if="prop.tooltip"
|
||||
style="color: white;"
|
||||
popover-trigger="focus"
|
||||
popover-html-unsafe="{{prop.tooltip}}"></a>
|
||||
</span>
|
||||
<ul data-drop="true"
|
||||
ng-model="config[prop.name]"
|
||||
jqyoui-droppable="{onDrop:'save()'}"
|
||||
class="list-unstyled"
|
||||
style="height:36px; border-radius: 6px; margin-top: 7px; overflow: visible !important;">
|
||||
<li ng-if="config[prop.name]">
|
||||
<div class="btn btn-default btn-xs"
|
||||
style="background-color: #EFEFEF;">
|
||||
{{config[prop.name].name}} <span class="fa fa-close" ng-click="remove(prop.name)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</div> <!-- panel-body -->
|
||||
</div> <!-- panel -->
|
||||
|
|
|
|||
|
|
@ -11,78 +11,104 @@ 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.
|
||||
-->
|
||||
All fields:
|
||||
<div class="allFields row">
|
||||
<ul class="noDot">
|
||||
<li class="liVertical" ng-repeat="col in tableDataColumns">
|
||||
<div class="btn btn-default btn-xs"
|
||||
data-drag="true"
|
||||
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
|
||||
ng-model="tableDataColumns"
|
||||
jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
|
||||
{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="graphMode!='scatterChart'">
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Keys
|
||||
<ul data-drop="true"
|
||||
ng-model="config.keys"
|
||||
jqyoui-droppable="{multiple:true, onDrop:'save()'}"
|
||||
class="list-unstyled">
|
||||
<li ng-repeat="item in config.keys">
|
||||
<div class="btn btn-primary btn-xs">
|
||||
{{item.name}} <span class="fa fa-close" ng-click="removeKey($index)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<div class="panel panel-default" style="margin-top: 10px; margin-bottom: 11px;">
|
||||
<div class="panel-heading"
|
||||
style="padding: 6px 12px 6px 12px; font-size: 13px;">
|
||||
<span style="vertical-align: middle; display: inline-block; margin-top: 3px;">Available Fields</span>
|
||||
<div style="clear: both;"></div> <!-- to fix previous span which has float: right -->
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Groups
|
||||
<ul data-drop="true"
|
||||
ng-model="config.groups"
|
||||
jqyoui-droppable="{multiple:true, onDrop:'save()'}"
|
||||
class="list-unstyled">
|
||||
<li ng-repeat="item in config.groups">
|
||||
<div class="btn btn-success btn-xs">
|
||||
{{item.name}} <span class="fa fa-close" ng-click="removeGroup($index)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
<!-- panel-body: columns -->
|
||||
<div class="panel-body"
|
||||
style="padding: 8px; margin-top: 3px;">
|
||||
<ul class="noDot">
|
||||
<li class="liVertical" ng-repeat="col in tableDataColumns">
|
||||
<div class="btn btn-default btn-xs"
|
||||
style="background-color: #EFEFEF;"
|
||||
data-drag="true"
|
||||
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
|
||||
ng-model="tableDataColumns"
|
||||
jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
|
||||
{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Values
|
||||
<ul data-drop="true"
|
||||
ng-model="config.values"
|
||||
jqyoui-droppable="{multiple:true, onDrop:'save()'}"
|
||||
class="list-unstyled">
|
||||
<li ng-repeat="item in config.values">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-info btn-xs dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown">
|
||||
{{item.name | limitTo: 30}}{{item.name.length > 30 ? '...' : ''}}
|
||||
<font style="color:#EEEEEE;"><span class="lightBold" style="text-transform: uppercase;">{{item.aggr}}</span></font>
|
||||
<span class="fa fa-close" ng-click="removeValue($index)"></span>
|
||||
|
||||
<!-- panel-body: axis -->
|
||||
<hr style="margin: 1px;" />
|
||||
<div class="panel-body"
|
||||
style="margin-top: 7px; padding-top: 9px; padding-bottom: 4px;">
|
||||
<!-- axis: keys -->
|
||||
<div class="col-sm-4">
|
||||
<div class="columns lightBold">
|
||||
<span class="label label-default"
|
||||
style="background-color: #906ebd; font-weight: 300; font-size: 13px; margin-left: 1px;">keys</span>
|
||||
<ul ng-model="config.keys"
|
||||
data-drop="true" jqyoui-droppable="{multiple:true, onDrop:'save()'}"
|
||||
class="list-unstyled"
|
||||
style="border-radius: 6px; margin-top: 7px; overflow: visible !important;">
|
||||
<li ng-repeat="item in config.keys">
|
||||
<div class="btn btn-default btn-xs"
|
||||
style="background-color: #EFEFEF; margin: 2px 0px 0px 2px;">
|
||||
{{item.name}}
|
||||
<span class="fa fa-close" ng-click="removeKey($index)"></span>
|
||||
</div>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-click="setValueAggr($index, 'sum')"><a>sum</a></li>
|
||||
<li ng-click="setValueAggr($index, 'count')"><a>count</a></li>
|
||||
<li ng-click="setValueAggr($index, 'avg')"><a>avg</a></li>
|
||||
<li ng-click="setValueAggr($index, 'min')"><a>min</a></li>
|
||||
<li ng-click="setValueAggr($index, 'max')"><a>max</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- axis: groups -->
|
||||
<div class="col-sm-4">
|
||||
<div class="columns lightBold">
|
||||
<span class="label label-default"
|
||||
style="background-color: #cd5c5c; font-weight: 300; font-size: 13px; margin-left: 1px;">groups</span>
|
||||
<ul data-drop="true"
|
||||
ng-model="config.groups"
|
||||
jqyoui-droppable="{multiple:true, onDrop:'save()'}"
|
||||
class="list-unstyled"
|
||||
style="border-radius: 6px; margin-top: 7px; overflow: visible !important;">
|
||||
<li ng-repeat="item in config.groups">
|
||||
<div class="btn btn-default btn-xs"
|
||||
style="background-color: #EFEFEF; margin: 2px 0px 0px 2px;">
|
||||
{{item.name}}
|
||||
<span class="fa fa-close" ng-click="removeGroup($index)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- axis: values -->
|
||||
<div class="col-sm-4">
|
||||
<div class="columns lightBold">
|
||||
<span class="label label-default"
|
||||
style="background-color: #5782bd; font-weight: 300; font-size: 13px; margin-left: 1px;">values</span>
|
||||
<ul data-drop="true"
|
||||
ng-model="config.values"
|
||||
jqyoui-droppable="{multiple:true, onDrop:'save()'}"
|
||||
class="list-unstyled"
|
||||
style="border-radius: 6px; margin-top: 7px; overflow: visible !important;">
|
||||
<li ng-repeat="item in config.values">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-xs dropdown-toggle"
|
||||
style="background-color: #EFEFEF;"
|
||||
type="button" data-toggle="dropdown">
|
||||
{{item.name | limitTo: 30}}{{item.name.length > 30 ? '...' : ''}}
|
||||
<span class="lightBold" style="text-transform: uppercase; color:#717171;">{{item.aggr}}</span>
|
||||
<span class="fa fa-close" ng-click="removeValue($index)"></span>
|
||||
</div>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-click="setValueAggr($index, 'sum')"><a>sum</a></li>
|
||||
<li ng-click="setValueAggr($index, 'count')"><a>count</a></li>
|
||||
<li ng-click="setValueAggr($index, 'avg')"><a>avg</a></li>
|
||||
<li ng-click="setValueAggr($index, 'min')"><a>min</a></li>
|
||||
<li ng-click="setValueAggr($index, 'max')"><a>max</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- panel-body -->
|
||||
</div> <!-- panel -->
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ export default class AreachartVisualization extends Nvd3ChartVisualization {
|
|||
super(targetEl, config)
|
||||
|
||||
this.pivot = new PivotTransformation(config)
|
||||
|
||||
try {
|
||||
this.config.rotate = {degree: config.rotate.degree}
|
||||
} catch (e) {
|
||||
this.config.rotate = {degree: '-45'}
|
||||
}
|
||||
}
|
||||
|
||||
type () {
|
||||
|
|
@ -46,6 +52,7 @@ export default class AreachartVisualization extends Nvd3ChartVisualization {
|
|||
|
||||
this.xLabels = d3Data.xLabels
|
||||
super.render(d3Data)
|
||||
this.config.changeXLabel(this.config.xLabelStatus)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,11 +65,61 @@ export default class AreachartVisualization extends Nvd3ChartVisualization {
|
|||
|
||||
configureChart (chart) {
|
||||
let self = this
|
||||
let configObj = self.config
|
||||
|
||||
chart.xAxis.tickFormat(function (d) { return self.xAxisTickFormat(d, self.xLabels) })
|
||||
chart.yAxis.tickFormat(function (d) { return self.yAxisTickFormat(d) })
|
||||
chart.yAxis.axisLabelDistance(50)
|
||||
chart.useInteractiveGuideline(true) // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
|
||||
|
||||
self.config.changeXLabel = function(type) {
|
||||
switch (type) {
|
||||
case 'default':
|
||||
self.chart._options['showXAxis'] = true
|
||||
self.chart._options['margin'] = {bottom: 50}
|
||||
self.chart.xAxis.rotateLabels(0)
|
||||
configObj.xLabelStatus = 'default'
|
||||
break
|
||||
case 'rotate':
|
||||
self.chart._options['showXAxis'] = true
|
||||
self.chart._options['margin'] = {bottom: 140}
|
||||
self.chart.xAxis.rotateLabels(configObj.rotate.degree)
|
||||
configObj.xLabelStatus = 'rotate'
|
||||
break
|
||||
case 'hide':
|
||||
self.chart._options['showXAxis'] = false
|
||||
self.chart._options['margin'] = {bottom: 50}
|
||||
d3.select('#' + self.targetEl[0].id + '> svg').select('g.nv-axis.nv-x').selectAll('*').remove()
|
||||
configObj.xLabelStatus = 'hide'
|
||||
break
|
||||
}
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
self.config.isXLabelStatus = function(type) {
|
||||
if (configObj.xLabelStatus === type) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.config.setDegree = function(type) {
|
||||
configObj.rotate.degree = type
|
||||
self.chart.xAxis.rotateLabels(type)
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
self.config.isDegreeEmpty = function() {
|
||||
if (configObj.rotate.degree.length > 0) {
|
||||
return true
|
||||
} else {
|
||||
configObj.rotate.degree = '-45'
|
||||
self.emitConfig(configObj)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
this.chart.style(this.config.style || 'stack')
|
||||
|
||||
this.chart.dispatch.on('stateChange', function (s) {
|
||||
|
|
@ -74,4 +131,25 @@ export default class AreachartVisualization extends Nvd3ChartVisualization {
|
|||
}, 500)
|
||||
})
|
||||
}
|
||||
getSetting(chart) {
|
||||
let self = this
|
||||
let configObj = self.config
|
||||
|
||||
// default to visualize xLabel
|
||||
if (typeof (configObj.xLabelStatus) === 'undefined') {
|
||||
configObj.changeXLabel('default')
|
||||
}
|
||||
|
||||
if (typeof (configObj.rotate.degree) === 'undefined' || configObj.rotate.degree === '') {
|
||||
configObj.rotate.degree = '-45'
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
return {
|
||||
template: 'app/visualization/builtins/visualization-displayXAxis.html',
|
||||
scope: {
|
||||
config: configObj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ export default class BarchartVisualization extends Nvd3ChartVisualization {
|
|||
super(targetEl, config)
|
||||
|
||||
this.pivot = new PivotTransformation(config)
|
||||
|
||||
try {
|
||||
this.config.rotate = {degree: config.rotate.degree}
|
||||
} catch (e) {
|
||||
this.config.rotate = {degree: '-45'}
|
||||
}
|
||||
}
|
||||
|
||||
type () {
|
||||
|
|
@ -76,7 +82,7 @@ export default class BarchartVisualization extends Nvd3ChartVisualization {
|
|||
case 'rotate':
|
||||
self.chart._options['showXAxis'] = true
|
||||
self.chart._options['margin'] = {bottom: 140}
|
||||
self.chart.xAxis.rotateLabels(-45)
|
||||
self.chart.xAxis.rotateLabels(configObj.rotate.degree)
|
||||
configObj.xLabelStatus = 'rotate'
|
||||
break
|
||||
case 'hide':
|
||||
|
|
@ -86,6 +92,7 @@ export default class BarchartVisualization extends Nvd3ChartVisualization {
|
|||
configObj.xLabelStatus = 'hide'
|
||||
break
|
||||
}
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
self.config.isXLabelStatus = function(type) {
|
||||
|
|
@ -96,6 +103,12 @@ export default class BarchartVisualization extends Nvd3ChartVisualization {
|
|||
}
|
||||
}
|
||||
|
||||
self.config.setDegree = function(type) {
|
||||
configObj.rotate.degree = type
|
||||
self.chart.xAxis.rotateLabels(type)
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
this.chart.dispatch.on('stateChange', function(s) {
|
||||
configObj.stacked = s.stacked
|
||||
|
||||
|
|
@ -115,39 +128,15 @@ export default class BarchartVisualization extends Nvd3ChartVisualization {
|
|||
configObj.changeXLabel('default')
|
||||
}
|
||||
|
||||
if (typeof (configObj.rotate.degree) === 'undefined' || configObj.rotate.degree === '') {
|
||||
configObj.rotate.degree = '-45'
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
return {
|
||||
template: `<div>
|
||||
xAxis :
|
||||
</div>
|
||||
|
||||
<div class='btn-group'>
|
||||
<button type="button"
|
||||
class="xLabel btn btn-default btn-sm"
|
||||
ng-class="{'active' : this.config.isXLabelStatus('default')}"
|
||||
ng-click="save('default')" >
|
||||
Default
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-default btn-sm"
|
||||
ng-class="{'active' : this.config.isXLabelStatus('rotate')}"
|
||||
ng-click="save('rotate')" >
|
||||
Rotate
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-default btn-sm"
|
||||
ng-class="{'active' : this.config.isXLabelStatus('hide')}"
|
||||
ng-click="save('hide')" >
|
||||
Hide
|
||||
</button>
|
||||
</div>`,
|
||||
template: 'app/visualization/builtins/visualization-displayXAxis.html',
|
||||
scope: {
|
||||
config: configObj,
|
||||
save: function(type) {
|
||||
configObj.changeXLabel(type)
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
config: configObj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<div>
|
||||
xAxis :
|
||||
</div>
|
||||
|
||||
<div class='btn-group'>
|
||||
<button type="button"
|
||||
class="btn btn-default btn-sm"
|
||||
ng-class="{'active' : config.isXLabelStatus('default')}"
|
||||
ng-click="config.changeXLabel('default')" >
|
||||
Default
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-default btn-sm"
|
||||
ng-class="{'active' : config.isXLabelStatus('rotate')}"
|
||||
ng-click="config.changeXLabel('rotate')" >
|
||||
Rotate
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-default btn-sm"
|
||||
ng-class="{'active' : config.isXLabelStatus('hide')}"
|
||||
ng-click="config.changeXLabel('hide')" >
|
||||
Hide
|
||||
</button>
|
||||
|
||||
|
||||
<span style="line-height: 2.5;"
|
||||
ng-show="config.isXLabelStatus('rotate')">
|
||||
degree :
|
||||
<input type="text" class="input-rotate"
|
||||
placeholder="-45"
|
||||
ng-model="config.rotate.degree"
|
||||
ng-enter="config.setDegree(config.rotate.degree)"
|
||||
ng-blur="config.setDegree(config.rotate.degree)"
|
||||
ng-pattern="/^-?[0-9]{1,3}$/" />
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -23,6 +23,12 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
|
|||
super(targetEl, config)
|
||||
|
||||
this.pivot = new PivotTransformation(config)
|
||||
|
||||
try {
|
||||
this.config.rotate = {degree: config.rotate.degree}
|
||||
} catch (e) {
|
||||
this.config.rotate = {degree: '-45'}
|
||||
}
|
||||
}
|
||||
|
||||
type () {
|
||||
|
|
@ -50,6 +56,7 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
|
|||
|
||||
this.xLabels = d3Data.xLabels
|
||||
super.render(d3Data)
|
||||
this.config.changeXLabel(this.config.xLabelStatus)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,6 +75,8 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
|
|||
|
||||
configureChart (chart) {
|
||||
let self = this
|
||||
let configObj = self.config
|
||||
|
||||
chart.xAxis.tickFormat(function (d) { return self.xAxisTickFormat(d, self.xLabels) })
|
||||
chart.yAxis.tickFormat(function (d) {
|
||||
if (d === undefined) {
|
||||
|
|
@ -84,12 +93,60 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
|
|||
} else {
|
||||
chart.forceY([])
|
||||
}
|
||||
|
||||
self.config.changeXLabel = function(type) {
|
||||
switch (type) {
|
||||
case 'default':
|
||||
self.chart._options['showXAxis'] = true
|
||||
self.chart._options['margin'] = {bottom: 50}
|
||||
self.chart.xAxis.rotateLabels(0)
|
||||
configObj.xLabelStatus = 'default'
|
||||
break
|
||||
case 'rotate':
|
||||
self.chart._options['showXAxis'] = true
|
||||
self.chart._options['margin'] = {bottom: 140}
|
||||
self.chart.xAxis.rotateLabels(configObj.rotate.degree)
|
||||
configObj.xLabelStatus = 'rotate'
|
||||
break
|
||||
case 'hide':
|
||||
self.chart._options['showXAxis'] = false
|
||||
self.chart._options['margin'] = {bottom: 50}
|
||||
d3.select('#' + self.targetEl[0].id + '> svg').select('g.nv-axis.nv-x').selectAll('*').remove()
|
||||
configObj.xLabelStatus = 'hide'
|
||||
break
|
||||
}
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
self.config.isXLabelStatus = function(type) {
|
||||
if (configObj.xLabelStatus === type) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.config.setDegree = function(type) {
|
||||
configObj.rotate.degree = type
|
||||
self.chart.xAxis.rotateLabels(type)
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
}
|
||||
|
||||
getSetting (chart) {
|
||||
let self = this
|
||||
let configObj = self.config
|
||||
|
||||
// default to visualize xLabel
|
||||
if (typeof (configObj.xLabelStatus) === 'undefined') {
|
||||
configObj.changeXLabel('default')
|
||||
}
|
||||
|
||||
if (typeof (configObj.rotate.degree) === 'undefined' || configObj.rotate.degree === '') {
|
||||
configObj.rotate.degree = '-45'
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
|
||||
return {
|
||||
template: `<div>
|
||||
<label>
|
||||
|
|
@ -106,7 +163,9 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
|
|||
ng-click="save()" />
|
||||
zoom
|
||||
</label>
|
||||
</div>`,
|
||||
</div>
|
||||
<ng-include src="'app/visualization/builtins/visualization-displayXAxis.html'">
|
||||
</ng-include>`,
|
||||
scope: {
|
||||
config: configObj,
|
||||
save: function () {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="ui-grid-filter-container"
|
||||
ng-repeat="colFilter in col.filters"
|
||||
ng-class="{'ui-grid-filter-cancel-button-hidden' : colFilter.disableCancelFilterButton === true }">
|
||||
<div ng-if="colFilter.type !== 'select'">
|
||||
<input type="text" class="input-sm form-control"
|
||||
style="font-size: 14px; font-weight: 500"
|
||||
|
||||
ng-model="colFilter.term"
|
||||
ng-model-options="{ debounce : { 'default' : 300, 'blur' : 0 }}"
|
||||
ng-attr-placeholder="{{colFilter.placeholder || ''}}"
|
||||
aria-label="{{colFilter.ariaLabel || aria.defaultFilterLabel}}" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="panel panel-default" style="margin-top: 10px; margin-bottom: 11px;">
|
||||
<div class="panel-heading" style="padding: 6px 12px 6px 12px; font-size: 13px;">
|
||||
<span style="vertical-align: middle; display: inline-block; margin-top: 3px;">Table Options</span>
|
||||
<span style="float: right;">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<div type="button" ng-click="resetTableOption()"
|
||||
uib-tooltip="Restore the default setting" tooltip-placement="left"
|
||||
class="btn btn-default" style="font-size: 11px; padding: 2px 5px 2px 5px;">
|
||||
<i class="fa fa-undo" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<div style="clear: both;"></div> <!-- to fix previous span which has float: right -->
|
||||
</div>
|
||||
|
||||
<div class="panel-body" style="padding: 8px 12px; margin-top: 3px;">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th style="font-size: 12px; font-style: italic">Name</th>
|
||||
<th style="font-size: 12px; font-style: italic">Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
</tr>
|
||||
|
||||
<tr data-ng-repeat="optSpec in tableOptionSpecs">
|
||||
<td style="font-weight: 400; vertical-align: middle;">
|
||||
<span uib-tooltip="{{optSpec.description}}" tooltip-placement="right">
|
||||
{{optSpec.name}}
|
||||
<i class="fa fa-info-circle" style="margin-top: 2px; margin-left: 3px; color: #7b7bbd;" aria-hidden="true"></i>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-if="isInputWidget(optSpec)"
|
||||
class="input-group">
|
||||
<input type="text" class="form-control input-sm"
|
||||
style="font-weight: 400; font-size: 12px; vertical-align:middle; border-radius: 5px;"
|
||||
ng-keydown="tableWidgetOnKeyDown($event, optSpec)"
|
||||
data-ng-model="config.tableOptionValue[optSpec.name]" />
|
||||
</div>
|
||||
<div class="btn-group"
|
||||
ng-if="isOptionWidget(optSpec)">
|
||||
<select class="form-control input-sm"
|
||||
ng-change="tableOptionValueChanged(optSpec)"
|
||||
data-ng-model="config.tableOptionValue[optSpec.name]"
|
||||
data-ng-options="optionValue for optionValue in optSpec.optionValues"
|
||||
style="font-weight: 400; font-size: 12px;">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div ng-if="isCheckboxWidget(optSpec)">
|
||||
<input type="checkbox"
|
||||
ng-keydown="parameterOnKeyDown($event, optSpec)"
|
||||
ng-click="tableOptionValueChanged(optSpec)"
|
||||
data-ng-model="config.tableOptionValue[optSpec.name]"
|
||||
data-ng-checked="config.tableOptionValue[optSpec.name]" />
|
||||
</div>
|
||||
|
||||
<div ng-if="isTextareaWidget(optSpec)">
|
||||
<textarea class="form-control" rows="3"
|
||||
ng-keydown="tableWidgetOnKeyDown($event, optSpec)"
|
||||
data-ng-model="config.tableOptionValue[optSpec.name]"
|
||||
style="font-weight: 400; font-size: 12px;">
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -14,7 +14,41 @@
|
|||
|
||||
import Visualization from '../visualization'
|
||||
import PassthroughTransformation from '../../tabledata/passthrough'
|
||||
import HandsonHelper from '../../handsontable/handsonHelper'
|
||||
|
||||
import {
|
||||
Widget, ValueType,
|
||||
isInputWidget, isOptionWidget, isCheckboxWidget,
|
||||
isTextareaWidget, isBtnGroupWidget,
|
||||
initializeTableConfig, resetTableOptionConfig,
|
||||
DefaultTableColumnType, TableColumnType, updateColumnTypeState,
|
||||
parseTableOption,
|
||||
} from './visualization-util'
|
||||
|
||||
const SETTING_TEMPLATE = require('./visualization-table-setting.html')
|
||||
|
||||
const TABLE_OPTION_SPECS = [
|
||||
{
|
||||
name: 'useFilter',
|
||||
valueType: ValueType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
widget: Widget.CHECKBOX,
|
||||
description: 'Enable filter for columns',
|
||||
},
|
||||
{
|
||||
name: 'showPagination',
|
||||
valueType: ValueType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
widget: Widget.CHECKBOX,
|
||||
description: 'Enable pagination for better navigation',
|
||||
},
|
||||
{
|
||||
name: 'showAggregationFooter',
|
||||
valueType: ValueType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
widget: Widget.CHECKBOX,
|
||||
description: 'Enable a footer for displaying aggregated values',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Visualize data in table format
|
||||
|
|
@ -22,42 +56,349 @@ import HandsonHelper from '../../handsontable/handsonHelper'
|
|||
export default class TableVisualization extends Visualization {
|
||||
constructor (targetEl, config) {
|
||||
super(targetEl, config)
|
||||
console.log('Init table viz')
|
||||
targetEl.addClass('table')
|
||||
this.passthrough = new PassthroughTransformation(config)
|
||||
this.emitTimeout = null
|
||||
this.isRestoring = false
|
||||
|
||||
initializeTableConfig(config, TABLE_OPTION_SPECS)
|
||||
}
|
||||
|
||||
refresh () {
|
||||
this.hot.render()
|
||||
createGridOptions(tableData, onRegisterApiCallback, config) {
|
||||
const rows = tableData.rows
|
||||
const columnNames = tableData.columns.map(c => c.name)
|
||||
|
||||
const gridData = rows.map(r => {
|
||||
return columnNames.reduce((acc, colName, index) => {
|
||||
acc[colName] = r[index]
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
|
||||
const gridOptions = {
|
||||
data: gridData,
|
||||
enableGridMenu: true,
|
||||
modifierKeysToMultiSelectCells: true,
|
||||
exporterMenuCsv: true,
|
||||
exporterMenuPdf: false,
|
||||
flatEntityAccess: true,
|
||||
fastWatch: false,
|
||||
treeRowHeaderAlwaysVisible: false,
|
||||
|
||||
columnDefs: columnNames.map(colName => {
|
||||
return {
|
||||
name: colName,
|
||||
type: DefaultTableColumnType,
|
||||
cellTemplate: `
|
||||
<div ng-if="!grid.getCellValue(row, col).startsWith('%html')"
|
||||
class="ui-grid-cell-contents">
|
||||
{{grid.getCellValue(row, col)}}
|
||||
</div>
|
||||
<div ng-if="grid.getCellValue(row, col).startsWith('%html')"
|
||||
ng-bind-html="grid.getCellValue(row, col).split('%html')[1]"
|
||||
class="ui-grid-cell-contents">
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
}),
|
||||
rowEditWaitInterval: -1, /** disable saveRow event */
|
||||
enableRowHashing: true,
|
||||
saveFocus: false,
|
||||
saveScroll: false,
|
||||
saveSort: true,
|
||||
savePinning: true,
|
||||
saveGrouping: true,
|
||||
saveGroupingExpandedStates: true,
|
||||
saveOrder: true, // column order
|
||||
saveVisible: true, // column visibility
|
||||
saveTreeView: true,
|
||||
saveFilter: true,
|
||||
saveSelection: false,
|
||||
}
|
||||
|
||||
return gridOptions
|
||||
}
|
||||
|
||||
getGridElemId() {
|
||||
// angular doesn't allow `-` in scope variable name
|
||||
const gridElemId = `${this.targetEl[0].id}_grid`.replace('-', '_')
|
||||
return gridElemId
|
||||
}
|
||||
|
||||
getGridApiId() {
|
||||
// angular doesn't allow `-` in scope variable name
|
||||
const gridApiId = `${this.targetEl[0].id}_gridApi`.replace('-', '_')
|
||||
return gridApiId
|
||||
}
|
||||
|
||||
refresh() {
|
||||
const gridElemId = this.getGridElemId()
|
||||
const gridElem = angular.element(`#${gridElemId}`)
|
||||
|
||||
if (gridElem) {
|
||||
gridElem.css('height', this.targetEl.height() - 10)
|
||||
}
|
||||
}
|
||||
|
||||
refreshGrid() {
|
||||
const gridElemId = this.getGridElemId()
|
||||
const gridElem = angular.element(`#${gridElemId}`)
|
||||
|
||||
if (gridElem) {
|
||||
const scope = this.getScope()
|
||||
const gridApiId = this.getGridApiId()
|
||||
scope[gridApiId].core.notifyDataChange(this._uiGridConstants.dataChange.ALL)
|
||||
}
|
||||
}
|
||||
|
||||
updateColDefType(colDef, type) {
|
||||
if (type === colDef.type) { return }
|
||||
|
||||
colDef.type = type
|
||||
const colName = colDef.name
|
||||
const config = this.config
|
||||
if (config.tableColumnTypeState.names && config.tableColumnTypeState.names[colName]) {
|
||||
config.tableColumnTypeState.names[colName] = type
|
||||
this.persistConfigWithGridState(this.config)
|
||||
}
|
||||
}
|
||||
|
||||
addColumnMenus(gridOptions) {
|
||||
if (!gridOptions || !gridOptions.columnDefs) { return }
|
||||
|
||||
const self = this // for closure
|
||||
|
||||
// SHOULD use `function() { ... }` syntax for each action to get `this`
|
||||
gridOptions.columnDefs.map(colDef => {
|
||||
colDef.menuItems = [
|
||||
{
|
||||
title: 'Type: String',
|
||||
action: function() {
|
||||
self.updateColDefType(this.context.col.colDef, TableColumnType.STRING)
|
||||
},
|
||||
active: function() {
|
||||
return this.context.col.colDef.type === TableColumnType.STRING
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Type: Number',
|
||||
action: function() {
|
||||
self.updateColDefType(this.context.col.colDef, TableColumnType.NUMBER)
|
||||
},
|
||||
active: function() {
|
||||
return this.context.col.colDef.type === TableColumnType.NUMBER
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Type: Date',
|
||||
action: function() {
|
||||
self.updateColDefType(this.context.col.colDef, TableColumnType.DATE)
|
||||
},
|
||||
active: function() {
|
||||
return this.context.col.colDef.type === TableColumnType.DATE
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
setDynamicGridOptions(gridOptions, config) {
|
||||
// parse based on their type definitions
|
||||
const parsed = parseTableOption(TABLE_OPTION_SPECS, config.tableOptionValue)
|
||||
|
||||
const { showAggregationFooter, useFilter, showPagination, } = parsed
|
||||
|
||||
gridOptions.showGridFooter = false
|
||||
gridOptions.showColumnFooter = showAggregationFooter
|
||||
gridOptions.enableFiltering = useFilter
|
||||
|
||||
gridOptions.enablePagination = showPagination
|
||||
gridOptions.enablePaginationControls = showPagination
|
||||
|
||||
if (showPagination) {
|
||||
gridOptions.paginationPageSize = 50
|
||||
gridOptions.paginationPageSizes = [25, 50, 100, 250, 1000]
|
||||
}
|
||||
|
||||
// selection can't be rendered dynamically in ui-grid 4.0.4
|
||||
gridOptions.enableRowSelection = false
|
||||
gridOptions.enableRowHeaderSelection = false
|
||||
gridOptions.enableFullRowSelection = false
|
||||
gridOptions.enableSelectAll = false
|
||||
gridOptions.enableGroupHeaderSelection = false
|
||||
gridOptions.enableSelectionBatchEvent = false
|
||||
}
|
||||
|
||||
render (tableData) {
|
||||
let height = this.targetEl.height()
|
||||
let container = this.targetEl.css('height', height).get(0)
|
||||
let resultRows = tableData.rows
|
||||
let columnNames = _.pluck(tableData.columns, 'name')
|
||||
// eslint-disable-next-line prefer-spread
|
||||
let columns = Array.apply(null, Array(tableData.columns.length)).map(function () {
|
||||
return {type: 'text'}
|
||||
})
|
||||
const gridElemId = this.getGridElemId()
|
||||
let gridElem = document.getElementById(gridElemId)
|
||||
|
||||
if (this.hot) {
|
||||
this.hot.destroy()
|
||||
const config = this.config
|
||||
const self = this // for closure
|
||||
|
||||
if (!gridElem) {
|
||||
// create, compile and append grid elem
|
||||
gridElem = angular.element(
|
||||
`<div id="${gridElemId}" ui-grid="${gridElemId}"
|
||||
ui-grid-edit ui-grid-row-edit
|
||||
ui-grid-pagination
|
||||
ui-grid-selection
|
||||
ui-grid-cellNav ui-grid-pinning
|
||||
ui-grid-empty-base-layer
|
||||
ui-grid-resize-columns ui-grid-move-columns
|
||||
ui-grid-grouping
|
||||
ui-grid-save-state
|
||||
ui-grid-exporter></div>`)
|
||||
|
||||
gridElem.css('height', this.targetEl.height() - 10)
|
||||
const scope = this.getScope()
|
||||
gridElem = this._compile(gridElem)(scope)
|
||||
this.targetEl.append(gridElem)
|
||||
|
||||
// set gridOptions for this elem
|
||||
const gridOptions = this.createGridOptions(tableData, onRegisterApiCallback, config)
|
||||
this.setDynamicGridOptions(gridOptions, config)
|
||||
this.addColumnMenus(gridOptions)
|
||||
scope[gridElemId] = gridOptions
|
||||
|
||||
// set gridApi for this elem
|
||||
const gridApiId = this.getGridApiId()
|
||||
const onRegisterApiCallback = (gridApi) => {
|
||||
scope[gridApiId] = gridApi
|
||||
// should restore state before registering APIs
|
||||
|
||||
// register callbacks for change evens
|
||||
// should persist `self.config` instead `config` (closure issue)
|
||||
gridApi.core.on.columnVisibilityChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.colMovable.on.columnPositionChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.core.on.sortChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.core.on.filterChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.grouping.on.aggregationChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.grouping.on.groupingChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.treeBase.on.rowCollapsed(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
gridApi.treeBase.on.rowExpanded(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
|
||||
// pagination doesn't follow usual life-cycle in ui-grid v4.0.4
|
||||
// gridApi.pagination.on.paginationChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
// TBD: do we need to propagate row selection?
|
||||
// gridApi.selection.on.rowSelectionChanged(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
// gridApi.selection.on.rowSelectionChangedBatch(scope, () => { self.persistConfigWithGridState(self.config) })
|
||||
}
|
||||
gridOptions.onRegisterApi = onRegisterApiCallback
|
||||
} else {
|
||||
// don't need to update gridOptions.data since it's synchronized by paragraph execution
|
||||
const gridOptions = this.getGridOptions()
|
||||
this.setDynamicGridOptions(gridOptions, config)
|
||||
this.refreshGrid()
|
||||
}
|
||||
|
||||
let handsonHelper = new HandsonHelper()
|
||||
this.hot = new Handsontable(container, handsonHelper.getHandsonTableConfig(
|
||||
columns, columnNames, resultRows))
|
||||
this.hot.validateCells(null)
|
||||
const columnDefs = this.getGridOptions().columnDefs
|
||||
updateColumnTypeState(tableData.columns, config, columnDefs)
|
||||
// SHOULD restore grid state after columnDefs are updated
|
||||
this.restoreGridState(config.tableGridState)
|
||||
}
|
||||
|
||||
restoreGridState(gridState) {
|
||||
if (!gridState) { return }
|
||||
|
||||
// should set isRestoring to avoid that changed* events are triggered while restoring
|
||||
this.isRestoring = true
|
||||
const gridApi = this.getGridApi()
|
||||
|
||||
// restore grid state when gridApi is available
|
||||
if (!gridApi) {
|
||||
setTimeout(() => this.restoreGridState(gridState), 100)
|
||||
} else {
|
||||
gridApi.saveState.restore(this.getScope(), gridState)
|
||||
this.isRestoring = false
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (this.hot) {
|
||||
this.hot.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
getTransformation () {
|
||||
return this.passthrough
|
||||
}
|
||||
|
||||
getScope() {
|
||||
const scope = this.targetEl.scope()
|
||||
return scope
|
||||
}
|
||||
|
||||
getGridOptions() {
|
||||
const scope = this.getScope()
|
||||
const gridElemId = this.getGridElemId()
|
||||
return scope[gridElemId]
|
||||
}
|
||||
|
||||
getGridApi() {
|
||||
const scope = this.targetEl.scope()
|
||||
const gridApiId = this.getGridApiId()
|
||||
return scope[gridApiId]
|
||||
}
|
||||
|
||||
persistConfigImmediatelyWithGridState(config) {
|
||||
this.persistConfigWithGridState(config)
|
||||
}
|
||||
|
||||
persistConfigWithGridState(config) {
|
||||
if (this.isRestoring) { return }
|
||||
|
||||
const gridApi = this.getGridApi()
|
||||
config.tableGridState = gridApi.saveState.save()
|
||||
this.emitConfig(config)
|
||||
}
|
||||
|
||||
persistConfig(config) {
|
||||
this.emitConfig(config)
|
||||
}
|
||||
|
||||
getSetting (chart) {
|
||||
const self = this // for closure in scope
|
||||
const configObj = self.config
|
||||
|
||||
// emit config if it's updated in `render`
|
||||
if (configObj.initialized) {
|
||||
configObj.initialized = false
|
||||
this.persistConfig(configObj) // should persist w/o state
|
||||
} else if (configObj.tableColumnTypeState &&
|
||||
configObj.tableColumnTypeState.updated) {
|
||||
configObj.tableColumnTypeState.updated = false
|
||||
this.persistConfig(configObj) // should persist w/o state
|
||||
}
|
||||
|
||||
return {
|
||||
template: SETTING_TEMPLATE,
|
||||
scope: {
|
||||
config: configObj,
|
||||
tableOptionSpecs: TABLE_OPTION_SPECS,
|
||||
isInputWidget: isInputWidget,
|
||||
isOptionWidget: isOptionWidget,
|
||||
isCheckboxWidget: isCheckboxWidget,
|
||||
isTextareaWidget: isTextareaWidget,
|
||||
isBtnGroupWidget: isBtnGroupWidget,
|
||||
tableOptionValueChanged: () => {
|
||||
self.persistConfigWithGridState(configObj)
|
||||
},
|
||||
saveTableOption: () => {
|
||||
self.persistConfigWithGridState(configObj)
|
||||
},
|
||||
resetTableOption: () => {
|
||||
resetTableOptionConfig(configObj)
|
||||
initializeTableConfig(configObj, TABLE_OPTION_SPECS)
|
||||
self.persistConfigWithGridState(configObj)
|
||||
},
|
||||
tableWidgetOnKeyDown: (event, optSpec) => {
|
||||
const code = event.keyCode || event.which
|
||||
if (code === 13 && isInputWidget(optSpec)) {
|
||||
self.persistConfigWithGridState(configObj)
|
||||
} else if (code === 13 && event.shiftKey && isTextareaWidget(optSpec)) {
|
||||
self.persistConfigWithGridState(configObj)
|
||||
}
|
||||
|
||||
event.stopPropagation() /** avoid to conflict with paragraph shortcuts */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const Widget = {
|
||||
CHECKBOX: 'checkbox',
|
||||
INPUT: 'input',
|
||||
TEXTAREA: 'textarea',
|
||||
OPTION: 'option',
|
||||
BTN_GROUP: 'btn-group',
|
||||
}
|
||||
|
||||
export const ValueType = {
|
||||
INT: 'int',
|
||||
FLOAT: 'float',
|
||||
BOOLEAN: 'boolean',
|
||||
STRING: 'string',
|
||||
JSON: 'JSON',
|
||||
}
|
||||
|
||||
export const TableColumnType = {
|
||||
STRING: 'string',
|
||||
BOOLEAN: 'boolean',
|
||||
NUMBER: 'number',
|
||||
DATE: 'date',
|
||||
OBJECT: 'object',
|
||||
NUMBER_STR: 'numberStr',
|
||||
}
|
||||
|
||||
export const DefaultTableColumnType = TableColumnType.STRING
|
||||
|
||||
export function isInputWidget (spec) { return spec.widget === Widget.INPUT }
|
||||
export function isOptionWidget (spec) { return spec.widget === Widget.OPTION }
|
||||
export function isCheckboxWidget (spec) { return spec.widget === Widget.CHECKBOX }
|
||||
export function isTextareaWidget (spec) { return spec.widget === Widget.TEXTAREA }
|
||||
export function isBtnGroupWidget (spec) { return spec.widget === Widget.BTN_GROUP }
|
||||
|
||||
export function resetTableOptionConfig(config) {
|
||||
delete config.tableOptionSpecHash
|
||||
config.tableOptionSpecHash = {}
|
||||
delete config.tableOptionValue
|
||||
config.tableOptionValue = {}
|
||||
delete config.tableColumnTypeState.names
|
||||
config.tableColumnTypeState.names = {}
|
||||
config.updated = false
|
||||
return config
|
||||
}
|
||||
|
||||
export function initializeTableConfig(config, tableOptionSpecs) {
|
||||
if (typeof config.tableOptionValue === 'undefined') { config.tableOptionValue = {} }
|
||||
if (typeof config.tableGridState === 'undefined') { config.tableGridState = {} }
|
||||
if (typeof config.tableColumnTypeState === 'undefined') { config.tableColumnTypeState = {} }
|
||||
|
||||
// should remove `$$hashKey` using angular.toJson
|
||||
const newSpecHash = JSON.stringify(JSON.parse(angular.toJson(tableOptionSpecs)))
|
||||
const previousSpecHash = config.tableOptionSpecHash
|
||||
|
||||
// check whether spec is updated or not
|
||||
if (typeof previousSpecHash === 'undefined' || (previousSpecHash !== newSpecHash)) {
|
||||
resetTableOptionConfig(config)
|
||||
|
||||
config.tableOptionSpecHash = newSpecHash
|
||||
config.initialized = true
|
||||
|
||||
// reset all persisted option values if spec is updated
|
||||
for (let i = 0; i < tableOptionSpecs.length; i++) {
|
||||
const option = tableOptionSpecs[i]
|
||||
config.tableOptionValue[option.name] = option.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
export function parseTableOption(specs, persistedTableOption) {
|
||||
/** copy original params */
|
||||
const parsed = JSON.parse(JSON.stringify(persistedTableOption))
|
||||
|
||||
for (let i = 0; i < specs.length; i++) {
|
||||
const s = specs[i]
|
||||
const name = s.name
|
||||
|
||||
if (s.valueType === ValueType.INT &&
|
||||
typeof parsed[name] !== 'number') {
|
||||
try { parsed[name] = parseInt(parsed[name]) } catch (error) { parsed[name] = s.defaultValue }
|
||||
} else if (s.valueType === ValueType.FLOAT &&
|
||||
typeof parsed[name] !== 'number') {
|
||||
try { parsed[name] = parseFloat(parsed[name]) } catch (error) { parsed[name] = s.defaultValue }
|
||||
} else if (s.valueType === ValueType.BOOLEAN) {
|
||||
if (parsed[name] === 'false') {
|
||||
parsed[name] = false
|
||||
} else if (parsed[name] === 'true') {
|
||||
parsed[name] = true
|
||||
} else if (typeof parsed[name] !== 'boolean') {
|
||||
parsed[name] = s.defaultValue
|
||||
}
|
||||
} else if (s.valueType === ValueType.JSON) {
|
||||
if (parsed[name] !== null && typeof parsed[name] !== 'object') {
|
||||
try { parsed[name] = JSON.parse(parsed[name]) } catch (error) { parsed[name] = s.defaultValue }
|
||||
} else if (parsed[name] === null) {
|
||||
parsed[name] = s.defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
export function isColumnNameUpdated(prevColumnNames, newColumnNames) {
|
||||
if (typeof prevColumnNames === 'undefined') { return true }
|
||||
|
||||
let columnNameUpdated = false
|
||||
|
||||
for (let prevColName in prevColumnNames) {
|
||||
if (!newColumnNames[prevColName]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (!columnNameUpdated) {
|
||||
for (let newColName in newColumnNames) {
|
||||
if (!prevColumnNames[newColName]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function updateColumnTypeState(columns, config, columnDefs) {
|
||||
const columnTypeState = config.tableColumnTypeState
|
||||
|
||||
if (!columnTypeState) { return }
|
||||
|
||||
// compare objects because order might be changed
|
||||
const prevColumnNames = columnTypeState.names || {}
|
||||
const newColumnNames = columns.reduce((acc, c) => {
|
||||
const prevColumnType = prevColumnNames[c.name]
|
||||
|
||||
// use previous column type if exists
|
||||
if (prevColumnType) {
|
||||
acc[c.name] = prevColumnType
|
||||
} else {
|
||||
acc[c.name] = DefaultTableColumnType
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
let columnNameUpdated = isColumnNameUpdated(prevColumnNames, newColumnNames)
|
||||
|
||||
if (columnNameUpdated) {
|
||||
columnTypeState.names = newColumnNames
|
||||
columnTypeState.updated = true
|
||||
}
|
||||
|
||||
// update `columnDefs[n].type`
|
||||
for (let i = 0; i < columnDefs.length; i++) {
|
||||
const colName = columnDefs[i].name
|
||||
columnDefs[i].type = columnTypeState.names[colName]
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* Base class for visualization
|
||||
* Base class for visualization.
|
||||
*/
|
||||
export default class Visualization {
|
||||
constructor (targetEl, config) {
|
||||
|
|
@ -25,17 +25,22 @@ export default class Visualization {
|
|||
}
|
||||
|
||||
/**
|
||||
* get transformation
|
||||
* Get transformation.
|
||||
* @abstract
|
||||
* @return {Transformation}
|
||||
*/
|
||||
getTransformation () {
|
||||
// override this
|
||||
throw new TypeError('Visualization.getTransformation() should be overrided')
|
||||
}
|
||||
|
||||
/**
|
||||
* Method will be invoked when data or configuration changed
|
||||
* Method will be invoked when data or configuration changed.
|
||||
* @abstract
|
||||
*/
|
||||
render (tableData) {
|
||||
// override this
|
||||
throw new TypeError('Visualization.render() should be overrided')
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,7 +51,7 @@ export default class Visualization {
|
|||
}
|
||||
|
||||
/**
|
||||
* method will be invoked when visualization need to be destroyed.
|
||||
* Method will be invoked when visualization need to be destroyed.
|
||||
* Don't need to destroy this.targetEl.
|
||||
*/
|
||||
destroy () {
|
||||
|
|
@ -64,7 +69,7 @@ export default class Visualization {
|
|||
}
|
||||
|
||||
/**
|
||||
* Activate. invoked when visualization is selected
|
||||
* Activate. Invoked when visualization is selected.
|
||||
*/
|
||||
activate () {
|
||||
if (!this._active || this._dirty) {
|
||||
|
|
@ -75,21 +80,21 @@ export default class Visualization {
|
|||
}
|
||||
|
||||
/**
|
||||
* Activate. invoked when visualization is de selected
|
||||
* Deactivate. Invoked when visualization is de selected.
|
||||
*/
|
||||
deactivate () {
|
||||
this._active = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Is active
|
||||
* Is active.
|
||||
*/
|
||||
isActive () {
|
||||
return this._active
|
||||
}
|
||||
|
||||
/**
|
||||
* When window or paragraph is resized
|
||||
* When window or paragraph is resized.
|
||||
*/
|
||||
resize () {
|
||||
if (this.isActive()) {
|
||||
|
|
@ -100,7 +105,7 @@ export default class Visualization {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set new config
|
||||
* Set new config.
|
||||
*/
|
||||
setConfig (config) {
|
||||
this.config = config
|
||||
|
|
@ -119,7 +124,7 @@ export default class Visualization {
|
|||
}
|
||||
|
||||
/**
|
||||
* render setting
|
||||
* Render setting.
|
||||
*/
|
||||
renderSetting (targetEl) {
|
||||
let setting = this.getSetting()
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ function baseUrlSrv () {
|
|||
skipTrailingSlash(location.pathname) + '/ws'
|
||||
}
|
||||
|
||||
this.getBase = function() {
|
||||
return location.protocol + '//' + location.hostname + ':' + this.getPort() + location.pathname
|
||||
}
|
||||
|
||||
this.getRestApiBase = function () {
|
||||
return location.protocol + '//' + location.hostname + ':' +
|
||||
this.getPort() + skipTrailingSlash(location.pathname) +
|
||||
'/api'
|
||||
return skipTrailingSlash(this.getBase()) + '/api'
|
||||
}
|
||||
|
||||
const skipTrailingSlash = function (path) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ limitations under the License.
|
|||
-->
|
||||
|
||||
<div class="editor"
|
||||
ui-ace="{
|
||||
onLoad : onLoad,
|
||||
require : ['ace/ext/language_tools']
|
||||
}"
|
||||
ui-ace="{ onLoad : onLoad, require : ['ace/ext/language_tools'] }"
|
||||
ng-model="paragraph.text"
|
||||
ng-class="{'paragraph-disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' || revisionView === true,
|
||||
'paragraph-text--dirty' : dirtyText !== originalText && dirtyText !== undefined}">
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function codeEditor ($templateRequest, $compile) {
|
|||
editor.attr('id', scope.paragraphId + '_editor')
|
||||
element.append(editor)
|
||||
$compile(editor)(scope)
|
||||
console.log('codeEditor directive revision view is ' + scope.revisionView)
|
||||
console.debug('codeEditor directive revision view is ' + scope.revisionView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ function expandCollapse () {
|
|||
angular.element(element).click(function (event) {
|
||||
if (angular.element(element).find('.expandable:visible').length > 1) {
|
||||
angular.element(element).find('.expandable:visible').slideUp('slow')
|
||||
angular.element(element).find('i.icon-folder-alt').toggleClass('icon-folder icon-folder-alt')
|
||||
angular.element(element).find('i.fa-folder-open').toggleClass('fa-folder fa-folder-open')
|
||||
} else {
|
||||
angular.element(element).find('.expandable').first().slideToggle('200', function () {
|
||||
// do not toggle trash folder
|
||||
if (angular.element(element).find('.fa-trash-o').length === 0) {
|
||||
angular.element(element).find('i').first().toggleClass('icon-folder icon-folder-alt')
|
||||
angular.element(element).find('i').first().toggleClass('fa-folder fa-folder-open')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,9 @@ 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.
|
||||
-->
|
||||
<input type="text" class="note-name-query form-control" ng-click="$event.stopPropagation()"
|
||||
placeholder=" Filter" ng-model="$parent.query.q" />
|
||||
<input type="text"
|
||||
class="note-name-query form-control"
|
||||
ng-click="$event.stopPropagation()"
|
||||
placeholder=" Filter"
|
||||
ng-model="$parent.query.q"
|
||||
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }" />
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ limitations under the License.
|
|||
<div ng-mouseenter="showFolderButton=true" ng-mouseleave="showFolderButton=false">
|
||||
<a class="notebook-list-item" href="javascript:void(0)">
|
||||
<div ng-if="node.id !== navbar.TRASH_FOLDER_ID">
|
||||
<i style="font-size: 10px; margin-right: 5px;" class="icon-folder"></i>
|
||||
<i style="font-size: 10px; margin-right: 5px;" class="fa fa-folder"></i>
|
||||
<span>{{noteName(node)}}</span>
|
||||
<i data-toggle="modal" data-target="#noteNameModal" ng-controller="NotenameCtrl as notenamectrl"
|
||||
ng-click="notenamectrl.getInterpreterSettings()" data-path="{{node.id}}"
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ function NavCtrl ($scope, $rootScope, $http, $routeParams, $location,
|
|||
message: 'Logout Success'
|
||||
})
|
||||
setTimeout(function () {
|
||||
window.location.replace('/')
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,22 +12,63 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#searchTermId {
|
||||
min-width: 200px;
|
||||
/* ------------------------------------------- */
|
||||
/* Navbar
|
||||
/* ------------------------------------------- */
|
||||
|
||||
.navbar-brand.navbar-title {
|
||||
margin-top: -3px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.navbar-title > span {
|
||||
font-family: 'Patua One', cursive;
|
||||
font-size: 25px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a.navbar-menu-notebook:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a.navbar-menu-notebook:hover{
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.navbar-search {
|
||||
font-size: 14px;
|
||||
font-family: 'FontAwesome', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#navbar-search {
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
@media (min-width: 795px) and (max-width: 830px) {
|
||||
input#searchTermId {
|
||||
width: 170px;
|
||||
min-width: 170px;
|
||||
#navbar-search {
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 794px) {
|
||||
input#searchTermId {
|
||||
width: 140px;
|
||||
min-width: 140px;
|
||||
#navbar-search {
|
||||
width: 130px;
|
||||
min-width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,3 +82,151 @@
|
|||
}
|
||||
.navbar-fixed-top.headroom--unpinned { top: -100px; }
|
||||
.navbar-fixed-top.headroom--pinned { top: 0; /** `navbar` top */ }
|
||||
|
||||
.navbar-inverse {
|
||||
background: #3071a9;
|
||||
color: #fff;
|
||||
border-color: #3071a9;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > .open > a,
|
||||
.navbar-inverse .navbar-nav > .open > a:hover,
|
||||
.navbar-inverse .navbar-nav > .open > a:focus {
|
||||
color: #fff;
|
||||
background: #3071a9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-toggle:hover,
|
||||
.navbar-inverse .navbar-toggle:focus {
|
||||
background: #3071a9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a:hover,
|
||||
.navbar-inverse .navbar-nav > li > a:focus {
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-toggle {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > .active > a,
|
||||
.navbar-inverse .navbar-nav > .active > a:hover,
|
||||
.navbar-inverse .navbar-nav > .active > a:focus {
|
||||
color: #fff;
|
||||
background: #080808;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-brand {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-family: 'Patua One', cursive;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
a.navbar-brand:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a,
|
||||
#notebook-list li a {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
.navbar-nav .open .dropdown-menu > .scrollbar-container > li > a,
|
||||
#notebook-list li a {
|
||||
padding: 5px 15px 5px 25px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a {
|
||||
color: #D3D3D3;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a:hover,
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a:focus,
|
||||
#notebook-list li a:hover {
|
||||
color: #fff;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a,
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a:hover,
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a:focus {
|
||||
color: #fff;
|
||||
background: #080808;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav .open .dropdown-menu .divider {
|
||||
background: #3071A9;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
|
||||
border-color: #3071A9;
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 120px;
|
||||
display: inline-block;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* About Dialog */
|
||||
|
||||
.modal-header-about {
|
||||
background-color: #3071a9;
|
||||
border: 2px solid #3071a9;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-header-about > .close {
|
||||
color: #cfcfcf;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-header-about > .modal-title {
|
||||
font-weight: 300;
|
||||
font-size: 20px;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
.modal-body-about-version {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modal-body-about {
|
||||
height: 250px;
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.modal-body-about .logo {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.modal-body-about .logo img {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.modal-body-about .content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-body-about .content h3 {
|
||||
font-family: 'Patua One';
|
||||
color: #3071A9;
|
||||
font-size: 30px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
-->
|
||||
|
||||
<headroom class="navbar navbar-inverse navbar-fixed-top"
|
||||
<headroom tolerance="10" offset="30" class="navbar navbar-inverse navbar-fixed-top"
|
||||
style="display: none;" role="navigation"
|
||||
ng-class="{'displayNavBar': !asIframe}">
|
||||
<div class="container">
|
||||
|
|
@ -20,18 +20,30 @@ limitations under the License.
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#/">
|
||||
<img style="margin-top: -7px;" src="assets/images/zepLogoW.png" width="50" alt="I'm zeppelin" /> Zeppelin
|
||||
<a class="navbar-brand navbar-logo" href="#/">
|
||||
<img style="margin-top: -7px;" src="assets/images/zepLogoW.png" width="50" alt="Zeppelin" />
|
||||
</a>
|
||||
<a class="navbar-brand navbar-title" href="#/">
|
||||
<span>Zeppelin</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" ng-controller="NavCtrl as navbar">
|
||||
<ul class="nav navbar-nav" ng-if="ticket">
|
||||
<!-- menu: Notebook -->
|
||||
<li class="dropdown notebook-list-dropdown" uib-dropdown>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" uib-dropdown-toggle>Notebook <span class="caret"></span></a>
|
||||
<a href="#" class="navbar-menu navbar-menu-notebook dropdown-toggle"
|
||||
data-toggle="dropdown" uib-dropdown-toggle>
|
||||
<span>Notebook</span>
|
||||
<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-controller="NotenameCtrl as notenamectrl"><a href="" data-toggle="modal" data-target="#noteNameModal" ng-click="notenamectrl.getInterpreterSettings()"><i class="fa fa-plus"></i> Create new note</a></li>
|
||||
<li class="divider"></li>
|
||||
<li ng-controller="NotenameCtrl as notenamectrl">
|
||||
<a href="" data-toggle="modal" data-target="#noteNameModal" ng-click="notenamectrl.getInterpreterSettings()">
|
||||
<i class="fa fa-plus"></i>
|
||||
Create new note
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider hidden-xs"></li>
|
||||
<div id="notebook-list" class="scrollbar-container" ng-if="isDrawNavbarNoteList">
|
||||
<li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
|
||||
<div ng-if="!query.q || query.q === ''">
|
||||
|
|
@ -47,36 +59,28 @@ limitations under the License.
|
|||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#/jobmanager">Job</a></li>
|
||||
<!-- menu: Job -->
|
||||
<li>
|
||||
<a class="navbar-menu navbar-menu-job" href="#/jobmanager">
|
||||
<span>Job</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right" style="margin-right:5px;">
|
||||
<li class="nav-component" ng-if="ticket">
|
||||
<!--TODO(bzz): move to Typeahead https://angular-ui.github.io/bootstrap -->
|
||||
|
||||
<form role="search" data-ng-model="navbar.searchForm"
|
||||
style="display: inline-block; margin: 0px"
|
||||
class="navbar-form"
|
||||
ng-submit="navbar.search(navbar.searchForm.searchTerm)">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
ng-model="navbar.searchForm.searchTerm"
|
||||
id="searchTermId"
|
||||
ng-disabled="!navbar.connected"
|
||||
class="form-control"
|
||||
placeholder="Search your Notes"
|
||||
/>
|
||||
<span class="input-group-btn">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
ng-disabled="!navbar.connected || !navbar.searchForm.searchTerm"
|
||||
>
|
||||
<i class="glyphicon glyphicon-search"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
style="display: inline-block; margin: 0px"
|
||||
class="navbar-form"
|
||||
ng-submit="navbar.search(navbar.searchForm.searchTerm)">
|
||||
<input
|
||||
type="text"
|
||||
ng-model="navbar.searchForm.searchTerm"
|
||||
ng-disabled="!navbar.connected"
|
||||
id="navbar-search"
|
||||
class="form-control navbar-search"
|
||||
placeholder=" Search" />
|
||||
</form>
|
||||
</li>
|
||||
<li style="margin-left: 10px;">
|
||||
|
|
@ -112,19 +116,19 @@ limitations under the License.
|
|||
</div>
|
||||
</div>
|
||||
</headroom>
|
||||
<div id="aboutModal" class="modal fade" role="dialog"
|
||||
tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content" id="NoteImportCtrl" ng-init="NoteImportInit">
|
||||
<div class="modal-header">
|
||||
<!-- about dialog -->
|
||||
<div id="aboutModal" class="modal fade" role="dialog" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<!-- about dialog header -->
|
||||
<div class="modal-header modal-header-about">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">About Zeppelin</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="row about">
|
||||
<!-- about dialog body -->
|
||||
<div class="modal-body modal-body-about">
|
||||
<div class="hidden-xs col-sm-4 logo">
|
||||
<img src="assets/images/zepLogo.png" alt="Apache Zeppelin" title="Apache Zeppelin" />
|
||||
</div>
|
||||
|
|
@ -132,13 +136,12 @@ limitations under the License.
|
|||
<h3>Apache Zeppelin</h3>
|
||||
<br/>
|
||||
<span id="i18n-14">Version</span>
|
||||
{{zeppelinVersion}}
|
||||
<span class="modal-body-about-version"> {{zeppelinVersion}} </span>
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="http://zeppelin.apache.org/" target="_blank"><span id="i18n-15">Get involved!</span></a>
|
||||
<br/>
|
||||
<a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank"><span id="i18n-16">Licensed under the Apache License, Version 2.0</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.modal-header-note-name {
|
||||
background-color: #3071a9;
|
||||
border: 2px solid #3071a9;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-header-note-name > .modal-title {
|
||||
font-weight: 300;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-header-note-name > .close {
|
||||
color: #cfcfcf;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-body-note-name label {
|
||||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.note-name-create-input {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.note-name-desc-panel {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.default-interpreter-select {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
|
@ -11,42 +11,56 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<div id="noteNameModal" class="modal fade" role="dialog" modalvisible previsiblecallback="notenamectrl.preVisible"
|
||||
targetinput="noteName" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div id="noteNameModal" class="modal fade" role="dialog"
|
||||
modalvisible previsiblecallback="notenamectrl.preVisible"
|
||||
targetinput="noteName" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content" id="NotenameCtrl">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title" ng-show="!notenamectrl.clone">Create new note</h4>
|
||||
<h4 class="modal-title" ng-show="notenamectrl.clone">Clone note</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="noteName">Note Name</label> <input
|
||||
placeholder="Note name" type="text" class="form-control"
|
||||
id="noteName" ng-model="note.notename" ng-enter="notenamectrl.handleNameEnter()"/><br/>
|
||||
<div ng-show="!notenamectrl.clone">
|
||||
<label for="defaultInterpreter">Default Interpreter </label>
|
||||
<select ng-model="note.defaultInterpreter"
|
||||
class="selectpicker"
|
||||
name="defaultInterpreter"
|
||||
id="defaultInterpreter"
|
||||
ng-options="option.name for option in interpreterSettings">
|
||||
</select>
|
||||
</div>
|
||||
<!-- modal content-->
|
||||
<div class="modal-content" id="NotenameCtrl">
|
||||
<!-- modal header -->
|
||||
<div class="modal-header modal-header-note-name">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title" ng-show="!notenamectrl.clone">Create New Note</h4>
|
||||
<h4 class="modal-title" ng-show="notenamectrl.clone">Clone Note</h4>
|
||||
</div>
|
||||
|
||||
<!-- modal body-->
|
||||
<div class="modal-body modal-body-note-name">
|
||||
<div class="form-group">
|
||||
<!-- note name -->
|
||||
<div>
|
||||
<label for="noteName">Note Name</label>
|
||||
<input placeholder="Insert Note Name" type="text"
|
||||
class="form-control note-name-create-input"
|
||||
id="noteName" ng-model="note.notename"
|
||||
ng-enter="notenamectrl.handleNameEnter()" />
|
||||
</div>
|
||||
<!-- default interpreter -->
|
||||
<div class="btn-group default-interpreter-select" ng-show="!notenamectrl.clone">
|
||||
<label for="defaultInterpreter">Default Interpreter</label>
|
||||
<select id="defaultInterpreter"
|
||||
name="defaultInterpreter"
|
||||
class="form-control"
|
||||
ng-model="note.defaultInterpreter"
|
||||
ng-options="option.name for option in interpreterSettings">
|
||||
</select>
|
||||
</div>
|
||||
</div> <!-- end: form-group -->
|
||||
<div class="panel panel-default note-name-desc-panel">
|
||||
<div class="panel-heading">
|
||||
Use '/' to create folders. Example: /NoteDirA/Note1
|
||||
</div>
|
||||
Use '/' to create folders. Example: /NoteDirA/Note1
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="createNoteButton"
|
||||
class="btn btn-default"
|
||||
data-dismiss="modal" ng-click="notenamectrl.createNote()">
|
||||
<span ng-show="!notenamectrl.clone">Create Note</span>
|
||||
<span ng-show="notenamectrl.clone">Clone Note</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="createNoteButton"
|
||||
class="btn btn-primary"
|
||||
data-dismiss="modal" ng-click="notenamectrl.createNote()">
|
||||
<span ng-show="!notenamectrl.clone">Create</span>
|
||||
<span ng-show="notenamectrl.clone">Clone</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './note-name-dialog.css'
|
||||
|
||||
angular.module('zeppelinWebApp').controller('NotenameCtrl', NotenameCtrl)
|
||||
|
||||
function NotenameCtrl ($scope, noteListDataFactory, $routeParams, websocketMsgSrv) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.modal-header-import {
|
||||
background-color: #3071a9;
|
||||
border: 2px solid #3071a9;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-header-import .close {
|
||||
color: #cfcfcf;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-header-import .modal-title {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.modal-body-import .note-name-input {
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.modal-body-import label {
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.modal-body-import {
|
||||
min-height: 420px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-body-import .import-btn-image-group {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.modal-body-import .import-btn-image {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.modal-body-import .import-btn-image a {
|
||||
background: #fff;
|
||||
border: 1px solid #e6e6e6;
|
||||
/*border-radius: 20px;*/
|
||||
border-radius: 20%;
|
||||
color: #7c828e;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 98px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
height: 240px;
|
||||
padding-top: 60px;
|
||||
margin: 0 10px 0px 10px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.modal-body-import .import-btn-image a:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.modal-body-import .modal-body-import-desc {
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin-top: 30px;
|
||||
color: black;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.modal-footer-import {
|
||||
min-height: 65px;
|
||||
}
|
||||
|
|
@ -11,71 +11,70 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<div id="noteImportModal" class="modal fade" role="dialog" tabindex="-1"
|
||||
data-backdrop="static" data-keyboard="false">
|
||||
|
||||
<div id="noteImportModal" class="modal fade" role="dialog"
|
||||
tabindex="-1" data-backdrop="static" data-keyboard="false">
|
||||
<div class="modal-dialog" >
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content" id="NoteImportCtrl" ng-init="NoteImportInit">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content" id="NoteImportCtrl" ng-init="NoteImportInit">
|
||||
<div class="modal-header">
|
||||
<!-- close button -->
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="noteimportctrl.resetFlags()">×</button>
|
||||
<!-- modal-header -->
|
||||
<div class="modal-header modal-header-import">
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
ng-click="noteimportctrl.resetFlags()">×</button>
|
||||
<h4 class="modal-title">Import New Note</h4>
|
||||
</div>
|
||||
|
||||
<h4 class="modal-title">Import new note</h4>
|
||||
<!-- modal-body -->
|
||||
<div class="modal-body modal-body-import">
|
||||
<div class="form-group">
|
||||
<label for="noteImportName">Import As</label>
|
||||
<input class="form-control note-name-input" id="noteImportName"
|
||||
placeholder="Insert Note Name" type="text"
|
||||
ng-model="note.noteImportName" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="noteImportName">Import AS</label>
|
||||
<input placeholder="Note name" type="text" class="form-control" id="noteImportName"
|
||||
ng-model="note.noteImportName" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fileSizeLimit">JSON file size cannot exceed {{maxLimit}} MB</label>
|
||||
</div>
|
||||
<div class="form-group" ng-show="note.errorText">
|
||||
<div class="alert alert-danger">{{note.errorText}}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="note.errorText">
|
||||
<div class="alert alert-danger">{{note.errorText}}</div>
|
||||
</div>
|
||||
<label>JSON file size cannot exceed {{maxLimit}} MB</label>
|
||||
|
||||
<div class="form-group slide-left" ng-show="note.step1">
|
||||
<div class="display-inline">
|
||||
<a class="fa fa-cloud-upload import-file-upload" ng-click="uploadFile()">
|
||||
<p>Choose a JSON here</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group slide-left import-btn-image-group" ng-show="note.step1">
|
||||
<div class="import-btn-image">
|
||||
<a class="fa fa-cloud-upload import-file-upload" ng-click="uploadFile()">
|
||||
<p class="modal-body-import-desc">Select JSON File </p>
|
||||
</a>
|
||||
<div style="display: none">
|
||||
<input placeholder="Note name" type="file" class="form-control" id="noteImportFile"
|
||||
<input class="form-control note-name-input" id="noteImportFile"
|
||||
placeholder="Note name" type="file"
|
||||
ng-model="note.importFile" onchange="angular.element(this).scope().importFile(this)" />
|
||||
</div>
|
||||
<div class="display-inline">
|
||||
<a href="javascript:void(0);" ng-click="uploadURL()" class="fa fa-link">
|
||||
<p>Add from URL</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group slide-right" ng-show="note.step2">
|
||||
|
||||
<label for="noteImportUrl">URL</label>
|
||||
<input placeholder="Note url" type="text" class="form-control" id="noteImportUrl"
|
||||
ng-model="note.importUrl" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div ng-show="note.step2">
|
||||
<button type="button" id="importBackButton"
|
||||
class="btn btn-default"
|
||||
ng-click="noteimportctrl.importBack()">Back
|
||||
</button>
|
||||
<button type="button" id="importNoteButton"
|
||||
class="btn btn-default"
|
||||
ng-click="noteimportctrl.importNote()">Import Note
|
||||
</button>
|
||||
<div class="import-btn-image">
|
||||
<a href="javascript:void(0);" ng-click="uploadURL()" class="fa fa-link">
|
||||
<p class="modal-body-import-desc">Add from URL</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group slide-right" ng-show="note.step2">
|
||||
|
||||
<label for="noteImportUrl">URL</label>
|
||||
<input placeholder="Note URL" type="text" class="form-control" id="noteImportUrl"
|
||||
ng-model="note.importUrl" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer modal-footer-import" ng-show="note.step2">
|
||||
<button type="button" id="importBackButton"
|
||||
class="btn btn-default"
|
||||
ng-click="noteimportctrl.importBack()">Back
|
||||
</button>
|
||||
<button type="button" id="importNoteButton"
|
||||
class="btn btn-default"
|
||||
ng-click="noteimportctrl.importNote()">Import Note
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue