Merge remote-tracking branch 'upstream/master' into ZEPPELIN-2403

This commit is contained in:
isys.mreshetov 2017-06-29 12:47:35 +05:00
commit b17dfb592d
18 changed files with 461 additions and 107 deletions

View file

@ -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
##

View file

@ -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
```

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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());

View file

@ -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());
}

View file

@ -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);

View file

@ -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");

View file

@ -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")));

View file

@ -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
}
}

View file

@ -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
*/

View file

@ -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>

View file

@ -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'
})
}
})
}

View file

@ -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)

View file

@ -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()
}

View file

@ -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();
}

View file

@ -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);
}
}