@@ -64,9 +70,11 @@
diff --git a/zeppelin-web/src/assets/styles/looknfeel/default.css b/zeppelin-web/src/assets/styles/looknfeel/default.css
index 78a3bb339a8..bc6da00a429 100644
--- a/zeppelin-web/src/assets/styles/looknfeel/default.css
+++ b/zeppelin-web/src/assets/styles/looknfeel/default.css
@@ -28,6 +28,13 @@ body {
display: block;
}
+.paragraphId {
+ display: block;
+ font-size: 10px;
+ text-align: left;
+ margin-left: 5px;
+}
+
.paragraph-col .focused {
box-shadow: 0px 2px 7px rgba(0, 0, 0, 0.3);
border-color: white;
diff --git a/zeppelin-web/src/assets/styles/looknfeel/report.css b/zeppelin-web/src/assets/styles/looknfeel/report.css
index 0a15e8a59a6..580e42e89d1 100644
--- a/zeppelin-web/src/assets/styles/looknfeel/report.css
+++ b/zeppelin-web/src/assets/styles/looknfeel/report.css
@@ -65,6 +65,7 @@ body {
}
.executionTime,
+.paragraphId,
.nv-controlsWrap,
.lastEmptyParagraph {
display: none;
diff --git a/zeppelin-web/src/assets/styles/looknfeel/simple.css b/zeppelin-web/src/assets/styles/looknfeel/simple.css
index 7817da28ec8..4eba33570ef 100644
--- a/zeppelin-web/src/assets/styles/looknfeel/simple.css
+++ b/zeppelin-web/src/assets/styles/looknfeel/simple.css
@@ -67,6 +67,10 @@ body {
margin-right: 5px;
}
+.paragraphId {
+ display: none;
+}
+
.paragraph:hover .paragraphFooter {
visibility: visible;
}
diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
index df440100942..b88b510f18e 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
@@ -70,6 +70,32 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope,
});
},
+ clientUpdateAngularObject: function(noteId, name, value, params) {
+ websocketEvents.sendNewEvent({
+ op: 'ANGULAR_OBJECT_CLIENT_UPDATE',
+ data: {
+ noteId: noteId,
+ name: name,
+ value: value,
+ interpreters: params.interpreters,
+ paragraphs: params.paragraphs,
+ scope: params.scope
+ }
+ });
+ },
+
+ clientRemoveAngularObject: function(noteId, name, params) {
+ websocketEvents.sendNewEvent({
+ op: 'ANGULAR_OBJECT_CLIENT_REMOVE',
+ data: {
+ noteId: noteId,
+ name: name,
+ interpreters: params.interpreters,
+ paragraphs: params.paragraphs
+ }
+ });
+ },
+
cancelParagraphRun: function(paragraphId) {
websocketEvents.sendNewEvent({op: 'CANCEL_PARAGRAPH', data: {id: paragraphId}});
},
diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml
index 0ef60bf3a69..d13750c0764 100644
--- a/zeppelin-zengine/pom.xml
+++ b/zeppelin-zengine/pom.xml
@@ -194,6 +194,13 @@
test
+
+ org.assertj
+ assertj-core
+ 2.0.0
+ test
+
+
com.google.truth
truth
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
index 65210f52bdb..881765793c2 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
@@ -17,6 +17,7 @@
package org.apache.zeppelin.notebook;
+import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.display.Input;
@@ -202,6 +203,12 @@ protected Object jobRun() throws Throwable {
String scriptBody = getScriptBody();
Map inputs = Input.extractSimpleQueryParam(scriptBody); // inputs will be built
// from script body
+
+ final AngularObjectRegistry angularRegistry = repl.getInterpreterGroup()
+ .getAngularObjectRegistry();
+
+ scriptBody = extractVariablesFromAngularRegistry(scriptBody, inputs, angularRegistry);
+
settings.setForms(inputs);
script = Input.getSimpleQuery(settings.getParams(), scriptBody);
}
@@ -346,4 +353,26 @@ public Object clone() throws CloneNotSupportedException {
Paragraph paraClone = (Paragraph) this.clone();
return paraClone;
}
+
+
+ String extractVariablesFromAngularRegistry(String scriptBody, Map inputs,
+ AngularObjectRegistry angularRegistry) {
+
+ final String noteId = this.getNote().getId();
+ final String paragraphId = this.getId();
+
+ final Set keys = new HashSet<>(inputs.keySet());
+
+ for (String varName : keys) {
+ final AngularObject paragraphScoped = angularRegistry.get(varName, noteId, paragraphId);
+ final AngularObject noteScoped = angularRegistry.get(varName, noteId, null);
+ final AngularObject angularObject = paragraphScoped != null ? paragraphScoped : noteScoped;
+ if (angularObject != null) {
+ inputs.remove(varName);
+ final String pattern = "[$][{]\\s*" + varName + "\\s*(?:=[^}]+)?[}]";
+ scriptBody = scriptBody.replaceAll(pattern, angularObject.get().toString());
+ }
+ }
+ return scriptBody;
+ }
}
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/display/AngularObjectBuilder.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/display/AngularObjectBuilder.java
new file mode 100644
index 00000000000..f64a9d8c286
--- /dev/null
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/display/AngularObjectBuilder.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Utility class to build an angular object for tests
+ */
+public class AngularObjectBuilder {
+
+ public static AngularObject build(String varName, T value, String noteId,
+ String paragraphId) {
+
+ return new AngularObject<>(varName, value, noteId, paragraphId, null);
+ }
+}
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
index 87805cef133..b918f72e20a 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
@@ -17,9 +17,24 @@
package org.apache.zeppelin.notebook;
+import org.apache.zeppelin.display.AngularObject;
+import org.apache.zeppelin.display.AngularObjectBuilder;
+import org.apache.zeppelin.display.AngularObjectRegistry;
+import org.apache.zeppelin.display.Input;
+import org.assertj.core.api.Assertions;
import org.junit.Test;
+import org.mockito.Mockito;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
public class ParagraphTest {
@Test
@@ -30,9 +45,48 @@ public void scriptBodyWithReplName() {
text = "%table 1234567";
assertEquals("1234567", Paragraph.getScriptBody(text));
}
+
@Test
public void scriptBodyWithoutReplName() {
String text = "12345678";
assertEquals(text, Paragraph.getScriptBody(text));
}
+
+ @Test
+ public void should_extract_variable_from_angular_object_registry() throws Exception {
+ //Given
+ final String noteId = "noteId";
+
+ final AngularObjectRegistry registry = mock(AngularObjectRegistry.class);
+ final Note note = mock(Note.class);
+ final Map inputs = new HashMap<>();
+ inputs.put("name", null);
+ inputs.put("age", null);
+ inputs.put("job", null);
+
+ final String scriptBody = "My name is ${name} and I am ${age=20} years old. " +
+ "My occupation is ${ job = engineer | developer | artists}";
+
+ final Paragraph paragraph = new Paragraph(note, null, null);
+ final String paragraphId = paragraph.getId();
+
+ final AngularObject nameAO = AngularObjectBuilder.build("name", "DuyHai DOAN", noteId,
+ paragraphId);
+
+ final AngularObject ageAO = AngularObjectBuilder.build("age", 34, noteId, null);
+
+ when(note.getId()).thenReturn(noteId);
+ when(registry.get("name", noteId, paragraphId)).thenReturn(nameAO);
+ when(registry.get("age", noteId, null)).thenReturn(ageAO);
+
+ final String expected = "My name is DuyHai DOAN and I am 34 years old. " +
+ "My occupation is ${ job = engineer | developer | artists}";
+ //When
+ final String actual = paragraph.extractVariablesFromAngularRegistry(scriptBody, inputs, registry);
+
+ //Then
+ verify(registry).get("name", noteId, paragraphId);
+ verify(registry).get("age", noteId, null);
+ assertThat(actual).isEqualTo(expected);
+ }
}