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
This commit is contained in:
commit
b17dfb592d
18 changed files with 461 additions and 107 deletions
|
|
@ -53,6 +53,11 @@
|
|||
|
||||
#### Spark interpreter configuration ####
|
||||
|
||||
## Kerberos ticket refresh setting
|
||||
##
|
||||
#export KINIT_FAIL_THRESHOLD # (optional) How many times should kinit retry. The default value is 5.
|
||||
#export LAUNCH_KERBEROS_REFRESH_INTERVAL # (optional) The refresh interval for Kerberos ticket. The default value is 1d.
|
||||
|
||||
## Use provided spark installation ##
|
||||
## defining SPARK_HOME makes Zeppelin run spark interpreter process using spark-submit
|
||||
##
|
||||
|
|
|
|||
|
|
@ -66,4 +66,14 @@ The following example demonstrates the basic usage of Shell in a Zeppelin notebo
|
|||
<img src="/assets/themes/zeppelin/img/docs-img/shell-example.png" />
|
||||
|
||||
If you need further information about **Zeppelin Interpreter Setting** for using Shell interpreter,
|
||||
please read [What is interpreter setting?](../usage/interpreter/overview.html#what-is-interpreter-setting) section first.
|
||||
please read [What is interpreter setting?](../usage/interpreter/overview.html#what-is-interpreter-setting) section first.
|
||||
|
||||
## Kerberos refresh interval
|
||||
For changing the default behavior of when to renew Kerberos ticket following changes can be made in `conf/zeppelin-env.sh`.
|
||||
|
||||
```bash
|
||||
# Change Kerberos refresh interval (default value is 1d). Allowed postfix are ms, s, m, min, h, and d.
|
||||
export LAUNCH_KERBEROS_REFRESH_INTERVAL=4h
|
||||
# Change kinit number retries (default value is 5), which means if the kinit command fails for 5 retries consecutively it will close the interpreter.
|
||||
export KINIT_FAIL_THRESHOLD=10
|
||||
```
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ import org.apache.commons.exec.ExecuteException;
|
|||
import org.apache.commons.exec.ExecuteWatchdog;
|
||||
import org.apache.commons.exec.PumpStreamHandler;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.zeppelin.interpreter.Interpreter;
|
||||
import org.apache.zeppelin.interpreter.InterpreterContext;
|
||||
import org.apache.zeppelin.interpreter.KerberosInterpreter;
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult;
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
|
||||
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
|
||||
|
|
@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory;
|
|||
/**
|
||||
* Shell interpreter for Zeppelin.
|
||||
*/
|
||||
public class ShellInterpreter extends Interpreter {
|
||||
public class ShellInterpreter extends KerberosInterpreter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ShellInterpreter.class);
|
||||
private static final String TIMEOUT_PROPERTY = "shell.command.timeout.millisecs";
|
||||
private final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
|
||||
|
|
@ -60,12 +60,25 @@ public class ShellInterpreter extends Interpreter {
|
|||
LOGGER.info("Command timeout property: {}", getProperty(TIMEOUT_PROPERTY));
|
||||
executors = new ConcurrentHashMap<>();
|
||||
if (!StringUtils.isAnyEmpty(getProperty("zeppelin.shell.auth.type"))) {
|
||||
ShellSecurityImpl.createSecureConfiguration(getProperty(), shell);
|
||||
startKerberosLoginThread();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
public void close() {
|
||||
shutdownExecutorService();
|
||||
|
||||
for (String executorKey : executors.keySet()) {
|
||||
DefaultExecutor executor = executors.remove(executorKey);
|
||||
if (executor != null) {
|
||||
try {
|
||||
executor.getWatchdog().destroyProcess();
|
||||
} catch (Exception e){
|
||||
LOGGER.error("error destroying executor for paragraphId: " + executorKey, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -100,7 +113,7 @@ public class ShellInterpreter extends Interpreter {
|
|||
if (exitValue == 143) {
|
||||
code = Code.INCOMPLETE;
|
||||
message += "Paragraph received a SIGTERM\n";
|
||||
LOGGER.info("The paragraph " + contextInterpreter.getParagraphId()
|
||||
LOGGER.info("The paragraph " + contextInterpreter.getParagraphId()
|
||||
+ " stopped executing: " + message);
|
||||
}
|
||||
message += "ExitValue: " + exitValue;
|
||||
|
|
@ -117,7 +130,11 @@ public class ShellInterpreter extends Interpreter {
|
|||
public void cancel(InterpreterContext context) {
|
||||
DefaultExecutor executor = executors.remove(context.getParagraphId());
|
||||
if (executor != null) {
|
||||
executor.getWatchdog().destroyProcess();
|
||||
try {
|
||||
executor.getWatchdog().destroyProcess();
|
||||
} catch (Exception e){
|
||||
LOGGER.error("error destroying executor for paragraphId: " + context.getParagraphId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,4 +160,15 @@ public class ShellInterpreter extends Interpreter {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean runKerberosLogin() {
|
||||
try {
|
||||
ShellSecurityImpl.createSecureConfiguration(getProperty(), shell);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to run kinit for zeppelin", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@
|
|||
*/
|
||||
|
||||
package org.apache.zeppelin.interpreter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Interpreter related constants
|
||||
*
|
||||
|
|
@ -32,4 +37,17 @@ public class Constants {
|
|||
|
||||
public static final int ZEPPELIN_INTERPRETER_OUTPUT_LIMIT = 1024 * 100;
|
||||
|
||||
public static final Map<String, TimeUnit> TIME_SUFFIXES;
|
||||
|
||||
static {
|
||||
TIME_SUFFIXES = new HashMap<>();
|
||||
TIME_SUFFIXES.put("us", TimeUnit.MICROSECONDS);
|
||||
TIME_SUFFIXES.put("ms", TimeUnit.MILLISECONDS);
|
||||
TIME_SUFFIXES.put("s", TimeUnit.SECONDS);
|
||||
TIME_SUFFIXES.put("m", TimeUnit.MINUTES);
|
||||
TIME_SUFFIXES.put("min", TimeUnit.MINUTES);
|
||||
TIME_SUFFIXES.put("h", TimeUnit.HOURS);
|
||||
TIME_SUFFIXES.put("d", TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.interpreter;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.zeppelin.annotation.ZeppelinApi;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Interpreter wrapper for Kerberos initialization
|
||||
*
|
||||
* runKerberosLogin() method you need to implement that determine Zeppelin's behavior.
|
||||
* startKerberosLoginThread() needs to be called inside the open() and
|
||||
* shutdownExecutorService() inside close().
|
||||
*/
|
||||
public abstract class KerberosInterpreter extends Interpreter {
|
||||
|
||||
private Integer kinitFailCount = 0;
|
||||
protected ScheduledExecutorService scheduledExecutorService;
|
||||
public static Logger logger = LoggerFactory.getLogger(KerberosInterpreter.class);
|
||||
|
||||
public KerberosInterpreter(Properties property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
@ZeppelinApi
|
||||
protected abstract boolean runKerberosLogin();
|
||||
|
||||
public String getKerberosRefreshInterval() {
|
||||
if (System.getenv("KERBEROS_REFRESH_INTERVAL") == null) {
|
||||
return "1d";
|
||||
} else {
|
||||
return System.getenv("KERBEROS_REFRESH_INTERVAL");
|
||||
}
|
||||
}
|
||||
|
||||
public Integer kinitFailThreshold() {
|
||||
if (System.getenv("KINIT_FAIL_THRESHOLD") == null) {
|
||||
return 5;
|
||||
} else {
|
||||
return new Integer(System.getenv("KINIT_FAIL_THRESHOLD"));
|
||||
}
|
||||
}
|
||||
|
||||
public Long getTimeAsMs(String time) {
|
||||
if (time == null) {
|
||||
logger.error("Cannot convert to time value.", time);
|
||||
time = "1d";
|
||||
}
|
||||
|
||||
Matcher m = Pattern.compile("(-?[0-9]+)([a-z]+)?").matcher(time.toLowerCase());
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Invalid time string: " + time);
|
||||
}
|
||||
|
||||
long val = Long.parseLong(m.group(1));
|
||||
String suffix = m.group(2);
|
||||
|
||||
if (suffix != null && !Constants.TIME_SUFFIXES.containsKey(suffix)) {
|
||||
throw new IllegalArgumentException("Invalid suffix: \"" + suffix + "\"");
|
||||
}
|
||||
|
||||
return TimeUnit.MILLISECONDS.convert(val,
|
||||
suffix != null ? Constants.TIME_SUFFIXES.get(suffix) : TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
protected ScheduledExecutorService startKerberosLoginThread() {
|
||||
scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
||||
|
||||
scheduledExecutorService.schedule(new Callable() {
|
||||
public Object call() throws Exception {
|
||||
|
||||
if (runKerberosLogin()) {
|
||||
logger.info("Ran runKerberosLogin command successfully.");
|
||||
kinitFailCount = 0;
|
||||
// schedule another kinit run with a fixed delay.
|
||||
scheduledExecutorService
|
||||
.schedule(this, getTimeAsMs(getKerberosRefreshInterval()), TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
kinitFailCount++;
|
||||
logger.info("runKerberosLogin failed for " + kinitFailCount + " time(s).");
|
||||
// schedule another retry at once or close the interpreter if too many times kinit fails
|
||||
if (kinitFailCount >= kinitFailThreshold()) {
|
||||
logger.error("runKerberosLogin failed for max attempts, calling close interpreter.");
|
||||
close();
|
||||
} else {
|
||||
scheduledExecutorService.submit(this);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, getTimeAsMs(getKerberosRefreshInterval()), TimeUnit.MILLISECONDS);
|
||||
|
||||
return scheduledExecutorService;
|
||||
}
|
||||
|
||||
protected void shutdownExecutorService() {
|
||||
if (scheduledExecutorService != null) {
|
||||
scheduledExecutorService.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -210,13 +210,6 @@ public class HeliumRestApi {
|
|||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("order/visualization")
|
||||
public Response getVisualizationPackageOrder() {
|
||||
List<String> order = helium.setVisualizationPackageOrder();
|
||||
return new JsonResponse(Response.Status.OK, order).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("spell/config/{packageName}")
|
||||
public Response getSpellConfigUsingMagic(@PathParam("packageName") String packageName) {
|
||||
|
|
@ -309,9 +302,16 @@ public class HeliumRestApi {
|
|||
return new JsonResponse(Response.Status.OK, packageConfig).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("order/visualization")
|
||||
public Response getVisualizationPackageOrder() {
|
||||
List<String> order = helium.getVisualizationPackageOrder();
|
||||
return new JsonResponse(Response.Status.OK, order).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("order/visualization")
|
||||
public Response getVisualizationPackageOrder(String orderedPackageNameList) {
|
||||
public Response setVisualizationPackageOrder(String orderedPackageNameList) {
|
||||
List<String> orderedList = gson.fromJson(
|
||||
orderedPackageNameList, new TypeToken<List<String>>(){}.getType());
|
||||
|
||||
|
|
|
|||
|
|
@ -208,9 +208,6 @@ public class NotebookRestApi {
|
|||
HashSet<String> writers = permMap.get("writers");
|
||||
// Set readers, if writers and owners is empty -> set to user requesting the change
|
||||
if (readers != null && !readers.isEmpty()) {
|
||||
if (writers.isEmpty()) {
|
||||
writers = Sets.newHashSet(SecurityUtils.getPrincipal());
|
||||
}
|
||||
if (owners.isEmpty()) {
|
||||
owners = Sets.newHashSet(SecurityUtils.getPrincipal());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1587,8 +1587,15 @@ public class NotebookServer extends WebSocketServlet
|
|||
userAndRoles, fromMessage.principal, "write")) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> config;
|
||||
if (fromMessage.get("config") != null) {
|
||||
config = (Map<String, Object>) fromMessage.get("config");
|
||||
} else {
|
||||
config = new HashMap<>();
|
||||
}
|
||||
|
||||
Paragraph newPara = note.insertNewParagraph(index, subject);
|
||||
newPara.setConfig(config);
|
||||
note.persist(subject);
|
||||
broadcastNewParagraph(note, newPara);
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ abstract public class AbstractZeppelinIT {
|
|||
protected final static Logger LOG = LoggerFactory.getLogger(AbstractZeppelinIT.class);
|
||||
protected static final long MAX_IMPLICIT_WAIT = 30;
|
||||
protected static final long MAX_BROWSER_TIMEOUT_SEC = 30;
|
||||
protected static final long MAX_PARAGRAPH_TIMEOUT_SEC = 60;
|
||||
protected static final long MAX_PARAGRAPH_TIMEOUT_SEC = 120;
|
||||
|
||||
protected void setTextOfParagraph(int paragraphNo, String text) {
|
||||
String editorId = driver.findElement(By.xpath(getParagraphXPath(paragraphNo) + "//div[contains(@class, 'editor')]")).getAttribute("id");
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
clickAndWait(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']"));
|
||||
String visibleText = newWidth.toString();
|
||||
new Select(driver.findElement(By.xpath(getParagraphXPath(1)
|
||||
+ "//ul/li/a/form/select[(@ng-model='paragraph.config.colWidth')]"))).selectByVisibleText(visibleText);
|
||||
+ "//ul/li/a/select[(@ng-model='paragraph.config.colWidth')]"))).selectByVisibleText(visibleText);
|
||||
collector.checkThat("New Width is : " + newWidth,
|
||||
driver.findElement(By.xpath("//div[contains(@class,'col-md-" + newWidth + "')]")).isDisplayed(),
|
||||
CoreMatchers.equalTo(true));
|
||||
|
|
@ -368,7 +368,34 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
} catch (Exception e) {
|
||||
handleException("Exception in ParagraphActionsIT while testWidth ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFontSize() throws Exception {
|
||||
if (!endToEndTestEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
createNewNote();
|
||||
waitForParagraph(1, "READY");
|
||||
Float height = Float.valueOf(driver.findElement(By.xpath("//div[contains(@class,'ace_content')]"))
|
||||
.getCssValue("height").replace("px", ""));
|
||||
for (Integer newFontSize = 10; newFontSize <= 20; newFontSize++) {
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']"));
|
||||
String visibleText = newFontSize.toString();
|
||||
new Select(driver.findElement(By.xpath(getParagraphXPath(1)
|
||||
+ "//ul/li/a/select[(@ng-model='paragraph.config.fontSize')]"))).selectByVisibleText(visibleText);
|
||||
Float newHeight = Float.valueOf(driver.findElement(By.xpath("//div[contains(@class,'ace_content')]"))
|
||||
.getCssValue("height").replace("px", ""));
|
||||
collector.checkThat("New Font size is : " + newFontSize,
|
||||
newHeight > height,
|
||||
CoreMatchers.equalTo(true));
|
||||
height = newHeight;
|
||||
}
|
||||
deleteTestNotebook(driver);
|
||||
} catch (Exception e) {
|
||||
handleException("Exception in ParagraphActionsIT while testFontSize ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -396,7 +423,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
clickAndWait(By.xpath(xpathToSettingIcon));
|
||||
collector.checkThat("Before Show Title : The title option in option panel of paragraph is labeled as",
|
||||
driver.findElement(By.xpath(xpathToShowTitle)).getText(),
|
||||
CoreMatchers.allOf(CoreMatchers.startsWith("Show title"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.allOf(CoreMatchers.endsWith("Show title"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.anyOf(CoreMatchers.containsString("Option"), CoreMatchers.containsString("Alt")),
|
||||
CoreMatchers.containsString("+T")));
|
||||
|
||||
|
|
@ -408,7 +435,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
clickAndWait(By.xpath(xpathToSettingIcon));
|
||||
collector.checkThat("After Show Title : The title option in option panel of paragraph is labeled as",
|
||||
driver.findElement(By.xpath(xpathToHideTitle)).getText(),
|
||||
CoreMatchers.allOf(CoreMatchers.startsWith("Hide title"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.allOf(CoreMatchers.endsWith("Hide title"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.anyOf(CoreMatchers.containsString("Option"), CoreMatchers.containsString("Alt")),
|
||||
CoreMatchers.containsString("+T")));
|
||||
|
||||
|
|
@ -462,7 +489,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
collector.checkThat("Before \"Show line number\" The option panel in paragraph has button labeled ",
|
||||
driver.findElement(By.xpath(xpathToShowLineNumberButton)).getText(),
|
||||
CoreMatchers.allOf(CoreMatchers.startsWith("Show line numbers"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.allOf(CoreMatchers.endsWith("Show line numbers"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.anyOf(CoreMatchers.containsString("Option"), CoreMatchers.containsString("Alt")),
|
||||
CoreMatchers.containsString("+M")));
|
||||
|
||||
|
|
@ -475,7 +502,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
clickAndWait(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']"));
|
||||
collector.checkThat("After \"Show line number\" The option panel in paragraph has button labeled ",
|
||||
driver.findElement(By.xpath(xpathToHideLineNumberButton)).getText(),
|
||||
CoreMatchers.allOf(CoreMatchers.startsWith("Hide line numbers"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.allOf(CoreMatchers.endsWith("Hide line numbers"), CoreMatchers.containsString("Ctrl+"),
|
||||
CoreMatchers.anyOf(CoreMatchers.containsString("Option"), CoreMatchers.containsString("Alt")),
|
||||
CoreMatchers.containsString("+M")));
|
||||
|
||||
|
|
|
|||
|
|
@ -50,16 +50,18 @@ export default function HeliumCtrl ($scope, $rootScope, $sce,
|
|||
})
|
||||
.then(defaultPackageConfigs => {
|
||||
$scope.defaultPackageConfigs = defaultPackageConfigs
|
||||
return heliumService.getVisualizationPackageOrder()
|
||||
})
|
||||
|
||||
// 2. get vis package order
|
||||
heliumService.getVisualizationPackageOrder()
|
||||
.then(visPackageOrder => {
|
||||
$scope.bundleOrder = visPackageOrder
|
||||
$scope.bundleOrderChanged = false
|
||||
setVisPackageOrder(visPackageOrder)
|
||||
})
|
||||
}
|
||||
|
||||
const setVisPackageOrder = function(visPackageOrder) {
|
||||
$scope.bundleOrder = visPackageOrder
|
||||
$scope.bundleOrderChanged = false
|
||||
}
|
||||
|
||||
let orderPackageByPubDate = function (a, b) {
|
||||
if (!a.pkg.published) {
|
||||
// Because local registry pkgs don't have 'published' field, put current time instead to show them first
|
||||
|
|
@ -133,18 +135,18 @@ export default function HeliumCtrl ($scope, $rootScope, $sce,
|
|||
confirm.$modalFooter.find('button:contains("OK")')
|
||||
.html('<i class="fa fa-circle-o-notch fa-spin"></i> Enabling')
|
||||
heliumService.setVisualizationPackageOrder($scope.bundleOrder)
|
||||
.success(function (data, status) {
|
||||
init()
|
||||
confirm.close()
|
||||
})
|
||||
.error(function (data, status) {
|
||||
confirm.close()
|
||||
console.log('Failed to save order')
|
||||
BootstrapDialog.show({
|
||||
title: 'Error on saving order ',
|
||||
message: data.message
|
||||
.success(function (data, status) {
|
||||
setVisPackageOrder($scope.bundleOrder)
|
||||
confirm.close()
|
||||
})
|
||||
.error(function (data, status) {
|
||||
confirm.close()
|
||||
console.log('Failed to save order')
|
||||
BootstrapDialog.show({
|
||||
title: 'Error on saving order ',
|
||||
message: data.message
|
||||
})
|
||||
})
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -761,7 +761,40 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
}
|
||||
|
||||
$scope.savePermissions = function () {
|
||||
if ($scope.isAnonymous || $rootScope.ticket.principal.trim().length === 0) {
|
||||
$scope.blockAnonUsers()
|
||||
}
|
||||
convertPermissionsToArray()
|
||||
if ($scope.isOwnerEmpty()) {
|
||||
BootstrapDialog.show({
|
||||
closable: false,
|
||||
title: 'Setting Owners Permissions',
|
||||
message: 'Please fill the [Owners] field. If not, it will set as current user.\n\n' +
|
||||
'Current user : [ ' + $rootScope.ticket.principal + ']',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Set',
|
||||
action: function(dialog) {
|
||||
dialog.close()
|
||||
$scope.permissions.owners = [$rootScope.ticket.principal]
|
||||
$scope.setPermissions()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
action: function(dialog) {
|
||||
dialog.close()
|
||||
$scope.openPermissions()
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
} else {
|
||||
$scope.setPermissions()
|
||||
}
|
||||
}
|
||||
|
||||
$scope.setPermissions = function() {
|
||||
$http.put(baseUrlSrv.getRestApiBase() + '/notebook/' + $scope.note.id + '/permissions',
|
||||
$scope.permissions, {withCredentials: true})
|
||||
.success(function (data, status, headers, config) {
|
||||
|
|
@ -769,7 +802,7 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
console.log('Note permissions %o saved', $scope.permissions)
|
||||
BootstrapDialog.alert({
|
||||
closable: true,
|
||||
title: 'Permissions Saved Successfully!!!',
|
||||
title: 'Permissions Saved Successfully',
|
||||
message: 'Owners : ' + $scope.permissions.owners + '\n\n' + 'Readers : ' +
|
||||
$scope.permissions.readers + '\n\n' + 'Writers : ' + $scope.permissions.writers
|
||||
})
|
||||
|
|
@ -877,6 +910,20 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
angular.element('.ace_autocomplete').hide()
|
||||
})
|
||||
|
||||
$scope.isOwnerEmpty = function() {
|
||||
if ($scope.permissions.owners.length > 0) {
|
||||
for (let i = 0; i < $scope.permissions.owners.length; i++) {
|
||||
if ($scope.permissions.owners[i].trim().length > 0) {
|
||||
return false
|
||||
} else if (i === $scope.permissions.owners.length - 1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** $scope.$on functions below
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -103,65 +103,98 @@ limitations under the License.
|
|||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="$event.stopPropagation()" class="dropdown"><span class="fa fa-arrows-h shortcut-icon"></span>Width
|
||||
<form style="display:inline; margin-left:5px; float:right">
|
||||
<select ng-model="paragraph.config.colWidth"
|
||||
class="selectpicker"
|
||||
ng-change="changeColWidth(paragraph, paragraph.config.colWidth)"
|
||||
ng-options="col for col in colWidthOption"></select>
|
||||
</form>
|
||||
<a ng-click="$event.stopPropagation()" class="dropdown">
|
||||
<span style="margin-top: 2px;" class="fa fa-arrows-h shortcut-icon"></span>
|
||||
<select style="margin-left:5px;float: right" ng-model="paragraph.config.colWidth"
|
||||
class="selectpicker"
|
||||
ng-change="changeColWidth(paragraph, paragraph.config.colWidth)"
|
||||
ng-options="col for col in colWidthOption"></select>
|
||||
Width
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="moveUp(paragraph)" ng-hide="$first"><span class="icon-arrow-up shortcut-icon"></span>Move up
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+K</span></a>
|
||||
<a ng-click="$event.stopPropagation()" class="dropdown">
|
||||
<span class="fa fa-text-height shortcut-icon"></span>
|
||||
<select style="margin-left:5px;float: right" ng-model="paragraph.config.fontSize"
|
||||
class="selectpicker"
|
||||
ng-change="changeFontSize(paragraph, paragraph.config.fontSize)"
|
||||
ng-options="s for s in fontSizeOption"></select>
|
||||
Font size
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="moveDown(paragraph)" ng-hide="$last"><span class="icon-arrow-down shortcut-icon"></span>Move down
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+J</span></a>
|
||||
<a ng-click="moveUp(paragraph)" ng-hide="$first"><span class="icon-arrow-up shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+K</span>
|
||||
Move up
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="insertNew('below')"><span class="icon-plus shortcut-icon"></span>Insert new
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+B</span></a>
|
||||
<a ng-click="moveDown(paragraph)" ng-hide="$last"><span class="icon-arrow-down shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+J</span>
|
||||
Move down
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="copyParagraph(getEditorValue())"><span class="fa fa-copy shortcut-icon"></span>Clone paragraph
|
||||
<span class="shortcut-keys">Ctrl+Shift+C</span></a>
|
||||
<a ng-click="insertNew('below')"><span class="icon-plus shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+B</span>
|
||||
Insert new
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="copyParagraph(getEditorValue())"><span class="fa fa-copy shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+Shift+C</span>
|
||||
Clone paragraph
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<!-- paragraph handler -->
|
||||
<a ng-click="hideTitle(paragraph)"
|
||||
ng-show="paragraph.config.title"><span class="fa fa-font shortcut-icon"></span>Hide title
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+T</span></a>
|
||||
ng-show="paragraph.config.title"><span class="fa fa-font shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+T</span>
|
||||
Hide title
|
||||
</a>
|
||||
<a ng-click="showTitle(paragraph)"
|
||||
ng-show="!paragraph.config.title"><span class="fa fa-font shortcut-icon"></span>Show title
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+T</span></a>
|
||||
ng-show="!paragraph.config.title"><span class="fa fa-font shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+T</span>
|
||||
Show title
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="hideLineNumbers(paragraph)"
|
||||
ng-show="paragraph.config.lineNumbers"><span class="fa fa-list-ol shortcut-icon"></span>Hide line numbers
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+M</span></a>
|
||||
ng-show="paragraph.config.lineNumbers"><span class="fa fa-list-ol shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+M</span>
|
||||
Hide line numbers
|
||||
</a>
|
||||
<a ng-click="showLineNumbers(paragraph)"
|
||||
ng-show="!paragraph.config.lineNumbers"><span class="fa fa-list-ol shortcut-icon"></span>Show line numbers
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+M</span></a>
|
||||
ng-show="!paragraph.config.lineNumbers"><span class="fa fa-list-ol shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+M</span>
|
||||
Show line numbers
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="toggleEnableDisable(paragraph)"><span class="icon-control-play shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+ {{ isMac ? 'Option' : 'Alt'}}+R</span>
|
||||
{{paragraph.config.enabled ? "Disable" : "Enable"}} run
|
||||
<span class="shortcut-keys">Ctrl+ {{ isMac ? 'Option' : 'Alt'}}+R</span></a>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="goToSingleParagraph()"><span class="icon-share-alt shortcut-icon"></span>Link this paragraph
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+W</span></a>
|
||||
<a ng-click="goToSingleParagraph()"><span class="icon-share-alt shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+W</span>
|
||||
Link this paragraph
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="clearParagraphOutput(paragraph)"><span class="fa fa-eraser shortcut-icon"></span>Clear output
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+L</span></a>
|
||||
<a ng-click="clearParagraphOutput(paragraph)"><span class="fa fa-eraser shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+L</span>
|
||||
Clear output
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<!-- remove paragraph -->
|
||||
<a ng-click="removeParagraph(paragraph)" ng-hide="$last"><span class="fa fa-times shortcut-icon"></span>Remove
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+D</span></a>
|
||||
<a ng-click="removeParagraph(paragraph)" ng-hide="$last"><span class="fa fa-times shortcut-icon"></span>
|
||||
<span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+D</span>
|
||||
Remove
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
$scope.chart = {}
|
||||
$scope.baseMapOption = ['Streets', 'Satellite', 'Hybrid', 'Topo', 'Gray', 'Oceans', 'Terrain']
|
||||
$scope.colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
$scope.fontSizeOption = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||
$scope.paragraphFocused = false
|
||||
if (newParagraph.focus) {
|
||||
$scope.paragraphFocused = true
|
||||
|
|
@ -149,6 +150,10 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
config.colWidth = 12
|
||||
}
|
||||
|
||||
if (!config.fontSize) {
|
||||
config.fontSize = 9
|
||||
}
|
||||
|
||||
if (config.enabled === undefined) {
|
||||
config.enabled = true
|
||||
}
|
||||
|
|
@ -603,6 +608,18 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
commitParagraph(paragraph)
|
||||
}
|
||||
|
||||
$scope.changeFontSize = function (paragraph, fontSize) {
|
||||
angular.element('.navbar-right.open').removeClass('open')
|
||||
if ($scope.editor) {
|
||||
$scope.editor.setOptions({
|
||||
fontSize: fontSize + 'pt'
|
||||
})
|
||||
autoAdjustEditorHeight($scope.editor)
|
||||
paragraph.config.fontSize = fontSize
|
||||
commitParagraph(paragraph)
|
||||
}
|
||||
}
|
||||
|
||||
$scope.toggleOutput = function (paragraph) {
|
||||
paragraph.config.tableHide = !paragraph.config.tableHide
|
||||
commitParagraph(paragraph)
|
||||
|
|
@ -743,6 +760,7 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
langTools.textCompleter])
|
||||
|
||||
$scope.editor.setOptions({
|
||||
fontSize: $scope.paragraph.config.fontSize + 'pt',
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: false,
|
||||
enableLiveAutocompletion: false
|
||||
|
|
@ -1246,8 +1264,12 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
|
||||
$scope.updateParagraphObjectWhenUpdated = function (newPara) {
|
||||
// resize col width
|
||||
if ($scope.paragraph.config.colWidth !== newPara.colWidth) {
|
||||
$rootScope.$broadcast('paragraphResized', $scope.paragraph.id)
|
||||
if ($scope.paragraph.config.colWidth !== newPara.config.colWidth) {
|
||||
$scope.$broadcast('paragraphResized', $scope.paragraph.id)
|
||||
}
|
||||
|
||||
if ($scope.paragraph.config.fontSize !== newPara.config.fontSize) {
|
||||
$rootScope.$broadcast('fontSizeChanged', newPara.config.fontSize)
|
||||
}
|
||||
|
||||
/** push the rest */
|
||||
|
|
@ -1262,6 +1284,7 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
$scope.paragraph.title = newPara.title
|
||||
$scope.paragraph.lineNumbers = newPara.lineNumbers
|
||||
$scope.paragraph.status = newPara.status
|
||||
$scope.paragraph.fontSize = newPara.fontSize
|
||||
if (newPara.status !== ParagraphStatus.RUNNING) {
|
||||
$scope.paragraph.results = newPara.results
|
||||
}
|
||||
|
|
@ -1514,4 +1537,12 @@ function ParagraphCtrl ($scope, $rootScope, $route, $window, $routeParams, $loca
|
|||
|
||||
$scope.cleanupSpellTransaction()
|
||||
})
|
||||
|
||||
$scope.$on('fontSizeChanged', function (event, fontSize) {
|
||||
if ($scope.editor) {
|
||||
$scope.editor.setOptions({
|
||||
fontSize: fontSize + 'pt'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,18 +170,24 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
}
|
||||
|
||||
$scope.init = function (result, config, paragraph, index) {
|
||||
// register helium plugin vis
|
||||
let visBundles = heliumService.getVisualizationBundles()
|
||||
visBundles.forEach(function (vis) {
|
||||
$scope.builtInTableDataVisualizationList.push({
|
||||
id: vis.id,
|
||||
name: vis.name,
|
||||
icon: $sce.trustAsHtml(vis.icon),
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
// register helium plugin vis packages
|
||||
let visPackages = heliumService.getVisualizationCachedPackages()
|
||||
const visPackageOrder = heliumService.getVisualizationCachedPackageOrder()
|
||||
|
||||
// push the helium vis packages following the order
|
||||
visPackageOrder.map(visName => {
|
||||
visPackages.map(vis => {
|
||||
if (vis.name !== visName) { return }
|
||||
$scope.builtInTableDataVisualizationList.push({
|
||||
id: vis.id,
|
||||
name: vis.name,
|
||||
icon: $sce.trustAsHtml(vis.icon),
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
})
|
||||
builtInVisualizations[vis.id] = {
|
||||
class: vis.class
|
||||
}
|
||||
})
|
||||
builtInVisualizations[vis.id] = {
|
||||
class: vis.class
|
||||
}
|
||||
})
|
||||
|
||||
updateData(result, config, paragraph, index)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export default function heliumService ($http, $sce, baseUrlSrv) {
|
|||
'ngInject'
|
||||
|
||||
let visualizationBundles = []
|
||||
let visualizationPackageOrder = []
|
||||
// name `heliumBundles` should be same as `HeliumBundleFactory.HELIUM_BUNDLES_VAR`
|
||||
let heliumBundles = []
|
||||
// map for `{ magic: interpreter }`
|
||||
|
|
@ -70,17 +71,23 @@ export default function heliumService ($http, $sce, baseUrlSrv) {
|
|||
})
|
||||
}
|
||||
|
||||
this.getVisualizationBundles = function () {
|
||||
this.getVisualizationCachedPackages = function () {
|
||||
return visualizationBundles
|
||||
}
|
||||
|
||||
this.getVisualizationCachedPackageOrder = function () {
|
||||
return visualizationPackageOrder
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise} which returns bundleOrder
|
||||
* @returns {Promise} which returns bundleOrder and cache it in `visualizationPackageOrder`
|
||||
*/
|
||||
this.getVisualizationPackageOrder = function () {
|
||||
return $http.get(baseUrlSrv.getRestApiBase() + '/helium/order/visualization')
|
||||
.then(function (response, status) {
|
||||
return response.data.body
|
||||
const order = response.data.body
|
||||
visualizationPackageOrder = order
|
||||
return order
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error('Can not get bundle order', error)
|
||||
|
|
@ -284,4 +291,11 @@ export default function heliumService ($http, $sce, baseUrlSrv) {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.init = function() {
|
||||
this.getVisualizationPackageOrder()
|
||||
}
|
||||
|
||||
// init
|
||||
this.init()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -309,13 +309,21 @@ public class Helium {
|
|||
return;
|
||||
}
|
||||
|
||||
// if package is visualization, rebuild bundle
|
||||
// if package is bundle, rebuild bundle
|
||||
if (HeliumPackage.isBundleType(pkgInfo.getPkg().getType())) {
|
||||
bundleFactory.buildPackage(pkgInfo.getPkg(), true, true);
|
||||
}
|
||||
|
||||
// update conf and save
|
||||
// set `enable` field
|
||||
heliumConf.enablePackage(name, artifact);
|
||||
// set display order
|
||||
if (pkgInfo.getPkg().getType() == HeliumType.VISUALIZATION) {
|
||||
List<String> currentDisplayOrder = heliumConf.getBundleDisplayOrder();
|
||||
if (!currentDisplayOrder.contains(name)) {
|
||||
currentDisplayOrder.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
|
|
@ -326,8 +334,16 @@ public class Helium {
|
|||
return;
|
||||
}
|
||||
|
||||
// update conf and save
|
||||
HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact);
|
||||
|
||||
// set `enable` field
|
||||
heliumConf.disablePackage(name);
|
||||
if (pkgInfo.getPkg().getType() == HeliumType.VISUALIZATION) {
|
||||
List<String> currentDisplayOrder = heliumConf.getBundleDisplayOrder();
|
||||
if (currentDisplayOrder.contains(name)) {
|
||||
currentDisplayOrder.remove(name);
|
||||
}
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
|
|
@ -437,26 +453,13 @@ public class Helium {
|
|||
* Get enabled package list in order
|
||||
* @return
|
||||
*/
|
||||
public List<String> setVisualizationPackageOrder() {
|
||||
List orderedPackageList = new LinkedList<>();
|
||||
List<HeliumPackage> packages = getBundlePackagesToBundle();
|
||||
|
||||
for (HeliumPackage pkg : packages) {
|
||||
if (HeliumType.VISUALIZATION == pkg.getType()) {
|
||||
orderedPackageList.add(pkg.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return orderedPackageList;
|
||||
public List<String> getVisualizationPackageOrder() {
|
||||
return heliumConf.getBundleDisplayOrder();
|
||||
}
|
||||
|
||||
public void setVisualizationPackageOrder(List<String> orderedPackageList)
|
||||
throws IOException {
|
||||
heliumConf.setBundleDisplayOrder(orderedPackageList);
|
||||
|
||||
// if package is visualization, rebuild buildBundle
|
||||
bundleFactory.buildAllPackages(getBundlePackagesToBundle());
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ public class HeliumConf {
|
|||
new HashMap<String, Map<String, Object>>());
|
||||
|
||||
// enabled visualization package display order
|
||||
private List<String> bundleDisplayOrder = new LinkedList<>();
|
||||
private List<String> bundleDisplayOrder =
|
||||
Collections.synchronizedList(new LinkedList<String>());
|
||||
|
||||
public Map<String, String> getEnabledPackages() {
|
||||
return new HashMap<>(enabled);
|
||||
|
|
@ -88,6 +89,6 @@ public class HeliumConf {
|
|||
}
|
||||
|
||||
public void setBundleDisplayOrder(List<String> orderedPackageList) {
|
||||
bundleDisplayOrder = orderedPackageList;
|
||||
bundleDisplayOrder = Collections.synchronizedList(orderedPackageList);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue