Merge remote-tracking branch 'origin/master' into ZEPPELIN-2403_backup

# Conflicts:
#	zeppelin-web/src/app/interpreter/interpreter.controller.js
#	zeppelin-web/src/index.js
This commit is contained in:
Tinkoff DWH 2017-04-20 15:42:05 +05:00
commit fd8d27810c
128 changed files with 6722 additions and 5896 deletions

View file

@ -255,6 +255,7 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
(Apache 2.0) Bootstrap v3.0.2 (http://getbootstrap.com/) - https://github.com/twbs/bootstrap/blob/v3.0.2/LICENSE
(Apache 2.0) Software under ./bigquery/* was developed at Google (http://www.google.com/). Licensed under the Apache v2.0 License.
(Apache 2.0) Roboto Font (https://github.com/google/roboto/)
(Apache 2.0) Gson extra (https://github.com/DanySK/gson-extras)
========================================================================
BSD 3-Clause licenses

View file

@ -30,7 +30,7 @@ import com.datastax.driver.core.exceptions.DriverException
import com.datastax.driver.core.policies.{LoggingRetryPolicy, FallthroughRetryPolicy, DowngradingConsistencyRetryPolicy, Policies}
import org.apache.zeppelin.cassandra.TextBlockHierarchy._
import org.apache.zeppelin.display.AngularObjectRegistry
import org.apache.zeppelin.display.Input.ParamOption
import org.apache.zeppelin.display.ui.OptionInput.ParamOption
import org.apache.zeppelin.interpreter.InterpreterResult.Code
import org.apache.zeppelin.interpreter.{InterpreterException, InterpreterResult, InterpreterContext}
import org.slf4j.LoggerFactory

View file

@ -34,7 +34,7 @@ import com.datastax.driver.core.Statement;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.display.Input.ParamOption;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.junit.Rule;

View file

@ -36,7 +36,7 @@ import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.display.Input.ParamOption;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.annotation.ZeppelinApi;
import org.apache.zeppelin.interpreter.RemoteWorksController;
import org.apache.zeppelin.interpreter.InterpreterException;

View file

@ -93,6 +93,7 @@
<log4j.version>1.2.17</log4j.version>
<libthrift.version>0.9.2</libthrift.version>
<gson.version>2.2</gson.version>
<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>
@ -192,6 +193,12 @@
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>org.danilopianini</groupId>
<artifactId>gson-extras</artifactId>
<version>${gson-extras.version}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>

View file

@ -53,7 +53,7 @@ class PyZeppelinContext(object):
def __init__(self, z):
self.z = z
self.paramOption = gateway.jvm.org.apache.zeppelin.display.Input.ParamOption
self.paramOption = gateway.jvm.org.apache.zeppelin.display.ui.OptionInput.ParamOption
self.javaList = gateway.jvm.java.util.ArrayList
self.max_result = 1000
self._displayhook = lambda *args: None

View file

@ -40,7 +40,7 @@ import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectWatcher;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.display.Input.ParamOption;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterException;
@ -114,14 +114,33 @@ public class ZeppelinContext {
public SQLContext sqlContext;
private GUI gui;
/**
* @deprecated use z.textbox instead
*
*/
@Deprecated
@ZeppelinApi
public Object input(String name) {
return input(name, "");
return textbox(name);
}
/**
* @deprecated use z.textbox instead
*/
@Deprecated
@ZeppelinApi
public Object input(String name, Object defaultValue) {
return textbox(name, defaultValue.toString());
}
@ZeppelinApi
public Object input(String name, Object defaultValue) {
return gui.input(name, defaultValue);
public Object textbox(String name) {
return textbox(name, "");
}
@ZeppelinApi
public Object textbox(String name, String defaultValue) {
return gui.textbox(name, defaultValue);
}
@ZeppelinApi
@ -136,7 +155,7 @@ public class ZeppelinContext {
}
@ZeppelinApi
public scala.collection.Iterable<Object> checkbox(String name,
public scala.collection.Seq<Object> checkbox(String name,
scala.collection.Iterable<Tuple2<Object, String>> options) {
List<Object> allChecked = new LinkedList<>();
for (Tuple2<Object, String> option : asJavaIterable(options)) {
@ -146,11 +165,12 @@ public class ZeppelinContext {
}
@ZeppelinApi
public scala.collection.Iterable<Object> checkbox(String name,
public scala.collection.Seq<Object> checkbox(String name,
scala.collection.Iterable<Object> defaultChecked,
scala.collection.Iterable<Tuple2<Object, String>> options) {
return collectionAsScalaIterable(gui.checkbox(name, asJavaCollection(defaultChecked),
tuplesToParamOptions(options)));
return scala.collection.JavaConversions.asScalaBuffer(
gui.checkbox(name, asJavaCollection(defaultChecked),
tuplesToParamOptions(options))).toSeq();
}
private ParamOption[] tuplesToParamOptions(
@ -311,7 +331,7 @@ public class ZeppelinContext {
*/
@ZeppelinApi
public void run(String noteId, String paragraphId) {
run(noteId, paragraphId, interpreterContext);
run(noteId, paragraphId, interpreterContext, true);
}
/**
@ -320,8 +340,27 @@ public class ZeppelinContext {
*/
@ZeppelinApi
public void run(String paragraphId) {
run(paragraphId, true);
}
/**
* Run paragraph by id
* @param paragraphId
* @param checkCurrentParagraph
*/
@ZeppelinApi
public void run(String paragraphId, boolean checkCurrentParagraph) {
String noteId = interpreterContext.getNoteId();
run(noteId, paragraphId, interpreterContext);
run(noteId, paragraphId, interpreterContext, checkCurrentParagraph);
}
/**
* Run paragraph by id
* @param noteId
*/
@ZeppelinApi
public void run(String noteId, String paragraphId, InterpreterContext context) {
run(noteId, paragraphId, context, true);
}
/**
@ -330,8 +369,9 @@ public class ZeppelinContext {
* @param context
*/
@ZeppelinApi
public void run(String noteId, String paragraphId, InterpreterContext context) {
if (paragraphId.equals(context.getParagraphId())) {
public void run(String noteId, String paragraphId, InterpreterContext context,
boolean checkCurrentParagraph) {
if (paragraphId.equals(context.getParagraphId()) && checkCurrentParagraph) {
throw new InterpreterException("Can not run current Paragraph");
}
@ -411,24 +451,50 @@ public class ZeppelinContext {
*/
@ZeppelinApi
public void run(int idx) {
run(idx, true);
}
/**
*
* @param idx paragraph index
* @param checkCurrentParagraph check whether you call this run method in the current paragraph.
* Set it to false only when you are sure you are not invoking this method to run current
* paragraph. Otherwise you would run current paragraph in infinite loop.
*/
public void run(int idx, boolean checkCurrentParagraph) {
String noteId = interpreterContext.getNoteId();
run(noteId, idx, interpreterContext);
run(noteId, idx, interpreterContext, checkCurrentParagraph);
}
/**
* Run paragraph at index
* @param noteId
* @param idx index starting from 0
* @param context interpreter context
*/
public void run(String noteId, int idx, InterpreterContext context) {
run(noteId, idx, context, true);
}
/**
*
* @param noteId
* @param idx paragraph index
* @param context interpreter context
* @param checkCurrentParagraph check whether you call this run method in the current paragraph.
* Set it to false only when you are sure you are not invoking this method to run current
* paragraph. Otherwise you would run current paragraph in infinite loop.
*/
public void run(String noteId, int idx, InterpreterContext context,
boolean checkCurrentParagraph) {
List<InterpreterContextRunner> runners = getInterpreterContextRunner(noteId, context);
if (idx >= runners.size()) {
throw new InterpreterException("Index out of bound");
}
InterpreterContextRunner runner = runners.get(idx);
if (runner.getParagraphId().equals(context.getParagraphId())) {
throw new InterpreterException("Can not run current Paragraph");
if (runner.getParagraphId().equals(context.getParagraphId()) && checkCurrentParagraph) {
throw new InterpreterException("Can not run current Paragraph: " + runner.getParagraphId());
}
runner.run();

View file

@ -97,13 +97,15 @@ class PyZeppelinContext(dict):
def checkbox(self, name, options, defaultChecked=None):
if defaultChecked is None:
defaultChecked = list(map(lambda items: items[0], options))
defaultChecked = []
optionTuples = list(map(lambda items: self.__tupleToScalaTuple2(items), options))
optionIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(optionTuples)
defaultCheckedIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(defaultChecked)
checkedIterables = self.z.checkbox(name, defaultCheckedIterables, optionIterables)
return gateway.jvm.scala.collection.JavaConversions.asJavaCollection(checkedIterables)
checkedItems = gateway.jvm.scala.collection.JavaConversions.seqAsJavaList(self.z.checkbox(name, defaultCheckedIterables, optionIterables))
result = []
for checkedItem in checkedItems:
result.append(checkedItem)
return result;
def registerHook(self, event, cmd, replName=None):
if replName is None:

View file

@ -61,6 +61,11 @@
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.danilopianini</groupId>
<artifactId>gson-extras</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>

View file

@ -34,6 +34,8 @@ import org.slf4j.LoggerFactory;
* @param <T>
*/
public class AngularObject<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(AngularObject.class);
private String name;
private T object;
@ -172,7 +174,7 @@ public class AngularObject<T> {
if (emit) {
emit();
}
LOGGER.debug("Update angular object: " + name + " with value: " + o);
final Logger logger = LoggerFactory.getLogger(AngularObject.class);
List<AngularObjectWatcher> ws = new LinkedList<>();
synchronized (watchers) {

View file

@ -17,17 +17,27 @@
package org.apache.zeppelin.display;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.zeppelin.display.ui.CheckBox;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.display.ui.Select;
import org.apache.zeppelin.display.ui.TextBox;
import java.io.Serializable;
import java.util.*;
import org.apache.zeppelin.display.Input.ParamOption;
/**
* Settings of a form.
*/
public class GUI implements Serializable {
private static Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(Input.TypeAdapterFactory)
.create();
Map<String, Object> params = new HashMap<>(); // form parameters from client
LinkedHashMap<String, Input> forms = new LinkedHashMap<>(); // form configuration
@ -51,19 +61,29 @@ public class GUI implements Serializable {
this.forms = forms;
}
@Deprecated
public Object input(String id) {
return textbox(id, "");
}
@Deprecated
public Object input(String id, Object defaultValue) {
return textbox(id, defaultValue.toString());
}
public Object textbox(String id, String defaultValue) {
// first find values from client and then use default
Object value = params.get(id);
if (value == null) {
value = defaultValue;
}
forms.put(id, new Input(id, defaultValue, "input"));
forms.put(id, new TextBox(id, defaultValue));
return value;
}
public Object input(String id) {
return input(id, "");
public Object textbox(String id) {
return textbox(id, "");
}
public Object select(String id, Object defaultValue, ParamOption[] options) {
@ -71,18 +91,18 @@ public class GUI implements Serializable {
if (value == null) {
value = defaultValue;
}
forms.put(id, new Input(id, defaultValue, "select", options));
forms.put(id, new Select(id, defaultValue, options));
return value;
}
public Collection<Object> checkbox(String id, Collection<Object> defaultChecked,
public List<Object> checkbox(String id, Collection<Object> defaultChecked,
ParamOption[] options) {
Collection<Object> checked = (Collection<Object>) params.get(id);
if (checked == null) {
checked = defaultChecked;
}
forms.put(id, new Input(id, defaultChecked, "checkbox", options));
Collection<Object> filtered = new LinkedList<>();
forms.put(id, new CheckBox(id, defaultChecked, options));
List<Object> filtered = new LinkedList<>();
for (Object o : checked) {
if (isValidOption(o, options)) {
filtered.add(o);
@ -103,4 +123,41 @@ public class GUI implements Serializable {
public void clear() {
this.forms = new LinkedHashMap<>();
}
public String toJson() {
return gson.toJson(this);
}
public void convertOldInput() {
for (Map.Entry<String, Input> entry : forms.entrySet()) {
if (entry.getValue() instanceof OldInput) {
Input convertedInput = convertFromOldInput((OldInput) entry.getValue());
forms.put(entry.getKey(), convertedInput);
}
}
}
public static GUI fromJson(String json) {
GUI gui = gson.fromJson(json, GUI.class);
gui.convertOldInput();
return gui;
}
private Input convertFromOldInput(OldInput oldInput) {
Input convertedInput = null;
if (oldInput.options == null || oldInput instanceof OldInput.OldTextBox) {
convertedInput = new TextBox(oldInput.name, oldInput.defaultValue.toString());
} else if (oldInput instanceof OldInput.OldCheckBox) {
convertedInput = new CheckBox(oldInput.name, (List) oldInput.defaultValue, oldInput.options);
} else if (oldInput instanceof OldInput && oldInput.options != null) {
convertedInput = new Select(oldInput.name, oldInput.defaultValue, oldInput.options);
} else {
throw new RuntimeException("Can not convert this OldInput.");
}
convertedInput.setDisplayName(oldInput.getDisplayName());
convertedInput.setHidden(oldInput.isHidden());
convertedInput.setArgument(oldInput.getArgument());
return convertedInput;
}
}

View file

@ -18,6 +18,8 @@
package org.apache.zeppelin.display;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.display.ui.*;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import java.io.Serializable;
import java.util.*;
@ -25,105 +27,43 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Input type.
* Base class for dynamic forms. Also used as factory class of dynamic forms.
*
* @param <T>
*/
public class Input implements Serializable {
/**
* Parameters option.
*/
public static class ParamOption {
Object value;
String displayName;
public class Input<T> implements Serializable {
public ParamOption(Object value, String displayName) {
super();
this.value = value;
this.displayName = displayName;
}
// @TODO(zjffdu). Use gson's RuntimeTypeAdapterFactory and remove the old input form support
// in future.
public static final RuntimeTypeAdapterFactory TypeAdapterFactory =
RuntimeTypeAdapterFactory.of(Input.class, "type")
.registerSubtype(TextBox.class, "TextBox")
.registerSubtype(Select.class, "Select")
.registerSubtype(CheckBox.class, "CheckBox")
.registerSubtype(OldInput.OldTextBox.class, "input")
.registerSubtype(OldInput.OldSelect.class, "select")
.registerSubtype(OldInput.OldCheckBox.class, "checkbox")
.registerSubtype(OldInput.class, null);
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParamOption that = (ParamOption) o;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return displayName != null ? displayName.equals(that.displayName) : that.displayName == null;
}
@Override
public int hashCode() {
int result = value != null ? value.hashCode() : 0;
result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
return result;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
protected String name;
protected String displayName;
protected T defaultValue;
protected boolean hidden;
protected String argument;
public Input() {
}
String name;
String displayName;
String type;
String argument;
Object defaultValue;
ParamOption[] options;
boolean hidden;
public Input(String name, Object defaultValue, String type) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.type = type;
}
public Input(String name, Object defaultValue, String type, ParamOption[] options) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.type = type;
this.options = options;
}
public Input(String name, String displayName, String type, String argument, Object defaultValue,
ParamOption[] options, boolean hidden) {
super();
this.name = name;
this.displayName = displayName;
this.argument = argument;
this.type = type;
this.defaultValue = defaultValue;
this.options = options;
this.hidden = hidden;
}
@Override
public boolean equals(Object o) {
return name.equals(((Input) o).getName());
public boolean isHidden() {
return hidden;
}
public String getName() {
return name;
return this.name;
}
public void setName(String name) {
this.name = name;
public T getDefaultValue() {
return defaultValue;
}
public String getDisplayName() {
@ -134,41 +74,37 @@ public class Input implements Serializable {
this.displayName = displayName;
}
public String getType() {
return type;
public void setArgument(String argument) {
this.argument = argument;
}
public void setType(String type) {
this.type = type;
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public Object getDefaultValue() {
return defaultValue;
public String getArgument() {
return argument;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
public static TextBox textbox(String name, String defaultValue) {
return new TextBox(name, defaultValue);
}
public ParamOption[] getOptions() {
return options;
public static Select select(String name, Object defaultValue, ParamOption[] options) {
return new Select(name, defaultValue, options);
}
public void setOptions(ParamOption[] options) {
this.options = options;
}
public boolean isHidden() {
return hidden;
public static CheckBox checkbox(String name, Object[] defaultChecked, ParamOption[] options) {
return new CheckBox(name, defaultChecked, options);
}
// Syntax of variables: ${TYPE:NAME=DEFAULT_VALUE1|DEFAULT_VALUE2|...,VALUE1|VALUE2|...}
// Type is optional. Type may contain an optional argument with syntax: TYPE(ARG)
// NAME and VALUEs may contain an optional display name with syntax: NAME(DISPLAY_NAME)
// DEFAULT_VALUEs may not contain display name
// Examples: ${age} input form without default value
// ${age=3} input form with default value
// ${age(Age)=3} input form with display name and default value
// Examples: ${age} textbox form without default value
// ${age=3} textbox form with default value
// ${age(Age)=3} textbox form with display name and default value
// ${country=US(United States)|UK|JP} select form with
// ${checkbox( or ):country(Country)=US|JP,US(United States)|UK|JP}
// checkbox form with " or " as delimiter: will be
@ -282,7 +218,22 @@ public class Input implements Serializable {
}
return new Input(varName, displayName, type, arg, defaultValue, paramOptions, hidden);
Input input = null;
if (type == null) {
if (paramOptions == null) {
input = new TextBox(varName, (String) defaultValue);
} else {
input = new Select(varName, defaultValue, paramOptions);
}
} else if (type.equals("checkbox")) {
input = new CheckBox(varName, (Object[]) defaultValue, paramOptions);
} else {
throw new RuntimeException("Could not recognize dynamic form with type: " + type);
}
input.setArgument(arg);
input.setDisplayName(displayName);
input.setHidden(hidden);
return input;
}
public static LinkedHashMap<String, Input> extractSimpleQueryForm(String script) {
@ -314,11 +265,12 @@ public class Input implements Serializable {
if (params.containsKey(input.name)) {
value = params.get(input.name);
} else {
value = input.defaultValue;
value = input.getDefaultValue();
}
String expanded;
if (value instanceof Object[] || value instanceof Collection) { // multi-selection
OptionInput optionInput = (OptionInput) input;
String delimiter = input.argument;
if (delimiter == null) {
delimiter = DEFAULT_DELIMITER;
@ -327,7 +279,7 @@ public class Input implements Serializable {
: Arrays.asList((Object[]) value);
List<Object> validChecked = new LinkedList<>();
for (Object o : checked) { // filter out obsolete checked values
for (ParamOption option : input.getOptions()) {
for (ParamOption option : optionInput.getOptions()) {
if (option.getValue().equals(o)) {
validChecked.add(o);
break;

View file

@ -0,0 +1,87 @@
/*
* 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.display;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
/**
* Old Input type.
* The reason I still keep Old Input is for compatibility. There's one bug in the old input forms.
* There's 2 ways to create input forms: frontend & backend.
* The bug is in frontend. The type would not be set correctly when input form
* is created in frontend (Input.getInputForm).
*/
public class OldInput extends Input<Object> {
ParamOption[] options;
public OldInput() {}
public OldInput(String name, Object defaultValue) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
}
public OldInput(String name, Object defaultValue, ParamOption[] options) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.options = options;
}
@Override
public boolean equals(Object o) {
return name.equals(((OldInput) o).getName());
}
public ParamOption[] getOptions() {
return options;
}
public void setOptions(ParamOption[] options) {
this.options = options;
}
/**
*
*/
public static class OldTextBox extends OldInput {
public OldTextBox(String name, Object defaultValue) {
super(name, defaultValue);
}
}
/**
*
*/
public static class OldSelect extends OldInput {
public OldSelect(String name, Object defaultValue, ParamOption[] options) {
super(name, defaultValue, options);
}
}
/**
*
*/
public static class OldCheckBox extends OldInput {
public OldCheckBox(String name, Object defaultValue, ParamOption[] options) {
super(name, defaultValue, options);
}
}
}

View file

@ -0,0 +1,149 @@
/*
* 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.display;
import com.google.gson.*;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Copied from gson with minor changes to support old input forms
*
* @param <T>
*/
public class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
if (typeFieldName == null || baseType == null) {
throw new NullPointerException();
}
this.baseType = baseType;
this.typeFieldName = typeFieldName;
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
}
/**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
* the type field name.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeTypeAdapterFactory<T>(baseType, "type");
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple
* name}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
if (type.getRawType() != baseType) {
return null;
}
final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<String, TypeAdapter<?>>();
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate =
new LinkedHashMap<Class<?>, TypeAdapter<?>>();
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
return new TypeAdapter<R>() {
@Override public R read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
String label = (labelJsonElement == null ? null : labelJsonElement.getAsString());
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
@Override public void write(JsonWriter out, R value) throws IOException {
Class<?> srcType = value.getClass();
String label = subtypeToLabel.get(srcType);
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
if (jsonObject.has(typeFieldName) && !srcType.getSimpleName().equals("OldInput")) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
}
JsonObject clone = new JsonObject();
if (!srcType.getSimpleName().equals("OldInput")) {
clone.add(typeFieldName, new JsonPrimitive(label));
}
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
}
}.nullSafe();
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.display.ui;
import java.awt.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* Html Checkbox
*/
public class CheckBox extends OptionInput<Object[]> {
public CheckBox() {
}
public CheckBox(String name, Object[] defaultValue, ParamOption[] options) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.options = options;
}
public CheckBox(String name, Collection<Object> defaultValue, ParamOption[] options) {
this(name, defaultValue.toArray(), options);
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.display.ui;
import org.apache.zeppelin.display.Input;
/**
* Base class for Input with options
*
* @param <T>
*/
public abstract class OptionInput<T> extends Input<T> {
/**
* Parameters option.
*/
public static class ParamOption {
Object value;
String displayName;
public ParamOption(Object value, String displayName) {
super();
this.value = value;
this.displayName = displayName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParamOption that = (ParamOption) o;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return displayName != null ? displayName.equals(that.displayName) : that.displayName == null;
}
@Override
public int hashCode() {
int result = value != null ? value.hashCode() : 0;
result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
return result;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
protected ParamOption[] options;
public ParamOption[] getOptions() {
return options;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.display.ui;
/**
* Html Dropdown list
*/
public class Select extends OptionInput<Object> {
public Select() {
}
public Select(String name, Object defaultValue, ParamOption[] options) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
this.options = options;
}
}

View file

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.display.ui;
import org.apache.zeppelin.display.Input;
/**
* Html TextBox control
*/
public class TextBox extends Input<String> {
public TextBox() {
}
public TextBox(String name, String defaultValue) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
}
}

View file

@ -92,7 +92,12 @@ public class LazyOpenInterpreter
@Override
public InterpreterResult interpret(String st, InterpreterContext context) {
open();
return intp.interpret(st, context);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
return intp.interpret(st, context);
} finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
@Override

View file

@ -381,14 +381,14 @@ public class RemoteInterpreter extends Interpreter {
context.getConfig().putAll(remoteConfig);
if (form == FormType.NATIVE) {
GUI remoteGui = gson.fromJson(remoteResult.getGui(), GUI.class);
GUI remoteGui = GUI.fromJson(remoteResult.getGui());
currentGUI.clear();
currentGUI.setParams(remoteGui.getParams());
currentGUI.setForms(remoteGui.getForms());
} else if (form == FormType.SIMPLE) {
final Map<String, Input> currentForms = currentGUI.getForms();
final Map<String, Object> currentParams = currentGUI.getParams();
final GUI remoteGUI = gson.fromJson(remoteResult.getGui(), GUI.class);
final GUI remoteGUI = GUI.fromJson(remoteResult.getGui());
final Map<String, Input> remoteForms = remoteGUI.getForms();
final Map<String, Object> remoteParams = remoteGUI.getParams();
currentForms.putAll(remoteForms);

View file

@ -592,7 +592,7 @@ public class RemoteInterpreterServer
gson.fromJson(ric.getAuthenticationInfo(), AuthenticationInfo.class),
(Map<String, Object>) gson.fromJson(ric.getConfig(),
new TypeToken<Map<String, Object>>() {}.getType()),
gson.fromJson(ric.getGui(), GUI.class),
GUI.fromJson(ric.getGui()),
interpreterGroup.getAngularObjectRegistry(),
interpreterGroup.getResourcePool(),
contextRunners, output, remoteWorksController, eventClient);
@ -737,7 +737,7 @@ public class RemoteInterpreterServer
result.code().name(),
msg,
gson.toJson(config),
gson.toJson(gui));
gui.toJson());
}
@Override

View file

@ -0,0 +1,120 @@
/*
* 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.display;
import org.apache.commons.io.IOUtils;
import org.apache.zeppelin.display.ui.CheckBox;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.display.ui.Select;
import org.apache.zeppelin.display.ui.TextBox;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class GUITest {
private ParamOption[] options = new ParamOption[]{
new ParamOption("1", "value_1"),
new ParamOption("2", "value_2")
};
private List<Object> checkedItems;
@Before
public void setUp() {
checkedItems = new ArrayList<>();
checkedItems.add("1");
}
@Test
public void testGson() {
GUI gui = new GUI();
gui.textbox("textbox_1", "default_text_1");
gui.select("select_1", "1", options);
List<Object> list = new ArrayList();
list.add("1");
gui.checkbox("checkbox_1", list, options);
String json = gui.toJson();
System.out.println(json);
GUI gui2 = GUI.fromJson(json);
assertEquals(gui2.toJson(), json);
assertEquals(gui2.forms, gui2.forms);
assertEquals(gui2.params, gui2.params);
}
// Case 1. Old input forms are created in backend, in this case type is always set
@Test
public void testOldGson_1() throws IOException {
GUI gui = new GUI();
gui.forms.put("textbox_1", new OldInput.OldTextBox("textbox_1", "default_text_1"));
gui.forms.put("select_1", new OldInput.OldSelect("select_1", "1", options));
gui.forms.put("checkbox_1",
new OldInput.OldCheckBox("checkbox_1", checkedItems, options));
// convert to old json format.
String json = gui.toJson();
// convert to new input forms
GUI gui2 = GUI.fromJson(json);
assertTrue(3 == gui2.forms.size());
assertTrue(gui2.forms.get("textbox_1") instanceof TextBox);
assertEquals("default_text_1", gui2.forms.get("textbox_1").getDefaultValue());
assertTrue(gui2.forms.get("select_1") instanceof Select);
assertEquals(options, ((Select) gui2.forms.get("select_1")).getOptions());
assertTrue(gui2.forms.get("checkbox_1") instanceof CheckBox);
assertEquals(options, ((CheckBox) gui2.forms.get("checkbox_1")).getOptions());
}
// Case 2. Old input forms are created in frontend, in this case type is only set for checkbox
// Actually this is a bug due to method Input#getInputForm
@Test
public void testOldGson_2() throws IOException {
GUI gui = new GUI();
gui.forms.put("textbox_1", new OldInput("textbox_1", "default_text_1"));
gui.forms.put("select_1", new OldInput("select_1", "1", options));
gui.forms.put("checkbox_1",
new OldInput.OldCheckBox("checkbox_1", checkedItems, options));
// convert to old json format.
String json = gui.toJson();
// convert to new input forms
GUI gui2 = GUI.fromJson(json);
assertTrue(3 == gui2.forms.size());
assertTrue(gui2.forms.get("textbox_1") instanceof TextBox);
assertEquals("default_text_1", gui2.forms.get("textbox_1").getDefaultValue());
assertTrue(gui2.forms.get("select_1") instanceof Select);
assertEquals(options, ((Select) gui2.forms.get("select_1")).getOptions());
assertTrue(gui2.forms.get("checkbox_1") instanceof CheckBox);
assertEquals(options, ((CheckBox) gui2.forms.get("checkbox_1")).getOptions());
}
}

View file

@ -20,16 +20,19 @@ package org.apache.zeppelin.display;
import java.util.HashMap;
import java.util.Map;
import org.apache.zeppelin.display.ui.CheckBox;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.display.ui.Select;
import org.apache.zeppelin.display.ui.TextBox;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import org.apache.zeppelin.display.Input.ParamOption;
public class InputTest {
@Before
@ -42,7 +45,7 @@ public class InputTest {
@Test
public void testFormExtraction() {
// input form
// textbox form
String script = "${input_form=}";
Map<String, Input> forms = Input.extractSimpleQueryForm(script);
assertEquals(1, forms.size());
@ -50,50 +53,57 @@ public class InputTest {
assertEquals("input_form", form.name);
assertNull(form.displayName);
assertEquals("", form.defaultValue);
assertNull(form.options);
assertTrue(form instanceof TextBox);
// input form with display name & default value
// textbox form with display name & default value
script = "${input_form(Input Form)=xxx}";
forms = Input.extractSimpleQueryForm(script);
form = forms.get("input_form");
assertEquals("xxx", form.defaultValue);
assertTrue(form instanceof TextBox);
// selection form
script = "${select_form(Selection Form)=op1,op1|op2(Option 2)|op3}";
form = Input.extractSimpleQueryForm(script).get("select_form");
assertEquals("select_form", form.name);
assertEquals("op1", form.defaultValue);
assertTrue(form instanceof Select);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
new ParamOption("op2", "Option 2"), new ParamOption("op3", null)}, form.options);
new ParamOption("op2", "Option 2"), new ParamOption("op3", null)},
((Select) form).getOptions());
// checkbox form
script = "${checkbox:checkbox_form=op1,op1|op2|op3}";
form = Input.extractSimpleQueryForm(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("checkbox", form.type);
assertTrue(form instanceof CheckBox);
assertArrayEquals(new Object[]{"op1"}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
new ParamOption("op2", null), new ParamOption("op3", null)},
((CheckBox) form).getOptions());
// checkbox form with multiple default checks
script = "${checkbox:checkbox_form(Checkbox Form)=op1|op3,op1(Option 1)|op2|op3}";
form = Input.extractSimpleQueryForm(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("Checkbox Form", form.displayName);
assertEquals("checkbox", form.type);
assertTrue(form instanceof CheckBox);
assertArrayEquals(new Object[]{"op1", "op3"}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", "Option 1"),
new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
new ParamOption("op2", null), new ParamOption("op3", null)},
((CheckBox) form).getOptions());
// checkbox form with no default check
script = "${checkbox:checkbox_form(Checkbox Form)=,op1(Option 1)|op2(Option 2)|op3(Option 3)}";
form = Input.extractSimpleQueryForm(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("Checkbox Form", form.displayName);
assertEquals("checkbox", form.type);
assertTrue(form instanceof CheckBox);
assertArrayEquals(new Object[]{}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", "Option 1"),
new ParamOption("op2", "Option 2"), new ParamOption("op3", "Option 3")}, form.options);
new ParamOption("op2", "Option 2"), new ParamOption("op3", "Option 3")},
((CheckBox) form).getOptions());
}
@ -125,4 +135,5 @@ public class InputTest {
assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1\n" +
"NEW_CHECKED=nc_a and nc_c", replaced);
}
}

View file

@ -341,11 +341,11 @@ public class NotebookRestApi {
List<NewParagraphRequest> initialParagraphs = request.getParagraphs();
if (initialParagraphs != null) {
for (NewParagraphRequest paragraphRequest : initialParagraphs) {
Paragraph p = note.addParagraph(subject);
Paragraph p = note.addNewParagraph(subject);
initParagraph(p, paragraphRequest, user);
}
}
note.addParagraph(subject); // add one paragraph to the last
note.addNewParagraph(subject); // add one paragraph to the last
String noteName = request.getName();
if (noteName.isEmpty()) {
noteName = "Note " + note.getId();
@ -433,9 +433,9 @@ public class NotebookRestApi {
Paragraph p;
Double indexDouble = request.getIndex();
if (indexDouble == null) {
p = note.addParagraph(subject);
p = note.addNewParagraph(subject);
} else {
p = note.insertParagraph(indexDouble.intValue(), subject);
p = note.insertNewParagraph(indexDouble.intValue(), subject);
}
initParagraph(p, request, user);
note.persist(subject);

View file

@ -37,7 +37,6 @@ import javax.servlet.http.HttpServletRequest;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gson.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
@ -45,6 +44,7 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectRegistryListener;
import org.apache.zeppelin.display.Input;
import org.apache.zeppelin.helium.ApplicationEventListener;
import org.apache.zeppelin.helium.HeliumPackage;
import org.apache.zeppelin.interpreter.Interpreter;
@ -134,7 +134,9 @@ public class NotebookServer extends WebSocketServlet
}
}
}
}).setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create();
}).setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.registerTypeAdapterFactory(Input.TypeAdapterFactory).create();
final Map<String, List<NotebookSocket>> noteSocketMap = new HashMap<>();
final Queue<NotebookSocket> connectedSockets = new ConcurrentLinkedQueue<>();
final Map<String, Queue<NotebookSocket>> userConnectedSockets = new ConcurrentHashMap<>();
@ -183,10 +185,11 @@ public class NotebookServer extends WebSocketServlet
Notebook notebook = notebook();
try {
Message messagereceived = deserializeMessage(msg);
LOG.debug("RECEIVE << " + messagereceived.op);
LOG.debug("RECEIVE PRINCIPAL << " + messagereceived.principal);
LOG.debug("RECEIVE TICKET << " + messagereceived.ticket);
LOG.debug("RECEIVE ROLES << " + messagereceived.roles);
LOG.debug("RECEIVE << " + messagereceived.op +
", RECEIVE PRINCIPAL << " + messagereceived.principal +
", RECEIVE TICKET << " + messagereceived.ticket +
", RECEIVE ROLES << " + messagereceived.roles +
", RECEIVE DATA << " + messagereceived.data);
if (LOG.isTraceEnabled()) {
LOG.trace("RECEIVE MSG = " + messagereceived);
@ -643,6 +646,22 @@ public class NotebookServer extends WebSocketServlet
broadcast(noteId, new Message(OP.INTERPRETER_BINDINGS).put("interpreterBindings", settingList));
}
public void unicastParagraph(Note note, Paragraph p, String user) {
if (!note.isPersonalizedMode() || p == null || user == null) {
return;
}
if (!userConnectedSockets.containsKey(user)) {
LOG.warn("Failed to send unicast. user {} that is not in connections map", user);
return;
}
for (NotebookSocket conn : userConnectedSockets.get(user)) {
Message m = new Message(OP.PARAGRAPH).put("paragraph", p);
unicast(m, conn);
}
}
public void broadcastParagraph(Note note, Paragraph p) {
if (note.isPersonalizedMode()) {
broadcastParagraphs(p.getUserParagraphMap(), p);
@ -1004,7 +1023,7 @@ public class NotebookServer extends WebSocketServlet
note = notebook.createNote(subject);
}
note.addParagraph(subject); // it's an empty note. so add one paragraph
note.addNewParagraph(subject); // it's an empty note. so add one paragraph
if (message != null) {
String noteName = (String) message.get("name");
if (StringUtils.isEmpty(noteName)) {
@ -1300,9 +1319,15 @@ public class NotebookServer extends WebSocketServlet
}
final Note note = notebook.getNote(noteId);
note.clearParagraphOutput(paragraphId);
Paragraph paragraph = note.getParagraph(paragraphId);
broadcastParagraph(note, paragraph);
if (note.isPersonalizedMode()) {
String user = fromMessage.principal;
Paragraph p = note.clearPersonalizedParagraphOutput(paragraphId, user);
unicastParagraph(note, p, user);
} else {
note.clearParagraphOutput(paragraphId);
Paragraph paragraph = note.getParagraph(paragraphId);
broadcastParagraph(note, paragraph);
}
}
private void completion(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
@ -1568,7 +1593,7 @@ public class NotebookServer extends WebSocketServlet
return null;
}
Paragraph newPara = note.insertParagraph(index, subject);
Paragraph newPara = note.insertNewParagraph(index, subject);
note.persist(subject);
broadcastNewParagraph(note, newPara);
@ -1695,12 +1720,21 @@ public class NotebookServer extends WebSocketServlet
return;
}
// 1. clear paragraph only if personalized,
// otherwise this will be handed in `onOutputClear`
final Note note = notebook.getNote(noteId);
if (note.isPersonalizedMode()) {
String user = fromMessage.principal;
Paragraph p = note.clearPersonalizedParagraphOutput(paragraphId, user);
unicastParagraph(note, p, user);
}
// 2. set paragraph values
String text = (String) fromMessage.get("paragraph");
String title = (String) fromMessage.get("title");
Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
final Note note = notebook.getNote(noteId);
Paragraph p = setParagraphUsingMessage(note, fromMessage, paragraphId,
text, title, params, config);
@ -1713,7 +1747,7 @@ public class NotebookServer extends WebSocketServlet
if (!(p.getText().trim().equals(p.getMagic()) ||
Strings.isNullOrEmpty(p.getText())) &&
isTheLastParagraph) {
Paragraph newPara = note.addParagraph(p.getAuthenticationInfo());
Paragraph newPara = note.addNewParagraph(p.getAuthenticationInfo());
broadcastNewParagraph(note, newPara);
}
}

View file

@ -548,7 +548,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
try {
createNewNote();
setTextOfParagraph(1, "%spark println(\"Hello \"+z.input(\"name\", \"world\")) ");
setTextOfParagraph(1, "%spark println(\"Hello \"+z.textbox(\"name\", \"world\")) ");
runParagraph(1);
waitForParagraph(1, "FINISHED");

View file

@ -137,7 +137,7 @@ public class ZeppelinIT extends AbstractZeppelinIT {
* z.run(2, context)
* }
*/
setTextOfParagraph(4, "z.angularWatch(\"myVar\", (before:Object, after:Object, context:org.apache.zeppelin.interpreter.InterpreterContext)=>{ z.run(2)})");
setTextOfParagraph(4, "z.angularWatch(\"myVar\", (before:Object, after:Object, context:org.apache.zeppelin.interpreter.InterpreterContext)=>{ z.run(2, false)})");
runParagraph(4);
waitForParagraph(4, "FINISHED");
@ -157,6 +157,21 @@ public class ZeppelinIT extends AbstractZeppelinIT {
waitForText("myVar=3", By.xpath(
getParagraphXPath(3) + "//div[contains(@id,\"_text\") and @class=\"text\"]"));
/*
* Click element, again and see watcher still works
*/
driver.findElement(By.xpath(
getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")).click();
// check expected text
waitForText("BindingTest_4_", By.xpath(
getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"));
waitForParagraph(3, "FINISHED");
// check expected text by watcher
waitForText("myVar=4", By.xpath(
getParagraphXPath(3) + "//div[contains(@id,\"_text\") and @class=\"text\"]"));
/*
* Unbind
* z.angularUnbind("myVar")

View file

@ -242,7 +242,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
public void testInterpreterRestart() throws IOException, InterruptedException {
// when: create new note
Note note = ZeppelinServer.notebook.createNote(anonymous);
note.addParagraph(AuthenticationInfo.ANONYMOUS);
note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.getLastParagraph();
Map config = p.getConfig();
config.put("enabled", true);
@ -269,7 +269,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
}
// when: run markdown paragraph, again
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p.setConfig(config);
p.setText("%md markdown restarted");
p.setAuthenticationInfo(anonymous);
@ -287,7 +287,7 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
public void testRestartInterpreterPerNote() throws IOException, InterruptedException {
// when: create new note
Note note = ZeppelinServer.notebook.createNote(anonymous);
note.addParagraph(AuthenticationInfo.ANONYMOUS);
note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.getLastParagraph();
Map config = p.getConfig();
config.put("enabled", true);

View file

@ -17,7 +17,6 @@
package org.apache.zeppelin.rest;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.httpclient.methods.GetMethod;
@ -72,7 +71,7 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
@Test
public void testGetNoteParagraphJobStatus() throws IOException {
Note note1 = ZeppelinServer.notebook.createNote(anonymous);
note1.addParagraph(AuthenticationInfo.ANONYMOUS);
note1.addNewParagraph(AuthenticationInfo.ANONYMOUS);
String paragraphId = note1.getLastParagraph().getId();
@ -94,9 +93,9 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
@Test
public void testRunParagraphJob() throws IOException {
Note note1 = ZeppelinServer.notebook.createNote(anonymous);
note1.addParagraph(AuthenticationInfo.ANONYMOUS);
note1.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note1.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note1.addNewParagraph(AuthenticationInfo.ANONYMOUS);
// run blank paragraph
PostMethod post = httpPost("/notebook/job/" + note1.getId() + "/" + p.getId(), "");
@ -150,7 +149,7 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
public void testUpdateParagraphConfig() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
String noteId = note.getId();
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
assertNull(p.getConfig().get("colWidth"));
String paragraphId = p.getId();
String jsonRequest = "{\"colWidth\": 6.0}";
@ -176,11 +175,11 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
public void testClearAllParagraphOutput() throws IOException {
// Create note and set result explicitly
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p1 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
InterpreterResult result = new InterpreterResult(InterpreterResult.Code.SUCCESS, InterpreterResult.Type.TEXT, "result");
p1.setResult(result);
Paragraph p2 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p2 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p2.setReturn(result, new Throwable());
// clear paragraph result

View file

@ -27,7 +27,6 @@ import com.google.common.collect.Sets;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.lang3.StringUtils;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Paragraph;
@ -88,7 +87,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName("note");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
paragraph.setConfig(config);
@ -228,7 +227,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName("source note for export");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
paragraph.setConfig(config);
@ -261,7 +260,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName(noteName);
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
paragraph.setConfig(config);
@ -323,7 +322,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName("source note for clone");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
paragraph.setConfig(config);
@ -374,7 +373,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName("note for run test");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
@ -429,7 +428,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName("note for run test");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
@ -483,7 +482,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
assertNotNull("can't create new note", note);
note.setName("note for run test");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = paragraph.getConfig();
config.put("enabled", true);
@ -527,7 +526,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
note.setName("note for run test");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
paragraph.setText("%md This is test paragraph.");
Map config = paragraph.getConfig();
@ -576,7 +575,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
note.setName("note for run test");
Paragraph paragraph = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph paragraph = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
paragraph.setText("%spark\nval param = z.input(\"param\").toString\nprintln(param)");
note.persist(anonymous);
@ -656,7 +655,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
public void testGetParagraph() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p.setTitle("hello");
p.setText("world");
note.persist(anonymous);
@ -685,11 +684,11 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
public void testMoveParagraph() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p.setTitle("title1");
p.setText("text1");
Paragraph p2 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p2 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p2.setTitle("title2");
p2.setText("text2");
@ -717,7 +716,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
public void testDeleteParagraph() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p.setTitle("title1");
p.setText("text1");

View file

@ -80,7 +80,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
public void scalaOutputTest() throws IOException {
// create new note
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -106,7 +106,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
Note note = ZeppelinServer.notebook.createNote(anonymous);
// run markdown paragraph, again
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -127,7 +127,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
// DataFrame API is available from spark 1.3
if (sparkVersion >= 13) {
// test basic dataframe api
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -141,7 +141,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
"Array[org.apache.spark.sql.Row] = Array([hello,20])"));
// test display DataFrame
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -156,7 +156,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
// test display DataSet
if (sparkVersion >= 20) {
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -195,7 +195,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
if (sparkVersion >= 20) {
sqlContextName = "spark";
}
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -222,7 +222,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
if (isPyspark() && sparkVersion >= 12) { // pyspark supported from 1.2.1
// run markdown paragraph, again
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -234,7 +234,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assertEquals("55\n", p.getResult().message().get(0).getData());
if (sparkVersion >= 13) {
// run sqlContext test
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -248,7 +248,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assertEquals("[Row(age=20, id=1)]\n", p.getResult().message().get(0).getData());
// test display Dataframe
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -264,7 +264,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assertEquals("age\tid\n20\t1\n", p.getResult().message().get(0).getData());
// test udf
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -278,7 +278,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
}
if (sparkVersion >= 20) {
// run SparkSession test
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -292,7 +292,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assertEquals("[Row(age=20, id=1)]\n", p.getResult().message().get(0).getData());
// test udf
p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -319,7 +319,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
if (isPyspark() && sparkVersionNumber >= 14) { // auto_convert enabled from spark 1.4
// run markdown paragraph, again
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
@ -344,19 +344,19 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
public void zRunTest() throws IOException {
// create new note
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p0 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p0 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config0 = p0.getConfig();
config0.put("enabled", true);
p0.setConfig(config0);
p0.setText("%spark z.run(1)");
p0.setAuthenticationInfo(anonymous);
Paragraph p1 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config1 = p1.getConfig();
config1.put("enabled", true);
p1.setConfig(config1);
p1.setText("%spark val a=10");
p1.setAuthenticationInfo(anonymous);
Paragraph p2 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p2 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config2 = p2.getConfig();
config2.put("enabled", true);
p2.setConfig(config2);
@ -374,7 +374,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assertEquals(Status.FINISHED, p2.getStatus());
assertEquals("10", p2.getResult().message().get(0).getData());
Paragraph p3 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p3 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config3 = p3.getConfig();
config3.put("enabled", true);
p3.setConfig(config3);
@ -424,7 +424,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
}
// load dep
Paragraph p0 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p0 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
Map config = p0.getConfig();
config.put("enabled", true);
p0.setConfig(config);
@ -439,7 +439,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
FileUtils.write(tmpFile, "a,b\n1,2");
// load data using libraries from dep loader
Paragraph p1 = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p1.setConfig(config);
String sqlContextName = "sqlContext";
@ -465,7 +465,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
* eg. 1.1.x => 11, 1.2.x => 12, 1.3.x => 13 ...
*/
private int getSparkVersionNumber(Note note) {
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
note.setName("note");
Map config = p.getConfig();
config.put("enabled", true);
@ -483,18 +483,19 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
}
@Test
public void testZeppelinContextDynamicForms() throws IOException {
public void testSparkZeppelinContextDynamicForms() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p = note.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
note.setName("note");
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
String code = "%spark.spark z.input(\"my_input\", \"default_name\")\n" +
"z.select(\"my_select\", \"select_2\"," +
"Seq((\"1\", \"select_1\"), (\"2\", \"select_2\")))\n" +
"z.checkbox(\"my_checkbox\", Seq(\"check_1\"), " +
"Seq((\"1\", \"check_1\"), (\"2\", \"check_2\")))";
String code = "%spark.spark println(z.textbox(\"my_input\", \"default_name\"))\n" +
"println(z.select(\"my_select\", \"1\"," +
"Seq((\"1\", \"select_1\"), (\"2\", \"select_2\"))))\n" +
"val items=z.checkbox(\"my_checkbox\", Seq(\"2\"), " +
"Seq((\"1\", \"check_1\"), (\"2\", \"check_2\")))\n" +
"println(items(0))";
p.setText(code);
p.setAuthenticationInfo(anonymous);
note.run(p.getId());
@ -505,5 +506,46 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assert(formIter.next().equals("my_input"));
assert(formIter.next().equals("my_select"));
assert(formIter.next().equals("my_checkbox"));
// check dynamic forms values
String[] result = p.getResult().message().get(0).getData().split("\n");
assertEquals(4, result.length);
assertEquals("default_name", result[0]);
assertEquals("1", result[1]);
assertEquals("items: Seq[Object] = Buffer(2)", result[2]);
assertEquals("2", result[3]);
}
@Test
public void testPySparkZeppelinContextDynamicForms() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
note.setName("note");
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
String code = "%spark.pyspark print(z.input('my_input', 'default_name'))\n" +
"print(z.select('my_select', " +
"[('1', 'select_1'), ('2', 'select_2')], defaultValue='1'))\n" +
"items=z.checkbox('my_checkbox', " +
"[('1', 'check_1'), ('2', 'check_2')], defaultChecked=['2'])\n" +
"print(items[0])";
p.setText(code);
p.setAuthenticationInfo(anonymous);
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
Iterator<String> formIter = p.settings.getForms().keySet().iterator();
assert(formIter.next().equals("my_input"));
assert(formIter.next().equals("my_select"));
assert(formIter.next().equals("my_checkbox"));
// check dynamic forms values
String[] result = p.getResult().message().get(0).getData().split("\n");
assertEquals(3, result.length);
assertEquals("default_name", result[0]);
assertEquals("1", result[1]);
assertEquals("2", result[2]);
}
}

View file

@ -110,7 +110,7 @@ public class NotebookServerTest extends AbstractTestRestApi {
}
// start interpreter process
Paragraph p1 = note1.addParagraph(AuthenticationInfo.ANONYMOUS);
Paragraph p1 = note1.addNewParagraph(AuthenticationInfo.ANONYMOUS);
p1.setText("%md start remote interpreter process");
p1.setAuthenticationInfo(anonymous);
note1.run(p1.getId());

View file

@ -1,5 +1,5 @@
{
"preset": "google",
"extends": ["eslint:recommended", "google", "standard"],
"env": {
"browser": true,
"jasmine": true,
@ -31,6 +31,20 @@
"process": false
},
"rules": {
"array-bracket-spacing": 0,
"space-before-function-paren": 0,
"no-unneeded-ternary": 0,
"comma-dangle": 0,
"object-curly-spacing": 0,
"standard/object-curly-even-spacing": 0,
"arrow-parens": 0,
"require-jsdoc": 0,
"valid-jsdoc": 0,
"no-invalid-this": 0,
"no-console": 0,
"guard-for-in": 0,
"no-mixed-operators": 1,
"no-useless-escape": 1,
"no-bitwise": 2,
"camelcase": 2,
"curly": 2,

View file

@ -104,13 +104,6 @@ module.exports = function(grunt) {
files: ['bower.json'],
tasks: ['wiredep']
},
js: {
files: [
'<%= yeoman.app %>/app/**/*.js',
'<%= yeoman.app %>/components/**/*.js'
],
tasks: ['newer:eslint:all'],
},
html: {
files: [
'<%= yeoman.app %>/**/*.html'
@ -120,7 +113,6 @@ module.exports = function(grunt) {
jsTest: {
files: ['test/spec/{,*/}*.js'],
tasks: [
'newer:eslint:test',
'karma'
]
},
@ -148,24 +140,6 @@ module.exports = function(grunt) {
}
},
eslint: {
all: {
src: [
'Gruntfile.js',
'<%= yeoman.app %>/app/**/*.js',
'<%= yeoman.app %>/components/**/*.js'
]
},
test: {
options: {
rules: {
'no-undef': 0
}
},
src: ['test/spec/{,*/}*.js']
}
},
// Add vendor prefixed styles
postcss: {
options: {
@ -430,7 +404,6 @@ module.exports = function(grunt) {
]);
grunt.registerTask('pre-webpack-dist', [
'eslint',
'htmlhint',
'wiredep',
]);

View file

@ -8,14 +8,15 @@
"scripts": {
"clean": "rimraf dist && rimraf .tmp",
"postinstall": "bower install --silent",
"prebuild": "npm-run-all clean",
"prebuild": "npm-run-all clean lint:once",
"build": "grunt pre-webpack-dist && webpack && grunt post-webpack-dist",
"lint:watch": "esw --watch src",
"lint:once": "eslint src",
"predev": "grunt pre-webpack-dev",
"dev:server": "webpack-dev-server --hot",
"dev:helium": "HELIUM_BUNDLE_DEV=true webpack-dev-server --hot",
"dev:watch": "grunt watch-webpack-dev",
"dev": "npm-run-all --parallel dev:server dev:watch",
"visdev": "npm-run-all --parallel visdev:server dev:watch",
"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"
},
@ -33,7 +34,14 @@
"bower": "^1.8.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.26.1",
"eslint": "^3.19.0",
"eslint-config-google": "^0.7.1",
"eslint-config-standard": "^10.2.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-watch": "^3.1.0",
"express": "^4.14.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
@ -47,7 +55,6 @@
"grunt-contrib-htmlmin": "^0.3.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-eslint": "^19.0.0",
"grunt-filerev": "^0.2.1",
"grunt-htmlhint": "^0.9.13",
"grunt-newer": "^0.7.0",
@ -70,11 +77,11 @@
"postcss-loader": "^1.2.1",
"raw-loader": "^0.5.1",
"rimraf": "^2.5.4",
"string-replace-webpack-plugin": "^0.1.3",
"style-loader": "^0.13.1",
"time-grunt": "^0.3.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2",
"string-replace-webpack-plugin": "^0.1.3"
"webpack-dev-server": "^1.16.2"
},
"repository": {
"type": "git",

View file

@ -12,49 +12,48 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('MainCtrl', MainCtrl);
angular.module('zeppelinWebApp').controller('MainCtrl', MainCtrl)
function MainCtrl($scope, $rootScope, $window, arrayOrderingSrv) {
'ngInject';
function MainCtrl ($scope, $rootScope, $window, arrayOrderingSrv) {
'ngInject'
$scope.looknfeel = 'default';
$scope.looknfeel = 'default'
var init = function() {
$scope.asIframe = (($window.location.href.indexOf('asIframe') > -1) ? true : false);
};
let init = function () {
$scope.asIframe = (($window.location.href.indexOf('asIframe') > -1) ? true : false)
}
init();
init()
$rootScope.$on('setIframe', function(event, data) {
$rootScope.$on('setIframe', function (event, data) {
if (!event.defaultPrevented) {
$scope.asIframe = data;
event.preventDefault();
$scope.asIframe = data
event.preventDefault()
}
});
})
$rootScope.$on('setLookAndFeel', function(event, data) {
$rootScope.$on('setLookAndFeel', function (event, data) {
if (!event.defaultPrevented && data && data !== '' && data !== $scope.looknfeel) {
$scope.looknfeel = data;
event.preventDefault();
$scope.looknfeel = data
event.preventDefault()
}
});
})
// Set The lookAndFeel to default on every page
$rootScope.$on('$routeChangeStart', function(event, next, current) {
$rootScope.$broadcast('setLookAndFeel', 'default');
});
$rootScope.$on('$routeChangeStart', function (event, next, current) {
$rootScope.$broadcast('setLookAndFeel', 'default')
})
$rootScope.noteName = function(note) {
$rootScope.noteName = function (note) {
if (!_.isEmpty(note)) {
return arrayOrderingSrv.getNoteName(note);
return arrayOrderingSrv.getNoteName(note)
}
};
}
BootstrapDialog.defaultOptions.onshown = function() {
angular.element('#' + this.id).find('.btn:last').focus();
};
BootstrapDialog.defaultOptions.onshown = function () {
angular.element('#' + this.id).find('.btn:last').focus()
}
// Remove BootstrapDialog animation
BootstrapDialog.configDefaultOptions({animate: false});
BootstrapDialog.configDefaultOptions({animate: false})
}

View file

@ -1,29 +1,28 @@
describe('Controller: MainCtrl', function() {
beforeEach(angular.mock.module('zeppelinWebApp'));
describe('Controller: MainCtrl', function () {
beforeEach(angular.mock.module('zeppelinWebApp'))
var scope;
var rootScope;
let scope
let rootScope
beforeEach(inject(function($controller, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new();
beforeEach(inject(function ($controller, $rootScope) {
rootScope = $rootScope
scope = $rootScope.$new()
$controller('MainCtrl', {
$scope: scope
});
}));
})
}))
it('should attach "asIframe" to the scope and the default value should be false', function() {
expect(scope.asIframe).toBeDefined();
expect(scope.asIframe).toEqual(false);
});
it('should attach "asIframe" to the scope and the default value should be false', function () {
expect(scope.asIframe).toBeDefined()
expect(scope.asIframe).toEqual(false)
})
it('should set the default value of "looknfeel to "default"', function() {
expect(scope.looknfeel).toEqual('default');
});
it('should set the default value of "looknfeel to "default"', function () {
expect(scope.looknfeel).toEqual('default')
})
it('should set "asIframe" flag to true when a controller broadcasts setIframe event', function() {
rootScope.$broadcast('setIframe', true);
expect(scope.asIframe).toEqual(true);
});
});
it('should set "asIframe" flag to true when a controller broadcasts setIframe event', function () {
rootScope.$broadcast('setIframe', true)
expect(scope.asIframe).toEqual(true)
})
})

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
var zeppelinWebApp = angular.module('zeppelinWebApp', [
let zeppelinWebApp = angular.module('zeppelinWebApp', [
'ngCookies',
'ngAnimate',
'ngRoute',
@ -35,22 +35,23 @@ var zeppelinWebApp = angular.module('zeppelinWebApp', [
'ngResource',
'ngclipboard'
])
.filter('breakFilter', function() {
return function(text) {
.filter('breakFilter', function () {
return function (text) {
// eslint-disable-next-line no-extra-boolean-cast
if (!!text) {
return text.replace(/\n/g, '<br />');
return text.replace(/\n/g, '<br />')
}
};
}
})
.config(function($httpProvider, $routeProvider, ngToastProvider) {
.config(function ($httpProvider, $routeProvider, ngToastProvider) {
// withCredentials when running locally via grunt
$httpProvider.defaults.withCredentials = true;
$httpProvider.defaults.withCredentials = true
var visBundleLoad = {
load: ['heliumService', function(heliumService) {
return heliumService.load;
let visBundleLoad = {
load: ['heliumService', function (heliumService) {
return heliumService.load
}]
};
}
$routeProvider
.when('/', {
@ -107,67 +108,67 @@ var zeppelinWebApp = angular.module('zeppelinWebApp', [
})
.otherwise({
redirectTo: '/'
});
})
ngToastProvider.configure({
dismissButton: true,
dismissOnClick: false,
combineDuplications: true,
timeout: 6000
});
})
})
//handel logout on API failure
// handel logout on API failure
.config(function ($httpProvider, $provide) {
$provide.factory('httpInterceptor', function ($q, $rootScope) {
return {
'responseError': function (rejection) {
if (rejection.status === 405) {
var data = {};
data.info = '';
$rootScope.$broadcast('session_logout', data);
let data = {}
data.info = ''
$rootScope.$broadcast('session_logout', data)
}
$rootScope.$broadcast('httpResponseError', rejection);
return $q.reject(rejection);
$rootScope.$broadcast('httpResponseError', rejection)
return $q.reject(rejection)
}
};
});
$httpProvider.interceptors.push('httpInterceptor');
}
})
$httpProvider.interceptors.push('httpInterceptor')
})
.constant('TRASH_FOLDER_ID', '~Trash');
.constant('TRASH_FOLDER_ID', '~Trash')
function auth() {
var $http = angular.injector(['ng']).get('$http');
var baseUrlSrv = angular.injector(['zeppelinWebApp']).get('baseUrlSrv');
function auth () {
let $http = angular.injector(['ng']).get('$http')
let baseUrlSrv = angular.injector(['zeppelinWebApp']).get('baseUrlSrv')
// withCredentials when running locally via grunt
$http.defaults.withCredentials = true;
$http.defaults.withCredentials = true
jQuery.ajaxSetup({
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true
});
return $http.get(baseUrlSrv.getRestApiBase() + '/security/ticket').then(function(response) {
zeppelinWebApp.run(function($rootScope) {
$rootScope.ticket = angular.fromJson(response.data).body;
});
}, function(errorResponse) {
})
return $http.get(baseUrlSrv.getRestApiBase() + '/security/ticket').then(function (response) {
zeppelinWebApp.run(function ($rootScope) {
$rootScope.ticket = angular.fromJson(response.data).body
})
}, function (errorResponse) {
// Handle error case
});
})
}
function bootstrapApplication() {
zeppelinWebApp.run(function($rootScope, $location) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
function bootstrapApplication () {
zeppelinWebApp.run(function ($rootScope, $location) {
$rootScope.$on('$routeChangeStart', function (event, next, current) {
if (!$rootScope.ticket && next.$$route && !next.$$route.publicAccess) {
$location.path('/');
$location.path('/')
}
});
});
angular.bootstrap(document, ['zeppelinWebApp']);
})
})
angular.bootstrap(document, ['zeppelinWebApp'])
}
angular.element(document).ready(function() {
auth().then(bootstrapApplication);
});
angular.element(document).ready(function () {
auth().then(bootstrapApplication)
})

View file

@ -12,38 +12,38 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('ConfigurationCtrl', ConfigurationCtrl);
angular.module('zeppelinWebApp').controller('ConfigurationCtrl', ConfigurationCtrl)
function ConfigurationCtrl($scope, $rootScope, $http, baseUrlSrv, ngToast) {
'ngInject';
function ConfigurationCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
'ngInject'
$scope.configrations = [];
$scope._ = _;
ngToast.dismiss();
$scope.configrations = []
$scope._ = _
ngToast.dismiss()
var getConfigurations = function() {
$http.get(baseUrlSrv.getRestApiBase() + '/configurations/all').
success(function(data, status, headers, config) {
$scope.configurations = data.body;
}).
error(function(data, status, headers, config) {
let getConfigurations = function () {
$http.get(baseUrlSrv.getRestApiBase() + '/configurations/all')
.success(function (data, status, headers, config) {
$scope.configurations = data.body
})
.error(function (data, status, headers, config) {
if (status === 401) {
ngToast.danger({
content: 'You don\'t have permission on this page',
verticalPosition: 'bottom',
timeout: '3000'
});
setTimeout(function() {
window.location.replace('/');
}, 3000);
})
setTimeout(function () {
window.location.replace('/')
}, 3000)
}
console.log('Error %o %o', status, data.message);
});
};
console.log('Error %o %o', status, data.message)
})
}
var init = function() {
getConfigurations();
};
let init = function () {
getConfigurations()
}
init();
init()
}

View file

@ -12,179 +12,178 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('CredentialCtrl', CredentialCtrl);
angular.module('zeppelinWebApp').controller('CredentialCtrl', CredentialCtrl)
function CredentialCtrl($scope, $rootScope, $http, baseUrlSrv, ngToast) {
'ngInject';
function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
'ngInject'
$scope._ = _;
ngToast.dismiss();
$scope._ = _
ngToast.dismiss()
$scope.credentialInfo = [];
$scope.showAddNewCredentialInfo = false;
$scope.availableInterpreters = [];
$scope.credentialInfo = []
$scope.showAddNewCredentialInfo = false
$scope.availableInterpreters = []
var getCredentialInfo = function() {
$http.get(baseUrlSrv.getRestApiBase() + '/credential').
success(function(data, status, headers, config) {
$scope.credentialInfo = _.map(data.body.userCredentials, function(value, prop) {
return {entity: prop, password: value.password, username: value.username};
});
console.log('Success %o %o', status, $scope.credentialInfo);
}).
error(function(data, status, headers, config) {
let getCredentialInfo = function () {
$http.get(baseUrlSrv.getRestApiBase() + '/credential')
.success(function (data, status, headers, config) {
$scope.credentialInfo = _.map(data.body.userCredentials, function (value, prop) {
return {entity: prop, password: value.password, username: value.username}
})
console.log('Success %o %o', status, $scope.credentialInfo)
})
.error(function (data, status, headers, config) {
if (status === 401) {
ngToast.danger({
content: 'You don\'t have permission on this page',
verticalPosition: 'bottom',
timeout: '3000'
});
setTimeout(function() {
window.location.replace('/');
}, 3000);
})
setTimeout(function () {
window.location.replace('/')
}, 3000)
}
console.log('Error %o %o', status, data.message);
});
};
console.log('Error %o %o', status, data.message)
})
}
$scope.addNewCredentialInfo = function() {
$scope.addNewCredentialInfo = function () {
if ($scope.entity && _.isEmpty($scope.entity.trim()) &&
$scope.username && _.isEmpty($scope.username.trim())) {
ngToast.danger({
content: 'Username \\ Entity can not be empty.',
verticalPosition: 'bottom',
timeout: '3000'
});
return;
})
return
}
var newCredential = {
let newCredential = {
'entity': $scope.entity,
'username': $scope.username,
'password': $scope.password
};
}
$http.put(baseUrlSrv.getRestApiBase() + '/credential', newCredential).
success(function(data, status, headers, config) {
$http.put(baseUrlSrv.getRestApiBase() + '/credential', newCredential)
.success(function (data, status, headers, config) {
ngToast.success({
content: 'Successfully saved credentials.',
verticalPosition: 'bottom',
timeout: '3000'
});
$scope.credentialInfo.push(newCredential);
resetCredentialInfo();
$scope.showAddNewCredentialInfo = false;
console.log('Success %o %o', status, data.message);
}).
error(function(data, status, headers, config) {
})
$scope.credentialInfo.push(newCredential)
resetCredentialInfo()
$scope.showAddNewCredentialInfo = false
console.log('Success %o %o', status, data.message)
})
.error(function (data, status, headers, config) {
ngToast.danger({
content: 'Error saving credentials',
verticalPosition: 'bottom',
timeout: '3000'
});
console.log('Error %o %o', status, data.message);
});
};
})
console.log('Error %o %o', status, data.message)
})
}
var getAvailableInterpreters = function() {
let getAvailableInterpreters = function () {
$http.get(baseUrlSrv.getRestApiBase() + '/interpreter/setting')
.success(function(data, status, headers, config) {
for (var setting = 0; setting < data.body.length; setting++) {
.success(function (data, status, headers, config) {
for (let setting = 0; setting < data.body.length; setting++) {
$scope.availableInterpreters.push(
data.body[setting].group + '.' + data.body[setting].name);
data.body[setting].group + '.' + data.body[setting].name)
}
angular.element('#entityname').autocomplete({
source: $scope.availableInterpreters,
select: function(event, selected) {
$scope.entity = selected.item.value;
return false;
select: function (event, selected) {
$scope.entity = selected.item.value
return false
}
});
}).error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
};
})
}).error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message)
})
}
$scope.toggleAddNewCredentialInfo = function() {
$scope.toggleAddNewCredentialInfo = function () {
if ($scope.showAddNewCredentialInfo) {
$scope.showAddNewCredentialInfo = false;
$scope.showAddNewCredentialInfo = false
} else {
$scope.showAddNewCredentialInfo = true;
$scope.showAddNewCredentialInfo = true
}
};
}
$scope.cancelCredentialInfo = function() {
$scope.showAddNewCredentialInfo = false;
resetCredentialInfo();
};
$scope.cancelCredentialInfo = function () {
$scope.showAddNewCredentialInfo = false
resetCredentialInfo()
}
var resetCredentialInfo = function() {
$scope.entity = '';
$scope.username = '';
$scope.password = '';
};
const resetCredentialInfo = function () {
$scope.entity = ''
$scope.username = ''
$scope.password = ''
}
$scope.copyOriginCredentialsInfo = function() {
$scope.copyOriginCredentialsInfo = function () {
ngToast.info({
content: 'Since entity is a unique key, you can edit only username & password',
verticalPosition: 'bottom',
timeout: '3000'
});
};
})
}
$scope.updateCredentialInfo = function(form, data, entity) {
var request = {
$scope.updateCredentialInfo = function (form, data, entity) {
let request = {
entity: entity,
username: data.username,
password: data.password
};
}
$http.put(baseUrlSrv.getRestApiBase() + '/credential/', request).
success(function(data, status, headers, config) {
var index = _.findIndex($scope.credentialInfo, {'entity': entity});
$scope.credentialInfo[index] = request;
return true;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
$http.put(baseUrlSrv.getRestApiBase() + '/credential/', request)
.success(function (data, status, headers, config) {
let index = _.findIndex($scope.credentialInfo, {'entity': entity})
$scope.credentialInfo[index] = request
return true
})
.error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message)
ngToast.danger({
content: 'We couldn\'t save the credential',
verticalPosition: 'bottom',
timeout: '3000'
});
form.$show();
});
return false;
};
})
form.$show()
})
return false
}
$scope.removeCredentialInfo = function(entity) {
$scope.removeCredentialInfo = function (entity) {
BootstrapDialog.confirm({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: '',
message: 'Do you want to delete this credential information?',
callback: function(result) {
callback: function (result) {
if (result) {
$http.delete(baseUrlSrv.getRestApiBase() + '/credential/' + entity).
success(function(data, status, headers, config) {
var index = _.findIndex($scope.credentialInfo, {'entity': entity});
$scope.credentialInfo.splice(index, 1);
console.log('Success %o %o', status, data.message);
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
$http.delete(baseUrlSrv.getRestApiBase() + '/credential/' + entity)
.success(function (data, status, headers, config) {
let index = _.findIndex($scope.credentialInfo, {'entity': entity})
$scope.credentialInfo.splice(index, 1)
console.log('Success %o %o', status, data.message)
})
.error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message)
})
}
}
});
};
})
}
var init = function() {
getAvailableInterpreters();
getCredentialInfo();
};
let init = function () {
getAvailableInterpreters()
getCredentialInfo()
}
init();
init()
}

View file

@ -16,15 +16,15 @@
* 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);
};
constructor (columns, rows, comment) {
this.columns = columns || []
this.rows = rows || []
this.comment = comment || ''
this._numericValidator = this._numericValidator.bind(this)
}
getHandsonTableConfig(columns, columnNames, resultRows) {
var self = this;
getHandsonTableConfig (columns, columnNames, resultRows) {
let self = this
return {
colHeaders: columnNames,
data: resultRows,
@ -41,159 +41,161 @@ export default class HandsonHelper {
fillHandle: false,
fragmentSelection: true,
disableVisualSelection: true,
cells: function(ro, co, pro) {
var cellProperties = {};
var 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) {
var instance = this;
var menu = self._buildDropDownMenu(columns[col].type);
var 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);
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)
}
TH.firstChild.appendChild(button);
TH.style['white-space'] = 'normal';
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) {
var changeTypeMenu;
var position;
var removeMenu;
_addButtonMenuEvent (button, menu) {
Handsontable.Dom.addEvent(button, 'click', function (event) {
let changeTypeMenu
let position
let removeMenu
document.body.appendChild(menu);
document.body.appendChild(menu)
event.preventDefault();
event.stopImmediatePropagation();
event.preventDefault()
event.stopImmediatePropagation()
changeTypeMenu = document.querySelectorAll('.changeTypeMenu');
changeTypeMenu = document.querySelectorAll('.changeTypeMenu')
for (var i = 0, len = changeTypeMenu.length; i < len; i++) {
changeTypeMenu[i].style.display = 'none';
for (let i = 0, len = changeTypeMenu.length; i < len; i++) {
changeTypeMenu[i].style.display = 'none'
}
menu.style.display = 'block';
position = button.getBoundingClientRect();
menu.style.display = 'block'
position = button.getBoundingClientRect()
menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px';
menu.style.left = (position.left) + 'px';
menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px'
menu.style.left = (position.left) + 'px'
removeMenu = function(event) {
removeMenu = function (event) {
if (menu.parentNode) {
menu.parentNode.removeChild(menu);
menu.parentNode.removeChild(menu)
}
};
Handsontable.Dom.removeEvent(document, 'click', removeMenu);
Handsontable.Dom.addEvent(document, 'click', removeMenu);
});
}
Handsontable.Dom.removeEvent(document, 'click', removeMenu)
Handsontable.Dom.addEvent(document, 'click', removeMenu)
})
}
_buildDropDownMenu(activeCellType) {
var menu = document.createElement('UL');
var types = ['text', 'numeric', 'date'];
var item;
_buildDropDownMenu (activeCellType) {
let menu = document.createElement('UL')
let types = ['text', 'numeric', 'date']
let item
menu.className = 'changeTypeMenu';
menu.className = 'changeTypeMenu'
for (var i = 0, len = types.length; i < len; i++) {
item = document.createElement('LI');
for (let i = 0, len = types.length; i < len; i++) {
item = document.createElement('LI')
if ('innerText' in item) {
item.innerText = types[i];
item.innerText = types[i]
} else {
item.textContent = types[i];
item.textContent = types[i]
}
item.data = {'colType': types[i]};
item.data = {'colType': types[i]}
if (activeCellType === types[i]) {
item.className = 'active';
item.className = 'active'
}
menu.appendChild(item);
menu.appendChild(item)
}
return menu;
return menu
}
_buildTypeSwitchButton() {
var button = document.createElement('BUTTON');
_buildTypeSwitchButton () {
let button = document.createElement('BUTTON')
button.innerHTML = '\u25BC';
button.className = 'changeType';
button.innerHTML = '\u25BC'
button.className = 'changeType'
return button;
return button
}
_isNumeric(value) {
_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 true
}
}
}
return false;
return false
}
_cellRenderer(instance, td, row, col, prop, value, cellProperties, colType) {
_cellRenderer (instance, td, row, col, prop, value, cellProperties, colType) {
if (colType === 'numeric' && this._isNumeric(value)) {
cellProperties.format = '0,0.[00000]';
td.style.textAlign = 'left';
Handsontable.renderers.NumericRenderer.apply(this, arguments);
} else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) {
td.innerHTML = value.substring('%html'.length);
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 {
Handsontable.renderers.TextRenderer.apply(this, arguments);
// eslint-disable-next-line prefer-rest-params
Handsontable.renderers.TextRenderer.apply(this, arguments)
}
}
_dateValidator(value, callback) {
var d = moment(value);
return callback(d.isValid());
_dateValidator (value, callback) {
let d = moment(value)
return callback(d.isValid())
}
_numericValidator(value, callback) {
return callback(this._isNumeric(value));
_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);
_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);
instance.sort(col, instance.sortOrder)
}
}
_isColumnSorted(instance, col) {
return instance.sortingEnabled && instance.sortColumn === col;
_isColumnSorted (instance, col) {
return instance.sortingEnabled && instance.sortColumn === col
}
_setColumnValidator(columns, col) {
_setColumnValidator (columns, col) {
if (columns[col].type === 'numeric') {
columns[col].validator = this._numericValidator;
columns[col].validator = this._numericValidator
} else if (columns[col].type === 'date') {
columns[col].validator = this._dateValidator;
columns[col].validator = this._dateValidator
} else {
columns[col].validator = null;
columns[col].validator = null
}
}
}

View file

@ -16,85 +16,86 @@ export const HeliumConfFieldType = {
NUMBER: 'number',
JSON: 'json',
STRING: 'string',
};
}
/**
* @param persisted <Object> including `type`, `description`, `defaultValue` for each conf key
* @param spec <Object> including `value` for each conf key
*/
export function mergePersistedConfWithSpec(persisted, spec) {
const confs = [];
export function mergePersistedConfWithSpec (persisted, spec) {
const confs = []
for(let name in spec) {
const specField = spec[name];
const persistedValue = persisted[name];
for (let name in spec) {
const specField = spec[name]
const persistedValue = persisted[name]
const value = (persistedValue) ? persistedValue : specField.defaultValue;
const value = (persistedValue) ? persistedValue : specField.defaultValue
const merged = {
name: name, type: specField.type, description: specField.description,
value: value, defaultValue: specField.defaultValue,
};
confs.push(merged);
}
return confs;
}
export function createPackageConf(defaultPackages, persistedPackacgeConfs) {
let packageConfs = {};
for (let name in defaultPackages) {
const pkgInfo = defaultPackages[name];
const configSpec = pkgInfo.pkg.config;
if (!configSpec) { continue; }
const version = pkgInfo.pkg.version;
if (!version) { continue; }
let config = {};
if (persistedPackacgeConfs[name] && persistedPackacgeConfs[name][version]) {
config = persistedPackacgeConfs[name][version];
name: name,
type: specField.type,
description: specField.description,
value: value,
defaultValue: specField.defaultValue,
}
const confs = mergePersistedConfWithSpec(config, configSpec);
packageConfs[name] = confs;
confs.push(merged)
}
return packageConfs;
return confs
}
export function parseConfigValue(type, stringified) {
let value = stringified;
export function createPackageConf (defaultPackages, persistedPackacgeConfs) {
let packageConfs = {}
for (let name in defaultPackages) {
const pkgInfo = defaultPackages[name]
const configSpec = pkgInfo.pkg.config
if (!configSpec) { continue }
const version = pkgInfo.pkg.version
if (!version) { continue }
let config = {}
if (persistedPackacgeConfs[name] && persistedPackacgeConfs[name][version]) {
config = persistedPackacgeConfs[name][version]
}
const confs = mergePersistedConfWithSpec(config, configSpec)
packageConfs[name] = confs
}
return packageConfs
}
export function parseConfigValue (type, stringified) {
let value = stringified
try {
if (HeliumConfFieldType.NUMBER === type) {
value = parseFloat(stringified);
value = parseFloat(stringified)
} else if (HeliumConfFieldType.JSON === type) {
value = JSON.parse(stringified);
value = JSON.parse(stringified)
}
} catch(error) {
} catch (error) {
// return just the stringified one
console.error(`Failed to parse conf type ${type}, value ${value}`);
console.error(`Failed to parse conf type ${type}, value ${value}`)
}
return value;
return value
}
/**
* create persistable config object
*/
export function createPersistableConfig(currentConf) {
export function createPersistableConfig (currentConf) {
// persist key-value only
// since other info (e.g type, desc) can be provided by default config
const filtered = currentConf.reduce((acc, c) => {
let value = parseConfigValue(c.type, c.value);
acc[c.name] = value;
return acc;
}, {});
let value = parseConfigValue(c.type, c.value)
acc[c.name] = value
return acc
}, {})
return filtered;
return filtered
}

View file

@ -12,17 +12,17 @@
* limitations under the License.
*/
import { HeliumType, } from '../../components/helium/helium-type';
import { HeliumType, } from '../../components/helium/helium-type'
export default function HeliumCtrl($scope, $rootScope, $sce,
export default function HeliumCtrl ($scope, $rootScope, $sce,
baseUrlSrv, ngToast, heliumService) {
'ngInject';
'ngInject'
$scope.pkgSearchResults = {};
$scope.defaultPackages = {};
$scope.showVersions = {};
$scope.bundleOrder = [];
$scope.bundleOrderChanged = false;
$scope.pkgSearchResults = {}
$scope.defaultPackages = {}
$scope.showVersions = {}
$scope.bundleOrder = []
$scope.bundleOrderChanged = false
$scope.vizTypePkg = {}
$scope.spellTypePkg = {}
$scope.intpTypePkg = {}
@ -30,168 +30,168 @@ export default function HeliumCtrl($scope, $rootScope, $sce,
$scope.numberOfEachPackageByType = {}
$scope.allPackageTypes = [HeliumType][0]
$scope.pkgListByType = 'VISUALIZATION'
$scope.defaultPackageConfigs = {}; // { pkgName, [{name, type, desc, value, defaultValue}] }
$scope.intpDefaultIcon = $sce.trustAsHtml('<img src="../assets/images/maven_default_icon.png" style="width: 12px"/>');
$scope.defaultPackageConfigs = {} // { pkgName, [{name, type, desc, value, defaultValue}] }
$scope.intpDefaultIcon = $sce.trustAsHtml('<img src="../assets/images/maven_default_icon.png" style="width: 12px"/>')
function init() {
function init () {
// get all package info and set config
heliumService.getAllPackageInfoAndDefaultPackages()
.then(({ pkgSearchResults, defaultPackages }) => {
// pagination
$scope.itemsPerPage = 10;
$scope.currentPage = 1;
$scope.maxSize = 5;
$scope.pkgSearchResults = pkgSearchResults;
$scope.defaultPackages = defaultPackages;
classifyPkgType($scope.defaultPackages);
$scope.itemsPerPage = 10
$scope.currentPage = 1
$scope.maxSize = 5
$scope.pkgSearchResults = pkgSearchResults
$scope.defaultPackages = defaultPackages
classifyPkgType($scope.defaultPackages)
return heliumService.getAllPackageConfigs()
})
.then(defaultPackageConfigs => {
$scope.defaultPackageConfigs = defaultPackageConfigs;
});
$scope.defaultPackageConfigs = defaultPackageConfigs
})
// 2. get vis package order
heliumService.getVisualizationPackageOrder()
.then(visPackageOrder => {
$scope.bundleOrder = visPackageOrder;
$scope.bundleOrderChanged = false;
});
};
$scope.bundleOrder = visPackageOrder
$scope.bundleOrderChanged = false
})
}
var orderPackageByPubDate = function(a, b) {
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
a.pkg.published = new Date().getTime()
}
return new Date(a.pkg.published).getTime() - new Date(b.pkg.published).getTime();
};
return new Date(a.pkg.published).getTime() - new Date(b.pkg.published).getTime()
}
var classifyPkgType = function(packageInfo) {
var allTypesOfPkg = {};
var vizTypePkg = [];
var spellTypePkg = [];
var intpTypePkg = [];
var appTypePkg = [];
const classifyPkgType = function (packageInfo) {
let allTypesOfPkg = {}
let vizTypePkg = []
let spellTypePkg = []
let intpTypePkg = []
let appTypePkg = []
var packageInfoArr = Object.keys(packageInfo).map(key => packageInfo[key])
packageInfoArr = packageInfoArr.sort(orderPackageByPubDate).reverse();
let packageInfoArr = Object.keys(packageInfo).map(key => packageInfo[key])
packageInfoArr = packageInfoArr.sort(orderPackageByPubDate).reverse()
for (var name in packageInfoArr) {
var pkgs = packageInfoArr[name];
var pkgType = pkgs.pkg.type;
for (let name in packageInfoArr) {
let pkgs = packageInfoArr[name]
let pkgType = pkgs.pkg.type
switch (pkgType) {
case HeliumType.VISUALIZATION:
vizTypePkg.push(pkgs);
break;
vizTypePkg.push(pkgs)
break
case HeliumType.SPELL:
spellTypePkg.push(pkgs);
break;
spellTypePkg.push(pkgs)
break
case HeliumType.INTERPRETER:
intpTypePkg.push(pkgs);
break;
intpTypePkg.push(pkgs)
break
case HeliumType.APPLICATION:
appTypePkg.push(pkgs);
break;
appTypePkg.push(pkgs)
break
}
}
var pkgsArr = [
let pkgsArr = [
vizTypePkg,
spellTypePkg,
intpTypePkg,
appTypePkg
]
for (var idx in _.keys(HeliumType)) {
allTypesOfPkg[_.keys(HeliumType)[idx]] = pkgsArr[idx];
for (let idx in _.keys(HeliumType)) {
allTypesOfPkg[_.keys(HeliumType)[idx]] = pkgsArr[idx]
}
$scope.allTypesOfPkg = allTypesOfPkg;
};
$scope.allTypesOfPkg = allTypesOfPkg
}
$scope.bundleOrderListeners = {
accept: function(sourceItemHandleScope, destSortableScope) {return true;},
itemMoved: function(event) {},
orderChanged: function(event) {
$scope.bundleOrderChanged = true;
accept: function (sourceItemHandleScope, destSortableScope) { return true },
itemMoved: function (event) {},
orderChanged: function (event) {
$scope.bundleOrderChanged = true
}
};
}
$scope.saveBundleOrder = function() {
var confirm = BootstrapDialog.confirm({
$scope.saveBundleOrder = function () {
const confirm = BootstrapDialog.confirm({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: '',
message: 'Save changes?',
callback: function(result) {
callback: function (result) {
if (result) {
confirm.$modalFooter.find('button').addClass('disabled');
confirm.$modalFooter.find('button').addClass('disabled')
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');
.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
});
});
return false;
})
})
return false
}
}
});
};
})
}
var getLicense = function(name, artifact) {
var filteredPkgSearchResults = _.filter($scope.defaultPackages[name], function(p) {
return p.artifact === artifact;
});
let getLicense = function (name, artifact) {
let filteredPkgSearchResults = _.filter($scope.defaultPackages[name], function (p) {
return p.artifact === artifact
})
var license;
let license
if (filteredPkgSearchResults.length === 0) {
filteredPkgSearchResults = _.filter($scope.pkgSearchResults[name], function(p) {
return p.pkg.artifact === artifact;
});
filteredPkgSearchResults = _.filter($scope.pkgSearchResults[name], function (p) {
return p.pkg.artifact === artifact
})
if (filteredPkgSearchResults.length > 0) {
license = filteredPkgSearchResults[0].pkg.license;
license = filteredPkgSearchResults[0].pkg.license
}
} else {
license = filteredPkgSearchResults[0].license;
license = filteredPkgSearchResults[0].license
}
if (!license) {
license = 'Unknown';
license = 'Unknown'
}
return license;
return license
}
const getHeliumTypeText = function(type) {
const getHeliumTypeText = function (type) {
if (type === HeliumType.VISUALIZATION) {
return `<a target="_blank" href="https://zeppelin.apache.org/docs/${$rootScope.zeppelinVersion}/development/writingzeppelinvisualization.html">${type}</a>`; // eslint-disable-line max-len
return `<a target="_blank" href="https://zeppelin.apache.org/docs/${$rootScope.zeppelinVersion}/development/writingzeppelinvisualization.html">${type}</a>` // eslint-disable-line max-len
} else if (type === HeliumType.SPELL) {
return `<a target="_blank" href="https://zeppelin.apache.org/docs/${$rootScope.zeppelinVersion}/development/writingzeppelinspell.html">${type}</a>`; // eslint-disable-line max-len
return `<a target="_blank" href="https://zeppelin.apache.org/docs/${$rootScope.zeppelinVersion}/development/writingzeppelinspell.html">${type}</a>` // eslint-disable-line max-len
} else {
return type;
return type
}
}
$scope.enable = function(name, artifact, type, groupId, description) {
var license = getLicense(name, artifact);
var mavenArtifactInfoToHTML = groupId +':'+ artifact.split('@')[0] + ':' + artifact.split('@')[1];
var zeppelinVersion = $rootScope.zeppelinVersion;
var url = 'https://zeppelin.apache.org/docs/' + zeppelinVersion + '/manual/interpreterinstallation.html';
$scope.enable = function (name, artifact, type, groupId, description) {
let license = getLicense(name, artifact)
let mavenArtifactInfoToHTML = groupId + ':' + artifact.split('@')[0] + ':' + artifact.split('@')[1]
let zeppelinVersion = $rootScope.zeppelinVersion
let url = 'https://zeppelin.apache.org/docs/' + zeppelinVersion + '/manual/interpreterinstallation.html'
var confirm = ''
let confirm = ''
if (type === HeliumType.INTERPRETER) {
confirm = BootstrapDialog.show({
title: '',
@ -201,11 +201,11 @@ export default function HeliumCtrl($scope, $rootScope, $sce,
' and all of its transitive dependencies into interpreter/interpreter-name directory.<p>' +
'<div class="highlight"><pre><code class="text language-text" data-lang="text" style="font-size: 11.5px">' +
'./bin/install-interpreter.sh --name "interpreter-name" --artifact ' +
mavenArtifactInfoToHTML +' </code></pre>' +
mavenArtifactInfoToHTML + ' </code></pre>' +
'<p>After restart Zeppelin, create interpreter setting and bind it with your note. ' +
'For more detailed information, see <a target="_blank" href=' +
url + '>Interpreter Installation.</a></p>'
});
})
} else {
confirm = BootstrapDialog.confirm({
closable: false,
@ -226,136 +226,136 @@ export default function HeliumCtrl($scope, $rootScope, $sce,
`<div style="color:gray">${license}</div>`,
callback: function (result) {
if (result) {
confirm.$modalFooter.find('button').addClass('disabled');
confirm.$modalFooter.find('button').addClass('disabled')
confirm.$modalFooter.find('button:contains("OK")')
.html('<i class="fa fa-circle-o-notch fa-spin"></i> Enabling');
.html('<i class="fa fa-circle-o-notch fa-spin"></i> Enabling')
heliumService.enable(name, artifact, type).success(function (data, status) {
init();
confirm.close();
init()
confirm.close()
}).error(function (data, status) {
confirm.close();
console.log('Failed to enable package %o %o. %o', name, artifact, data);
confirm.close()
console.log('Failed to enable package %o %o. %o', name, artifact, data)
BootstrapDialog.show({
title: 'Error on enabling ' + name,
message: data.message
});
});
return false;
})
})
return false
}
}
});
})
}
};
}
$scope.disable = function(name, artifact) {
var confirm = BootstrapDialog.confirm({
$scope.disable = function (name, artifact) {
const confirm = BootstrapDialog.confirm({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: '<div style="font-weight: 300;">Do you want to disable Helium Package?</div>',
message: artifact,
callback: function(result) {
callback: function (result) {
if (result) {
confirm.$modalFooter.find('button').addClass('disabled');
confirm.$modalFooter.find('button').addClass('disabled')
confirm.$modalFooter.find('button:contains("OK")')
.html('<i class="fa fa-circle-o-notch fa-spin"></i> Disabling');
heliumService.disable(name).
success(function(data, status) {
init();
confirm.close();
}).
error(function(data, status) {
confirm.close();
console.log('Failed to disable package %o. %o', name, data);
.html('<i class="fa fa-circle-o-notch fa-spin"></i> Disabling')
heliumService.disable(name)
.success(function (data, status) {
init()
confirm.close()
})
.error(function (data, status) {
confirm.close()
console.log('Failed to disable package %o. %o', name, data)
BootstrapDialog.show({
title: 'Error on disabling ' + name,
message: data.message
});
});
return false;
})
})
return false
}
}
});
};
})
}
$scope.toggleVersions = function(pkgName) {
$scope.toggleVersions = function (pkgName) {
if ($scope.showVersions[pkgName]) {
$scope.showVersions[pkgName] = false;
$scope.showVersions[pkgName] = false
} else {
$scope.showVersions[pkgName] = true;
$scope.showVersions[pkgName] = true
}
};
}
$scope.isLocalPackage = function(pkgSearchResult) {
const pkg = pkgSearchResult.pkg;
return pkg.artifact && !pkg.artifact.includes('@');
};
$scope.isLocalPackage = function (pkgSearchResult) {
const pkg = pkgSearchResult.pkg
return pkg.artifact && !pkg.artifact.includes('@')
}
$scope.hasNpmLink = function(pkgSearchResult) {
const pkg = pkgSearchResult.pkg;
$scope.hasNpmLink = function (pkgSearchResult) {
const pkg = pkgSearchResult.pkg
return (pkg.type === HeliumType.SPELL || pkg.type === HeliumType.VISUALIZATION) &&
!$scope.isLocalPackage(pkgSearchResult);
};
!$scope.isLocalPackage(pkgSearchResult)
}
$scope.hasMavenLink = function(pkgSearchResult) {
const pkg = pkgSearchResult.pkg;
$scope.hasMavenLink = function (pkgSearchResult) {
const pkg = pkgSearchResult.pkg
return (pkg.type === HeliumType.APPLICATION || pkg.type === HeliumType.INTERPRETER) &&
!$scope.isLocalPackage(pkgSearchResult);
};
!$scope.isLocalPackage(pkgSearchResult)
}
$scope.getPackageSize = function(pkgSearchResult, targetPkgType) {
var result = []
$scope.getPackageSize = function (pkgSearchResult, targetPkgType) {
let result = []
_.map(pkgSearchResult, function (pkg) {
result.push(_.find(pkg, {type: targetPkgType}))
})
return _.compact(result).length
}
$scope.configExists = function(pkgSearchResult) {
$scope.configExists = function (pkgSearchResult) {
// helium package config is persisted per version
return pkgSearchResult.pkg.config && pkgSearchResult.pkg.artifact;
};
$scope.configOpened = function(pkgSearchResult) {
return pkgSearchResult.configOpened && !pkgSearchResult.configFetching;
};
$scope.getConfigButtonClass = function(pkgSearchResult) {
return (pkgSearchResult.configOpened && pkgSearchResult.configFetching) ?
'disabled' : '';
return pkgSearchResult.pkg.config && pkgSearchResult.pkg.artifact
}
$scope.toggleConfigButton = function(pkgSearchResult) {
$scope.configOpened = function (pkgSearchResult) {
return pkgSearchResult.configOpened && !pkgSearchResult.configFetching
}
$scope.getConfigButtonClass = function (pkgSearchResult) {
return (pkgSearchResult.configOpened && pkgSearchResult.configFetching)
? 'disabled' : ''
}
$scope.toggleConfigButton = function (pkgSearchResult) {
if (pkgSearchResult.configOpened) {
pkgSearchResult.configOpened = false;
return;
pkgSearchResult.configOpened = false
return
}
const pkg = pkgSearchResult.pkg;
const pkgName = pkg.name;
pkgSearchResult.configFetching = true;
pkgSearchResult.configOpened = true;
const pkg = pkgSearchResult.pkg
const pkgName = pkg.name
pkgSearchResult.configFetching = true
pkgSearchResult.configOpened = true
heliumService.getSinglePackageConfigs(pkg)
.then(confs => {
$scope.defaultPackageConfigs[pkgName] = confs;
pkgSearchResult.configFetching = false;
});
};
$scope.defaultPackageConfigs[pkgName] = confs
pkgSearchResult.configFetching = false
})
}
$scope.saveConfig = function(pkgSearchResult) {
const pkgName = pkgSearchResult.pkg.name;
const currentConf = $scope.defaultPackageConfigs[pkgName];
$scope.saveConfig = function (pkgSearchResult) {
const pkgName = pkgSearchResult.pkg.name
const currentConf = $scope.defaultPackageConfigs[pkgName]
heliumService.saveConfig(pkgSearchResult.pkg, currentConf, () => {
// close after config is saved
pkgSearchResult.configOpened = false;
});
};
pkgSearchResult.configOpened = false
})
}
$scope.getDescriptionText = function(pkgSearchResult) {
return $sce.trustAsHtml(pkgSearchResult.pkg.description);
};
$scope.getDescriptionText = function (pkgSearchResult) {
return $sce.trustAsHtml(pkgSearchResult.pkg.description)
}
init();
init()
}

View file

@ -12,8 +12,7 @@
* limitations under the License.
*/
import HeliumController from './helium.controller';
import HeliumController from './helium.controller'
angular.module('zeppelinWebApp')
.controller('HeliumCtrl', HeliumController);
.controller('HeliumCtrl', HeliumController)

View file

@ -12,135 +12,135 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('HomeCtrl', HomeCtrl);
angular.module('zeppelinWebApp').controller('HomeCtrl', HomeCtrl)
function HomeCtrl($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arrayOrderingSrv,
function HomeCtrl ($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arrayOrderingSrv,
ngToast, noteActionSrv, TRASH_FOLDER_ID) {
'ngInject';
'ngInject'
ngToast.dismiss();
var vm = this;
vm.notes = noteListDataFactory;
vm.websocketMsgSrv = websocketMsgSrv;
vm.arrayOrderingSrv = arrayOrderingSrv;
ngToast.dismiss()
let vm = this
vm.notes = noteListDataFactory
vm.websocketMsgSrv = websocketMsgSrv
vm.arrayOrderingSrv = arrayOrderingSrv
vm.notebookHome = false;
vm.noteCustomHome = true;
vm.notebookHome = false
vm.noteCustomHome = true
if ($rootScope.ticket !== undefined) {
vm.staticHome = false;
vm.staticHome = false
} else {
vm.staticHome = true;
vm.staticHome = true
}
$scope.isReloading = false;
$scope.TRASH_FOLDER_ID = TRASH_FOLDER_ID;
$scope.query = {q: ''};
$scope.isReloading = false
$scope.TRASH_FOLDER_ID = TRASH_FOLDER_ID
$scope.query = {q: ''}
$scope.initHome = function() {
websocketMsgSrv.getHomeNote();
vm.noteCustomHome = false;
};
$scope.initHome = function () {
websocketMsgSrv.getHomeNote()
vm.noteCustomHome = false
}
$scope.reloadNoteList = function() {
websocketMsgSrv.reloadAllNotesFromRepo();
$scope.isReloadingNotes = true;
};
$scope.reloadNoteList = function () {
websocketMsgSrv.reloadAllNotesFromRepo()
$scope.isReloadingNotes = true
}
$scope.toggleFolderNode = function(node) {
node.hidden = !node.hidden;
};
$scope.toggleFolderNode = function (node) {
node.hidden = !node.hidden
}
angular.element('#loginModal').on('hidden.bs.modal', function(e) {
$rootScope.$broadcast('initLoginValues');
});
angular.element('#loginModal').on('hidden.bs.modal', function (e) {
$rootScope.$broadcast('initLoginValues')
})
/*
** $scope.$on functions below
*/
$scope.$on('setNoteMenu', function(event, notes) {
$scope.isReloadingNotes = false;
});
$scope.$on('setNoteMenu', function (event, notes) {
$scope.isReloadingNotes = false
})
$scope.$on('setNoteContent', function(event, note) {
$scope.$on('setNoteContent', function (event, note) {
if (vm.noteCustomHome) {
return;
return
}
if (note) {
vm.note = note;
vm.note = note
// initialize look And Feel
$rootScope.$broadcast('setLookAndFeel', 'home');
$rootScope.$broadcast('setLookAndFeel', 'home')
// make it read only
vm.viewOnly = true;
vm.viewOnly = true
vm.notebookHome = true;
vm.staticHome = false;
vm.notebookHome = true
vm.staticHome = false
} else {
vm.staticHome = true;
vm.notebookHome = false;
vm.staticHome = true
vm.notebookHome = false
}
});
})
$scope.renameNote = function(nodeId, nodePath) {
noteActionSrv.renameNote(nodeId, nodePath);
};
$scope.renameNote = function (nodeId, nodePath) {
noteActionSrv.renameNote(nodeId, nodePath)
}
$scope.moveNoteToTrash = function(noteId) {
noteActionSrv.moveNoteToTrash(noteId, false);
};
$scope.moveNoteToTrash = function (noteId) {
noteActionSrv.moveNoteToTrash(noteId, false)
}
$scope.moveFolderToTrash = function(folderId) {
noteActionSrv.moveFolderToTrash(folderId);
};
$scope.moveFolderToTrash = function (folderId) {
noteActionSrv.moveFolderToTrash(folderId)
}
$scope.restoreNote = function(noteId) {
websocketMsgSrv.restoreNote(noteId);
};
$scope.restoreNote = function (noteId) {
websocketMsgSrv.restoreNote(noteId)
}
$scope.restoreFolder = function(folderId) {
websocketMsgSrv.restoreFolder(folderId);
};
$scope.restoreFolder = function (folderId) {
websocketMsgSrv.restoreFolder(folderId)
}
$scope.restoreAll = function() {
noteActionSrv.restoreAll();
};
$scope.restoreAll = function () {
noteActionSrv.restoreAll()
}
$scope.renameFolder = function(node) {
noteActionSrv.renameFolder(node.id);
};
$scope.renameFolder = function (node) {
noteActionSrv.renameFolder(node.id)
}
$scope.removeNote = function(noteId) {
noteActionSrv.removeNote(noteId, false);
};
$scope.removeNote = function (noteId) {
noteActionSrv.removeNote(noteId, false)
}
$scope.removeFolder = function(folderId) {
noteActionSrv.removeFolder(folderId);
};
$scope.removeFolder = function (folderId) {
noteActionSrv.removeFolder(folderId)
}
$scope.emptyTrash = function() {
noteActionSrv.emptyTrash();
};
$scope.emptyTrash = function () {
noteActionSrv.emptyTrash()
}
$scope.clearAllParagraphOutput = function(noteId) {
noteActionSrv.clearAllParagraphOutput(noteId);
};
$scope.clearAllParagraphOutput = function (noteId) {
noteActionSrv.clearAllParagraphOutput(noteId)
}
$scope.isFilterNote = function(note) {
$scope.isFilterNote = function (note) {
if (!$scope.query.q) {
return true;
return true
}
var noteName = note.name;
let noteName = note.name
if (noteName.toLowerCase().indexOf($scope.query.q.toLowerCase()) > -1) {
return true;
return true
}
return false;
};
return false
}
$scope.getNoteName = function(note) {
return arrayOrderingSrv.getNoteName(note);
};
$scope.getNoteName = function (note) {
return arrayOrderingSrv.getNoteName(note)
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,11 +12,11 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').filter('sortByKey', sortByKey);
angular.module('zeppelinWebApp').filter('sortByKey', sortByKey)
function sortByKey() {
return function(properties) {
var sortedKeys = properties ? Object.keys(properties) : [];
return sortedKeys.sort();
};
function sortByKey () {
return function (properties) {
let sortedKeys = properties ? Object.keys(properties) : []
return sortedKeys.sort()
}
}

View file

@ -13,172 +13,169 @@
*/
angular.module('zeppelinWebApp')
.controller('JobmanagerCtrl', JobmanagerCtrl);
.controller('JobmanagerCtrl', JobmanagerCtrl)
function JobmanagerCtrl($scope, websocketMsgSrv, $interval, ngToast, $q, $timeout, jobManagerFilter) {
'ngInject';
function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeout, jobManagerFilter) {
'ngInject'
ngToast.dismiss();
var asyncNotebookJobFilter = function(jobInfomations, filterConfig) {
return $q(function(resolve, reject) {
$scope.JobInfomationsByFilter = $scope.jobTypeFilter(jobInfomations, filterConfig);
resolve($scope.JobInfomationsByFilter);
});
};
ngToast.dismiss()
let asyncNotebookJobFilter = function (jobInfomations, filterConfig) {
return $q(function (resolve, reject) {
$scope.JobInfomationsByFilter = $scope.jobTypeFilter(jobInfomations, filterConfig)
resolve($scope.JobInfomationsByFilter)
})
}
$scope.doFiltering = function(jobInfomations, filterConfig) {
$scope.doFiltering = function (jobInfomations, filterConfig) {
asyncNotebookJobFilter(jobInfomations, filterConfig).then(
function() {
function () {
// success
$scope.isLoadingFilter = false;
$scope.isLoadingFilter = false
},
function() {
function () {
// failed
});
};
})
}
$scope.filterValueToName = function(filterValue, maxStringLength) {
$scope.filterValueToName = function (filterValue, maxStringLength) {
if ($scope.activeInterpreters === undefined) {
return;
return
}
var index = _.findIndex($scope.activeInterpreters, {value: filterValue});
let index = _.findIndex($scope.activeInterpreters, {value: filterValue})
if ($scope.activeInterpreters[index].name !== undefined) {
if (maxStringLength !== undefined && maxStringLength > $scope.activeInterpreters[index].name) {
return $scope.activeInterpreters[index].name.substr(0, maxStringLength - 3) + '...';
return $scope.activeInterpreters[index].name.substr(0, maxStringLength - 3) + '...'
}
return $scope.activeInterpreters[index].name;
return $scope.activeInterpreters[index].name
} else {
return 'Interpreter is not set';
return 'Interpreter is not set'
}
};
}
$scope.setFilterValue = function(filterValue) {
$scope.filterConfig.filterValueInterpreter = filterValue;
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig);
};
$scope.setFilterValue = function (filterValue) {
$scope.filterConfig.filterValueInterpreter = filterValue
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
}
$scope.onChangeRunJobToAlwaysTopToggle = function() {
$scope.filterConfig.isRunningAlwaysTop = !$scope.filterConfig.isRunningAlwaysTop;
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig);
};
$scope.onChangeRunJobToAlwaysTopToggle = function () {
$scope.filterConfig.isRunningAlwaysTop = !$scope.filterConfig.isRunningAlwaysTop
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
}
$scope.onChangeSortAsc = function() {
$scope.filterConfig.isSortByAsc = !$scope.filterConfig.isSortByAsc;
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig);
};
$scope.onChangeSortAsc = function () {
$scope.filterConfig.isSortByAsc = !$scope.filterConfig.isSortByAsc
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
}
$scope.doFilterInputTyping = function(keyEvent, jobInfomations, filterConfig) {
var RETURN_KEY_CODE = 13;
$timeout.cancel($scope.dofilterTimeoutObject);
$scope.isActiveSearchTimer = true;
$scope.dofilterTimeoutObject = $timeout(function() {
$scope.doFiltering(jobInfomations, filterConfig);
$scope.isActiveSearchTimer = false;
}, 10000);
$scope.doFilterInputTyping = function (keyEvent, jobInfomations, filterConfig) {
let RETURN_KEY_CODE = 13
$timeout.cancel($scope.dofilterTimeoutObject)
$scope.isActiveSearchTimer = true
$scope.dofilterTimeoutObject = $timeout(function () {
$scope.doFiltering(jobInfomations, filterConfig)
$scope.isActiveSearchTimer = false
}, 10000)
if (keyEvent.which === RETURN_KEY_CODE) {
$timeout.cancel($scope.dofilterTimeoutObject);
$scope.doFiltering(jobInfomations, filterConfig);
$scope.isActiveSearchTimer = false;
$timeout.cancel($scope.dofilterTimeoutObject)
$scope.doFiltering(jobInfomations, filterConfig)
$scope.isActiveSearchTimer = false
}
};
}
$scope.doForceFilterInputTyping = function(keyEvent, jobInfomations, filterConfig) {
$timeout.cancel($scope.dofilterTimeoutObject);
$scope.doFiltering(jobInfomations, filterConfig);
$scope.isActiveSearchTimer = false;
};
$scope.doForceFilterInputTyping = function (keyEvent, jobInfomations, filterConfig) {
$timeout.cancel($scope.dofilterTimeoutObject)
$scope.doFiltering(jobInfomations, filterConfig)
$scope.isActiveSearchTimer = false
}
$scope.init = function() {
$scope.isLoadingFilter = true;
$scope.jobInfomations = [];
$scope.JobInfomationsByFilter = $scope.jobInfomations;
$scope.init = function () {
$scope.isLoadingFilter = true
$scope.jobInfomations = []
$scope.JobInfomationsByFilter = $scope.jobInfomations
$scope.filterConfig = {
isRunningAlwaysTop: true,
filterValueNotebookName: '',
filterValueInterpreter: '*',
isSortByAsc: true
};
$scope.sortTooltipMsg = 'Switch to sort by desc';
$scope.jobTypeFilter = jobManagerFilter;
}
$scope.sortTooltipMsg = 'Switch to sort by desc'
$scope.jobTypeFilter = jobManagerFilter
websocketMsgSrv.getNoteJobsList();
websocketMsgSrv.getNoteJobsList()
$scope.$watch('filterConfig.isSortByAsc', function (value) {
if (value) {
$scope.sortTooltipMsg = 'Switch to sort by desc';
$scope.sortTooltipMsg = 'Switch to sort by desc'
} else {
$scope.sortTooltipMsg = 'Switch to sort by asc';
$scope.sortTooltipMsg = 'Switch to sort by asc'
}
});
})
$scope.$on('$destroy', function() {
websocketMsgSrv.unsubscribeJobManager();
});
};
$scope.$on('$destroy', function () {
websocketMsgSrv.unsubscribeJobManager()
})
}
/*
** $scope.$on functions below
*/
$scope.$on('setNoteJobs', function(event, responseData) {
$scope.lastJobServerUnixTime = responseData.lastResponseUnixTime;
$scope.jobInfomations = responseData.jobs;
$scope.jobInfomationsIndexs = $scope.jobInfomations ? _.indexBy($scope.jobInfomations, 'noteId') : {};
$scope.jobTypeFilter($scope.jobInfomations, $scope.filterConfig);
$scope.$on('setNoteJobs', function (event, responseData) {
$scope.lastJobServerUnixTime = responseData.lastResponseUnixTime
$scope.jobInfomations = responseData.jobs
$scope.jobInfomationsIndexs = $scope.jobInfomations ? _.indexBy($scope.jobInfomations, 'noteId') : {}
$scope.jobTypeFilter($scope.jobInfomations, $scope.filterConfig)
$scope.activeInterpreters = [
{
name: 'ALL',
value: '*'
}
];
var interpreterLists = _.uniq(_.pluck($scope.jobInfomations, 'interpreter'), false);
for (var index = 0, length = interpreterLists.length; index < length; index++) {
]
let interpreterLists = _.uniq(_.pluck($scope.jobInfomations, 'interpreter'), false)
for (let index = 0, length = interpreterLists.length; index < length; index++) {
$scope.activeInterpreters.push({
name: interpreterLists[index],
value: interpreterLists[index]
});
})
}
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig);
});
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
})
$scope.$on('setUpdateNoteJobs', function(event, responseData) {
var jobInfomations = $scope.jobInfomations;
var indexStore = $scope.jobInfomationsIndexs;
$scope.lastJobServerUnixTime = responseData.lastResponseUnixTime;
var notes = responseData.jobs;
notes.map(function(changedItem) {
$scope.$on('setUpdateNoteJobs', function (event, responseData) {
let jobInfomations = $scope.jobInfomations
let indexStore = $scope.jobInfomationsIndexs
$scope.lastJobServerUnixTime = responseData.lastResponseUnixTime
let notes = responseData.jobs
notes.map(function (changedItem) {
if (indexStore[changedItem.noteId] === undefined) {
var newItem = angular.copy(changedItem);
jobInfomations.push(newItem);
indexStore[changedItem.noteId] = newItem;
let newItem = angular.copy(changedItem)
jobInfomations.push(newItem)
indexStore[changedItem.noteId] = newItem
} else {
var changeOriginTarget = indexStore[changedItem.noteId];
let changeOriginTarget = indexStore[changedItem.noteId]
if (changedItem.isRemoved !== undefined && changedItem.isRemoved === true) {
// remove Item.
var removeIndex = _.findIndex(indexStore, changedItem.noteId);
let removeIndex = _.findIndex(indexStore, changedItem.noteId)
if (removeIndex > -1) {
indexStore.splice(removeIndex, 1);
indexStore.splice(removeIndex, 1)
}
removeIndex = _.findIndex(jobInfomations, {'noteId': changedItem.noteId});
removeIndex = _.findIndex(jobInfomations, {'noteId': changedItem.noteId})
if (removeIndex) {
jobInfomations.splice(removeIndex, 1);
jobInfomations.splice(removeIndex, 1)
}
} else {
// change value for item.
changeOriginTarget.isRunningJob = changedItem.isRunningJob;
changeOriginTarget.noteName = changedItem.noteName;
changeOriginTarget.noteType = changedItem.noteType;
changeOriginTarget.interpreter = changedItem.interpreter;
changeOriginTarget.unixTimeLastRun = changedItem.unixTimeLastRun;
changeOriginTarget.paragraphs = changedItem.paragraphs;
changeOriginTarget.isRunningJob = changedItem.isRunningJob
changeOriginTarget.noteName = changedItem.noteName
changeOriginTarget.noteType = changedItem.noteType
changeOriginTarget.interpreter = changedItem.interpreter
changeOriginTarget.unixTimeLastRun = changedItem.unixTimeLastRun
changeOriginTarget.paragraphs = changedItem.paragraphs
}
}
});
$scope.doFiltering(jobInfomations, $scope.filterConfig);
});
})
$scope.doFiltering(jobInfomations, $scope.filterConfig)
})
}

View file

@ -12,37 +12,36 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').filter('jobManager', jobManagerFilter);
angular.module('zeppelinWebApp').filter('jobManager', jobManagerFilter)
function jobManagerFilter() {
function filterContext(jobItems, filterConfig) {
var filterValueInterpreter = filterConfig.filterValueInterpreter;
var filterValueNotebookName = filterConfig.filterValueNotebookName;
var isSortByAsc = filterConfig.isSortByAsc;
var filterItems = jobItems;
function jobManagerFilter () {
function filterContext (jobItems, filterConfig) {
let filterValueInterpreter = filterConfig.filterValueInterpreter
let filterValueNotebookName = filterConfig.filterValueNotebookName
let isSortByAsc = filterConfig.isSortByAsc
let filterItems = jobItems
if (filterValueInterpreter === undefined) {
filterItems = _.filter(filterItems, function(jobItem) {
return jobItem.interpreter === undefined ? true : false;
});
filterItems = _.filter(filterItems, function (jobItem) {
return jobItem.interpreter === undefined ? true : false
})
} else if (filterValueInterpreter !== '*') {
filterItems = _.where(filterItems, {interpreter: filterValueInterpreter});
filterItems = _.where(filterItems, {interpreter: filterValueInterpreter})
}
if (filterValueNotebookName !== '') {
filterItems = _.filter(filterItems, function(jobItem) {
var lowerFilterValue = filterValueNotebookName.toLocaleLowerCase();
var lowerNotebookName = jobItem.noteName.toLocaleLowerCase();
return lowerNotebookName.match(new RegExp('.*' + lowerFilterValue + '.*'));
});
filterItems = _.filter(filterItems, function (jobItem) {
let lowerFilterValue = filterValueNotebookName.toLocaleLowerCase()
let lowerNotebookName = jobItem.noteName.toLocaleLowerCase()
return lowerNotebookName.match(new RegExp('.*' + lowerFilterValue + '.*'))
})
}
filterItems = _.sortBy(filterItems, function(sortItem) {
return sortItem.noteName.toLowerCase();
});
filterItems = _.sortBy(filterItems, function (sortItem) {
return sortItem.noteName.toLowerCase()
})
return isSortByAsc ? filterItems : filterItems.reverse();
return isSortByAsc ? filterItems : filterItems.reverse()
}
return filterContext;
return filterContext
}

View file

@ -12,38 +12,38 @@
* limitations under the License.
*/
import { ParagraphStatus, } from '../../notebook/paragraph/paragraph.status';
import { ParagraphStatus, } from '../../notebook/paragraph/paragraph.status'
angular.module('zeppelinWebApp').controller('JobCtrl', JobCtrl);
angular.module('zeppelinWebApp').controller('JobCtrl', JobCtrl)
function JobCtrl($scope, $http, baseUrlSrv) {
'ngInject';
function JobCtrl ($scope, $http, baseUrlSrv) {
'ngInject'
$scope.init = function(jobInformation) {
$scope.progressValue = 0;
};
$scope.init = function (jobInformation) {
$scope.progressValue = 0
}
$scope.getProgress = function() {
var statusList = _.pluck($scope.notebookJob.paragraphs, 'status');
var runningJob = _.countBy(statusList, function(status) {
$scope.getProgress = function () {
let statusList = _.pluck($scope.notebookJob.paragraphs, 'status')
let runningJob = _.countBy(statusList, function (status) {
if (status === ParagraphStatus.RUNNING || status === ParagraphStatus.FINISHED) {
return 'matchCount';
return 'matchCount'
} else {
return 'none';
return 'none'
}
});
var totalCount = statusList.length;
var runningJobCount = runningJob.matchCount;
var result = Math.ceil(runningJobCount / totalCount * 100);
return isNaN(result) ? 0 : result;
};
})
let totalCount = statusList.length
let runningJobCount = runningJob.matchCount
let result = Math.ceil(runningJobCount / totalCount * 100)
return isNaN(result) ? 0 : result
}
$scope.runNotebookJob = function(notebookId) {
$scope.runNotebookJob = function (notebookId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Run all paragraphs?',
callback: function(result) {
callback: function (result) {
if (result) {
$http({
method: 'POST',
@ -51,30 +51,31 @@ function JobCtrl($scope, $http, baseUrlSrv) {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function successCallback(response) {
}).then(function successCallback (response) {
// success
}, function errorCallback(errorResponse) {
var errorText = 'SERVER ERROR';
}, function errorCallback (errorResponse) {
let errorText = 'SERVER ERROR'
// eslint-disable-next-line no-extra-boolean-cast
if (!!errorResponse.data.message) {
errorText = errorResponse.data.message;
errorText = errorResponse.data.message
}
BootstrapDialog.alert({
closable: true,
title: 'Execution Failure',
message: errorText
});
});
})
})
}
}
});
};
})
}
$scope.stopNotebookJob = function(notebookId) {
$scope.stopNotebookJob = function (notebookId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Stop all paragraphs?',
callback: function(result) {
callback: function (result) {
if (result) {
$http({
method: 'DELETE',
@ -82,28 +83,26 @@ function JobCtrl($scope, $http, baseUrlSrv) {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function successCallback(response) {
}).then(function successCallback (response) {
// success
}, function errorCallback(errorResponse) {
var errorText = 'SERVER ERROR';
}, function errorCallback (errorResponse) {
let errorText = 'SERVER ERROR'
// eslint-disable-next-line no-extra-boolean-cast
if (!!errorResponse.data.message) {
errorText = errorResponse.data.message;
errorText = errorResponse.data.message
}
BootstrapDialog.alert({
closable: true,
title: 'Stop Failure',
message: errorText
});
});
})
})
}
}
});
};
$scope.lastExecuteTime = function(unixtime) {
return moment.unix(unixtime / 1000).fromNow();
};
})
}
$scope.lastExecuteTime = function (unixtime) {
return moment.unix(unixtime / 1000).fromNow()
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,139 +1,139 @@
describe('Controller: NotebookCtrl', function() {
beforeEach(angular.mock.module('zeppelinWebApp'));
describe('Controller: NotebookCtrl', function () {
beforeEach(angular.mock.module('zeppelinWebApp'))
var scope;
let scope
var websocketMsgSrvMock = {
getNote: function() {},
listRevisionHistory: function() {},
getInterpreterBindings: function() {},
updateNote: function() {},
renameNote: function() {}
};
let websocketMsgSrvMock = {
getNote: function () {},
listRevisionHistory: function () {},
getInterpreterBindings: function () {},
updateNote: function () {},
renameNote: function () {}
}
var baseUrlSrvMock = {
getRestApiBase: function() {
return 'http://localhost:8080';
let baseUrlSrvMock = {
getRestApiBase: function () {
return 'http://localhost:8080'
}
};
}
var noteMock = {
let noteMock = {
id: 1,
name: 'my note',
config: {},
};
}
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new()
$controller('NotebookCtrl', {
$scope: scope,
websocketMsgSrv: websocketMsgSrvMock,
baseUrlSrv: baseUrlSrvMock
});
}));
})
}))
beforeEach(function() {
scope.note = noteMock;
});
beforeEach(function () {
scope.note = noteMock
})
var functions = ['getCronOptionNameFromValue', 'removeNote', 'runAllParagraphs', 'saveNote', 'toggleAllEditor',
let functions = ['getCronOptionNameFromValue', 'removeNote', 'runAllParagraphs', 'saveNote', 'toggleAllEditor',
'showAllEditor', 'hideAllEditor', 'toggleAllTable', 'hideAllTable', 'showAllTable', 'isNoteRunning',
'killSaveTimer', 'startSaveTimer', 'setLookAndFeel', 'setCronScheduler', 'setConfig', 'updateNoteName',
'openSetting', 'closeSetting', 'saveSetting', 'toggleSetting'];
'openSetting', 'closeSetting', 'saveSetting', 'toggleSetting']
functions.forEach(function(fn) {
it('check for scope functions to be defined : ' + fn, function() {
expect(scope[fn]).toBeDefined();
});
});
functions.forEach(function (fn) {
it('check for scope functions to be defined : ' + fn, function () {
expect(scope[fn]).toBeDefined()
})
})
it('should set default value of "editorToggled" to false', function() {
expect(scope.editorToggled).toEqual(false);
});
it('should set default value of "editorToggled" to false', function () {
expect(scope.editorToggled).toEqual(false)
})
it('should set "showSetting" to true when openSetting() is called', function() {
scope.openSetting();
expect(scope.showSetting).toEqual(true);
});
it('should set "showSetting" to true when openSetting() is called', function () {
scope.openSetting()
expect(scope.showSetting).toEqual(true)
})
it('should set "showSetting" to false when closeSetting() is called', function() {
scope.closeSetting();
expect(scope.showSetting).toEqual(false);
});
it('should set "showSetting" to false when closeSetting() is called', function () {
scope.closeSetting()
expect(scope.showSetting).toEqual(false)
})
it('should return the correct value for getCronOptionNameFromValue()', function() {
var none = scope.getCronOptionNameFromValue();
var oneMin = scope.getCronOptionNameFromValue('0 0/1 * * * ?');
var fiveMin = scope.getCronOptionNameFromValue('0 0/5 * * * ?');
var oneHour = scope.getCronOptionNameFromValue('0 0 0/1 * * ?');
var threeHours = scope.getCronOptionNameFromValue('0 0 0/3 * * ?');
var sixHours = scope.getCronOptionNameFromValue('0 0 0/6 * * ?');
var twelveHours = scope.getCronOptionNameFromValue('0 0 0/12 * * ?');
var oneDay = scope.getCronOptionNameFromValue('0 0 0 * * ?');
it('should return the correct value for getCronOptionNameFromValue()', function () {
let none = scope.getCronOptionNameFromValue()
let oneMin = scope.getCronOptionNameFromValue('0 0/1 * * * ?')
let fiveMin = scope.getCronOptionNameFromValue('0 0/5 * * * ?')
let oneHour = scope.getCronOptionNameFromValue('0 0 0/1 * * ?')
let threeHours = scope.getCronOptionNameFromValue('0 0 0/3 * * ?')
let sixHours = scope.getCronOptionNameFromValue('0 0 0/6 * * ?')
let twelveHours = scope.getCronOptionNameFromValue('0 0 0/12 * * ?')
let oneDay = scope.getCronOptionNameFromValue('0 0 0 * * ?')
expect(none).toEqual('');
expect(oneMin).toEqual('1m');
expect(fiveMin).toEqual('5m');
expect(oneHour).toEqual('1h');
expect(threeHours).toEqual('3h');
expect(sixHours).toEqual('6h');
expect(twelveHours).toEqual('12h');
expect(oneDay).toEqual('1d');
});
expect(none).toEqual('')
expect(oneMin).toEqual('1m')
expect(fiveMin).toEqual('5m')
expect(oneHour).toEqual('1h')
expect(threeHours).toEqual('3h')
expect(sixHours).toEqual('6h')
expect(twelveHours).toEqual('12h')
expect(oneDay).toEqual('1d')
})
it('should have "isNoteDirty" as null by default', function() {
expect(scope.isNoteDirty).toEqual(null);
});
it('should have "isNoteDirty" as null by default', function () {
expect(scope.isNoteDirty).toEqual(null)
})
it('should first call killSaveTimer() when calling startSaveTimer()', function() {
expect(scope.saveTimer).toEqual(null);
spyOn(scope, 'killSaveTimer');
scope.startSaveTimer();
expect(scope.killSaveTimer).toHaveBeenCalled();
});
it('should first call killSaveTimer() when calling startSaveTimer()', function () {
expect(scope.saveTimer).toEqual(null)
spyOn(scope, 'killSaveTimer')
scope.startSaveTimer()
expect(scope.killSaveTimer).toHaveBeenCalled()
})
it('should set "saveTimer" when saveTimer() and killSaveTimer() are called', function() {
expect(scope.saveTimer).toEqual(null);
scope.startSaveTimer();
expect(scope.saveTimer).toBeTruthy();
scope.killSaveTimer();
expect(scope.saveTimer).toEqual(null);
});
it('should set "saveTimer" when saveTimer() and killSaveTimer() are called', function () {
expect(scope.saveTimer).toEqual(null)
scope.startSaveTimer()
expect(scope.saveTimer).toBeTruthy()
scope.killSaveTimer()
expect(scope.saveTimer).toEqual(null)
})
it('should NOT update note name when updateNoteName() is called with an invalid name', function() {
spyOn(websocketMsgSrvMock, 'renameNote');
scope.updateNoteName('');
expect(scope.note.name).toEqual(noteMock.name);
expect(websocketMsgSrvMock.renameNote).not.toHaveBeenCalled();
scope.updateNoteName(' ');
expect(scope.note.name).toEqual(noteMock.name);
expect(websocketMsgSrvMock.renameNote).not.toHaveBeenCalled();
scope.updateNoteName(scope.note.name);
expect(scope.note.name).toEqual(noteMock.name);
expect(websocketMsgSrvMock.renameNote).not.toHaveBeenCalled();
});
it('should NOT update note name when updateNoteName() is called with an invalid name', function () {
spyOn(websocketMsgSrvMock, 'renameNote')
scope.updateNoteName('')
expect(scope.note.name).toEqual(noteMock.name)
expect(websocketMsgSrvMock.renameNote).not.toHaveBeenCalled()
scope.updateNoteName(' ')
expect(scope.note.name).toEqual(noteMock.name)
expect(websocketMsgSrvMock.renameNote).not.toHaveBeenCalled()
scope.updateNoteName(scope.note.name)
expect(scope.note.name).toEqual(noteMock.name)
expect(websocketMsgSrvMock.renameNote).not.toHaveBeenCalled()
})
it('should update note name when updateNoteName() is called with a valid name', function() {
spyOn(websocketMsgSrvMock, 'renameNote');
var newName = 'Your Note';
scope.updateNoteName(newName);
expect(scope.note.name).toEqual(newName);
expect(websocketMsgSrvMock.renameNote).toHaveBeenCalled();
});
it('should update note name when updateNoteName() is called with a valid name', function () {
spyOn(websocketMsgSrvMock, 'renameNote')
let newName = 'Your Note'
scope.updateNoteName(newName)
expect(scope.note.name).toEqual(newName)
expect(websocketMsgSrvMock.renameNote).toHaveBeenCalled()
})
it('should reload note info once per one "setNoteMenu" event', function() {
spyOn(websocketMsgSrvMock, 'getNote');
spyOn(websocketMsgSrvMock, 'listRevisionHistory');
it('should reload note info once per one "setNoteMenu" event', function () {
spyOn(websocketMsgSrvMock, 'getNote')
spyOn(websocketMsgSrvMock, 'listRevisionHistory')
scope.$broadcast('setNoteMenu');
expect(websocketMsgSrvMock.getNote.calls.count()).toEqual(0);
expect(websocketMsgSrvMock.listRevisionHistory.calls.count()).toEqual(0);
scope.$broadcast('setNoteMenu')
expect(websocketMsgSrvMock.getNote.calls.count()).toEqual(0)
expect(websocketMsgSrvMock.listRevisionHistory.calls.count()).toEqual(0)
websocketMsgSrvMock.getNote.calls.reset();
websocketMsgSrvMock.listRevisionHistory.calls.reset();
websocketMsgSrvMock.getNote.calls.reset()
websocketMsgSrvMock.listRevisionHistory.calls.reset()
scope.$broadcast('setNoteMenu');
expect(websocketMsgSrvMock.getNote.calls.count()).toEqual(0);
expect(websocketMsgSrvMock.listRevisionHistory.calls.count()).toEqual(0);
});
});
scope.$broadcast('setNoteMenu')
expect(websocketMsgSrvMock.getNote.calls.count()).toEqual(0)
expect(websocketMsgSrvMock.listRevisionHistory.calls.count()).toEqual(0)
})
})

View file

@ -20,7 +20,7 @@ limitations under the License.
<label class="control-label input-sm" ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }">{{formulaire.name}}</label>
<div>
<input class="form-control input-sm"
ng-if="!paragraph.settings.forms[formulaire.name].options"
ng-if="paragraph.settings.forms[formulaire.name].type == 'TextBox'"
ng-enter="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
@ -28,7 +28,7 @@ limitations under the License.
</div>
<div ng-if="paragraph.config.runOnSelectionChange == true">
<select class="form-control input-sm"
ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
ng-if="paragraph.settings.forms[formulaire.name].type == 'Select'"
ng-change="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
@ -38,16 +38,16 @@ limitations under the License.
</div>
<div ng-if="paragraph.config.runOnSelectionChange == false">
<select class="form-control input-sm"
ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
ng-enter="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}"
ng-options="option.value as (option.displayName||option.value) for option in paragraph.settings.forms[formulaire.name].options">
ng-if="paragraph.settings.forms[formulaire.name].type == 'Select'"
ng-enter="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}"
ng-options="option.value as (option.displayName||option.value) for option in paragraph.settings.forms[formulaire.name].options">
</select>
</div>
<div ng-if="paragraph.config.runOnSelectionChange == true &&
paragraph.settings.forms[formulaire.name].type == 'checkbox'">
paragraph.settings.forms[formulaire.name].type == 'CheckBox'">
<label ng-repeat="option in paragraph.settings.forms[formulaire.name].options"
class="checkbox-item input-sm">
<input type="checkbox"
@ -57,7 +57,7 @@ limitations under the License.
</label>
</div>
<div ng-if="paragraph.config.runOnSelectionChange == false &&
paragraph.settings.forms[formulaire.name].type == 'checkbox'">
paragraph.settings.forms[formulaire.name].type == 'CheckBox'">
<label ng-repeat="option in paragraph.settings.forms[formulaire.name].options"
class="checkbox-item input-sm">
<input type="checkbox"

File diff suppressed because it is too large Load diff

View file

@ -1,54 +1,53 @@
describe('Controller: ParagraphCtrl', function() {
describe('Controller: ParagraphCtrl', function () {
beforeEach(angular.mock.module('zeppelinWebApp'))
beforeEach(angular.mock.module('zeppelinWebApp'));
var scope;
var websocketMsgSrvMock = {};
var paragraphMock = {
let scope
let websocketMsgSrvMock = {}
let paragraphMock = {
config: {},
settings: {
forms: {}
}
};
var route = {
}
let route = {
current: {
pathParams: {
noteId: 'noteId'
}
}
};
}
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$rootScope.notebookScope = $rootScope.$new(true, $rootScope);
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new()
$rootScope.notebookScope = $rootScope.$new(true, $rootScope)
$controller('ParagraphCtrl', {
$scope: scope,
websocketMsgSrv: websocketMsgSrvMock,
$element: {},
$route: route
});
})
scope.init(paragraphMock);
}));
scope.init(paragraphMock)
}))
var functions = ['isRunning', 'getIframeDimensions', 'cancelParagraph', 'runParagraph', 'saveParagraph',
let functions = ['isRunning', 'getIframeDimensions', 'cancelParagraph', 'runParagraph', 'saveParagraph',
'moveUp', 'moveDown', 'insertNew', 'removeParagraph', 'toggleEditor', 'closeEditor', 'openEditor',
'closeTable', 'openTable', 'showTitle', 'hideTitle', 'setTitle', 'showLineNumbers', 'hideLineNumbers',
'changeColWidth', 'columnWidthClass', 'toggleOutput', 'loadForm',
'aceChanged', 'aceLoaded', 'getEditorValue', 'getProgress', 'getExecutionTime', 'isResultOutdated'];
'aceChanged', 'aceLoaded', 'getEditorValue', 'getProgress', 'getExecutionTime', 'isResultOutdated']
functions.forEach(function(fn) {
it('check for scope functions to be defined : ' + fn, function() {
expect(scope[fn]).toBeDefined();
});
});
functions.forEach(function (fn) {
it('check for scope functions to be defined : ' + fn, function () {
expect(scope[fn]).toBeDefined()
})
})
it('should have this array of values for "colWidthOption"', function() {
expect(scope.colWidthOption).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
});
it('should have this array of values for "colWidthOption"', function () {
expect(scope.colWidthOption).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
})
it('should set default value of "paragraphFocused" as false', function() {
expect(scope.paragraphFocused).toEqual(false);
});
});
it('should set default value of "paragraphFocused" as false', function () {
expect(scope.paragraphFocused).toEqual(false)
})
})

View file

@ -19,12 +19,12 @@ export const ParagraphStatus = {
FINISHED: 'FINISHED',
ABORT: 'ABORT',
ERROR: 'ERROR',
};
export function isParagraphRunning(paragraph) {
if (!paragraph) { return false; }
const status = paragraph.status;
if (!status) { return false; }
return status === ParagraphStatus.PENDING || status === ParagraphStatus.RUNNING;
}
export function isParagraphRunning (paragraph) {
if (!paragraph) { return false }
const status = paragraph.status
if (!status) { return false }
return status === ParagraphStatus.PENDING || status === ParagraphStatus.RUNNING
}

View file

@ -12,77 +12,76 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('NotebookReposCtrl', NotebookReposCtrl);
angular.module('zeppelinWebApp').controller('NotebookReposCtrl', NotebookReposCtrl)
function NotebookReposCtrl($http, baseUrlSrv, ngToast) {
'ngInject';
function NotebookReposCtrl ($http, baseUrlSrv, ngToast) {
'ngInject'
var vm = this;
vm.notebookRepos = [];
vm.showDropdownSelected = showDropdownSelected;
vm.saveNotebookRepo = saveNotebookRepo;
let vm = this
vm.notebookRepos = []
vm.showDropdownSelected = showDropdownSelected
vm.saveNotebookRepo = saveNotebookRepo
_init();
_init()
// Public functions
function saveNotebookRepo(valueform, repo, data) {
console.log('data %o', data);
function saveNotebookRepo (valueform, repo, data) {
console.log('data %o', data)
$http.put(baseUrlSrv.getRestApiBase() + '/notebook-repositories', {
'name': repo.className,
'settings': data
}).success(function(data) {
var index = _.findIndex(vm.notebookRepos, {'className': repo.className});
}).success(function (data) {
let index = _.findIndex(vm.notebookRepos, {'className': repo.className})
if (index >= 0) {
vm.notebookRepos[index] = data.body;
console.log('repos %o, data %o', vm.notebookRepos, data.body);
vm.notebookRepos[index] = data.body
console.log('repos %o, data %o', vm.notebookRepos, data.body)
}
valueform.$show();
}).error(function() {
valueform.$show()
}).error(function () {
ngToast.danger({
content: 'We couldn\'t save that NotebookRepo\'s settings',
verticalPosition: 'bottom',
timeout: '3000'
});
valueform.$show();
});
})
valueform.$show()
})
return 'manual';
return 'manual'
}
function showDropdownSelected(setting) {
var index = _.findIndex(setting.value, {'value': setting.selected});
function showDropdownSelected (setting) {
let index = _.findIndex(setting.value, {'value': setting.selected})
if (index < 0) {
return 'No value';
return 'No value'
} else {
return setting.value[index].name;
return setting.value[index].name
}
}
// Private functions
function _getInterpreterSettings() {
function _getInterpreterSettings () {
$http.get(baseUrlSrv.getRestApiBase() + '/notebook-repositories')
.success(function(data, status, headers, config) {
vm.notebookRepos = data.body;
console.log('ya notebookRepos %o', vm.notebookRepos);
}).error(function(data, status, headers, config) {
if (status === 401) {
ngToast.danger({
content: 'You don\'t have permission on this page',
verticalPosition: 'bottom',
timeout: '3000'
});
setTimeout(function() {
window.location.replace('/');
}, 3000);
}
console.log('Error %o %o', status, data.message);
});
.success(function (data, status, headers, config) {
vm.notebookRepos = data.body
console.log('ya notebookRepos %o', vm.notebookRepos)
}).error(function (data, status, headers, config) {
if (status === 401) {
ngToast.danger({
content: 'You don\'t have permission on this page',
verticalPosition: 'bottom',
timeout: '3000'
})
setTimeout(function () {
window.location.replace('/')
}, 3000)
}
console.log('Error %o %o', status, data.message)
})
}
function _init() {
_getInterpreterSettings();
};
function _init () {
_getInterpreterSettings()
}
}

View file

@ -12,108 +12,107 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('SearchResultCtrl', SearchResultCtrl);
angular.module('zeppelinWebApp').controller('SearchResultCtrl', SearchResultCtrl)
function SearchResultCtrl($scope, $routeParams, searchService) {
'ngInject';
function SearchResultCtrl ($scope, $routeParams, searchService) {
'ngInject'
$scope.isResult = true ;
$scope.searchTerm = $routeParams.searchTerm;
var results = searchService.search({'q': $routeParams.searchTerm}).query();
$scope.isResult = true
$scope.searchTerm = $routeParams.searchTerm
let results = searchService.search({'q': $routeParams.searchTerm}).query()
results.$promise.then(function(result) {
$scope.notes = result.body.map(function(note) {
results.$promise.then(function (result) {
$scope.notes = result.body.map(function (note) {
// redirect to notebook when search result is a notebook itself,
// not a paragraph
if (!/\/paragraph\//.test(note.id)) {
return note;
return note
}
note.id = note.id.replace('paragraph/', '?paragraph=') +
'&term=' + $routeParams.searchTerm;
'&term=' + $routeParams.searchTerm
return note;
});
return note
})
if ($scope.notes.length === 0) {
$scope.isResult = false;
$scope.isResult = false
} else {
$scope.isResult = true;
$scope.isResult = true
}
$scope.$on('$routeChangeStart', function(event, next, current) {
$scope.$on('$routeChangeStart', function (event, next, current) {
if (next.originalPath !== '/search/:searchTerm') {
searchService.searchTerm = '';
searchService.searchTerm = ''
}
});
});
})
})
$scope.page = 0;
$scope.allResults = false;
$scope.page = 0
$scope.allResults = false
$scope.highlightSearchResults = function(note) {
return function(_editor) {
function getEditorMode(text) {
var editorModes = {
$scope.highlightSearchResults = function (note) {
return function (_editor) {
function getEditorMode (text) {
let editorModes = {
'ace/mode/scala': /^%(\w*\.)?spark/,
'ace/mode/python': /^%(\w*\.)?(pyspark|python)/,
'ace/mode/r': /^%(\w*\.)?(r|sparkr|knitr)/,
'ace/mode/sql': /^%(\w*\.)?\wql/,
'ace/mode/markdown': /^%md/,
'ace/mode/sh': /^%sh/
};
}
return Object.keys(editorModes).reduce(function(res, mode) {
return editorModes[mode].test(text) ? mode : res;
}, 'ace/mode/scala');
return Object.keys(editorModes).reduce(function (res, mode) {
return editorModes[mode].test(text) ? mode : res
}, 'ace/mode/scala')
}
var Range = ace.require('ace/range').Range;
let Range = ace.require('ace/range').Range
_editor.setOption('highlightActiveLine', false);
_editor.$blockScrolling = Infinity;
_editor.setReadOnly(true);
_editor.renderer.setShowGutter(false);
_editor.setTheme('ace/theme/chrome');
_editor.getSession().setMode(getEditorMode(note.text));
_editor.setOption('highlightActiveLine', false)
_editor.$blockScrolling = Infinity
_editor.setReadOnly(true)
_editor.renderer.setShowGutter(false)
_editor.setTheme('ace/theme/chrome')
_editor.getSession().setMode(getEditorMode(note.text))
function getIndeces(term) {
return function(str) {
var indeces = [];
var i = -1;
function getIndeces (term) {
return function (str) {
let indeces = []
let i = -1
while ((i = str.indexOf(term, i + 1)) >= 0) {
indeces.push(i);
indeces.push(i)
}
return indeces;
};
return indeces
}
}
var result = '';
let result = ''
if (note.header !== '') {
result = note.header + '\n\n' + note.snippet;
result = note.header + '\n\n' + note.snippet
} else {
result = note.snippet;
result = note.snippet
}
var lines = result
let lines = result
.split('\n')
.map(function(line, row) {
var match = line.match(/<B>(.+?)<\/B>/);
.map(function (line, row) {
let match = line.match(/<B>(.+?)<\/B>/)
// return early if nothing to highlight
if (!match) {
return line;
return line
}
var term = match[1];
var __line = line
let term = match[1]
let __line = line
.replace(/<B>/g, '')
.replace(/<\/B>/g, '');
.replace(/<\/B>/g, '')
var indeces = getIndeces(term)(__line);
let indeces = getIndeces(term)(__line)
indeces.forEach(function(start) {
var end = start + term.length;
indeces.forEach(function (start) {
let end = start + term.length
if (note.header !== '' && row === 0) {
_editor
.getSession()
@ -121,14 +120,14 @@ function SearchResultCtrl($scope, $routeParams, searchService) {
new Range(row, 0, row, line.length),
'search-results-highlight-header',
'background'
);
)
_editor
.getSession()
.addMarker(
new Range(row, start, row, end),
'search-results-highlight',
'line'
);
)
} else {
_editor
.getSession()
@ -136,21 +135,19 @@ function SearchResultCtrl($scope, $routeParams, searchService) {
new Range(row, start, row, end),
'search-results-highlight',
'line'
);
)
}
});
return __line;
});
})
return __line
})
// resize editor based on content length
_editor.setOption(
'maxLines',
lines.reduce(function(len, line) {return len + line.length;}, 0)
);
lines.reduce(function (len, line) { return len + line.length }, 0)
)
_editor.getSession().setValue(lines.join('\n'));
};
};
_editor.getSession().setValue(lines.join('\n'))
}
}
}

View file

@ -18,8 +18,8 @@
export {
DefaultDisplayType,
SpellResult,
} from './spell-result';
} from './spell-result'
export {
SpellBase,
} from './spell-base';
} from './spell-base'

View file

@ -15,16 +15,16 @@
* limitations under the License.
*/
/*eslint-disable no-unused-vars */
/* eslint-disable no-unused-vars */
import {
DefaultDisplayType,
SpellResult,
} from './spell-result';
/*eslint-enable no-unused-vars */
} from './spell-result'
/* eslint-enable no-unused-vars */
export class SpellBase {
constructor(magic) {
this.magic = magic;
constructor (magic) {
this.magic = magic
}
/**
@ -34,8 +34,8 @@ export class SpellBase {
* @param config {Object}
* @return {SpellResult}
*/
interpret(paragraphText, config) {
throw new Error('SpellBase.interpret() should be overrided');
interpret (paragraphText, config) {
throw new Error('SpellBase.interpret() should be overrided')
}
/**
@ -43,7 +43,7 @@ export class SpellBase {
* (e.g `%flowchart`)
* @return {string}
*/
getMagic() {
return this.magic;
getMagic () {
return this.magic
}
}

View file

@ -21,7 +21,7 @@ export const DefaultDisplayType = {
HTML: 'HTML',
ANGULAR: 'ANGULAR',
TEXT: 'TEXT',
};
}
export const DefaultDisplayMagic = {
'%element': DefaultDisplayType.ELEMENT,
@ -29,12 +29,12 @@ export const DefaultDisplayMagic = {
'%html': DefaultDisplayType.HTML,
'%angular': DefaultDisplayType.ANGULAR,
'%text': DefaultDisplayType.TEXT,
};
}
export class DataWithType {
constructor(data, type, magic, text) {
this.data = data;
this.type = type;
constructor (data, type, magic, text) {
this.data = data
this.type = type
/**
* keep for `DefaultDisplayType.ELEMENT` (function data type)
@ -44,29 +44,29 @@ export class DataWithType {
* since they don't have context where they are created.
*/
this.magic = magic;
this.text = text;
this.magic = magic
this.text = text
}
static handleDefaultMagic(m) {
static handleDefaultMagic (m) {
// let's use default display type instead of magic in case of default
// to keep consistency with backend interpreter
if (DefaultDisplayMagic[m]) {
return DefaultDisplayMagic[m];
return DefaultDisplayMagic[m]
} else {
return m;
return m
}
}
static createPropagable(dataWithType) {
static createPropagable (dataWithType) {
if (!SpellResult.isFunction(dataWithType.data)) {
return dataWithType;
return dataWithType
}
const data = dataWithType.getText();
const type = dataWithType.getMagic();
const data = dataWithType.getText()
const type = dataWithType.getMagic()
return new DataWithType(data, type);
return new DataWithType(data, type)
}
/**
@ -75,45 +75,45 @@ export class DataWithType {
* @param customDisplayType
* @return {Array<DataWithType>}
*/
static parseStringData(data, customDisplayMagic) {
function availableMagic(magic) {
return magic && (DefaultDisplayMagic[magic] || customDisplayMagic[magic]);
static parseStringData (data, customDisplayMagic) {
function availableMagic (magic) {
return magic && (DefaultDisplayMagic[magic] || customDisplayMagic[magic])
}
const splited = data.split('\n');
const splited = data.split('\n')
const gensWithTypes = [];
let mergedGens = [];
let previousMagic = DefaultDisplayType.TEXT;
const gensWithTypes = []
let mergedGens = []
let previousMagic = DefaultDisplayType.TEXT
// create `DataWithType` whenever see available display type.
for(let i = 0; i < splited.length; i++) {
const g = splited[i];
const magic = SpellResult.extractMagic(g);
for (let i = 0; i < splited.length; i++) {
const g = splited[i]
const magic = SpellResult.extractMagic(g)
// create `DataWithType` only if see new magic
if (availableMagic(magic) && mergedGens.length > 0) {
gensWithTypes.push(new DataWithType(mergedGens.join(''), previousMagic));
mergedGens = [];
gensWithTypes.push(new DataWithType(mergedGens.join(''), previousMagic))
mergedGens = []
}
// accumulate `data` to mergedGens
if (availableMagic(magic)) {
const withoutMagic = g.split(magic)[1];
mergedGens.push(`${withoutMagic}\n`);
previousMagic = DataWithType.handleDefaultMagic(magic);
const withoutMagic = g.split(magic)[1]
mergedGens.push(`${withoutMagic}\n`)
previousMagic = DataWithType.handleDefaultMagic(magic)
} else {
mergedGens.push(`${g}\n`);
mergedGens.push(`${g}\n`)
}
}
// cleanup the last `DataWithType`
if (mergedGens.length > 0) {
previousMagic = DataWithType.handleDefaultMagic(previousMagic);
gensWithTypes.push(new DataWithType(mergedGens.join(''), previousMagic));
previousMagic = DataWithType.handleDefaultMagic(previousMagic)
gensWithTypes.push(new DataWithType(mergedGens.join(''), previousMagic))
}
return gensWithTypes;
return gensWithTypes
}
/**
@ -126,45 +126,44 @@ export class DataWithType {
* @param textWithoutMagic
* @return {Promise<Array<DataWithType>>}
*/
static produceMultipleData(dataWithType, customDisplayType,
static produceMultipleData (dataWithType, customDisplayType,
magic, textWithoutMagic) {
const data = dataWithType.getData();
const type = dataWithType.getType();
const data = dataWithType.getData()
const type = dataWithType.getType()
// if the type is specified, just return it
// handle non-specified dataWithTypes only
if (type) {
return new Promise((resolve) => { resolve([dataWithType]); });
return new Promise((resolve) => { resolve([dataWithType]) })
}
let wrapped;
let wrapped
if (SpellResult.isFunction(data)) {
// if data is a function, we consider it as ELEMENT type.
wrapped = new Promise((resolve) => {
const dt = new DataWithType(
data, DefaultDisplayType.ELEMENT, magic, textWithoutMagic);
const result = [dt];
return resolve(result);
});
data, DefaultDisplayType.ELEMENT, magic, textWithoutMagic)
const result = [dt]
return resolve(result)
})
} else if (SpellResult.isPromise(data)) {
// if data is a promise,
wrapped = data.then(generated => {
const result =
DataWithType.parseStringData(generated, customDisplayType);
return result;
DataWithType.parseStringData(generated, customDisplayType)
return result
})
} else {
// if data is a object, parse it to multiples
wrapped = new Promise((resolve) => {
const result =
DataWithType.parseStringData(data, customDisplayType);
return resolve(result);
});
DataWithType.parseStringData(data, customDisplayType)
return resolve(result)
})
}
return wrapped;
return wrapped
}
/**
@ -176,8 +175,8 @@ export class DataWithType {
* will be called in `then()` of this promise.
* @returns {*} `data` which can be object, function or promise.
*/
getData() {
return this.data;
getData () {
return this.data
}
/**
@ -186,66 +185,66 @@ export class DataWithType {
* by `SpellResult.parseStringData()`
* @returns {string}
*/
getType() {
return this.type;
getType () {
return this.type
}
getMagic() {
return this.magic;
getMagic () {
return this.magic
}
getText() {
return this.text;
getText () {
return this.text
}
}
export class SpellResult {
constructor(resultData, resultType) {
this.dataWithTypes = [];
this.add(resultData, resultType);
constructor (resultData, resultType) {
this.dataWithTypes = []
this.add(resultData, resultType)
}
static isFunction(data) {
return (data && typeof data === 'function');
static isFunction (data) {
return (data && typeof data === 'function')
}
static isPromise(data) {
return (data && typeof data.then === 'function');
static isPromise (data) {
return (data && typeof data.then === 'function')
}
static isObject(data) {
static isObject (data) {
return (data &&
!SpellResult.isFunction(data) &&
!SpellResult.isPromise(data));
!SpellResult.isPromise(data))
}
static extractMagic(allParagraphText) {
const pattern = /^\s*%(\S+)\s*/g;
static extractMagic (allParagraphText) {
const pattern = /^\s*%(\S+)\s*/g
try {
let match = pattern.exec(allParagraphText);
let match = pattern.exec(allParagraphText)
if (match) {
return `%${match[1].trim()}`;
return `%${match[1].trim()}`
}
} catch (error) {
// failed to parse, ignore
}
return undefined;
return undefined
}
static createPropagable(resultMsg) {
static createPropagable (resultMsg) {
return resultMsg.map(dt => {
return DataWithType.createPropagable(dt);
return DataWithType.createPropagable(dt)
})
}
add(resultData, resultType) {
add (resultData, resultType) {
if (resultData) {
this.dataWithTypes.push(
new DataWithType(resultData, resultType));
new DataWithType(resultData, resultType))
}
return this;
return this
}
/**
@ -253,23 +252,23 @@ export class SpellResult {
* @param textWithoutMagic
* @return {Promise<Array<DataWithType>>}
*/
getAllParsedDataWithTypes(customDisplayType, magic, textWithoutMagic) {
getAllParsedDataWithTypes (customDisplayType, magic, textWithoutMagic) {
const promises = this.dataWithTypes.map(dt => {
return DataWithType.produceMultipleData(
dt, customDisplayType, magic, textWithoutMagic);
});
dt, customDisplayType, magic, textWithoutMagic)
})
// some promises can include an array so we need to flatten them
const flatten = Promise.all(promises).then(values => {
return values.reduce((acc, cur) => {
if (Array.isArray(cur)) {
return acc.concat(cur);
return acc.concat(cur)
} else {
return acc.concat([cur]);
return acc.concat([cur])
}
})
});
})
return flatten;
return flatten
}
}

View file

@ -13,7 +13,7 @@
*/
export function getCurrentChart(config) {
return config.chart.current;
return config.chart.current
}
export function getCurrentChartTransform(config) {
@ -43,7 +43,7 @@ export function useSharedAxis(config, chart) {
export function serializeSharedAxes(config) {
const availableCharts = getAvailableChartNames(config.spec.charts)
for (let i = 0; i < availableCharts.length; i++) {
const chartName = availableCharts[i];
const chartName = availableCharts[i]
if (useSharedAxis(config, chartName)) {
/** use reference :) in case of sharedAxis */
config.axis[chartName] = config.sharedAxis
@ -59,19 +59,19 @@ export const Widget = {
}
export function isInputWidget(paramSpec) {
return (paramSpec && !paramSpec.widget) || (paramSpec && paramSpec.widget === Widget.INPUT);
return (paramSpec && !paramSpec.widget) || (paramSpec && paramSpec.widget === Widget.INPUT)
}
export function isOptionWidget(paramSpec) {
return paramSpec && paramSpec.widget === Widget.OPTION;
return paramSpec && paramSpec.widget === Widget.OPTION
}
export function isCheckboxWidget(paramSpec) {
return paramSpec && paramSpec.widget === Widget.CHECKBOX;
return paramSpec && paramSpec.widget === Widget.CHECKBOX
}
export function isTextareaWidget(paramSpec) {
return paramSpec && paramSpec.widget === Widget.TEXTAREA;
return paramSpec && paramSpec.widget === Widget.TEXTAREA
}
export const ParameterValueType = {
@ -86,37 +86,29 @@ export function parseParameter(paramSpecs, param) {
/** copy original params */
const parsed = JSON.parse(JSON.stringify(param))
for (let i = 0 ; i < paramSpecs.length; i++) {
for (let i = 0; i < paramSpecs.length; i++) {
const paramSpec = paramSpecs[i]
const name = paramSpec.name
if (paramSpec.valueType === ParameterValueType.INT &&
typeof parsed[name] !== 'number') {
try { parsed[name] = parseInt(parsed[name]); }
catch (error) { parsed[name] = paramSpec.defaultValue; }
}
else if (paramSpec.valueType === ParameterValueType.FLOAT &&
try { parsed[name] = parseInt(parsed[name]) } catch (error) { parsed[name] = paramSpec.defaultValue }
} else if (paramSpec.valueType === ParameterValueType.FLOAT &&
typeof parsed[name] !== 'number') {
try { parsed[name] = parseFloat(parsed[name]); }
catch (error) { parsed[name] = paramSpec.defaultValue; }
}
else if (paramSpec.valueType === ParameterValueType.BOOLEAN) {
try { parsed[name] = parseFloat(parsed[name]) } catch (error) { parsed[name] = paramSpec.defaultValue }
} else if (paramSpec.valueType === ParameterValueType.BOOLEAN) {
if (parsed[name] === 'false') {
parsed[name] = false;
parsed[name] = false
} else if (parsed[name] === 'true') {
parsed[name] = true;
parsed[name] = true
} else if (typeof parsed[name] !== 'boolean') {
parsed[name] = paramSpec.defaultValue;
parsed[name] = paramSpec.defaultValue
}
}
else if (paramSpec.valueType === ParameterValueType.JSON) {
} else if (paramSpec.valueType === ParameterValueType.JSON) {
if (parsed[name] !== null && typeof parsed[name] !== 'object') {
try { parsed[name] = JSON.parse(parsed[name]); }
catch (error) { parsed[name] = paramSpec.defaultValue; }
try { parsed[name] = JSON.parse(parsed[name]) } catch (error) { parsed[name] = paramSpec.defaultValue }
} else if (parsed[name] === null) {
parsed[name] = paramSpec.defaultValue;
parsed[name] = paramSpec.defaultValue
}
}
}
@ -150,12 +142,12 @@ export function isSingleDimensionAxis(axisSpec) {
* add the `name` field while converting to array to easily manipulate
*/
export function getSpecs(specObject) {
const specs = [];
const specs = []
for (let name in specObject) {
const singleSpec = specObject[name];
const singleSpec = specObject[name]
if (!singleSpec) { continue }
singleSpec.name = name;
specs.push(singleSpec);
singleSpec.name = name
specs.push(singleSpec)
}
return specs
@ -163,7 +155,7 @@ export function getSpecs(specObject) {
export function getAvailableChartNames(charts) {
const available = []
for (var name in charts) {
for (let name in charts) {
available.push(name)
}
@ -172,11 +164,11 @@ export function getAvailableChartNames(charts) {
export function applyMaxAxisCount(config, axisSpec) {
if (isSingleDimensionAxis(axisSpec) || typeof axisSpec.maxAxisCount === 'undefined') {
return;
return
}
const columns = getCurrentChartAxis(config)[axisSpec.name]
if (columns.length <= axisSpec.maxAxisCount) { return; }
if (columns.length <= axisSpec.maxAxisCount) { return }
const sliced = columns.slice(1)
getCurrentChartAxis(config)[axisSpec.name] = sliced
@ -213,23 +205,23 @@ export function initAxisConfig(config) {
const spec = config.spec
const availableCharts = getAvailableChartNames(spec.charts)
if (!config.axisSpecs) { config.axisSpecs = {}; }
if (!config.axisSpecs) { config.axisSpecs = {} }
for (let i = 0; i < availableCharts.length; i++) {
const chartName = availableCharts[i];
const chartName = availableCharts[i]
if (!config.axis[chartName]) {
config.axis[chartName] = {};
config.axis[chartName] = {}
}
const axisSpecs = getSpecs(spec.charts[chartName].axis)
if (!config.axisSpecs[chartName]) {
config.axisSpecs[chartName] = axisSpecs;
config.axisSpecs[chartName] = axisSpecs
}
/** initialize multi-dimension axes */
for (let i = 0; i < axisSpecs.length; i++) {
const axisSpec = axisSpecs[i]
if (isSingleDimensionAxis(axisSpec)) {
continue;
continue
}
/** intentionally nested if-stmt is used because order of conditions matter here */
@ -268,18 +260,18 @@ export function initParameterConfig(config) {
const spec = config.spec
const availableCharts = getAvailableChartNames(spec.charts)
if (!config.paramSpecs) { config.paramSpecs = {}; }
if (!config.paramSpecs) { config.paramSpecs = {} }
for (let i = 0; i < availableCharts.length; i++) {
const chartName = availableCharts[i];
const chartName = availableCharts[i]
if (!config.parameter[chartName]) { config.parameter[chartName] = {}; }
if (!config.parameter[chartName]) { config.parameter[chartName] = {} }
const paramSpecs = getSpecs(spec.charts[chartName].parameter)
if (!config.paramSpecs[chartName]) { config.paramSpecs[chartName] = paramSpecs; }
if (!config.paramSpecs[chartName]) { config.paramSpecs[chartName] = paramSpecs }
for (let i = 0; i < paramSpecs.length; i++) {
const paramSpec = paramSpecs[i];
const paramSpec = paramSpecs[i]
if (!config.parameter[chartName][paramSpec.name]) {
config.parameter[chartName][paramSpec.name] = paramSpec.defaultValue;
config.parameter[chartName][paramSpec.name] = paramSpec.defaultValue
}
}
}
@ -295,7 +287,7 @@ export function getSpecVersion(availableCharts, spec) {
const paramHash = {}
for (let i = 0; i < availableCharts.length; i++) {
const chartName = availableCharts[i];
const chartName = availableCharts[i]
const axisSpecs = getSpecs(spec.charts[chartName].axis)
axisHash[chartName] = axisSpecs
@ -318,7 +310,6 @@ export function initializeConfig(config, spec) {
if (!config.spec || !config.spec.version ||
!config.spec.version.axis ||
config.spec.version.axis !== axisVersion) {
spec.initialized = true
updated = true
@ -332,7 +323,6 @@ export function initializeConfig(config, spec) {
if (!config.spec || !config.spec.version ||
!config.spec.version.parameter ||
config.spec.version.parameter !== paramVersion) {
updated = true
clearParameterConfig(config)
@ -343,12 +333,12 @@ export function initializeConfig(config, spec) {
spec.version.axis = axisVersion
spec.version.parameter = paramVersion
if (!config.spec || updated) { config.spec = spec; }
if (!config.spec || updated) { config.spec = spec }
if (!config.chart) {
config.chart = {};
config.chart.current = availableCharts[0];
config.chart.available = availableCharts;
config.chart = {}
config.chart.current = availableCharts[0]
config.chart.available = availableCharts
}
/** initialize config.axis, config.axisSpecs for each chart */
@ -363,8 +353,8 @@ export function getColumnsForMultipleAxes(axisType, axisSpecs, axis) {
const axisNames = []
let column = {}
for(let i = 0; i < axisSpecs.length; i++) {
const axisSpec = axisSpecs[i];
for (let i = 0; i < axisSpecs.length; i++) {
const axisSpec = axisSpecs[i]
if (axisType === AxisType.KEY && isKeyAxis(axisSpec)) {
axisNames.push(axisSpec.name)
@ -375,8 +365,8 @@ export function getColumnsForMultipleAxes(axisType, axisSpecs, axis) {
}
}
for(let axisName of axisNames) {
const columns = axis[axisName];
for (let axisName of axisNames) {
const columns = axis[axisName]
if (typeof axis[axisName] === 'undefined') { continue }
if (!column[axisName]) { column[axisName] = [] }
column[axisName] = column[axisName].concat(columns)
@ -386,35 +376,39 @@ export function getColumnsForMultipleAxes(axisType, axisSpecs, axis) {
}
export function getColumnsFromAxis(axisSpecs, axis) {
const keyAxisNames = [];
const groupAxisNames = [];
const aggrAxisNames = [];
const keyAxisNames = []
const groupAxisNames = []
const aggrAxisNames = []
for(let i = 0; i < axisSpecs.length; i++) {
const axisSpec = axisSpecs[i];
for (let i = 0; i < axisSpecs.length; i++) {
const axisSpec = axisSpecs[i]
if (isKeyAxis(axisSpec)) { keyAxisNames.push(axisSpec.name); }
else if (isGroupAxis(axisSpec)) { groupAxisNames.push(axisSpec.name); }
else if (isAggregatorAxis(axisSpec)) { aggrAxisNames.push(axisSpec.name); }
if (isKeyAxis(axisSpec)) {
keyAxisNames.push(axisSpec.name)
} else if (isGroupAxis(axisSpec)) {
groupAxisNames.push(axisSpec.name)
} else if (isAggregatorAxis(axisSpec)) {
aggrAxisNames.push(axisSpec.name)
}
}
let keyColumns = [];
let groupColumns = [];
let aggregatorColumns = [];
let customColumn = {};
let keyColumns = []
let groupColumns = []
let aggregatorColumns = []
let customColumn = {}
for(let axisName in axis) {
const columns = axis[axisName];
for (let axisName in axis) {
const columns = axis[axisName]
if (keyAxisNames.includes(axisName)) {
keyColumns = keyColumns.concat(columns);
keyColumns = keyColumns.concat(columns)
} else if (groupAxisNames.includes(axisName)) {
groupColumns = groupColumns.concat(columns);
groupColumns = groupColumns.concat(columns)
} else if (aggrAxisNames.includes(axisName)) {
aggregatorColumns = aggregatorColumns.concat(columns);
aggregatorColumns = aggregatorColumns.concat(columns)
} else {
const axisType = axisSpecs.filter(s => s.name === axisName)[0].axisType
if (!customColumn[axisType]) { customColumn[axisType] = []; }
customColumn[axisType] = customColumn[axisType].concat(columns);
if (!customColumn[axisType]) { customColumn[axisType] = [] }
customColumn[axisType] = customColumn[axisType].concat(columns)
}
}
@ -466,10 +460,10 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
const method = transformSpec.method
const columns = getColumnsFromAxis(axisSpecs, axis);
const keyColumns = columns.key;
const groupColumns = columns.group;
const aggregatorColumns = columns.aggregator;
const columns = getColumnsFromAxis(axisSpecs, axis)
const keyColumns = columns.key
const groupColumns = columns.group
const aggregatorColumns = columns.aggregator
const customColumns = columns.custom
let column = {
@ -477,10 +471,10 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
}
if (method === TransformMethod.RAW) {
transformer = () => { return rows; }
transformer = () => { return rows }
} else if (method === TransformMethod.OBJECT) {
transformer = () => {
const { cube, schema, keyColumnName, keyNames, groupNameSet, selectorNameWithIndex, } =
const { cube, schema, keyColumnName, keyNames, groupNameSet, selectorNameWithIndex, } =
getKGACube(rows, keyColumns, groupColumns, aggregatorColumns)
const {
@ -489,7 +483,8 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
keyColumnName, keyNames, groupNameSet, selectorNameWithIndex)
return {
rows: transformed, keyColumnName,
rows: transformed,
keyColumnName,
keyNames,
groupNames: groupNames,
selectors: sortedSelectors,
@ -506,7 +501,8 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
keyColumnName, keyNames, groupNameSet, selectorNameWithIndex)
return {
rows: transformed, keyColumnName,
rows: transformed,
keyColumnName,
keyNames,
groupNames: groupNames,
selectors: sortedSelectors,
@ -524,8 +520,7 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
for (let axisName in keyAxisColumn) {
if (i === 2) { break }
if (i === 0) { key1Columns = keyAxisColumn[axisName] }
else if (i === 1) { key2Columns = keyAxisColumn[axisName] }
if (i === 0) { key1Columns = keyAxisColumn[axisName] } else if (i === 1) { key2Columns = keyAxisColumn[axisName] }
i++
}
@ -553,8 +548,7 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
selectors: sortedSelectors,
}
}
}
else if (method === TransformMethod.DRILL_DOWN) {
} else if (method === TransformMethod.DRILL_DOWN) {
transformer = () => {
const { cube, schema, keyColumnName, keyNames, groupNameSet, selectorNameWithIndex, } =
getKAGCube(rows, keyColumns, groupColumns, aggregatorColumns)
@ -565,7 +559,9 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
keyColumnName, keyNames, groupNameSet, selectorNameWithIndex)
return {
rows: transformed, keyColumnName, keyNames,
rows: transformed,
keyColumnName,
keyNames,
groupNames: groupNames,
selectors: sortedSelectors,
}
@ -577,31 +573,31 @@ export function getTransformer(conf, rows, axisSpecs, axis) {
const AggregatorFunctions = {
sum: function(a, b) {
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return varA + varB;
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return varA + varB
},
count: function(a, b) {
const varA = (a !== undefined) ? parseInt(a) : 0;
const varB = (b !== undefined) ? 1 : 0;
return varA + varB;
const varA = (a !== undefined) ? parseInt(a) : 0
const varB = (b !== undefined) ? 1 : 0
return varA + varB
},
min: function(a, b) {
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return Math.min(varA,varB);
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return Math.min(varA, varB)
},
max: function(a, b) {
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return Math.max(varA,varB);
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return Math.max(varA, varB)
},
avg: function(a, b, c) {
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return varA + varB;
const varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
const varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return varA + varB
}
};
}
const AggregatorFunctionDiv = {
sum: false,
@ -609,7 +605,7 @@ const AggregatorFunctionDiv = {
max: false,
count: false,
avg: true
};
}
/** nested cube `(key) -> (group) -> aggregator` */
export function getKGACube(rows, keyColumns, groupColumns, aggrColumns) {
@ -617,7 +613,7 @@ export function getKGACube(rows, keyColumns, groupColumns, aggrColumns) {
key: keyColumns.length !== 0,
group: groupColumns.length !== 0,
aggregator: aggrColumns.length !== 0,
};
}
let cube = {}
const entry = {}
@ -629,12 +625,12 @@ export function getKGACube(rows, keyColumns, groupColumns, aggrColumns) {
let indexCounter = 0
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let e = entry;
let c = cube;
const row = rows[i]
let e = entry
let c = cube
// key: add to entry
let mergedKeyName = undefined
let mergedKeyName
if (schema.key) {
mergedKeyName = keyColumns.map(c => row[c.index]).join('.')
if (!e[mergedKeyName]) { e[mergedKeyName] = { children: {}, } }
@ -646,7 +642,7 @@ export function getKGACube(rows, keyColumns, groupColumns, aggrColumns) {
keyNameSet.add(mergedKeyName)
}
let mergedGroupName = undefined
let mergedGroupName
if (schema.group) {
mergedGroupName = groupColumns.map(c => row[c.index]).join('.')
@ -690,13 +686,12 @@ export function getKGACube(rows, keyColumns, groupColumns, aggrColumns) {
} else {
const value = AggregatorFunctions[aggrColumn.aggr](
c[aggrName].value, row[aggrColumn.index], c[aggrName].count + 1)
const count = (AggregatorFunctionDiv[aggrColumn.aggr]) ?
c[aggrName].count + 1 : c[aggrName].count
const count = (AggregatorFunctionDiv[aggrColumn.aggr])
? c[aggrName].count + 1 : c[aggrName].count
c[aggrName].value = value
c[aggrName].count = count
}
} /** end loop for aggrColumns */
}
@ -725,7 +720,7 @@ export function getKAGCube(rows, keyColumns, groupColumns, aggrColumns) {
key: keyColumns.length !== 0,
group: groupColumns.length !== 0,
aggregator: aggrColumns.length !== 0,
};
}
let cube = {}
@ -736,11 +731,11 @@ export function getKAGCube(rows, keyColumns, groupColumns, aggrColumns) {
let indexCounter = 0
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let c = cube;
const row = rows[i]
let c = cube
// key: add to entry
let mergedKeyName = undefined
let mergedKeyName
if (schema.key) {
mergedKeyName = keyColumns.map(c => row[c.index]).join('.')
// key: add to row
@ -750,7 +745,7 @@ export function getKAGCube(rows, keyColumns, groupColumns, aggrColumns) {
keyNameSet.add(mergedKeyName)
}
let mergedGroupName = undefined
let mergedGroupName
if (schema.group) {
mergedGroupName = groupColumns.map(c => row[c.index]).join('.')
groupNameSet.add(mergedGroupName)
@ -781,8 +776,8 @@ export function getKAGCube(rows, keyColumns, groupColumns, aggrColumns) {
} else {
const value = AggregatorFunctions[aggrColumn.aggr](
c[aggrName].value, row[aggrColumn.index], c[aggrName].count + 1)
const count = (AggregatorFunctionDiv[aggrColumn.aggr]) ?
c[aggrName].count + 1 : c[aggrName].count
const count = (AggregatorFunctionDiv[aggrColumn.aggr])
? c[aggrName].count + 1 : c[aggrName].count
c[aggrName].value = value
c[aggrName].count = count
@ -800,15 +795,13 @@ export function getKAGCube(rows, keyColumns, groupColumns, aggrColumns) {
const drillDownedCount = c[aggrName].children[mergedGroupName].count
const value = AggregatorFunctions[aggrColumn.aggr](
drillDownedValue, row[aggrColumn.index], drillDownedCount + 1)
const count = (AggregatorFunctionDiv[aggrColumn.aggr]) ?
drillDownedCount + 1 : drillDownedCount
const count = (AggregatorFunctionDiv[aggrColumn.aggr])
? drillDownedCount + 1 : drillDownedCount
c[aggrName].children[mergedGroupName].value = value
c[aggrName].children[mergedGroupName].count = count
}
}
} /** end loop for aggrColumns */
}
@ -837,7 +830,7 @@ export function getKKGACube(rows, key1Columns, key2Columns, groupColumns, aggrCo
key2: key2Columns.length !== 0,
group: groupColumns.length !== 0,
aggregator: aggrColumns.length !== 0,
};
}
let cube = {}
const entry = {}
@ -851,12 +844,12 @@ export function getKKGACube(rows, key1Columns, key2Columns, groupColumns, aggrCo
let indexCounter = 0
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let e = entry;
let c = cube;
const row = rows[i]
let e = entry
let c = cube
// key1: add to entry
let mergedKey1Name = undefined
let mergedKey1Name
if (schema.key1) {
mergedKey1Name = key1Columns.map(c => row[c.index]).join('.')
if (!e[mergedKey1Name]) { e[mergedKey1Name] = { children: {}, } }
@ -869,7 +862,7 @@ export function getKKGACube(rows, key1Columns, key2Columns, groupColumns, aggrCo
}
// key2: add to entry
let mergedKey2Name = undefined
let mergedKey2Name
if (schema.key2) {
mergedKey2Name = key2Columns.map(c => row[c.index]).join('.')
if (!e[mergedKey2Name]) { e[mergedKey2Name] = { children: {}, } }
@ -881,7 +874,7 @@ export function getKKGACube(rows, key1Columns, key2Columns, groupColumns, aggrCo
if (!key2NameSet[mergedKey2Name]) { key2NameSet[mergedKey2Name] = true }
}
let mergedGroupName = undefined
let mergedGroupName
if (schema.group) {
mergedGroupName = groupColumns.map(c => row[c.index]).join('.')
@ -925,13 +918,12 @@ export function getKKGACube(rows, key1Columns, key2Columns, groupColumns, aggrCo
} else {
const value = AggregatorFunctions[aggrColumn.aggr](
c[aggrName].value, row[aggrColumn.index], c[aggrName].count + 1)
const count = (AggregatorFunctionDiv[aggrColumn.aggr]) ?
c[aggrName].count + 1 : c[aggrName].count
const count = (AggregatorFunctionDiv[aggrColumn.aggr])
? c[aggrName].count + 1 : c[aggrName].count
c[aggrName].value = value
c[aggrName].count = count
}
} /** end loop for aggrColumns */
}
@ -954,8 +946,8 @@ export function getSelectorName(mergedGroupName, aggrColumnLength, aggrColumnNam
if (!mergedGroupName) {
return aggrColumnName
} else {
return (aggrColumnLength > 1) ?
`${mergedGroupName} / ${aggrColumnName}` : mergedGroupName
return (aggrColumnLength > 1)
? `${mergedGroupName} / ${aggrColumnName}` : mergedGroupName
}
}
@ -990,7 +982,6 @@ export function getNameWithIndex(names) {
export function getArrayRowsFromKKGACube(cube, schema, aggregatorColumns,
key1Names, key2Names, groupNameSet, selectorNameWithIndex) {
const sortedSelectors = Object.keys(selectorNameWithIndex).sort()
const sortedSelectorNameWithIndex = getNameWithIndex(sortedSelectors)
@ -1015,7 +1006,6 @@ export function getArrayRowsFromKKGACube(cube, schema, aggregatorColumns,
export function fillSelectorRows(schema, cube, selectorRows,
aggrColumns, selectorNameWithIndex,
key1Names, key2Names) {
function fill(grouped, mergedGroupName, key1Name, key2Name) {
// should iterate aggrColumns in the most nested loop to utilize memory locality
for (let aggrColumn of aggrColumns) {
@ -1083,14 +1073,13 @@ export function fillSelectorRows(schema, cube, selectorRows,
export function getArrayRowsFromKGACube(cube, schema, aggregatorColumns,
keyColumnName, keyNames, groupNameSet,
selectorNameWithIndex) {
const sortedSelectors = Object.keys(selectorNameWithIndex).sort()
const sortedSelectorNameWithIndex = getNameWithIndex(sortedSelectors)
const keyArrowRows = new Array(sortedSelectors.length)
const keyNameWithIndex = getNameWithIndex(keyNames)
for(let i = 0; i < keyNames.length; i++) {
for (let i = 0; i < keyNames.length; i++) {
const key = keyNames[i]
const obj = cube[key]
@ -1110,7 +1099,6 @@ export function getArrayRowsFromKGACube(cube, schema, aggregatorColumns,
export function fillArrayRow(schema, aggrColumns, obj,
groupNameSet, selectorNameWithIndex,
keyName, keyNames, keyArrayRows, keyNameWithIndex) {
function fill(target, mergedGroupName, aggr, aggrName) {
const value = getCubeValue(target, aggr, aggrName)
const selector = getSelectorName(mergedGroupName, aggrColumns.length, aggrName)
@ -1127,13 +1115,13 @@ export function fillArrayRow(schema, aggrColumns, obj,
/** when group is empty */
if (!schema.group) {
for(let i = 0; i < aggrColumns.length; i++) {
for (let i = 0; i < aggrColumns.length; i++) {
const aggrColumn = aggrColumns[i]
const aggrName = `${aggrColumn.name}(${aggrColumn.aggr})`
fill(obj, undefined, aggrColumn.aggr, aggrName)
}
} else {
for(let i = 0; i < aggrColumns.length; i++) {
for (let i = 0; i < aggrColumns.length; i++) {
const aggrColumn = aggrColumns[i]
const aggrName = `${aggrColumn.name}(${aggrColumn.aggr})`
@ -1148,7 +1136,6 @@ export function fillArrayRow(schema, aggrColumns, obj,
export function getObjectRowsFromKGACube(cube, schema, aggregatorColumns,
keyColumnName, keyNames, groupNameSet,
selectorNameWithIndex) {
const rows = keyNames.reduce((acc, key) => {
const obj = cube[key]
const row = getObjectRow(schema, aggregatorColumns, obj, groupNameSet)
@ -1177,7 +1164,7 @@ export function getObjectRow(schema, aggrColumns, obj, groupNameSet) {
/** when group is empty */
if (!schema.group) {
for(let i = 0; i < aggrColumns.length; i++) {
for (let i = 0; i < aggrColumns.length; i++) {
const aggrColumn = aggrColumns[i]
const aggrName = `${aggrColumn.name}(${aggrColumn.aggr})`
@ -1188,7 +1175,7 @@ export function getObjectRow(schema, aggrColumns, obj, groupNameSet) {
}
/** when group is specified */
for(let i = 0; i < aggrColumns.length; i++) {
for (let i = 0; i < aggrColumns.length; i++) {
const aggrColumn = aggrColumns[i]
const aggrName = `${aggrColumn.name}(${aggrColumn.aggr})`
@ -1206,7 +1193,6 @@ export function getObjectRow(schema, aggrColumns, obj, groupNameSet) {
export function getDrilldownRowsFromKAGCube(cube, schema, aggregatorColumns,
keyColumnName, keyNames, groupNameSet, selectorNameWithIndex) {
const sortedSelectors = Object.keys(selectorNameWithIndex).sort()
const sortedSelectorNameWithIndex = getNameWithIndex(sortedSelectors)
@ -1232,7 +1218,7 @@ export function getDrilldownRowsFromKAGCube(cube, schema, aggregatorColumns,
export function fillDrillDownRow(schema, obj, rows, key,
selectorNameWithIndex, aggrColumns, groupNames) {
/** when group is empty */
for(let i = 0; i < aggrColumns.length; i++) {
for (let i = 0; i < aggrColumns.length; i++) {
const row = {}
const aggrColumn = aggrColumns[i]
const aggrName = `${aggrColumn.name}(${aggrColumn.aggr})`
@ -1248,7 +1234,7 @@ export function fillDrillDownRow(schema, obj, rows, key,
if (schema.group) {
row.drillDown = []
for(let groupName of groupNames) {
for (let groupName of groupNames) {
const value = getCubeValue(obj[aggrName].children, aggrColumn.aggr, groupName)
row.drillDown.push({ group: groupName, value: value, })
}

View file

@ -50,7 +50,6 @@ const MockAxis4 = {
'groupAxis': { dimension: 'multiple', axisType: 'group', },
}
// test spec for axis, param, widget
const MockSpec = {
charts: {
@ -138,23 +137,23 @@ const MockSpec2 = {
/* eslint-disable max-len */
const MockTableDataColumn = [
{'name': 'age', 'index': 0, 'aggr': 'sum',},
{'name': 'job', 'index': 1, 'aggr': 'sum',},
{'name': 'marital', 'index': 2, 'aggr': 'sum',},
{'name': 'education', 'index': 3, 'aggr': 'sum',},
{'name': 'default', 'index': 4, 'aggr': 'sum',},
{'name': 'balance', 'index': 5, 'aggr': 'sum',},
{'name': 'housing', 'index': 6, 'aggr': 'sum',},
{'name': 'loan', 'index': 7, 'aggr': 'sum',},
{'name': 'contact', 'index': 8, 'aggr': 'sum',},
{'name': 'day', 'index': 9, 'aggr': 'sum',},
{'name': 'month', 'index': 10, 'aggr': 'sum',},
{'name': 'duration', 'index': 11, 'aggr': 'sum',},
{'name': 'campaign', 'index': 12, 'aggr': 'sum',},
{'name': 'pdays', 'index': 13, 'aggr': 'sum',},
{'name': 'previous', 'index': 14, 'aggr': 'sum',},
{'name': 'poutcome', 'index': 15, 'aggr': 'sum',},
{'name': 'y', 'index': 16, 'aggr': 'sum',}
{'name': 'age', 'index': 0, 'aggr': 'sum', },
{'name': 'job', 'index': 1, 'aggr': 'sum', },
{'name': 'marital', 'index': 2, 'aggr': 'sum', },
{'name': 'education', 'index': 3, 'aggr': 'sum', },
{'name': 'default', 'index': 4, 'aggr': 'sum', },
{'name': 'balance', 'index': 5, 'aggr': 'sum', },
{'name': 'housing', 'index': 6, 'aggr': 'sum', },
{'name': 'loan', 'index': 7, 'aggr': 'sum', },
{'name': 'contact', 'index': 8, 'aggr': 'sum', },
{'name': 'day', 'index': 9, 'aggr': 'sum', },
{'name': 'month', 'index': 10, 'aggr': 'sum', },
{'name': 'duration', 'index': 11, 'aggr': 'sum', },
{'name': 'campaign', 'index': 12, 'aggr': 'sum', },
{'name': 'pdays', 'index': 13, 'aggr': 'sum', },
{'name': 'previous', 'index': 14, 'aggr': 'sum', },
{'name': 'poutcome', 'index': 15, 'aggr': 'sum', },
{'name': 'y', 'index': 16, 'aggr': 'sum', }
]
const MockTableDataRows1 = [
@ -169,7 +168,7 @@ const MockTableDataRows1 = [
describe('advanced-transformation-util', () => {
describe('getCurrent* funcs', () => {
it('should set return proper value of the current chart', () => {
const config = {}
const config = {}
const spec = JSON.parse(JSON.stringify(MockSpec))
Util.initializeConfig(config, spec)
expect(Util.getCurrentChart(config)).toEqual('object-chart')
@ -183,7 +182,7 @@ describe('advanced-transformation-util', () => {
describe('useSharedAxis', () => {
it('should set chartChanged for initial drawing', () => {
const config = {}
const config = {}
const spec = JSON.parse(JSON.stringify(MockSpec))
Util.initializeConfig(config, spec)
expect(Util.useSharedAxis(config, 'object-chart')).toEqual(true)
@ -194,7 +193,7 @@ describe('advanced-transformation-util', () => {
})
describe('initializeConfig', () => {
const config = {}
const config = {}
const spec = JSON.parse(JSON.stringify(MockSpec))
Util.initializeConfig(config, spec)
@ -269,7 +268,6 @@ describe('advanced-transformation-util', () => {
// it's ok not to set single dimension axis
expect(config.axis['raw-chart']).toEqual({ customAxis2: [], })
})
})
describe('axis', () => {
@ -354,7 +352,6 @@ describe('advanced-transformation-util', () => {
expect(typeof parsed.stringParam).toBe('string')
expect(parsed.stringParam).toBe('example')
})
})
describe('removeDuplicatedColumnsInMultiDimensionAxis', () => {
@ -472,7 +469,6 @@ describe('advanced-transformation-util', () => {
// it's hard to test all methods for transformation.
// so let's do behavioral (black-box) test instead of
describe('getTransformer', () => {
describe('method: raw', () => {
let config = {}
const spec = JSON.parse(JSON.stringify(MockSpec2))
@ -935,8 +931,8 @@ describe('advanced-transformation-util', () => {
expect(groupNames).toEqual([ 'age(sum)', ])
expect(selectors).toEqual([ 'age(sum)', ])
expect(rows).toEqual([
{ 'age(sum)': 82, marital: 'married', },
{ 'age(sum)': 77, marital: 'single', },
{ 'age(sum)': 82, 'marital': 'married', },
{ 'age(sum)': 77, 'marital': 'single', },
])
})
@ -1021,7 +1017,7 @@ describe('advanced-transformation-util', () => {
expect(groupNames).toEqual([ 'age(sum)', ])
expect(selectors).toEqual([ 'age(sum)', ])
expect(rows).toEqual([
{ selector: 'age(sum)', value: 44 + 43 + 39 + 33, drillDown: [ ], },
{ selector: 'age(sum)', value: 44 + 43 + 39 + 33, drillDown: [ ], },
])
})
@ -1035,7 +1031,7 @@ describe('advanced-transformation-util', () => {
const { rows, } = transformer()
expect(rows).toEqual([
{ selector: 'age(count)', value: 4, drillDown: [ ], },
{ selector: 'age(count)', value: 4, drillDown: [ ], },
])
})
@ -1049,7 +1045,7 @@ describe('advanced-transformation-util', () => {
const { rows, } = transformer()
expect(rows).toEqual([
{ selector: 'age(avg)', value: (44 + 43 + 39 + 33) / 4.0, drillDown: [ ], },
{ selector: 'age(avg)', value: (44 + 43 + 39 + 33) / 4.0, drillDown: [ ], },
])
})
@ -1063,7 +1059,7 @@ describe('advanced-transformation-util', () => {
const { rows, } = transformer()
expect(rows).toEqual([
{ selector: 'age(max)', value: 44, drillDown: [ ], },
{ selector: 'age(max)', value: 44, drillDown: [ ], },
])
})
@ -1077,7 +1073,7 @@ describe('advanced-transformation-util', () => {
const { rows, } = transformer()
expect(rows).toEqual([
{ selector: 'age(min)', value: 33, drillDown: [ ], },
{ selector: 'age(min)', value: 33, drillDown: [ ], },
])
})
@ -1098,8 +1094,8 @@ describe('advanced-transformation-util', () => {
expect(groupNames).toEqual([ 'age(sum)', 'balance(sum)', ])
expect(selectors).toEqual([ 'age(sum)', 'balance(sum)', ])
expect(rows).toEqual([
{ selector: 'age(sum)', value: 159, drillDown: [ ], },
{ selector: 'balance(sum)', value: 14181, drillDown: [ ], },
{ selector: 'age(sum)', value: 159, drillDown: [ ], },
{ selector: 'balance(sum)', value: 14181, drillDown: [ ], },
])
})
@ -1212,8 +1208,8 @@ describe('advanced-transformation-util', () => {
expect(groupNames).toEqual([ 'age(sum)', ])
expect(selectors).toEqual([ 'married', 'single', ])
expect(rows).toEqual([
{ selector: 'married', value: 82, drillDown: [ ], },
{ selector: 'single', value: 77, drillDown: [ ], },
{ selector: 'married', value: 82, drillDown: [ ], },
{ selector: 'single', value: 77, drillDown: [ ], },
])
})
@ -1234,9 +1230,9 @@ describe('advanced-transformation-util', () => {
expect(groupNames).toEqual([ 'age(sum)', ])
expect(selectors).toEqual([ 'married.primary', 'married.secondary', 'single.tertiary', ])
expect(rows).toEqual([
{ selector: 'married.primary', value: '43', drillDown: [ ], },
{ selector: 'married.secondary', value: '39', drillDown: [ ], },
{ selector: 'single.tertiary', value: 77, drillDown: [ ], },
{ selector: 'married.primary', value: '43', drillDown: [ ], },
{ selector: 'married.secondary', value: '39', drillDown: [ ], },
{ selector: 'single.tertiary', value: 77, drillDown: [ ], },
])
})
@ -1738,9 +1734,6 @@ describe('advanced-transformation-util', () => {
},
])
})
}) // end: describe('method: array:2-key')
}) // end: describe('getTransformer')
})

View file

@ -12,7 +12,7 @@
* limitations under the License.
*/
import Transformation from './transformation';
import Transformation from './transformation'
import {
getCurrentChart, getCurrentChartAxis, getCurrentChartParam,
@ -23,19 +23,19 @@ import {
removeDuplicatedColumnsInMultiDimensionAxis, applyMaxAxisCount,
isInputWidget, isOptionWidget, isCheckboxWidget, isTextareaWidget, parseParameter,
getTransformer,
} from './advanced-transformation-util';
} from './advanced-transformation-util'
const SETTING_TEMPLATE = 'app/tabledata/advanced-transformation-setting.html';
const SETTING_TEMPLATE = 'app/tabledata/advanced-transformation-setting.html'
export default class AdvancedTransformation extends Transformation {
constructor(config, spec) {
super(config);
super(config)
this.columns = []; /** [{ name, index, comment }] */
this.props = {};
this.columns = [] /** [{ name, index, comment }] */
this.props = {}
this.spec = spec
initializeConfig(config, spec);
initializeConfig(config, spec)
}
emitConfigChange(conf) {
@ -57,8 +57,8 @@ export default class AdvancedTransformation extends Transformation {
}
getSetting() {
const self = this; /** for closure */
const configInstance = self.config; /** for closure */
const self = this /** for closure */
const configInstance = self.config /** for closure */
if (self.spec.initialized) {
self.spec.initialized = false
@ -111,7 +111,7 @@ export default class AdvancedTransformation extends Transformation {
let comment = ''
if (minAxisCount) { comment = `min: ${minAxisCount}` }
if (minAxisCount && maxAxisCount) { comment = `${comment}, `}
if (minAxisCount && maxAxisCount) { comment = `${comment}, ` }
if (maxAxisCount) { comment = `${comment}max: ${maxAxisCount}` }
if (comment !== '') {
@ -123,13 +123,13 @@ export default class AdvancedTransformation extends Transformation {
getAxisTypeAnnotationColor: (axisSpec) => {
if (isAggregatorAxis(axisSpec)) {
return { 'background-color': '#5782bd' };
return { 'background-color': '#5782bd' }
} else if (isGroupAxis(axisSpec)) {
return { 'background-color': '#cd5c5c' };
return { 'background-color': '#cd5c5c' }
} else if (isKeyAxis(axisSpec)) {
return { 'background-color': '#906ebd' };
return { 'background-color': '#906ebd' }
} else {
return { 'background-color': '#62bda9' };
return { 'background-color': '#62bda9' }
}
},
@ -179,14 +179,13 @@ export default class AdvancedTransformation extends Transformation {
isTextareaWidget: function(paramSpec) { return isTextareaWidget(paramSpec) },
parameterChanged: (paramSpec) => {
configInstance.chartChanged = false
configInstance.parameterChanged = true
self.emitParameterChange(configInstance)
},
parameterOnKeyDown: function(event, paramSpec) {
const code = event.keyCode || event.which;
const code = event.keyCode || event.which
if (code === 13 && isInputWidget(paramSpec)) {
self.emitParameterChange(configInstance)
} else if (code === 13 && event.shiftKey && isTextareaWidget(paramSpec)) {
@ -201,7 +200,7 @@ export default class AdvancedTransformation extends Transformation {
}
transform(tableData) {
this.columns = tableData.columns; /** used in `getSetting` */
this.columns = tableData.columns /** used in `getSetting` */
/** initialize in `transform` instead of `getSetting` because this method is called before */
serializeSharedAxes(this.config)
@ -213,7 +212,7 @@ export default class AdvancedTransformation extends Transformation {
const paramSpecs = getCurrentChartParamSpecs(conf)
const parsedParam = parseParameter(paramSpecs, param)
let { transformer, column, } = getTransformer(conf, tableData.rows, axisSpecs, axis)
let { transformer, column, } = getTransformer(conf, tableData.rows, axisSpecs, axis)
return {
chartChanged: conf.chartChanged,

View file

@ -12,7 +12,7 @@
* limitations under the License.
*/
import Transformation from './transformation';
import Transformation from './transformation'
/**
* select columns
@ -26,57 +26,57 @@ import Transformation from './transformation';
* ]
*/
export default class ColumnselectorTransformation extends Transformation {
constructor(config, columnSelectorProp) {
super(config);
this.props = columnSelectorProp;
};
constructor (config, columnSelectorProp) {
super(config)
this.props = columnSelectorProp
}
getSetting() {
var self = this;
var configObj = self.config;
getSetting () {
let self = this
let configObj = self.config
return {
template: 'app/tabledata/columnselector_settings.html',
scope: {
config: self.config,
props: self.props,
tableDataColumns: self.tableDataColumns,
save: function() {
self.emitConfig(configObj);
save: function () {
self.emitConfig(configObj)
},
remove: function(selectorName) {
configObj[selectorName] = null;
self.emitConfig(configObj);
remove: function (selectorName) {
configObj[selectorName] = null
self.emitConfig(configObj)
}
}
};
};
}
}
/**
* Method will be invoked when tableData or config changes
*/
transform(tableData) {
this.tableDataColumns = tableData.columns;
this.removeUnknown();
return tableData;
};
transform (tableData) {
this.tableDataColumns = tableData.columns
this.removeUnknown()
return tableData
}
removeUnknown() {
var fields = this.config;
for (var f in fields) {
removeUnknown () {
let fields = this.config
for (let f in fields) {
if (fields[f]) {
var found = false;
for (var i = 0; i < this.tableDataColumns.length; i++) {
var a = fields[f];
var b = this.tableDataColumns[i];
let found = false
for (let i = 0; i < this.tableDataColumns.length; i++) {
let a = fields[f]
let b = this.tableDataColumns[i]
if (a.index === b.index && a.name === b.name) {
found = true;
break;
found = true
break
}
}
if (!found && (fields[f] instanceof Object) && !(fields[f] instanceof Array)) {
fields[f] = null;
fields[f] = null
}
}
}
};
}
}

View file

@ -12,20 +12,21 @@
* limitations under the License.
*/
import Transformation from './transformation';
import Transformation from './transformation'
/**
* passthough the data
*/
export default class PassthroughTransformation extends Transformation {
constructor(config) {
super(config);
};
// eslint-disable-next-line no-useless-constructor
constructor (config) {
super(config)
}
/**
* Method will be invoked when tableData or config changes
*/
transform(tableData) {
return tableData;
};
transform (tableData) {
return tableData
}
}

View file

@ -12,174 +12,175 @@
* limitations under the License.
*/
import Transformation from './transformation';
import Transformation from './transformation'
/**
* pivot table data and return d3 chart data
*/
export default class PivotTransformation extends Transformation {
constructor(config) {
super(config);
};
// eslint-disable-next-line no-useless-constructor
constructor (config) {
super(config)
}
getSetting() {
var self = this;
getSetting () {
let self = this
var configObj = self.config;
console.log('getSetting', configObj);
let configObj = self.config
console.log('getSetting', configObj)
return {
template: 'app/tabledata/pivot_settings.html',
scope: {
config: configObj.common.pivot,
tableDataColumns: self.tableDataColumns,
save: function() {
self.emitConfig(configObj);
save: function () {
self.emitConfig(configObj)
},
removeKey: function(idx) {
configObj.common.pivot.keys.splice(idx, 1);
self.emitConfig(configObj);
removeKey: function (idx) {
configObj.common.pivot.keys.splice(idx, 1)
self.emitConfig(configObj)
},
removeGroup: function(idx) {
configObj.common.pivot.groups.splice(idx, 1);
self.emitConfig(configObj);
removeGroup: function (idx) {
configObj.common.pivot.groups.splice(idx, 1)
self.emitConfig(configObj)
},
removeValue: function(idx) {
configObj.common.pivot.values.splice(idx, 1);
self.emitConfig(configObj);
removeValue: function (idx) {
configObj.common.pivot.values.splice(idx, 1)
self.emitConfig(configObj)
},
setValueAggr: function(idx, aggr) {
configObj.common.pivot.values[idx].aggr = aggr;
self.emitConfig(configObj);
setValueAggr: function (idx, aggr) {
configObj.common.pivot.values[idx].aggr = aggr
self.emitConfig(configObj)
}
}
};
};
}
}
/**
* Method will be invoked when tableData or config changes
*/
transform(tableData) {
this.tableDataColumns = tableData.columns;
this.config.common = this.config.common || {};
this.config.common.pivot = this.config.common.pivot || {};
var config = this.config.common.pivot;
var firstTime = (!config.keys && !config.groups && !config.values);
transform (tableData) {
this.tableDataColumns = tableData.columns
this.config.common = this.config.common || {}
this.config.common.pivot = this.config.common.pivot || {}
let config = this.config.common.pivot
let firstTime = (!config.keys && !config.groups && !config.values)
config.keys = config.keys || [];
config.groups = config.groups || [];
config.values = config.values || [];
config.keys = config.keys || []
config.groups = config.groups || []
config.values = config.values || []
this.removeUnknown();
this.removeUnknown()
if (firstTime) {
this.selectDefault();
this.selectDefault()
}
return this.pivot(
tableData,
config.keys,
config.groups,
config.values);
};
config.values)
}
removeUnknown() {
var config = this.config.common.pivot;
var tableDataColumns = this.tableDataColumns;
var unique = function(list) {
for (var i = 0; i < list.length; i++) {
for (var j = i + 1; j < list.length; j++) {
removeUnknown () {
let config = this.config.common.pivot
let tableDataColumns = this.tableDataColumns
let unique = function (list) {
for (let i = 0; i < list.length; i++) {
for (let j = i + 1; j < list.length; j++) {
if (angular.equals(list[i], list[j])) {
list.splice(j, 1);
list.splice(j, 1)
}
}
}
};
}
var removeUnknown = function(list) {
for (var i = 0; i < list.length; i++) {
let removeUnknown = function (list) {
for (let i = 0; i < list.length; i++) {
// remove non existing column
var found = false;
for (var j = 0; j < tableDataColumns.length; j++) {
var a = list[i];
var b = tableDataColumns[j];
let found = false
for (let j = 0; j < tableDataColumns.length; j++) {
let a = list[i]
let b = tableDataColumns[j]
if (a.index === b.index && a.name === b.name) {
found = true;
break;
found = true
break
}
}
if (!found) {
list.splice(i, 1);
list.splice(i, 1)
}
}
};
}
unique(config.keys);
removeUnknown(config.keys);
unique(config.groups);
removeUnknown(config.groups);
removeUnknown(config.values);
};
unique(config.keys)
removeUnknown(config.keys)
unique(config.groups)
removeUnknown(config.groups)
removeUnknown(config.values)
}
selectDefault() {
var config = this.config.common.pivot;
selectDefault () {
let config = this.config.common.pivot
if (config.keys.length === 0 &&
config.groups.length === 0 &&
config.values.length === 0) {
if (config.keys.length === 0 && this.tableDataColumns.length > 0) {
config.keys.push(this.tableDataColumns[0]);
config.keys.push(this.tableDataColumns[0])
}
if (config.values.length === 0 && this.tableDataColumns.length > 1) {
config.values.push(this.tableDataColumns[1]);
config.values.push(this.tableDataColumns[1])
}
}
};
}
pivot(data, keys, groups, values) {
var aggrFunc = {
sum: function(a, b) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return varA + varB;
pivot (data, keys, groups, values) {
let aggrFunc = {
sum: function (a, b) {
let varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
let varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return varA + varB
},
count: function(a, b) {
var varA = (a !== undefined) ? parseInt(a) : 0;
var varB = (b !== undefined) ? 1 : 0;
return varA + varB;
count: function (a, b) {
let varA = (a !== undefined) ? parseInt(a) : 0
let varB = (b !== undefined) ? 1 : 0
return varA + varB
},
min: function(a, b) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return Math.min(varA,varB);
min: function (a, b) {
let varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
let varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return Math.min(varA, varB)
},
max: function(a, b) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return Math.max(varA,varB);
max: function (a, b) {
let varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
let varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return Math.max(varA, varB)
},
avg: function(a, b, c) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return varA + varB;
avg: function (a, b, c) {
let varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0
let varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0
return varA + varB
}
};
}
var aggrFuncDiv = {
let aggrFuncDiv = {
sum: false,
count: false,
min: false,
max: false,
avg: true
};
}
var schema = {};
var rows = {};
let schema = {}
let rows = {}
for (var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
var s = schema;
var p = rows;
for (let i = 0; i < data.rows.length; i++) {
let row = data.rows[i]
let s = schema
let p = rows
for (var k = 0; k < keys.length; k++) {
var key = keys[k];
for (let k = 0; k < keys.length; k++) {
let key = keys[k]
// add key to schema
if (!s[key.name]) {
@ -188,21 +189,21 @@ export default class PivotTransformation extends Transformation {
index: key.index,
type: 'key',
children: {}
};
}
}
s = s[key.name].children;
s = s[key.name].children
// add key to row
var keyKey = row[key.index];
let keyKey = row[key.index]
if (!p[keyKey]) {
p[keyKey] = {};
p[keyKey] = {}
}
p = p[keyKey];
p = p[keyKey]
}
for (var g = 0; g < groups.length; g++) {
var group = groups[g];
var groupKey = row[group.index];
for (let g = 0; g < groups.length; g++) {
let group = groups[g]
let groupKey = row[group.index]
// add group to schema
if (!s[groupKey]) {
@ -211,20 +212,20 @@ export default class PivotTransformation extends Transformation {
index: group.index,
type: 'group',
children: {}
};
}
}
s = s[groupKey].children;
s = s[groupKey].children
// add key to row
if (!p[groupKey]) {
p[groupKey] = {};
p[groupKey] = {}
}
p = p[groupKey];
p = p[groupKey]
}
for (var v = 0; v < values.length; v++) {
var value = values[v];
var valueKey = value.name + '(' + value.aggr + ')';
for (let v = 0; v < values.length; v++) {
let value = values[v]
let valueKey = value.name + '(' + value.aggr + ')'
// add value to schema
if (!s[valueKey]) {
@ -232,7 +233,7 @@ export default class PivotTransformation extends Transformation {
type: 'value',
order: v,
index: value.index
};
}
}
// add value to row
@ -240,23 +241,23 @@ export default class PivotTransformation extends Transformation {
p[valueKey] = {
value: (value.aggr !== 'count') ? row[value.index] : 1,
count: 1
};
}
} else {
p[valueKey] = {
value: aggrFunc[value.aggr](p[valueKey].value, row[value.index], p[valueKey].count + 1),
count: (aggrFuncDiv[value.aggr]) ? p[valueKey].count + 1 : p[valueKey].count
};
count: (aggrFuncDiv[value.aggr]) ? p[valueKey].count + 1 : p[valueKey].count
}
}
}
}
//console.log('schema=%o, rows=%o', schema, rows);
// console.log('schema=%o, rows=%o', schema, rows);
return {
keys: keys,
groups: groups,
values: values,
schema: schema,
rows: rows
};
};
}
}
}

View file

@ -16,58 +16,58 @@
* Create table data object from paragraph table type result
*/
export default class TableData {
constructor(columns, rows, comment) {
this.columns = columns || [];
this.rows = rows || [];
this.comment = comment || '';
};
constructor (columns, rows, comment) {
this.columns = columns || []
this.rows = rows || []
this.comment = comment || ''
}
loadParagraphResult(paragraphResult) {
loadParagraphResult (paragraphResult) {
if (!paragraphResult || paragraphResult.type !== 'TABLE') {
console.log('Can not load paragraph result');
return;
console.log('Can not load paragraph result')
return
}
var columnNames = [];
var rows = [];
var array = [];
var textRows = paragraphResult.msg.split('\n');
var comment = '';
var commentRow = false;
let columnNames = []
let rows = []
let array = []
let textRows = paragraphResult.msg.split('\n')
let comment = ''
let commentRow = false
for (var i = 0; i < textRows.length; i++) {
var textRow = textRows[i];
for (let i = 0; i < textRows.length; i++) {
let textRow = textRows[i]
if (commentRow) {
comment += textRow;
continue;
comment += textRow
continue
}
if (textRow === '' || textRow === '<!--TABLE_COMMENT-->') {
if (rows.length > 0) {
commentRow = true;
commentRow = true
}
continue;
continue
}
var textCols = textRow.split('\t');
var cols = [];
var cols2 = [];
for (var j = 0; j < textCols.length; j++) {
var col = textCols[j];
let textCols = textRow.split('\t')
let cols = []
let cols2 = []
for (let j = 0; j < textCols.length; j++) {
let col = textCols[j]
if (i === 0) {
columnNames.push({name: col, index: j, aggr: 'sum'});
columnNames.push({name: col, index: j, aggr: 'sum'})
} else {
cols.push(col);
cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: col});
cols.push(col)
cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: col})
}
}
if (i !== 0) {
rows.push(cols);
array.push(cols2);
rows.push(cols)
array.push(cols2)
}
}
this.comment = comment;
this.columns = columnNames;
this.rows = rows;
};
this.comment = comment
this.columns = columnNames
this.rows = rows
}
}

View file

@ -12,30 +12,30 @@
* limitations under the License.
*/
import TableData from './tabledata.js';
import TableData from './tabledata.js'
describe('TableData build', function() {
var td;
describe('TableData build', function () {
let td
beforeEach(function() {
console.log(TableData);
td = new TableData();
});
beforeEach(function () {
console.log(TableData)
td = new TableData()
})
it('should initialize the default value', function() {
expect(td.columns.length).toBe(0);
expect(td.rows.length).toBe(0);
expect(td.comment).toBe('');
});
it('should initialize the default value', function () {
expect(td.columns.length).toBe(0)
expect(td.rows.length).toBe(0)
expect(td.comment).toBe('')
})
it('should able to create Tabledata from paragraph result', function() {
it('should able to create Tabledata from paragraph result', function () {
td.loadParagraphResult({
type: 'TABLE',
msg: 'key\tvalue\na\t10\nb\t20\n\nhello'
});
})
expect(td.columns.length).toBe(2);
expect(td.rows.length).toBe(2);
expect(td.comment).toBe('hello');
});
});
expect(td.columns.length).toBe(2)
expect(td.rows.length).toBe(2)
expect(td.comment).toBe('hello')
})
})

View file

@ -16,10 +16,10 @@
* Base class for visualization
*/
export default class Transformation {
constructor(config) {
this.config = config;
this._emitter;
};
constructor (config) {
this.config = config
this._emitter = () => {}
}
/**
* return {
@ -27,77 +27,77 @@ export default class Transformation {
* scope : an object to bind to template scope
* }
*/
getSetting() {
getSetting () {
// override this
};
}
/**
* Method will be invoked when tableData or config changes
*/
transform(tableData) {
transform (tableData) {
// override this
};
}
/**
* render setting
*/
renderSetting(targetEl) {
var setting = this.getSetting();
renderSetting (targetEl) {
let setting = this.getSetting()
if (!setting) {
return;
return
}
// already readered
if (this._scope) {
var self = this;
this._scope.$apply(function() {
for (var k in setting.scope) {
self._scope[k] = setting.scope[k];
let self = this
this._scope.$apply(function () {
for (let k in setting.scope) {
self._scope[k] = setting.scope[k]
}
for (var k in self._prevSettingScope) {
for (let k in self._prevSettingScope) {
if (!setting.scope[k]) {
self._scope[k] = setting.scope[k];
self._scope[k] = setting.scope[k]
}
}
});
return;
})
return
} else {
this._prevSettingScope = setting.scope;
this._prevSettingScope = setting.scope
}
var scope = this._createNewScope();
for (var k in setting.scope) {
scope[k] = setting.scope[k];
let scope = this._createNewScope()
for (let k in setting.scope) {
scope[k] = setting.scope[k]
}
var template = setting.template;
let template = setting.template
if (template.split('\n').length === 1 &&
template.endsWith('.html')) { // template is url
var self = this;
this._templateRequest(template).then(function(t) {
self._render(targetEl, t, scope);
});
let self = this
this._templateRequest(template).then(function (t) {
self._render(targetEl, t, scope)
})
} else {
this._render(targetEl, template, scope);
this._render(targetEl, template, scope)
}
};
}
_render(targetEl, template, scope) {
this._targetEl = targetEl;
targetEl.html(template);
this._compile(targetEl.contents())(scope);
this._scope = scope;
};
_render (targetEl, template, scope) {
this._targetEl = targetEl
targetEl.html(template)
this._compile(targetEl.contents())(scope)
this._scope = scope
}
setConfig(config) {
this.config = config;
};
setConfig (config) {
this.config = config
}
/**
* Emit config. config will sent to server and saved.
*/
emitConfig(config) {
this._emitter(config);
};
emitConfig (config) {
this._emitter(config)
}
}

View file

@ -12,29 +12,29 @@
* limitations under the License.
*/
import Nvd3ChartVisualization from './visualization-nvd3chart';
import PivotTransformation from '../../tabledata/pivot';
import Nvd3ChartVisualization from './visualization-nvd3chart'
import PivotTransformation from '../../tabledata/pivot'
/**
* Visualize data in area chart
*/
export default class AreachartVisualization extends Nvd3ChartVisualization {
constructor(targetEl, config) {
super(targetEl, config);
constructor (targetEl, config) {
super(targetEl, config)
this.pivot = new PivotTransformation(config);
};
this.pivot = new PivotTransformation(config)
}
type() {
return 'stackedAreaChart';
};
type () {
return 'stackedAreaChart'
}
getTransformation() {
return this.pivot;
};
getTransformation () {
return this.pivot
}
render(pivot) {
var d3Data = this.d3DataFromPivot(
render (pivot) {
let d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
@ -42,37 +42,36 @@ export default class AreachartVisualization extends Nvd3ChartVisualization {
pivot.values,
false,
true,
false);
false)
this.xLabels = d3Data.xLabels;
super.render(d3Data);
};
this.xLabels = d3Data.xLabels
super.render(d3Data)
}
/**
* Set new config
*/
setConfig(config) {
super.setConfig(config);
this.pivot.setConfig(config);
};
setConfig (config) {
super.setConfig(config)
this.pivot.setConfig(config)
}
configureChart(chart) {
var self = this;
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)
configureChart (chart) {
let self = this
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)
this.chart.style(this.config.style || 'stack');
this.chart.style(this.config.style || 'stack')
var self = this;
this.chart.dispatch.on('stateChange', function(s) {
self.config.style = s.style;
this.chart.dispatch.on('stateChange', function (s) {
self.config.style = s.style
// give some time to animation finish
setTimeout(function() {
self.emitConfig(self.config);
}, 500);
});
};
setTimeout(function () {
self.emitConfig(self.config)
}, 500)
})
}
}

View file

@ -12,29 +12,29 @@
* limitations under the License.
*/
import Nvd3ChartVisualization from './visualization-nvd3chart';
import PivotTransformation from '../../tabledata/pivot';
import Nvd3ChartVisualization from './visualization-nvd3chart'
import PivotTransformation from '../../tabledata/pivot'
/**
* Visualize data in bar char
*/
export default class BarchartVisualization extends Nvd3ChartVisualization {
constructor(targetEl, config) {
super(targetEl, config);
constructor (targetEl, config) {
super(targetEl, config)
this.pivot = new PivotTransformation(config);
};
this.pivot = new PivotTransformation(config)
}
type() {
return 'multiBarChart';
};
type () {
return 'multiBarChart'
}
getTransformation() {
return this.pivot;
};
getTransformation () {
return this.pivot
}
render(pivot) {
var d3Data = this.d3DataFromPivot(
render (pivot) {
let d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
@ -42,34 +42,113 @@ export default class BarchartVisualization extends Nvd3ChartVisualization {
pivot.values,
true,
true,
true);
true)
super.render(d3Data);
};
super.render(d3Data)
this.config.changeXLabel(this.config.xLabelStatus)
}
/**
* Set new config
*/
setConfig(config) {
super.setConfig(config);
this.pivot.setConfig(config);
};
setConfig (config) {
super.setConfig(config)
this.pivot.setConfig(config)
}
configureChart(chart) {
var self = this;
chart.yAxis.axisLabelDistance(50);
chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d);});
configureChart (chart) {
let self = this
let configObj = self.config
this.chart.stacked(this.config.stacked);
chart.yAxis.axisLabelDistance(50)
chart.yAxis.tickFormat(function (d) { return self.yAxisTickFormat(d) })
self.chart.stacked(this.config.stacked)
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(-45)
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.config.isXLabelStatus = function(type) {
if (configObj.xLabelStatus === type) {
return true
} else {
return false
}
}
var self = this;
this.chart.dispatch.on('stateChange', function(s) {
self.config.stacked = s.stacked;
configObj.stacked = s.stacked
// give some time to animation finish
setTimeout(function() {
self.emitConfig(self.config);
}, 500);
});
};
self.emitConfig(configObj)
}, 500)
})
}
getSetting(chart) {
let self = this
let configObj = self.config
// default to visualize xLabel
if (typeof (configObj.xLabelStatus) === 'undefined') {
configObj.changeXLabel('default')
}
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>`,
scope: {
config: configObj,
save: function(type) {
configObj.changeXLabel(type)
self.emitConfig(configObj)
}
}
}
}
}

View file

@ -12,33 +12,33 @@
* limitations under the License.
*/
import Nvd3ChartVisualization from './visualization-nvd3chart';
import PivotTransformation from '../../tabledata/pivot';
import Nvd3ChartVisualization from './visualization-nvd3chart'
import PivotTransformation from '../../tabledata/pivot'
/**
* Visualize data in line chart
*/
export default class LinechartVisualization extends Nvd3ChartVisualization {
constructor(targetEl, config) {
super(targetEl, config);
constructor (targetEl, config) {
super(targetEl, config)
this.pivot = new PivotTransformation(config);
};
this.pivot = new PivotTransformation(config)
}
type() {
type () {
if (this.config.lineWithFocus) {
return 'lineWithFocusChart';
return 'lineWithFocusChart'
} else {
return 'lineChart';
return 'lineChart'
}
};
}
getTransformation() {
return this.pivot;
};
getTransformation () {
return this.pivot
}
render(pivot) {
var d3Data = this.d3DataFromPivot(
render (pivot) {
let d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
@ -46,49 +46,49 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
pivot.values,
false,
true,
false);
false)
this.xLabels = d3Data.xLabels;
super.render(d3Data);
};
this.xLabels = d3Data.xLabels
super.render(d3Data)
}
/**
* Set new config
*/
setConfig(config) {
super.setConfig(config);
this.pivot.setConfig(config);
setConfig (config) {
super.setConfig(config)
this.pivot.setConfig(config)
// change mode
if (this.currentMode !== config.lineWithFocus) {
super.destroy();
this.currentMode = config.lineWithFocus;
super.destroy()
this.currentMode = config.lineWithFocus
}
};
}
configureChart(chart) {
var self = this;
chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
chart.yAxis.tickFormat(function(d) {
configureChart (chart) {
let self = this
chart.xAxis.tickFormat(function (d) { return self.xAxisTickFormat(d, self.xLabels) })
chart.yAxis.tickFormat(function (d) {
if (d === undefined) {
return 'N/A';
return 'N/A'
}
return self.yAxisTickFormat(d, self.xLabels);
});
chart.yAxis.axisLabelDistance(50);
return self.yAxisTickFormat(d, self.xLabels)
})
chart.yAxis.axisLabelDistance(50)
if (chart.useInteractiveGuideline) { // lineWithFocusChart hasn't got useInteractiveGuideline
chart.useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
chart.useInteractiveGuideline(true) // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
}
if (this.config.forceY) {
chart.forceY([0]); // force y-axis minimum to 0 for line chart.
chart.forceY([0]) // force y-axis minimum to 0 for line chart.
} else {
chart.forceY([]);
chart.forceY([])
}
};
}
getSetting(chart) {
var self = this;
var configObj = self.config;
getSetting (chart) {
let self = this
let configObj = self.config
return {
template: `<div>
@ -109,14 +109,14 @@ export default class LinechartVisualization extends Nvd3ChartVisualization {
</div>`,
scope: {
config: configObj,
save: function() {
self.emitConfig(configObj);
save: function () {
self.emitConfig(configObj)
}
}
};
};
}
}
defaultY() {
return undefined;
};
defaultY () {
return undefined
}
}

View file

@ -12,233 +12,232 @@
* limitations under the License.
*/
import Visualization from '../visualization';
import Visualization from '../visualization'
/**
* Visualize data in table format
*/
export default class Nvd3ChartVisualization extends Visualization {
constructor(targetEl, config) {
super(targetEl, config);
this.targetEl.append('<svg></svg>');
};
constructor (targetEl, config) {
super(targetEl, config)
this.targetEl.append('<svg></svg>')
}
refresh() {
refresh () {
if (this.chart) {
this.chart.update();
this.chart.update()
}
};
}
render(data) {
var type = this.type();
var d3g = data.d3g;
render (data) {
let type = this.type()
let d3g = data.d3g
if (!this.chart) {
this.chart = nv.models[type]();
this.chart = nv.models[type]()
}
this.configureChart(this.chart);
this.configureChart(this.chart)
var animationDuration = 300;
var numberOfDataThreshold = 150;
var height = this.targetEl.height();
let animationDuration = 300
let numberOfDataThreshold = 150
let height = this.targetEl.height()
// turn off animation when dataset is too large. (for performance issue)
// still, since dataset is large, the chart content sequentially appears like animated.
// still, since dataset is large, the chart content sequentially appears like animated
try {
if (d3g[0].values.length > numberOfDataThreshold) {
animationDuration = 0;
animationDuration = 0
}
} catch (ignoreErr) {
}
} catch (err) { /** ignore */ }
d3.select('#' + this.targetEl[0].id + ' svg')
.attr('height', height)
.datum(d3g)
.transition()
.duration(animationDuration)
.call(this.chart);
d3.select('#' + this.targetEl[0].id + ' svg').style.height = height + 'px';
};
.call(this.chart)
d3.select('#' + this.targetEl[0].id + ' svg').style.height = height + 'px'
}
type() {
type () {
// override this and return chart type
};
}
configureChart(chart) {
configureChart (chart) {
// override this to configure chart
};
}
groupedThousandsWith3DigitsFormatter(x) {
return d3.format(',')(d3.round(x, 3));
};
groupedThousandsWith3DigitsFormatter (x) {
return d3.format(',')(d3.round(x, 3))
}
customAbbrevFormatter(x) {
var s = d3.format('.3s')(x);
customAbbrevFormatter (x) {
let s = d3.format('.3s')(x)
switch (s[s.length - 1]) {
case 'G': return s.slice(0, -1) + 'B';
case 'G': return s.slice(0, -1) + 'B'
}
return s;
};
return s
}
defaultY() {
return 0;
};
defaultY () {
return 0
}
xAxisTickFormat(d, xLabels) {
xAxisTickFormat (d, xLabels) {
if (xLabels[d] && (isNaN(parseFloat(xLabels[d])) || !isFinite(xLabels[d]))) { // to handle string type xlabel
return xLabels[d];
return xLabels[d]
} else {
return d;
return d
}
};
}
yAxisTickFormat(d) {
if (Math.abs(d) >= Math.pow(10,6)) {
return this.customAbbrevFormatter(d);
yAxisTickFormat (d) {
if (Math.abs(d) >= Math.pow(10, 6)) {
return this.customAbbrevFormatter(d)
}
return this.groupedThousandsWith3DigitsFormatter(d);
};
return this.groupedThousandsWith3DigitsFormatter(d)
}
d3DataFromPivot(
d3DataFromPivot (
schema, rows, keys, groups, values, allowTextXAxis, fillMissingValues, multiBarChart) {
var self = this;
let self = this
// construct table data
var d3g = [];
let d3g = []
var concat = function(o, n) {
let concat = function (o, n) {
if (!o) {
return n;
return n
} else {
return o + '.' + n;
return o + '.' + n
}
};
}
var getSchemaUnderKey = function(key, s) {
for (var c in key.children) {
s[c] = {};
getSchemaUnderKey(key.children[c], s[c]);
const getSchemaUnderKey = function (key, s) {
for (let c in key.children) {
s[c] = {}
getSchemaUnderKey(key.children[c], s[c])
}
};
}
var traverse = function(sKey, s, rKey, r, func, rowName, rowValue, colName) {
//console.log("TRAVERSE sKey=%o, s=%o, rKey=%o, r=%o, rowName=%o, rowValue=%o, colName=%o", sKey, s, rKey, r, rowName, rowValue, colName);
const traverse = function (sKey, s, rKey, r, func, rowName, rowValue, colName) {
// console.log("TRAVERSE sKey=%o, s=%o, rKey=%o, r=%o, rowName=%o, rowValue=%o, colName=%o", sKey, s, rKey, r, rowName, rowValue, colName);
if (s.type === 'key') {
rowName = concat(rowName, sKey);
rowValue = concat(rowValue, rKey);
rowName = concat(rowName, sKey)
rowValue = concat(rowValue, rKey)
} else if (s.type === 'group') {
colName = concat(colName, rKey);
colName = concat(colName, rKey)
} else if (s.type === 'value' && sKey === rKey || valueOnly) {
colName = concat(colName, rKey);
func(rowName, rowValue, colName, r);
colName = concat(colName, rKey)
func(rowName, rowValue, colName, r)
}
for (var c in s.children) {
for (let c in s.children) {
if (fillMissingValues && s.children[c].type === 'group' && r[c] === undefined) {
var cs = {};
getSchemaUnderKey(s.children[c], cs);
traverse(c, s.children[c], c, cs, func, rowName, rowValue, colName);
continue;
let cs = {}
getSchemaUnderKey(s.children[c], cs)
traverse(c, s.children[c], c, cs, func, rowName, rowValue, colName)
continue
}
for (var j in r) {
for (let j in r) {
if (s.children[c].type === 'key' || c === j) {
traverse(c, s.children[c], j, r[j], func, rowName, rowValue, colName);
traverse(c, s.children[c], j, r[j], func, rowName, rowValue, colName)
}
}
}
};
}
var valueOnly = (keys.length === 0 && groups.length === 0 && values.length > 0);
var noKey = (keys.length === 0);
var isMultiBarChart = multiBarChart;
const valueOnly = (keys.length === 0 && groups.length === 0 && values.length > 0)
let noKey = (keys.length === 0)
let isMultiBarChart = multiBarChart
var sKey = Object.keys(schema)[0];
let sKey = Object.keys(schema)[0]
var rowNameIndex = {};
var rowIdx = 0;
var colNameIndex = {};
var colIdx = 0;
var rowIndexValue = {};
let rowNameIndex = {}
let rowIdx = 0
let colNameIndex = {}
let colIdx = 0
let rowIndexValue = {}
for (var k in rows) {
traverse(sKey, schema[sKey], k, rows[k], function(rowName, rowValue, colName, value) {
//console.log("RowName=%o, row=%o, col=%o, value=%o", rowName, rowValue, colName, value);
for (let k in rows) {
traverse(sKey, schema[sKey], k, rows[k], function (rowName, rowValue, colName, value) {
// console.log("RowName=%o, row=%o, col=%o, value=%o", rowName, rowValue, colName, value);
if (rowNameIndex[rowValue] === undefined) {
rowIndexValue[rowIdx] = rowValue;
rowNameIndex[rowValue] = rowIdx++;
rowIndexValue[rowIdx] = rowValue
rowNameIndex[rowValue] = rowIdx++
}
if (colNameIndex[colName] === undefined) {
colNameIndex[colName] = colIdx++;
colNameIndex[colName] = colIdx++
}
var i = colNameIndex[colName];
let i = colNameIndex[colName]
if (noKey && isMultiBarChart) {
i = 0;
i = 0
}
if (!d3g[i]) {
d3g[i] = {
values: [],
key: (noKey && isMultiBarChart) ? 'values' : colName
};
}
}
var xVar = isNaN(rowValue) ? ((allowTextXAxis) ? rowValue : rowNameIndex[rowValue]) : parseFloat(rowValue);
var yVar = self.defaultY();
if (xVar === undefined) { xVar = colName; }
let xVar = isNaN(rowValue) ? ((allowTextXAxis) ? rowValue : rowNameIndex[rowValue]) : parseFloat(rowValue)
let yVar = self.defaultY()
if (xVar === undefined) { xVar = colName }
if (value !== undefined) {
yVar = isNaN(value.value) ? self.defaultY() : parseFloat(value.value) / parseFloat(value.count);
yVar = isNaN(value.value) ? self.defaultY() : parseFloat(value.value) / parseFloat(value.count)
}
d3g[i].values.push({
x: xVar,
y: yVar
});
});
})
})
}
// clear aggregation name, if possible
var namesWithoutAggr = {};
var colName;
var withoutAggr;
let namesWithoutAggr = {}
let colName
let withoutAggr
// TODO - This part could use som refactoring - Weird if/else with similar actions and variable names
for (colName in colNameIndex) {
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
withoutAggr = colName.substring(0, colName.lastIndexOf('('))
if (!namesWithoutAggr[withoutAggr]) {
namesWithoutAggr[withoutAggr] = 1;
namesWithoutAggr[withoutAggr] = 1
} else {
namesWithoutAggr[withoutAggr]++;
namesWithoutAggr[withoutAggr]++
}
}
if (valueOnly) {
for (var valueIndex = 0; valueIndex < d3g[0].values.length; valueIndex++) {
colName = d3g[0].values[valueIndex].x;
for (let valueIndex = 0; valueIndex < d3g[0].values.length; valueIndex++) {
colName = d3g[0].values[valueIndex].x
if (!colName) {
continue;
continue
}
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
withoutAggr = colName.substring(0, colName.lastIndexOf('('))
if (namesWithoutAggr[withoutAggr] <= 1) {
d3g[0].values[valueIndex].x = withoutAggr;
d3g[0].values[valueIndex].x = withoutAggr
}
}
} else {
for (var d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key;
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
for (let d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key
withoutAggr = colName.substring(0, colName.lastIndexOf('('))
if (namesWithoutAggr[withoutAggr] <= 1) {
d3g[d3gIndex].key = withoutAggr;
d3g[d3gIndex].key = withoutAggr
}
}
// use group name instead of group.value as a column name, if there're only one group and one value selected.
if (groups.length === 1 && values.length === 1) {
for (d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key;
colName = colName.split('.').slice(0, -1).join('.');
d3g[d3gIndex].key = colName;
for (let d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key
colName = colName.split('.').slice(0, -1).join('.')
d3g[d3gIndex].key = colName
}
}
}
@ -246,17 +245,17 @@ export default class Nvd3ChartVisualization extends Visualization {
return {
xLabels: rowIndexValue,
d3g: d3g
};
};
}
}
/**
* method will be invoked when visualization need to be destroyed.
* Don't need to destroy this.targetEl.
*/
destroy() {
destroy () {
if (this.chart) {
d3.selectAll('#' + this.targetEl[0].id + ' svg > *').remove();
this.chart = undefined;
d3.selectAll('#' + this.targetEl[0].id + ' svg > *').remove()
this.chart = undefined
}
};
}
}

View file

@ -12,29 +12,29 @@
* limitations under the License.
*/
import Nvd3ChartVisualization from './visualization-nvd3chart';
import PivotTransformation from '../../tabledata/pivot';
import Nvd3ChartVisualization from './visualization-nvd3chart'
import PivotTransformation from '../../tabledata/pivot'
/**
* Visualize data in pie chart
*/
export default class PiechartVisualization extends Nvd3ChartVisualization {
constructor(targetEl, config) {
super(targetEl, config);
this.pivot = new PivotTransformation(config);
};
constructor (targetEl, config) {
super(targetEl, config)
this.pivot = new PivotTransformation(config)
}
type() {
return 'pieChart';
};
type () {
return 'pieChart'
}
getTransformation() {
return this.pivot;
};
getTransformation () {
return this.pivot
}
render(pivot) {
render (pivot) {
// [ZEPPELIN-2253] New chart function will be created each time inside super.render()
this.chart = null;
this.chart = null
const d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
@ -43,41 +43,41 @@ export default class PiechartVisualization extends Nvd3ChartVisualization {
pivot.values,
true,
false,
false);
const d = d3Data.d3g;
false)
const d = d3Data.d3g
let generateLabel;
let generateLabel
// data is grouped
if (pivot.groups && pivot.groups.length > 0) {
generateLabel = (suffix, prefix) => `${prefix}.${suffix}`;
generateLabel = (suffix, prefix) => `${prefix}.${suffix}`
} else { // data isn't grouped
generateLabel = suffix => suffix;
generateLabel = suffix => suffix
}
let d3g = d.map(group => {
return group.values.map(row => ({
label: generateLabel(row.x, group.key),
value: row.y
}));
});
}))
})
// the map function returns d3g as a nested array
// [].concat flattens it, http://stackoverflow.com/a/10865042/5154397
d3g = [].concat.apply([], d3g);
super.render({d3g: d3g});
};
d3g = [].concat.apply([], d3g) // eslint-disable-line prefer-spread
super.render({d3g: d3g})
}
/**
* Set new config
*/
setConfig(config) {
super.setConfig(config);
this.pivot.setConfig(config);
};
setConfig (config) {
super.setConfig(config)
this.pivot.setConfig(config)
}
configureChart(chart) {
chart.x(function(d) { return d.label;})
.y(function(d) { return d.value;})
.showLabels(false)
.showTooltipPercent(true);
};
configureChart (chart) {
chart.x(function (d) { return d.label })
.y(function (d) { return d.value })
.showLabels(false)
.showTooltipPercent(true)
}
}

View file

@ -12,15 +12,15 @@
* limitations under the License.
*/
import Nvd3ChartVisualization from './visualization-nvd3chart';
import ColumnselectorTransformation from '../../tabledata/columnselector';
import Nvd3ChartVisualization from './visualization-nvd3chart'
import ColumnselectorTransformation from '../../tabledata/columnselector'
/**
* Visualize data in scatter char
*/
export default class ScatterchartVisualization extends Nvd3ChartVisualization {
constructor(targetEl, config) {
super(targetEl, config);
constructor (targetEl, config) {
super(targetEl, config)
this.columnselectorProps = [
{
@ -41,198 +41,196 @@ export default class ScatterchartVisualization extends Nvd3ChartVisualization {
or the number of distinct values are bigger than 5% of total number of values.</li>
<li>Size field button turns to grey when the option you chose is not valid.</li>`
}
];
this.columnselector = new ColumnselectorTransformation(config, this.columnselectorProps);
};
]
this.columnselector = new ColumnselectorTransformation(config, this.columnselectorProps)
}
type() {
return 'scatterChart';
};
type () {
return 'scatterChart'
}
getTransformation() {
return this.columnselector;
};
getTransformation () {
return this.columnselector
}
render(tableData) {
this.tableData = tableData;
this.selectDefault();
var d3Data = this.setScatterChart(tableData, true);
this.xLabels = d3Data.xLabels;
this.yLabels = d3Data.yLabels;
render (tableData) {
this.tableData = tableData
this.selectDefault()
let d3Data = this.setScatterChart(tableData, true)
this.xLabels = d3Data.xLabels
this.yLabels = d3Data.yLabels
super.render(d3Data);
};
super.render(d3Data)
}
configureChart(chart) {
var self = this;
configureChart (chart) {
let self = this
chart.xAxis.tickFormat(function(d) { // TODO remove round after bump to nvd3 > 1.8.5
return self.xAxisTickFormat(Math.round(d * 1e3)/1e3, self.xLabels);
});
chart.xAxis.tickFormat(function (d) { // TODO remove round after bump to nvd3 > 1.8.5
return self.xAxisTickFormat(Math.round(d * 1e3) / 1e3, self.xLabels)
})
chart.yAxis.tickFormat(function(d) { // TODO remove round after bump to nvd3 > 1.8.5
return self.yAxisTickFormat(Math.round(d * 1e3)/1e3, self.yLabels);
});
chart.yAxis.tickFormat(function (d) { // TODO remove round after bump to nvd3 > 1.8.5
return self.yAxisTickFormat(Math.round(d * 1e3) / 1e3, self.yLabels)
})
chart.showDistX(true).showDistY(true);
//handle the problem of tooltip not showing when muliple points have same value.
};
chart.showDistX(true).showDistY(true)
// handle the problem of tooltip not showing when muliple points have same value.
}
yAxisTickFormat(d, yLabels){
if (yLabels[d] && (isNaN(parseFloat(yLabels[d])) || !isFinite(yLabels[d]))) { // to handle string type xlabel
return yLabels[d];
} else {
return super.yAxisTickFormat(d);
}
}
yAxisTickFormat (d, yLabels) {
if (yLabels[d] && (isNaN(parseFloat(yLabels[d])) || !isFinite(yLabels[d]))) { // to handle string type xlabel
return yLabels[d]
} else {
return super.yAxisTickFormat(d)
}
}
selectDefault() {
selectDefault () {
if (!this.config.xAxis && !this.config.yAxis) {
if (this.tableData.columns.length > 1) {
this.config.xAxis = this.tableData.columns[0];
this.config.yAxis = this.tableData.columns[1];
this.config.xAxis = this.tableData.columns[0]
this.config.yAxis = this.tableData.columns[1]
} else if (this.tableData.columns.length === 1) {
this.config.xAxis = this.tableData.columns[0];
this.config.xAxis = this.tableData.columns[0]
}
}
};
}
setScatterChart(data, refresh) {
var xAxis = this.config.xAxis;
var yAxis = this.config.yAxis;
var group = this.config.group;
var size = this.config.size;
setScatterChart (data, refresh) {
let xAxis = this.config.xAxis
let yAxis = this.config.yAxis
let group = this.config.group
let size = this.config.size
var xValues = [];
var yValues = [];
var rows = {};
var d3g = [];
let xValues = []
let yValues = []
let rows = {}
let d3g = []
var rowNameIndex = {};
var colNameIndex = {};
var grpNameIndex = {};
var rowIndexValue = {};
var colIndexValue = {};
var grpIndexValue = {};
var rowIdx = 0;
var colIdx = 0;
var grpIdx = 0;
var grpName = '';
let rowNameIndex = {}
let colNameIndex = {}
let grpNameIndex = {}
let rowIndexValue = {}
let colIndexValue = {}
let grpIndexValue = {}
let rowIdx = 0
let colIdx = 0
let grpIdx = 0
let grpName = ''
var xValue;
var yValue;
var row;
let xValue
let yValue
let row
if (!xAxis && !yAxis) {
return {
d3g: []
};
}
}
for (var i = 0; i < data.rows.length; i++) {
row = data.rows[i];
for (let i = 0; i < data.rows.length; i++) {
row = data.rows[i]
if (xAxis) {
xValue = row[xAxis.index];
xValues[i] = xValue;
xValue = row[xAxis.index]
xValues[i] = xValue
}
if (yAxis) {
yValue = row[yAxis.index];
yValues[i] = yValue;
yValue = row[yAxis.index]
yValues[i] = yValue
}
}
var isAllDiscrete = ((xAxis && yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
let isAllDiscrete = ((xAxis && yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
(!xAxis && this.isDiscrete(yValues)) ||
(!yAxis && this.isDiscrete(xValues)));
(!yAxis && this.isDiscrete(xValues)))
if (isAllDiscrete) {
rows = this.setDiscreteScatterData(data);
rows = this.setDiscreteScatterData(data)
} else {
rows = data.rows;
rows = data.rows
}
if (!group && isAllDiscrete) {
grpName = 'count';
grpName = 'count'
} else if (!group && !size) {
if (xAxis && yAxis) {
grpName = '(' + xAxis.name + ', ' + yAxis.name + ')';
grpName = '(' + xAxis.name + ', ' + yAxis.name + ')'
} else if (xAxis && !yAxis) {
grpName = xAxis.name;
grpName = xAxis.name
} else if (!xAxis && yAxis) {
grpName = yAxis.name;
grpName = yAxis.name
}
} else if (!group && size) {
grpName = size.name;
grpName = size.name
}
var epsilon = 1e-4; // TODO remove after bump to nvd3 > 1.8.5
let epsilon = 1e-4 // TODO remove after bump to nvd3 > 1.8.5
for (i = 0; i < rows.length; i++) {
row = rows[i];
for (let i = 0; i < rows.length; i++) {
row = rows[i]
if (xAxis) {
xValue = row[xAxis.index];
xValue = row[xAxis.index]
}
if (yAxis) {
yValue = row[yAxis.index];
yValue = row[yAxis.index]
}
if (group) {
grpName = row[group.index];
grpName = row[group.index]
}
var sz = (isAllDiscrete) ? row[row.length - 1] : ((size) ? row[size.index] : 1);
let sz = (isAllDiscrete) ? row[row.length - 1] : ((size) ? row[size.index] : 1)
if (grpNameIndex[grpName] === undefined) {
grpIndexValue[grpIdx] = grpName;
grpNameIndex[grpName] = grpIdx++;
grpIndexValue[grpIdx] = grpName
grpNameIndex[grpName] = grpIdx++
}
if (xAxis && rowNameIndex[xValue] === undefined) {
rowIndexValue[rowIdx] = xValue;
rowNameIndex[xValue] = rowIdx++;
rowIndexValue[rowIdx] = xValue
rowNameIndex[xValue] = rowIdx++
}
if (yAxis && colNameIndex[yValue] === undefined) {
colIndexValue[colIdx] = yValue;
colNameIndex[yValue] = colIdx++;
colIndexValue[colIdx] = yValue
colNameIndex[yValue] = colIdx++
}
if (!d3g[grpNameIndex[grpName]]) {
d3g[grpNameIndex[grpName]] = {
key: grpName,
values: []
};
}
}
// TODO remove epsilon jitter after bump to nvd3 > 1.8.5
var xval, yval = 0;
if ( xAxis ){
xval = (isNaN(xValue) ? rowNameIndex[xValue] : parseFloat(xValue)) + Math.random() * epsilon;
let xval = 0
let yval = 0
if (xAxis) {
xval = (isNaN(xValue) ? rowNameIndex[xValue] : parseFloat(xValue)) + Math.random() * epsilon
}
if ( yAxis ){
yval = (isNaN(yValue) ? colNameIndex[yValue] : parseFloat(yValue)) + Math.random() * epsilon;
if (yAxis) {
yval = (isNaN(yValue) ? colNameIndex[yValue] : parseFloat(yValue)) + Math.random() * epsilon
}
d3g[grpNameIndex[grpName]].values.push({
x: xval,
y: yval,
size: isNaN(parseFloat(sz)) ? 1 : parseFloat(sz)
});
})
}
// TODO remove sort and dedup after bump to nvd3 > 1.8.5
var d3gvalues = d3g[grpNameIndex[grpName]].values;
d3gvalues.sort(function(a,b){
return ((a['x'] - b['x']) || (a['y'] - b['y']))});
let d3gvalues = d3g[grpNameIndex[grpName]].values
d3gvalues.sort(function (a, b) {
return ((a['x'] - b['x']) || (a['y'] - b['y']))
})
for (var i = 0; i < d3gvalues.length - 1; ){
if ( (Math.abs(d3gvalues[i]['x'] - d3gvalues[i+1]['x']) < epsilon) &&
(Math.abs(d3gvalues[i]['y'] - d3gvalues[i+1]['y']) < epsilon) ){
d3gvalues.splice(i+1,1);
} else{
i++;
for (let i = 0; i < d3gvalues.length - 1;) {
if ((Math.abs(d3gvalues[i]['x'] - d3gvalues[i + 1]['x']) < epsilon) &&
(Math.abs(d3gvalues[i]['y'] - d3gvalues[i + 1]['y']) < epsilon)) {
d3gvalues.splice(i + 1, 1)
} else {
i++
}
}
@ -240,33 +238,33 @@ export default class ScatterchartVisualization extends Nvd3ChartVisualization {
xLabels: rowIndexValue,
yLabels: colIndexValue,
d3g: d3g
};
};
}
}
setDiscreteScatterData(data) {
var xAxis = this.config.xAxis;
var yAxis = this.config.yAxis;
var group = this.config.group;
setDiscreteScatterData (data) {
let xAxis = this.config.xAxis
let yAxis = this.config.yAxis
let group = this.config.group
var xValue;
var yValue;
var grp;
let xValue
let yValue
let grp
var rows = {};
let rows = {}
for (var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
for (let i = 0; i < data.rows.length; i++) {
let row = data.rows[i]
if (xAxis) {
xValue = row[xAxis.index];
xValue = row[xAxis.index]
}
if (yAxis) {
yValue = row[yAxis.index];
yValue = row[yAxis.index]
}
if (group) {
grp = row[group.index];
grp = row[group.index]
}
var key = xValue + ',' + yValue + ',' + grp;
let key = xValue + ',' + yValue + ',' + grp
if (!rows[key]) {
rows[key] = {
@ -274,89 +272,89 @@ export default class ScatterchartVisualization extends Nvd3ChartVisualization {
y: yValue,
group: grp,
size: 1
};
}
} else {
rows[key].size++;
rows[key].size++
}
}
// change object into array
var newRows = [];
for (var r in rows) {
var newRow = [];
if (xAxis) { newRow[xAxis.index] = rows[r].x; }
if (yAxis) { newRow[yAxis.index] = rows[r].y; }
if (group) { newRow[group.index] = rows[r].group; }
newRow[data.rows[0].length] = rows[r].size;
newRows.push(newRow);
let newRows = []
for (let r in rows) {
let newRow = []
if (xAxis) { newRow[xAxis.index] = rows[r].x }
if (yAxis) { newRow[yAxis.index] = rows[r].y }
if (group) { newRow[group.index] = rows[r].group }
newRow[data.rows[0].length] = rows[r].size
newRows.push(newRow)
}
return newRows;
};
return newRows
}
isDiscrete(field) {
var getUnique = function(f) {
var uniqObj = {};
var uniqArr = [];
var j = 0;
for (var i = 0; i < f.length; i++) {
var item = f[i];
isDiscrete (field) {
let getUnique = function (f) {
let uniqObj = {}
let uniqArr = []
let j = 0
for (let i = 0; i < f.length; i++) {
let item = f[i]
if (uniqObj[item] !== 1) {
uniqObj[item] = 1;
uniqArr[j++] = item;
uniqObj[item] = 1
uniqArr[j++] = item
}
}
return uniqArr;
};
return uniqArr
}
for (var i = 0; i < field.length; i++) {
for (let i = 0; i < field.length; i++) {
if (isNaN(parseFloat(field[i])) &&
(typeof field[i] === 'string' || field[i] instanceof String)) {
return true;
return true
}
}
var threshold = 0.05;
var unique = getUnique(field);
let threshold = 0.05
let unique = getUnique(field)
if (unique.length / field.length < threshold) {
return true;
return true
} else {
return false;
return false
}
};
}
isValidSizeOption(options) {
var xValues = [];
var yValues = [];
var rows = this.tableData.rows;
isValidSizeOption (options) {
let xValues = []
let yValues = []
let rows = this.tableData.rows
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var size = row[options.size.index];
for (let i = 0; i < rows.length; i++) {
let row = rows[i]
let size = row[options.size.index]
//check if the field is numeric
// check if the field is numeric
if (isNaN(parseFloat(size)) || !isFinite(size)) {
return false;
return false
}
if (options.xAxis) {
var x = row[options.xAxis.index];
xValues[i] = x;
let x = row[options.xAxis.index]
xValues[i] = x
}
if (options.yAxis) {
var y = row[options.yAxis.index];
yValues[i] = y;
let y = row[options.yAxis.index]
yValues[i] = y
}
}
//check if all existing fields are discrete
var isAllDiscrete = ((options.xAxis && options.yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
// check if all existing fields are discrete
let isAllDiscrete = ((options.xAxis && options.yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
(!options.xAxis && this.isDiscrete(yValues)) ||
(!options.yAxis && this.isDiscrete(xValues)));
(!options.yAxis && this.isDiscrete(xValues)))
if (isAllDiscrete) {
return false;
return false
}
return true;
};
return true
}
}

View file

@ -12,51 +12,52 @@
* limitations under the License.
*/
import Visualization from '../visualization';
import PassthroughTransformation from '../../tabledata/passthrough';
import HandsonHelper from '../../handsontable/handsonHelper';
import Visualization from '../visualization'
import PassthroughTransformation from '../../tabledata/passthrough'
import HandsonHelper from '../../handsontable/handsonHelper'
/**
* Visualize data in table format
*/
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);
};
constructor (targetEl, config) {
super(targetEl, config)
console.log('Init table viz')
targetEl.addClass('table')
this.passthrough = new PassthroughTransformation(config)
}
refresh() {
this.hot.render();
};
refresh () {
this.hot.render()
}
render(tableData) {
var height = this.targetEl.height();
var container = this.targetEl.css('height', height).get(0);
var resultRows = tableData.rows;
var columnNames = _.pluck(tableData.columns, 'name');
var columns = Array.apply(null, Array(tableData.columns.length)).map(function() {
return {type: 'text'};
});
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'}
})
if (this.hot) {
this.hot.destroy();
this.hot.destroy()
}
var handsonHelper = new HandsonHelper();
let handsonHelper = new HandsonHelper()
this.hot = new Handsontable(container, handsonHelper.getHandsonTableConfig(
columns, columnNames, resultRows));
this.hot.validateCells(null);
};
columns, columnNames, resultRows))
this.hot.validateCells(null)
}
destroy() {
destroy () {
if (this.hot) {
this.hot.destroy();
this.hot.destroy()
}
};
}
getTransformation() {
return this.passthrough;
};
getTransformation () {
return this.passthrough
}
}

View file

@ -16,42 +16,42 @@
* Base class for visualization
*/
export default class Visualization {
constructor(targetEl, config) {
this.targetEl = targetEl;
this.config = config;
this._dirty = false;
this._active = false;
this._emitter;
};
constructor (targetEl, config) {
this.targetEl = targetEl
this.config = config
this._dirty = false
this._active = false
this._emitter = () => {}
}
/**
* get transformation
*/
getTransformation() {
getTransformation () {
// override this
};
}
/**
* Method will be invoked when data or configuration changed
*/
render(tableData) {
render (tableData) {
// override this
};
}
/**
* Refresh visualization.
*/
refresh() {
refresh () {
// override this
};
}
/**
* method will be invoked when visualization need to be destroyed.
* Don't need to destroy this.targetEl.
*/
destroy() {
destroy () {
// override this
};
}
/**
* return {
@ -59,113 +59,113 @@ export default class Visualization {
* scope : an object to bind to template scope
* }
*/
getSetting() {
getSetting () {
// override this
};
}
/**
* Activate. invoked when visualization is selected
*/
activate() {
activate () {
if (!this._active || this._dirty) {
this.refresh();
this._dirty = false;
this.refresh()
this._dirty = false
}
this._active = true;
};
this._active = true
}
/**
* Activate. invoked when visualization is de selected
*/
deactivate() {
this._active = false;
};
deactivate () {
this._active = false
}
/**
* Is active
*/
isActive() {
return this._active;
};
isActive () {
return this._active
}
/**
* When window or paragraph is resized
*/
resize() {
resize () {
if (this.isActive()) {
this.refresh();
this.refresh()
} else {
this._dirty = true;
this._dirty = true
}
};
}
/**
* Set new config
*/
setConfig(config) {
this.config = config;
setConfig (config) {
this.config = config
if (this.isActive()) {
this.refresh();
this.refresh()
} else {
this._dirty = true;
this._dirty = true
}
};
}
/**
* Emit config. config will sent to server and saved.
*/
emitConfig(config) {
this._emitter(config);
};
emitConfig (config) {
this._emitter(config)
}
/**
* render setting
*/
renderSetting(targetEl) {
var setting = this.getSetting();
renderSetting (targetEl) {
let setting = this.getSetting()
if (!setting) {
return;
return
}
// already readered
if (this._scope) {
var self = this;
this._scope.$apply(function() {
for (var k in setting.scope) {
self._scope[k] = setting.scope[k];
let self = this
this._scope.$apply(function () {
for (let k in setting.scope) {
self._scope[k] = setting.scope[k]
}
for (var k in self._prevSettingScope) {
for (let k in self._prevSettingScope) {
if (!setting.scope[k]) {
self._scope[k] = setting.scope[k];
self._scope[k] = setting.scope[k]
}
}
});
return;
})
return
} else {
this._prevSettingScope = setting.scope;
this._prevSettingScope = setting.scope
}
var scope = this._createNewScope();
for (var k in setting.scope) {
scope[k] = setting.scope[k];
let scope = this._createNewScope()
for (let k in setting.scope) {
scope[k] = setting.scope[k]
}
var template = setting.template;
let template = setting.template
if (template.split('\n').length === 1 &&
template.endsWith('.html')) { // template is url
this._templateRequest(template).then(t =>
_renderSetting(this, targetEl, t, scope)
);
)
} else {
_renderSetting(this, targetEl, template, scope);
_renderSetting(this, targetEl, template, scope)
}
};
}
}
function _renderSetting(instance, targetEl, template, scope) {
instance._targetEl = targetEl;
targetEl.html(template);
instance._compile(targetEl.contents())(scope);
instance._scope = scope;
};
function _renderSetting (instance, targetEl, template, scope) {
instance._targetEl = targetEl
targetEl.html(template)
instance._compile(targetEl.contents())(scope)
instance._scope = scope
}

View file

@ -12,26 +12,25 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').service('arrayOrderingSrv', arrayOrderingSrv);
angular.module('zeppelinWebApp').service('arrayOrderingSrv', arrayOrderingSrv)
function arrayOrderingSrv(TRASH_FOLDER_ID) {
'ngInject';
function arrayOrderingSrv (TRASH_FOLDER_ID) {
'ngInject'
var arrayOrderingSrv = this;
let arrayOrderingSrv = this
this.noteListOrdering = function(note) {
this.noteListOrdering = function (note) {
if (note.id === TRASH_FOLDER_ID) {
return '\uFFFF';
return '\uFFFF'
}
return arrayOrderingSrv.getNoteName(note);
};
return arrayOrderingSrv.getNoteName(note)
}
this.getNoteName = function(note) {
this.getNoteName = function (note) {
if (note.name === undefined || note.name.trim() === '') {
return 'Note ' + note.id;
return 'Note ' + note.id
} else {
return note.name;
return note.name
}
};
}
}

View file

@ -12,38 +12,37 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').service('baseUrlSrv', baseUrlSrv);
angular.module('zeppelinWebApp').service('baseUrlSrv', baseUrlSrv)
function baseUrlSrv() {
this.getPort = function() {
var port = Number(location.port);
function baseUrlSrv () {
this.getPort = function () {
let port = Number(location.port)
if (!port) {
port = 80;
port = 80
if (location.protocol === 'https:') {
port = 443;
port = 443
}
}
//Exception for when running locally via grunt
// Exception for when running locally via grunt
if (port === process.env.WEB_PORT) {
port = process.env.SERVER_PORT;
port = process.env.SERVER_PORT
}
return port;
};
return port
}
this.getWebsocketUrl = function() {
var wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
this.getWebsocketUrl = function () {
let wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
return wsProtocol + '//' + location.hostname + ':' + this.getPort() +
skipTrailingSlash(location.pathname) + '/ws';
};
skipTrailingSlash(location.pathname) + '/ws'
}
this.getRestApiBase = function() {
this.getRestApiBase = function () {
return location.protocol + '//' + location.hostname + ':' +
this.getPort() + skipTrailingSlash(location.pathname) +
'/api';
};
'/api'
}
var skipTrailingSlash = function(path) {
return path.replace(/\/$/, '');
};
const skipTrailingSlash = function (path) {
return path.replace(/\/$/, '')
}
}

View file

@ -12,29 +12,28 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').service('browserDetectService', browserDetectService);
angular.module('zeppelinWebApp').service('browserDetectService', browserDetectService)
function browserDetectService() {
this.detectIE = function() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
function browserDetectService () {
this.detectIE = function () {
let ua = window.navigator.userAgent
let msie = ua.indexOf('MSIE ')
if (msie > 0) {
// IE 10 or older => return version number
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10)
}
var trident = ua.indexOf('Trident/');
let trident = ua.indexOf('Trident/')
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
let rv = ua.indexOf('rv:')
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10)
}
var edge = ua.indexOf('Edge/');
let edge = ua.indexOf('Edge/')
if (edge > 0) {
// IE 12 (aka Edge) => return version number
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10)
}
// other browser
return false;
};
return false
}
}

View file

@ -11,24 +11,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('clipboardCtrl', clipboardCtrl);
angular.module('zeppelinWebApp').controller('clipboardCtrl', clipboardCtrl)
function clipboardCtrl($scope) {
'ngInject';
function clipboardCtrl ($scope) {
'ngInject'
$scope.complete = function(e) {
$scope.copied = true;
$scope.tooltip = 'Copied!';
setTimeout(function() {
$scope.tooltip = 'Copy to clipboard';
}, 400);
};
$scope.$watch('input', function() {
$scope.copied = false;
$scope.tooltip = 'Copy to clipboard';
});
$scope.clipError = function(e) {
console.log('Error: ' + e.name + ' - ' + e.message);
$scope.tooltip = 'Not supported browser';
};
$scope.complete = function (e) {
$scope.copied = true
$scope.tooltip = 'Copied!'
setTimeout(function () {
$scope.tooltip = 'Copy to clipboard'
}, 400)
}
$scope.$watch('input', function () {
$scope.copied = false
$scope.tooltip = 'Copy to clipboard'
})
$scope.clipError = function (e) {
console.log('Error: ' + e.name + ' - ' + e.message)
$scope.tooltip = 'Not supported browser'
}
}

View file

@ -12,16 +12,15 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').directive('dropdownInput', dropdownInput);
angular.module('zeppelinWebApp').directive('dropdownInput', dropdownInput)
function dropdownInput() {
function dropdownInput () {
return {
restrict: 'A',
link: function(scope, element) {
element.bind('click', function(event) {
event.stopPropagation();
});
link: function (scope, element) {
element.bind('click', function (event) {
event.stopPropagation()
})
}
};
}
}

View file

@ -12,9 +12,9 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').directive('codeEditor', codeEditor);
angular.module('zeppelinWebApp').directive('codeEditor', codeEditor)
function codeEditor($templateRequest, $compile) {
function codeEditor ($templateRequest, $compile) {
return {
restrict: 'AE',
scope: {
@ -25,15 +25,14 @@ function codeEditor($templateRequest, $compile) {
onLoad: '=onLoad',
revisionView: '=revisionView'
},
link: function(scope, element, attrs, controller) {
$templateRequest('components/editor/ace.editor.directive.html').then(function(editorHtml) {
var editor = angular.element(editorHtml);
editor.attr('id', scope.paragraphId + '_editor');
element.append(editor);
$compile(editor)(scope);
console.log('codeEditor directive revision view is ' + scope.revisionView);
});
link: function (scope, element, attrs, controller) {
$templateRequest('components/editor/ace.editor.directive.html').then(function (editorHtml) {
let editor = angular.element(editorHtml)
editor.attr('id', scope.paragraphId + '_editor')
element.append(editor)
$compile(editor)(scope)
console.log('codeEditor directive revision view is ' + scope.revisionView)
})
}
};
}
}

View file

@ -12,11 +12,10 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('ElasticInputCtrl', ElasticInputCtrl);
angular.module('zeppelinWebApp').controller('ElasticInputCtrl', ElasticInputCtrl)
function ElasticInputCtrl() {
var vm = this;
vm.showEditor = false;
vm.value = '';
function ElasticInputCtrl () {
let vm = this
vm.showEditor = false
vm.value = ''
}

View file

@ -12,27 +12,26 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').directive('expandCollapse', expandCollapse);
angular.module('zeppelinWebApp').directive('expandCollapse', expandCollapse)
function expandCollapse() {
function expandCollapse () {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
angular.element(element).click(function(event) {
link: function (scope, element, attrs) {
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('.expandable:visible').slideUp('slow')
angular.element(element).find('i.icon-folder-alt').toggleClass('icon-folder icon-folder-alt')
} else {
angular.element(element).find('.expandable').first().slideToggle('200',function() {
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('icon-folder icon-folder-alt')
}
});
})
}
event.stopPropagation();
});
event.stopPropagation()
})
}
};
}
}

View file

@ -16,81 +16,84 @@ export const HeliumConfFieldType = {
NUMBER: 'number',
JSON: 'json',
STRING: 'string',
};
}
/**
* @param persisted <Object> including `type`, `description`, `defaultValue` for each conf key
* @param spec <Object> including `value` for each conf key
*/
export function mergePersistedConfWithSpec(persisted, spec) {
const confs = [];
export function mergePersistedConfWithSpec (persisted, spec) {
const confs = []
for(let name in spec) {
const specField = spec[name];
const persistedValue = persisted[name];
for (let name in spec) {
const specField = spec[name]
const persistedValue = persisted[name]
const value = (persistedValue) ? persistedValue : specField.defaultValue;
const value = (persistedValue) ? persistedValue : specField.defaultValue
const merged = {
name: name, type: specField.type, description: specField.description,
value: value, defaultValue: specField.defaultValue,
};
confs.push(merged);
}
return confs;
}
export function createAllPackageConfigs(defaultPackages, persistedConfs) {
let packageConfs = {};
for (let name in defaultPackages) {
const pkgSearchResult = defaultPackages[name];
const spec = pkgSearchResult.pkg.config;
if (!spec) { continue; }
const artifact = pkgSearchResult.pkg.artifact;
if (!artifact) { continue; }
let persistedConf = {};
if (persistedConfs[artifact]) {
persistedConf = persistedConfs[artifact];
name: name,
type: specField.type,
description: specField.description,
value: value,
defaultValue: specField.defaultValue,
}
const confs = mergePersistedConfWithSpec(persistedConf, spec);
packageConfs[name] = confs;
confs.push(merged)
}
return packageConfs;
return confs
}
export function parseConfigValue(type, stringified) {
let value = stringified;
export function createAllPackageConfigs (defaultPackages, persistedConfs) {
let packageConfs = {}
for (let name in defaultPackages) {
const pkgSearchResult = defaultPackages[name]
const spec = pkgSearchResult.pkg.config
if (!spec) { continue }
const artifact = pkgSearchResult.pkg.artifact
if (!artifact) { continue }
let persistedConf = {}
if (persistedConfs[artifact]) {
persistedConf = persistedConfs[artifact]
}
const confs = mergePersistedConfWithSpec(persistedConf, spec)
packageConfs[name] = confs
}
return packageConfs
}
export function parseConfigValue (type, stringified) {
let value = stringified
try {
if (HeliumConfFieldType.NUMBER === type) {
value = parseFloat(stringified);
value = parseFloat(stringified)
} else if (HeliumConfFieldType.JSON === type) {
value = JSON.parse(stringified);
value = JSON.parse(stringified)
}
} catch(error) {
} catch (error) {
// return just the stringified one
console.error(`Failed to parse conf type ${type}, value ${value}`);
console.error(`Failed to parse conf type ${type}, value ${value}`)
}
return value;
return value
}
/**
* persist key-value only
* since other info (e.g type, desc) can be provided by default config
*/
export function createPersistableConfig(currentConfs) {
export function createPersistableConfig (currentConfs) {
const filtered = currentConfs.reduce((acc, c) => {
acc[c.name] = parseConfigValue(c.type, c.value);
return acc;
}, {});
acc[c.name] = parseConfigValue(c.type, c.value)
return acc
}, {})
return filtered;
return filtered
}

View file

@ -12,20 +12,20 @@
* limitations under the License.
*/
export function createDefaultPackage(pkgSearchResult, sce) {
export function createDefaultPackage (pkgSearchResult, sce) {
for (let pkgIdx in pkgSearchResult) {
const pkg = pkgSearchResult[pkgIdx];
pkg.pkg.icon = sce.trustAsHtml(pkg.pkg.icon);
const pkg = pkgSearchResult[pkgIdx]
pkg.pkg.icon = sce.trustAsHtml(pkg.pkg.icon)
if (pkg.enabled) {
pkgSearchResult.splice(pkgIdx, 1);
return pkg;
pkgSearchResult.splice(pkgIdx, 1)
return pkg
}
}
// show first available version if package is not enabled
const result = pkgSearchResult[0];
pkgSearchResult.splice(0, 1);
return result;
const result = pkgSearchResult[0]
pkgSearchResult.splice(0, 1)
return result
}
/**
@ -35,13 +35,13 @@ export function createDefaultPackage(pkgSearchResult, sce) {
* @param sce angular `$sce` object
* @returns {Object} including {name, pkgInfo}
*/
export function createDefaultPackages(pkgSearchResults, sce) {
const defaultPackages = {};
export function createDefaultPackages (pkgSearchResults, sce) {
const defaultPackages = {}
// show enabled version if any version of package is enabled
for (let name in pkgSearchResults) {
const pkgSearchResult = pkgSearchResults[name];
const pkgSearchResult = pkgSearchResults[name]
defaultPackages[name] = createDefaultPackage(pkgSearchResult, sce)
}
return defaultPackages;
return defaultPackages
}

View file

@ -12,26 +12,26 @@
* limitations under the License.
*/
import { HeliumType, } from './helium-type';
import { HeliumType, } from './helium-type'
import {
createAllPackageConfigs,
createPersistableConfig,
mergePersistedConfWithSpec,
} from './helium-conf';
} from './helium-conf'
import {
createDefaultPackages,
} from './helium-package';
} from './helium-package'
angular.module('zeppelinWebApp').service('heliumService', heliumService);
angular.module('zeppelinWebApp').service('heliumService', heliumService)
export default function heliumService($http, $sce, baseUrlSrv) {
'ngInject';
export default function heliumService ($http, $sce, baseUrlSrv) {
'ngInject'
let visualizationBundles = [];
let visualizationBundles = []
// name `heliumBundles` should be same as `HeliumBundleFactory.HELIUM_BUNDLES_VAR`
let heliumBundles = [];
let heliumBundles = []
// map for `{ magic: interpreter }`
let spellPerMagic = {};
let spellPerMagic = {}
// map for `{ magic: package-name }`
let pkgNamePerMagic = {}
@ -39,211 +39,211 @@ export default function heliumService($http, $sce, baseUrlSrv) {
* @param magic {string} e.g `%flowchart`
* @returns {SpellBase} undefined if magic is not registered
*/
this.getSpellByMagic = function(magic) {
return spellPerMagic[magic];
};
this.getSpellByMagic = function (magic) {
return spellPerMagic[magic]
}
this.executeSpell = function(magic, textWithoutMagic) {
this.executeSpell = function (magic, textWithoutMagic) {
const promisedConf = this.getSinglePackageConfigUsingMagic(magic)
.then(confs => createPersistableConfig(confs));
.then(confs => createPersistableConfig(confs))
return promisedConf.then(conf => {
const spell = this.getSpellByMagic(magic);
const spellResult = spell.interpret(textWithoutMagic, conf);
const spell = this.getSpellByMagic(magic)
const spellResult = spell.interpret(textWithoutMagic, conf)
const parsed = spellResult.getAllParsedDataWithTypes(
spellPerMagic, magic, textWithoutMagic);
spellPerMagic, magic, textWithoutMagic)
return parsed;
});
};
return parsed
})
}
this.executeSpellAsDisplaySystem = function(magic, textWithoutMagic) {
this.executeSpellAsDisplaySystem = function (magic, textWithoutMagic) {
const promisedConf = this.getSinglePackageConfigUsingMagic(magic)
.then(confs => createPersistableConfig(confs));
.then(confs => createPersistableConfig(confs))
return promisedConf.then(conf => {
const spell = this.getSpellByMagic(magic);
const spellResult = spell.interpret(textWithoutMagic.trim(), conf);
const parsed = spellResult.getAllParsedDataWithTypes(spellPerMagic);
const spell = this.getSpellByMagic(magic)
const spellResult = spell.interpret(textWithoutMagic.trim(), conf)
const parsed = spellResult.getAllParsedDataWithTypes(spellPerMagic)
return parsed;
});
};
return parsed
})
}
this.getVisualizationBundles = function() {
return visualizationBundles;
};
this.getVisualizationBundles = function () {
return visualizationBundles
}
/**
* @returns {Promise} which returns bundleOrder
*/
this.getVisualizationPackageOrder = function() {
this.getVisualizationPackageOrder = function () {
return $http.get(baseUrlSrv.getRestApiBase() + '/helium/order/visualization')
.then(function(response, status) {
return response.data.body;
.then(function (response, status) {
return response.data.body
})
.catch(function(error) {
console.error('Can not get bundle order', error);
});
};
.catch(function (error) {
console.error('Can not get bundle order', error)
})
}
this.setVisualizationPackageOrder = function(list) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/order/visualization', list);
};
this.setVisualizationPackageOrder = function (list) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/order/visualization', list)
}
this.enable = function(name, artifact) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/enable/' + name, artifact);
};
this.enable = function (name, artifact) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/enable/' + name, artifact)
}
this.disable = function(name) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/disable/' + name);
};
this.disable = function (name) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/disable/' + name)
}
this.saveConfig = function(pkg , defaultPackageConfig, closeConfigPanelCallback) {
this.saveConfig = function (pkg, defaultPackageConfig, closeConfigPanelCallback) {
// in case of local package, it will include `/`
const pkgArtifact = encodeURIComponent(pkg.artifact);
const pkgName = pkg.name;
const filtered = createPersistableConfig(defaultPackageConfig);
const pkgArtifact = encodeURIComponent(pkg.artifact)
const pkgName = pkg.name
const filtered = createPersistableConfig(defaultPackageConfig)
if (!pkgName || !pkgArtifact|| !filtered) {
if (!pkgName || !pkgArtifact || !filtered) {
console.error(
`Can't save config for helium package '${pkgArtifact}'`, filtered);
return;
`Can't save config for helium package '${pkgArtifact}'`, filtered)
return
}
const url = `${baseUrlSrv.getRestApiBase()}/helium/config/${pkgName}/${pkgArtifact}`;
const url = `${baseUrlSrv.getRestApiBase()}/helium/config/${pkgName}/${pkgArtifact}`
return $http.post(url, filtered)
.then(() => {
if (closeConfigPanelCallback) { closeConfigPanelCallback(); }
if (closeConfigPanelCallback) { closeConfigPanelCallback() }
}).catch((error) => {
console.error(`Failed to save config for ${pkgArtifact}`, error);
});
};
console.error(`Failed to save config for ${pkgArtifact}`, error)
})
}
/**
* @returns {Promise<Object>} which including {name, Array<package info for artifact>}
*/
this.getAllPackageInfo = function() {
this.getAllPackageInfo = function () {
return $http.get(`${baseUrlSrv.getRestApiBase()}/helium/package`)
.then(function(response, status) {
return response.data.body;
.then(function (response, status) {
return response.data.body
})
.catch(function(error) {
console.error('Failed to get all package infos', error);
});
};
.catch(function (error) {
console.error('Failed to get all package infos', error)
})
}
this.getAllEnabledPackages = function() {
this.getAllEnabledPackages = function () {
return $http.get(`${baseUrlSrv.getRestApiBase()}/helium/enabledPackage`)
.then(function(response, status) {
return response.data.body;
.then(function (response, status) {
return response.data.body
})
.catch(function(error) {
console.error('Failed to get all enabled package infos', error);
});
};
.catch(function (error) {
console.error('Failed to get all enabled package infos', error)
})
}
this.getSingleBundle = function(pkgName) {
this.getSingleBundle = function (pkgName) {
let url = `${baseUrlSrv.getRestApiBase()}/helium/bundle/load/${pkgName}`
if (process.env.HELIUM_BUNDLE_DEV) {
url = url + '?refresh=true';
url = url + '?refresh=true'
}
return $http.get(url)
.then(function(response, status) {
.then(function (response, status) {
const bundle = response.data
if (bundle.substring(0, 'ERROR:'.length) === 'ERROR:') {
console.error(`Failed to get bundle: ${pkgName}`, bundle);
console.error(`Failed to get bundle: ${pkgName}`, bundle)
return '' // empty bundle will be filtered later
}
return bundle
})
.catch(function(error) {
console.error(`Failed to get single bundle: ${pkgName}`, error);
});
.catch(function (error) {
console.error(`Failed to get single bundle: ${pkgName}`, error)
})
}
this.getDefaultPackages = function() {
this.getDefaultPackages = function () {
return this.getAllPackageInfo()
.then(pkgSearchResults => {
return createDefaultPackages(pkgSearchResults, $sce);
});
};
return createDefaultPackages(pkgSearchResults, $sce)
})
}
this.getAllPackageInfoAndDefaultPackages = function() {
this.getAllPackageInfoAndDefaultPackages = function () {
return this.getAllPackageInfo()
.then(pkgSearchResults => {
return {
pkgSearchResults: pkgSearchResults,
defaultPackages: createDefaultPackages(pkgSearchResults, $sce),
};
});
};
}
})
}
/**
* get all package configs.
* @return { Promise<{name, Array<Object>}> }
*/
this.getAllPackageConfigs = function() {
const promisedDefaultPackages = this.getDefaultPackages();
this.getAllPackageConfigs = function () {
const promisedDefaultPackages = this.getDefaultPackages()
const promisedPersistedConfs =
$http.get(`${baseUrlSrv.getRestApiBase()}/helium/config`)
.then(function(response, status) {
return response.data.body;
});
.then(function (response, status) {
return response.data.body
})
return Promise.all([promisedDefaultPackages, promisedPersistedConfs])
.then(values => {
const defaultPackages = values[0];
const persistedConfs = values[1];
const defaultPackages = values[0]
const persistedConfs = values[1]
return createAllPackageConfigs(defaultPackages, persistedConfs);
return createAllPackageConfigs(defaultPackages, persistedConfs)
})
.catch(function(error) {
console.error('Failed to get all package configs', error);
});
};
.catch(function (error) {
console.error('Failed to get all package configs', error)
})
}
/**
* get the package config which is persisted in server.
* @return { Promise<Array<Object>> }
*/
this.getSinglePackageConfigs = function(pkg) {
const pkgName = pkg.name;
this.getSinglePackageConfigs = function (pkg) {
const pkgName = pkg.name
// in case of local package, it will include `/`
const pkgArtifact = encodeURIComponent(pkg.artifact);
const pkgArtifact = encodeURIComponent(pkg.artifact)
if (!pkgName || !pkgArtifact) {
console.error('Failed to fetch config for\n', pkg);
return Promise.resolve([]);
console.error('Failed to fetch config for\n', pkg)
return Promise.resolve([])
}
const confUrl = `${baseUrlSrv.getRestApiBase()}/helium/config/${pkgName}/${pkgArtifact}`;
const confUrl = `${baseUrlSrv.getRestApiBase()}/helium/config/${pkgName}/${pkgArtifact}`
const promisedConf = $http.get(confUrl)
.then(function(response, status) {
return response.data.body;
});
.then(function (response, status) {
return response.data.body
})
return promisedConf.then(({confSpec, confPersisted}) => {
const merged = mergePersistedConfWithSpec(confPersisted, confSpec)
return merged;
});
};
return merged
})
}
this.getSinglePackageConfigUsingMagic = function(magic) {
const pkgName = pkgNamePerMagic[magic];
this.getSinglePackageConfigUsingMagic = function (magic) {
const pkgName = pkgNamePerMagic[magic]
const confUrl = `${baseUrlSrv.getRestApiBase()}/helium/spell/config/${pkgName}`;
const confUrl = `${baseUrlSrv.getRestApiBase()}/helium/spell/config/${pkgName}`
const promisedConf = $http.get(confUrl)
.then(function(response, status) {
return response.data.body;
});
.then(function (response, status) {
return response.data.body
})
return promisedConf.then(({confSpec, confPersisted}) => {
const merged = mergePersistedConfWithSpec(confPersisted, confSpec)
return merged;
});
return merged
})
}
const p = this.getAllEnabledPackages()
@ -260,15 +260,15 @@ export default function heliumService($http, $sce, baseUrlSrv) {
// filter out empty bundle
if (b === '') { return acc }
acc.push(b)
return acc;
return acc
}, [])
})
// load should be promise
this.load = p.then(availableBundles => {
// evaluate bundles
availableBundles.map(b => {
// eslint-disable-next-line no-eval
eval(b)
})
@ -284,5 +284,4 @@ export default function heliumService($http, $sce, baseUrlSrv) {
}
})
})
}

View file

@ -12,21 +12,20 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').directive('interpreterDirective', interpreterDirective);
angular.module('zeppelinWebApp').directive('interpreterDirective', interpreterDirective)
function interpreterDirective($timeout) {
'ngInject';
function interpreterDirective ($timeout) {
'ngInject'
return {
restrict: 'A',
link: function(scope, element, attr) {
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function() {
var id = 'ngRenderFinished';
scope.$emit(id);
});
$timeout(function () {
let id = 'ngRenderFinished'
scope.$emit(id)
})
}
}
};
}
}

View file

@ -12,16 +12,15 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('LoginCtrl', LoginCtrl);
angular.module('zeppelinWebApp').controller('LoginCtrl', LoginCtrl)
function LoginCtrl($scope, $rootScope, $http, $httpParamSerializer, baseUrlSrv, $location, $timeout) {
'ngInject';
function LoginCtrl ($scope, $rootScope, $http, $httpParamSerializer, baseUrlSrv, $location, $timeout) {
'ngInject'
$scope.SigningIn = false;
$scope.loginParams = {};
$scope.login = function() {
$scope.SigningIn = true;
$scope.SigningIn = false
$scope.loginParams = {}
$scope.login = function () {
$scope.SigningIn = true
$http({
method: 'POST',
url: baseUrlSrv.getRestApiBase() + '/login',
@ -32,57 +31,54 @@ function LoginCtrl($scope, $rootScope, $http, $httpParamSerializer, baseUrlSrv,
'userName': $scope.loginParams.userName,
'password': $scope.loginParams.password
})
}).then(function successCallback(response) {
$rootScope.ticket = response.data.body;
angular.element('#loginModal').modal('toggle');
$rootScope.$broadcast('loginSuccess', true);
$rootScope.userName = $scope.loginParams.userName;
$scope.SigningIn = false;
}).then(function successCallback (response) {
$rootScope.ticket = response.data.body
angular.element('#loginModal').modal('toggle')
$rootScope.$broadcast('loginSuccess', true)
$rootScope.userName = $scope.loginParams.userName
$scope.SigningIn = false
//redirect to the page from where the user originally was
// redirect to the page from where the user originally was
if ($location.search() && $location.search()['ref']) {
$timeout(function() {
var redirectLocation = $location.search()['ref'];
$location.$$search = {};
$location.path(redirectLocation);
}, 100);
$timeout(function () {
let redirectLocation = $location.search()['ref']
$location.$$search = {}
$location.path(redirectLocation)
}, 100)
}
}, function errorCallback(errorResponse) {
$scope.loginParams.errorText = 'The username and password that you entered don\'t match.';
$scope.SigningIn = false;
});
}, function errorCallback (errorResponse) {
$scope.loginParams.errorText = 'The username and password that you entered don\'t match.'
$scope.SigningIn = false
})
}
};
var initValues = function() {
let initValues = function () {
$scope.loginParams = {
userName: '',
password: ''
};
};
//handle session logout message received from WebSocket
$rootScope.$on('session_logout', function(event, data) {
if ($rootScope.userName !== '') {
$rootScope.userName = '';
$rootScope.ticket = undefined;
setTimeout(function() {
$scope.loginParams = {};
$scope.loginParams.errorText = data.info;
angular.element('.nav-login-btn').click();
}, 1000);
var locationPath = $location.path();
$location.path('/').search('ref', locationPath);
}
});
}
// handle session logout message received from WebSocket
$rootScope.$on('session_logout', function (event, data) {
if ($rootScope.userName !== '') {
$rootScope.userName = ''
$rootScope.ticket = undefined
setTimeout(function () {
$scope.loginParams = {}
$scope.loginParams.errorText = data.info
angular.element('.nav-login-btn').click()
}, 1000)
let locationPath = $location.path()
$location.path('/').search('ref', locationPath)
}
})
/*
** $scope.$on functions below
*/
$scope.$on('initLoginValues', function() {
initValues();
});
$scope.$on('initLoginValues', function () {
initValues()
})
}

View file

@ -12,142 +12,142 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').controller('NavCtrl', NavCtrl);
angular.module('zeppelinWebApp').controller('NavCtrl', NavCtrl)
function NavCtrl($scope, $rootScope, $http, $routeParams, $location,
function NavCtrl ($scope, $rootScope, $http, $routeParams, $location,
noteListDataFactory, baseUrlSrv, websocketMsgSrv,
arrayOrderingSrv, searchService, TRASH_FOLDER_ID) {
'ngInject';
'ngInject'
var vm = this;
vm.arrayOrderingSrv = arrayOrderingSrv;
vm.connected = websocketMsgSrv.isConnected();
vm.isActive = isActive;
vm.logout = logout;
vm.notes = noteListDataFactory;
vm.search = search;
vm.searchForm = searchService;
vm.showLoginWindow = showLoginWindow;
vm.TRASH_FOLDER_ID = TRASH_FOLDER_ID;
vm.isFilterNote = isFilterNote;
let vm = this
vm.arrayOrderingSrv = arrayOrderingSrv
vm.connected = websocketMsgSrv.isConnected()
vm.isActive = isActive
vm.logout = logout
vm.notes = noteListDataFactory
vm.search = search
vm.searchForm = searchService
vm.showLoginWindow = showLoginWindow
vm.TRASH_FOLDER_ID = TRASH_FOLDER_ID
vm.isFilterNote = isFilterNote
$scope.query = {q: ''};
$scope.query = {q: ''}
initController();
initController()
function getZeppelinVersion() {
function getZeppelinVersion () {
$http.get(baseUrlSrv.getRestApiBase() + '/version').success(
function(data, status, headers, config) {
$rootScope.zeppelinVersion = data.body;
function (data, status, headers, config) {
$rootScope.zeppelinVersion = data.body
}).error(
function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
function (data, status, headers, config) {
console.log('Error %o %o', status, data.message)
})
}
function initController() {
$scope.isDrawNavbarNoteList = false;
angular.element('#notebook-list').perfectScrollbar({suppressScrollX: true});
function initController () {
$scope.isDrawNavbarNoteList = false
angular.element('#notebook-list').perfectScrollbar({suppressScrollX: true})
angular.element(document).click(function() {
$scope.query.q = '';
});
angular.element(document).click(function () {
$scope.query.q = ''
})
getZeppelinVersion();
loadNotes();
getZeppelinVersion()
loadNotes()
}
function isFilterNote(note) {
function isFilterNote (note) {
if (!$scope.query.q) {
return true;
return true
}
var noteName = note.name;
let noteName = note.name
if (noteName.toLowerCase().indexOf($scope.query.q.toLowerCase()) > -1) {
return true;
return true
}
return false;
return false
}
function isActive(noteId) {
return ($routeParams.noteId === noteId);
function isActive (noteId) {
return ($routeParams.noteId === noteId)
}
function listConfigurations() {
websocketMsgSrv.listConfigurations();
function listConfigurations () {
websocketMsgSrv.listConfigurations()
}
function loadNotes() {
websocketMsgSrv.getNoteList();
function loadNotes () {
websocketMsgSrv.getNoteList()
}
function getHomeNote(){
websocketMsgSrv.getHomeNote();
function getHomeNote () {
websocketMsgSrv.getHomeNote()
}
function logout() {
var logoutURL = baseUrlSrv.getRestApiBase() + '/login/logout';
function logout () {
let logoutURL = baseUrlSrv.getRestApiBase() + '/login/logout'
//for firefox and safari
logoutURL = logoutURL.replace('//', '//false:false@');
$http.post(logoutURL).error(function() {
//force authcBasic (if configured) to logout
$http.post(logoutURL).error(function() {
$rootScope.userName = '';
$rootScope.ticket.principal = '';
$rootScope.ticket.ticket = '';
$rootScope.ticket.roles = '';
// for firefox and safari
logoutURL = logoutURL.replace('//', '//false:false@')
$http.post(logoutURL).error(function () {
// force authcBasic (if configured) to logout
$http.post(logoutURL).error(function () {
$rootScope.userName = ''
$rootScope.ticket.principal = ''
$rootScope.ticket.ticket = ''
$rootScope.ticket.roles = ''
BootstrapDialog.show({
message: 'Logout Success'
});
setTimeout(function() {
window.location.replace('/');
}, 1000);
});
});
})
setTimeout(function () {
window.location.replace('/')
}, 1000)
})
})
}
function search(searchTerm) {
$location.path('/search/' + searchTerm);
function search (searchTerm) {
$location.path('/search/' + searchTerm)
}
function showLoginWindow() {
setTimeout(function() {
angular.element('#userName').focus();
}, 500);
function showLoginWindow () {
setTimeout(function () {
angular.element('#userName').focus()
}, 500)
}
/*
** $scope.$on functions below
*/
$scope.$on('setNoteMenu', function(event, notes) {
noteListDataFactory.setNotes(notes);
initNotebookListEventListener();
});
$scope.$on('setNoteMenu', function (event, notes) {
noteListDataFactory.setNotes(notes)
initNotebookListEventListener()
})
$scope.$on('setConnectedStatus', function(event, param) {
vm.connected = param;
});
$scope.$on('setConnectedStatus', function (event, param) {
vm.connected = param
})
$scope.$on('loginSuccess', function(event, param) {
listConfigurations();
loadNotes();
getHomeNote();
});
$scope.$on('loginSuccess', function (event, param) {
listConfigurations()
loadNotes()
getHomeNote()
})
/*
** Performance optimization for Browser Render.
*/
function initNotebookListEventListener() {
angular.element(document).ready(function() {
angular.element('.notebook-list-dropdown').on('show.bs.dropdown', function() {
$scope.isDrawNavbarNoteList = true;
});
function initNotebookListEventListener () {
angular.element(document).ready(function () {
angular.element('.notebook-list-dropdown').on('show.bs.dropdown', function () {
$scope.isDrawNavbarNoteList = true
})
angular.element('.notebook-list-dropdown').on('hide.bs.dropdown', function() {
$scope.isDrawNavbarNoteList = false;
});
});
angular.element('.notebook-list-dropdown').on('hide.bs.dropdown', function () {
$scope.isDrawNavbarNoteList = false
})
})
}
}

View file

@ -1,18 +1,18 @@
describe('Controller: NavCtrl', function() {
describe('Controller: NavCtrl', function () {
// load the controller's module
beforeEach(angular.mock.module('zeppelinWebApp'));
var NavCtrl;
var scope;
beforeEach(angular.mock.module('zeppelinWebApp'))
let NavCtrl
let scope
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new()
NavCtrl = $controller('NavCtrl', {
$scope: scope
});
})
it('NavCtrl to toBeDefined', function() {
expect(NavCtrl).toBeDefined();
expect(NavCtrl.loadNotes).toBeDefined();
});
}));
});
it('NavCtrl to toBeDefined', function () {
expect(NavCtrl).toBeDefined()
expect(NavCtrl.loadNotes).toBeDefined()
})
}))
})

View file

@ -12,20 +12,19 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').directive('ngEnter', ngEnter);
angular.module('zeppelinWebApp').directive('ngEnter', ngEnter)
function ngEnter() {
return function(scope, element, attrs) {
element.bind('keydown keypress', function(event) {
function ngEnter () {
return function (scope, element, attrs) {
element.bind('keydown keypress', function (event) {
if (event.which === 13) {
if (!event.shiftKey) {
scope.$apply(function() {
scope.$eval(attrs.ngEnter);
});
scope.$apply(function () {
scope.$eval(attrs.ngEnter)
})
}
event.preventDefault();
event.preventDefault()
}
});
};
})
}
}

View file

@ -1,25 +1,24 @@
describe('Directive: ngEnter', function() {
describe('Directive: ngEnter', function () {
// load the directive's module
beforeEach(angular.mock.module('zeppelinWebApp'));
beforeEach(angular.mock.module('zeppelinWebApp'))
var element;
var scope;
let element
let scope
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
}));
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new()
}))
it('should be define', inject(function($compile) {
element = angular.element('<ng-enter></ng-enter>');
element = $compile(element)(scope);
expect(element.text()).toBeDefined();
}));
it('should be define', inject(function ($compile) {
element = angular.element('<ng-enter></ng-enter>')
element = $compile(element)(scope)
expect(element.text()).toBeDefined()
}))
//Test the rest of function in ngEnter
// Test the rest of function in ngEnter
/* it('should make hidden element visible', inject(function ($compile) {
element = angular.element('<ng-enter></ng-enter>');
element = $compile(element)(scope);
expect(element.text()).toBe('this is the ngEnter directive');
}));*/
});
})); */
})

View file

@ -12,18 +12,17 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').directive('ngEscape', ngEscape);
angular.module('zeppelinWebApp').directive('ngEscape', ngEscape)
function ngEscape() {
return function(scope, element, attrs) {
element.bind('keydown keyup', function(event) {
function ngEscape () {
return function (scope, element, attrs) {
element.bind('keydown keyup', function (event) {
if (event.which === 27) {
scope.$apply(function() {
scope.$eval(attrs.ngEscape);
});
event.preventDefault();
scope.$apply(function () {
scope.$eval(attrs.ngEscape)
})
event.preventDefault()
}
});
};
})
}
}

View file

@ -12,172 +12,172 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').service('noteActionSrv', noteActionSrv);
angular.module('zeppelinWebApp').service('noteActionSrv', noteActionSrv)
function noteActionSrv(websocketMsgSrv, $location, renameSrv, noteListDataFactory) {
'ngInject';
function noteActionSrv (websocketMsgSrv, $location, renameSrv, noteListDataFactory) {
'ngInject'
this.moveNoteToTrash = function(noteId, redirectToHome) {
this.moveNoteToTrash = function (noteId, redirectToHome) {
BootstrapDialog.confirm({
closable: true,
title: 'Move this note to trash?',
message: 'This note will be moved to <strong>trash</strong>.',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.moveNoteToTrash(noteId);
websocketMsgSrv.moveNoteToTrash(noteId)
if (redirectToHome) {
$location.path('/');
$location.path('/')
}
}
}
});
};
})
}
this.moveFolderToTrash = function(folderId) {
this.moveFolderToTrash = function (folderId) {
BootstrapDialog.confirm({
closable: true,
title: 'Move this folder to trash?',
message: 'This folder will be moved to <strong>trash</strong>.',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.moveFolderToTrash(folderId);
websocketMsgSrv.moveFolderToTrash(folderId)
}
}
});
};
})
}
this.removeNote = function(noteId, redirectToHome) {
this.removeNote = function (noteId, redirectToHome) {
BootstrapDialog.confirm({
type: BootstrapDialog.TYPE_WARNING,
closable: true,
title: 'WARNING! This note will be removed permanently',
message: 'This cannot be undone. Are you sure?',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.deleteNote(noteId);
websocketMsgSrv.deleteNote(noteId)
if (redirectToHome) {
$location.path('/');
$location.path('/')
}
}
}
});
};
})
}
this.removeFolder = function(folderId) {
this.removeFolder = function (folderId) {
BootstrapDialog.confirm({
type: BootstrapDialog.TYPE_WARNING,
closable: true,
title: 'WARNING! This folder will be removed permanently',
message: 'This cannot be undone. Are you sure?',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.removeFolder(folderId);
websocketMsgSrv.removeFolder(folderId)
}
}
});
};
})
}
this.restoreAll = function() {
this.restoreAll = function () {
BootstrapDialog.confirm({
closable: true,
title: 'Are you sure want to restore all notes in the trash?',
message: 'Folders and notes in the trash will be ' +
'<strong>merged</strong> into their original position.',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.restoreAll();
websocketMsgSrv.restoreAll()
}
}
});
};
})
}
this.emptyTrash = function() {
this.emptyTrash = function () {
BootstrapDialog.confirm({
type: BootstrapDialog.TYPE_WARNING,
closable: true,
title: 'WARNING! Notes under trash will be removed permanently',
message: 'This cannot be undone. Are you sure?',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.emptyTrash();
websocketMsgSrv.emptyTrash()
}
}
});
};
})
}
this.clearAllParagraphOutput = function(noteId) {
this.clearAllParagraphOutput = function (noteId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to clear all output?',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.clearAllParagraphOutput(noteId);
websocketMsgSrv.clearAllParagraphOutput(noteId)
}
}
});
};
})
}
this.renameNote = function(noteId, notePath) {
this.renameNote = function (noteId, notePath) {
renameSrv.openRenameModal({
title: 'Rename note',
oldName: notePath,
callback: function(newName) {
websocketMsgSrv.renameNote(noteId, newName);
callback: function (newName) {
websocketMsgSrv.renameNote(noteId, newName)
}
});
};
})
}
this.renameFolder = function(folderId) {
this.renameFolder = function (folderId) {
renameSrv.openRenameModal({
title: 'Rename folder',
oldName: folderId,
callback: function(newName) {
var newFolderId = normalizeFolderId(newName);
callback: function (newName) {
let newFolderId = normalizeFolderId(newName)
if (_.has(noteListDataFactory.flatFolderMap, newFolderId)) {
BootstrapDialog.confirm({
type: BootstrapDialog.TYPE_WARNING,
closable: true,
title: 'WARNING! The folder will be MERGED',
message: 'The folder will be merged into <strong>' + newFolderId + '</strong>. Are you sure?',
callback: function(result) {
callback: function (result) {
if (result) {
websocketMsgSrv.renameFolder(folderId, newFolderId);
websocketMsgSrv.renameFolder(folderId, newFolderId)
}
}
});
})
} else {
websocketMsgSrv.renameFolder(folderId, newFolderId);
websocketMsgSrv.renameFolder(folderId, newFolderId)
}
}
});
};
})
}
function normalizeFolderId(folderId) {
folderId = folderId.trim();
function normalizeFolderId (folderId) {
folderId = folderId.trim()
while (folderId.indexOf('\\') > -1) {
folderId = folderId.replace('\\', '/');
folderId = folderId.replace('\\', '/')
}
while (folderId.indexOf('///') > -1) {
folderId = folderId.replace('///', '/');
folderId = folderId.replace('///', '/')
}
folderId = folderId.replace('//', '/');
folderId = folderId.replace('//', '/')
if (folderId === '/') {
return '/';
return '/'
}
if (folderId[0] === '/') {
folderId = folderId.substring(1);
folderId = folderId.substring(1)
}
if (folderId.slice(-1) === '/') {
folderId = folderId.slice(0, -1);
folderId = folderId.slice(0, -1)
}
return folderId;
return folderId
}
}

View file

@ -12,71 +12,70 @@
* limitations under the License.
*/
angular.module('zeppelinWebApp').factory('noteListDataFactory', noteListDataFactory);
angular.module('zeppelinWebApp').factory('noteListDataFactory', noteListDataFactory)
function noteListDataFactory(TRASH_FOLDER_ID) {
'ngInject';
function noteListDataFactory (TRASH_FOLDER_ID) {
'ngInject'
var notes = {
const notes = {
root: {children: []},
flatList: [],
flatFolderMap: {},
setNotes: function(notesList) {
setNotes: function (notesList) {
// a flat list to boost searching
notes.flatList = _.map(notesList, (note) => {
note.isTrash = note.name ?
note.name.split('/')[0] === TRASH_FOLDER_ID : false;
return note;
});
note.isTrash = note.name
? note.name.split('/')[0] === TRASH_FOLDER_ID : false
return note
})
// construct the folder-based tree
notes.root = {children: []};
notes.flatFolderMap = {};
_.reduce(notesList, function(root, note) {
var noteName = note.name || note.id;
var nodes = noteName.match(/([^\/][^\/]*)/g);
notes.root = {children: []}
notes.flatFolderMap = {}
_.reduce(notesList, function (root, note) {
let noteName = note.name || note.id
let nodes = noteName.match(/([^\/][^\/]*)/g)
// recursively add nodes
addNode(root, nodes, note.id);
addNode(root, nodes, note.id)
return root;
}, notes.root);
return root
}, notes.root)
}
};
}
var addNode = function(curDir, nodes, noteId) {
const addNode = function (curDir, nodes, noteId) {
if (nodes.length === 1) { // the leaf
curDir.children.push({
name: nodes[0],
id: noteId,
path: curDir.id ? curDir.id + '/' + nodes[0] : nodes[0],
isTrash: curDir.id ? curDir.id.split('/')[0] === TRASH_FOLDER_ID : false
});
})
} else { // a folder node
var node = nodes.shift();
var dir = _.find(curDir.children,
function(c) {return c.name === node && c.children !== undefined;});
let node = nodes.shift()
let dir = _.find(curDir.children,
function (c) { return c.name === node && c.children !== undefined })
if (dir !== undefined) { // found an existing dir
addNode(dir, nodes, noteId);
addNode(dir, nodes, noteId)
} else {
var newDir = {
let newDir = {
id: curDir.id ? curDir.id + '/' + node : node,
name: node,
hidden: true,
children: [],
isTrash: curDir.id ? curDir.id.split('/')[0] === TRASH_FOLDER_ID : false
};
}
// add the folder to flat folder map
notes.flatFolderMap[newDir.id] = newDir;
notes.flatFolderMap[newDir.id] = newDir
curDir.children.push(newDir);
addNode(newDir, nodes, noteId);
curDir.children.push(newDir)
addNode(newDir, nodes, noteId)
}
}
};
}
return notes;
return notes
}

View file

@ -1,17 +1,16 @@
describe('Factory: NoteList', function() {
describe('Factory: NoteList', function () {
let noteList
var noteList;
beforeEach(function () {
angular.mock.module('zeppelinWebApp')
beforeEach(function() {
angular.mock.module('zeppelinWebApp');
inject(function ($injector) {
noteList = $injector.get('noteListDataFactory')
})
})
inject(function($injector) {
noteList = $injector.get('noteListDataFactory');
});
});
it('should generate both flat list and folder-based list properly', function() {
var notesList = [
it('should generate both flat list and folder-based list properly', function () {
let notesList = [
{name: 'A', id: '000001'},
{name: 'B', id: '000002'},
{id: '000003'}, // note without name
@ -21,57 +20,56 @@ describe('Factory: NoteList', function() {
{name: '/C/CB/CBA', id: '000007'}, // same name with another note
{name: 'C///CB//CBB', id: '000008'},
{name: 'D/D[A/DA]B', id: '000009'} // check if '[' and ']' considered as folder seperator
];
noteList.setNotes(notesList);
]
noteList.setNotes(notesList)
var flatList = noteList.flatList;
expect(flatList.length).toBe(9);
expect(flatList[0].name).toBe('A');
expect(flatList[0].id).toBe('000001');
expect(flatList[1].name).toBe('B');
expect(flatList[2].name).toBeUndefined();
expect(flatList[3].name).toBe('/C/CA');
expect(flatList[4].name).toBe('/C/CB');
expect(flatList[5].name).toBe('/C/CB/CBA');
expect(flatList[6].name).toBe('/C/CB/CBA');
expect(flatList[7].name).toBe('C///CB//CBB');
expect(flatList[8].name).toBe('D/D[A/DA]B');
let flatList = noteList.flatList
expect(flatList.length).toBe(9)
expect(flatList[0].name).toBe('A')
expect(flatList[0].id).toBe('000001')
expect(flatList[1].name).toBe('B')
expect(flatList[2].name).toBeUndefined()
expect(flatList[3].name).toBe('/C/CA')
expect(flatList[4].name).toBe('/C/CB')
expect(flatList[5].name).toBe('/C/CB/CBA')
expect(flatList[6].name).toBe('/C/CB/CBA')
expect(flatList[7].name).toBe('C///CB//CBB')
expect(flatList[8].name).toBe('D/D[A/DA]B')
var folderList = noteList.root.children;
expect(folderList.length).toBe(5);
expect(folderList[0].name).toBe('A');
expect(folderList[0].id).toBe('000001');
expect(folderList[1].name).toBe('B');
expect(folderList[2].name).toBe('000003');
expect(folderList[3].name).toBe('C');
expect(folderList[3].id).toBe('C');
expect(folderList[3].children.length).toBe(3);
expect(folderList[3].children[0].name).toBe('CA');
expect(folderList[3].children[0].id).toBe('000004');
expect(folderList[3].children[0].children).toBeUndefined();
expect(folderList[3].children[1].name).toBe('CB');
expect(folderList[3].children[1].id).toBe('000005');
expect(folderList[3].children[1].children).toBeUndefined();
expect(folderList[3].children[2].name).toBe('CB');
expect(folderList[3].children[2].id).toBe('C/CB');
expect(folderList[3].children[2].children.length).toBe(3);
expect(folderList[3].children[2].children[0].name).toBe('CBA');
expect(folderList[3].children[2].children[0].id).toBe('000006');
expect(folderList[3].children[2].children[0].children).toBeUndefined();
expect(folderList[3].children[2].children[1].name).toBe('CBA');
expect(folderList[3].children[2].children[1].id).toBe('000007');
expect(folderList[3].children[2].children[1].children).toBeUndefined();
expect(folderList[3].children[2].children[2].name).toBe('CBB');
expect(folderList[3].children[2].children[2].id).toBe('000008');
expect(folderList[3].children[2].children[2].children).toBeUndefined();
expect(folderList[4].name).toBe('D');
expect(folderList[4].id).toBe('D');
expect(folderList[4].children.length).toBe(1);
expect(folderList[4].children[0].name).toBe('D[A');
expect(folderList[4].children[0].id).toBe('D/D[A');
expect(folderList[4].children[0].children[0].name).toBe('DA]B');
expect(folderList[4].children[0].children[0].id).toBe('000009');
expect(folderList[4].children[0].children[0].children).toBeUndefined();
});
});
let folderList = noteList.root.children
expect(folderList.length).toBe(5)
expect(folderList[0].name).toBe('A')
expect(folderList[0].id).toBe('000001')
expect(folderList[1].name).toBe('B')
expect(folderList[2].name).toBe('000003')
expect(folderList[3].name).toBe('C')
expect(folderList[3].id).toBe('C')
expect(folderList[3].children.length).toBe(3)
expect(folderList[3].children[0].name).toBe('CA')
expect(folderList[3].children[0].id).toBe('000004')
expect(folderList[3].children[0].children).toBeUndefined()
expect(folderList[3].children[1].name).toBe('CB')
expect(folderList[3].children[1].id).toBe('000005')
expect(folderList[3].children[1].children).toBeUndefined()
expect(folderList[3].children[2].name).toBe('CB')
expect(folderList[3].children[2].id).toBe('C/CB')
expect(folderList[3].children[2].children.length).toBe(3)
expect(folderList[3].children[2].children[0].name).toBe('CBA')
expect(folderList[3].children[2].children[0].id).toBe('000006')
expect(folderList[3].children[2].children[0].children).toBeUndefined()
expect(folderList[3].children[2].children[1].name).toBe('CBA')
expect(folderList[3].children[2].children[1].id).toBe('000007')
expect(folderList[3].children[2].children[1].children).toBeUndefined()
expect(folderList[3].children[2].children[2].name).toBe('CBB')
expect(folderList[3].children[2].children[2].id).toBe('000008')
expect(folderList[3].children[2].children[2].children).toBeUndefined()
expect(folderList[4].name).toBe('D')
expect(folderList[4].id).toBe('D')
expect(folderList[4].children.length).toBe(1)
expect(folderList[4].children[0].name).toBe('D[A')
expect(folderList[4].children[0].id).toBe('D/D[A')
expect(folderList[4].children[0].children[0].name).toBe('DA]B')
expect(folderList[4].children[0].children[0].id).toBe('000009')
expect(folderList[4].children[0].children[0].children).toBeUndefined()
})
})

Some files were not shown because too many files have changed in this diff Show more