Repository: zeppelin Updated Branches: refs/heads/branch-0.8 3fd2fec0d -> e27e8d4ff
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java ---------------------------------------------------------------------- diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java new file mode 100644 index 0000000..dc9099d --- /dev/null +++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java @@ -0,0 +1,643 @@ +/* + * 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.sap.universe; + +import org.apache.commons.lang.StringUtils; + +import java.util.*; + +/** + * Util class for convert request from Zeppelin to SAP + */ +public class UniverseUtil { + + private static final String COMPRASION_START_TEMPLATE = "<comparisonFilter path=\"%s\" " + + "operator=\"%s\" id=\"%s\">\n"; + private static final String COMPRASION_END_TEMPLATE = "</comparisonFilter>\n"; + private static final String COMPARISON_FILTER = "<comparisonFilter id=\"%s\" path=\"%s\" " + + "operator=\"%s\"/>\n"; + private static final String CONST_OPERAND_START_TEMPLATE = "<constantOperand>\n"; + private static final String CONST_OPERAND_END_TEMPLATE = "</constantOperand>\n"; + private static final String CONST_OPERAND_VALUE_TEMPLATE = "<value>\n" + + "<caption type=\"%s\">%s</caption>\n</value>\n"; + private static final String PREDEFINED_FILTER_TEMPLATE = "<predefinedFilter path=\"%s\"" + + " id=\"%s\"/>\n"; + private static final String OBJECT_OPERAND_TEMPLATE = "<objectOperand id=\"%s\" path=\"%s\"/>\n"; + private static final String RESULT_START_TEMPLATE = "<resultObjects>\n"; + private static final String RESULT_END_TEMPLATE = "</resultObjects>\n"; + private static final String RESULT_OBJ_TEMPLATE = "<resultObject path=\"%s\" id=\"%s\"/>\n"; + + private static final String MARKER_EQUAL = "#EqualTo#"; + private static final String MARKER_LESS_EQUAL = "#LessThanOrEqualTo#"; + private static final String MARKER_NOT_EQUAL = "#NotEqualTo#"; + private static final String MARKER_LESS = "#LessThan#"; + private static final String MARKER_GREATER_EQUALS = "#GreaterThanOrEqualTo#"; + private static final String MARKER_GREATER = "#GreaterThan#"; + private static final String MARKER_IN = "#InList#"; + private static final String MARKER_NOT_IN = "#NotInList#"; + private static final String MARKER_NULL = "#IsNull#"; + private static final String MARKER_NOT_NULL = "#IsNotNull#"; + private static final String MARKER_FILTER = "#filter#"; + private static final String MARKER_AND = "#and#"; + private static final String MARKER_OR = "#or#"; + private static final String MARKER_BACKSPACE = "#backspace#"; + private static final String MARKER_LEFT_BRACE = "#left_brace#"; + private static final String MARKER_RIGHT_BRACE = "#right_brace#"; + + + private static final String LEFT_BRACE = "("; + private static final String RIGHT_BRACE = ")"; + + public static final Map<String, Integer> OPERATIONS; + + static { + OPERATIONS = new HashMap<>(); + OPERATIONS.put(MARKER_EQUAL, 1); + OPERATIONS.put(MARKER_LESS_EQUAL, 1); + OPERATIONS.put(MARKER_NOT_EQUAL, 1); + OPERATIONS.put(MARKER_LESS, 1); + OPERATIONS.put(MARKER_GREATER_EQUALS, 1); + OPERATIONS.put(MARKER_GREATER, 1); + OPERATIONS.put(MARKER_IN, 1); + OPERATIONS.put(MARKER_NOT_IN, 1); + OPERATIONS.put(MARKER_NULL, 1); + OPERATIONS.put(MARKER_NOT_NULL, 1); + OPERATIONS.put(MARKER_FILTER, 1); + OPERATIONS.put(MARKER_AND, 2); + OPERATIONS.put(MARKER_OR, 3); + } + + public UniverseQuery convertQuery(String text, UniverseClient client, String token) + throws UniverseException { + StringBuilder select = new StringBuilder(); + StringBuilder universe = new StringBuilder(); + StringBuilder buf = new StringBuilder(); + StringBuilder resultObj = new StringBuilder(); + StringBuilder whereBuf = new StringBuilder(); + UniverseInfo universeInfo = null; + String where = null; + boolean singleQuoteClosed = true; + boolean pathClosed = true; + boolean universePart = false; + boolean selectPart = false; + boolean wherePart = false; + boolean listOperator = false; + boolean operatorPosition = false; + Map<String, UniverseNodeInfo> nodeInfos = null; + + char[] array = text.toCharArray(); + for (int i = 0; i < array.length; i++) { + char c = array[i]; + buf.append(c); + if (c == '\'') { + if (i == 0 || array[i - 1] != '\\') { + singleQuoteClosed = !singleQuoteClosed; + } + } + if (c == '[' && pathClosed && singleQuoteClosed) { + pathClosed = false; + if (wherePart) { + operatorPosition = false; + } + } + if (c == ']' && !pathClosed && singleQuoteClosed) { + pathClosed = true; + if (wherePart) { + operatorPosition = true; + if (i + 1 == array.length || (array[i + 1] != '.' + && isFilter(String.format("%s]", whereBuf.toString()), text.substring(i + 1)))) { + whereBuf.append(c); + whereBuf.append(MARKER_FILTER); + if (i + 1 == array.length) { + wherePart = false; + where = parseWhere(whereBuf.toString(), nodeInfos); + } + continue; + } + } + } + if (c == '(' && wherePart && pathClosed && singleQuoteClosed) { + if (listOperator) { + whereBuf.append(MARKER_LEFT_BRACE); + continue; + } else { + whereBuf.append(c); + continue; + } + } + if (c == ')' && wherePart && pathClosed && singleQuoteClosed) { + if (listOperator) { + whereBuf.append(MARKER_RIGHT_BRACE); + listOperator = false; + continue; + } else { + whereBuf.append(c); + continue; + } + } + + if (!universePart && singleQuoteClosed + && buf.toString().toLowerCase().endsWith("universe")) { + universePart = true; + continue; + } + + if (universePart) { + if (c == ';' && singleQuoteClosed) { + universePart = false; + if (universe.toString().trim().length() > 2) { + String universeName = + universe.toString().trim().substring(1, universe.toString().trim().length() - 1); + universeInfo = client.getUniverseInfo(universeName); + nodeInfos = client.getUniverseNodesInfo(token, universeName); + } + } else { + universe.append(c); + } + continue; + } + + if (!selectPart && pathClosed && singleQuoteClosed + && buf.toString().toLowerCase().endsWith("select")) { + if (StringUtils.isBlank(universe.toString())) { + throw new UniverseException("Not found universe name"); + } + selectPart = true; + select.append(RESULT_START_TEMPLATE); + continue; + } + + if (!wherePart && pathClosed && singleQuoteClosed) { + if (buf.toString().toLowerCase().endsWith("where")) { + wherePart = true; + } + if (buf.toString().toLowerCase().endsWith("where") || i == array.length - 1) { + selectPart = false; + select.append(parseResultObj(resultObj.toString().replaceAll("(?i)wher$", ""), nodeInfos)); + select.append(RESULT_END_TEMPLATE); + continue; + } + } + + if (selectPart) { + if (pathClosed && singleQuoteClosed && c == ',') { + select.append(parseResultObj(resultObj.toString(), nodeInfos)); + resultObj = new StringBuilder(); + } else { + resultObj.append(c); + } + continue; + } + + if (wherePart) { + if (c == ';' && pathClosed && singleQuoteClosed) { + wherePart = false; + where = parseWhere(whereBuf.toString(), nodeInfos); + } else { + if (!singleQuoteClosed || !pathClosed) { + switch (c) { + case ' ': + case '\n': + whereBuf.append(MARKER_BACKSPACE); + break; + case '(': + whereBuf.append(MARKER_LEFT_BRACE); + break; + case ')': + whereBuf.append(MARKER_RIGHT_BRACE); + break; + default: + whereBuf.append(c); + } + } else if (pathClosed) { + if ((c == 'a' || c == 'A') && i < array.length - 2 && + text.substring(i, i + 3).equalsIgnoreCase("and")) { + i += 2; + whereBuf.append(MARKER_AND); + operatorPosition = false; + continue; + } + if ((c == 'o' || c == 'O') && i < array.length - 1 && + text.substring(i, i + 2).equalsIgnoreCase("or")) { + i += 1; + whereBuf.append(MARKER_OR); + operatorPosition = false; + continue; + } + if (operatorPosition) { + switch (c) { + case '=': + whereBuf.append(MARKER_EQUAL); + operatorPosition = false; + break; + case '<': + if (i + 1 < array.length) { + if (array[i + 1] == '=') { + whereBuf.append(MARKER_LESS_EQUAL); + operatorPosition = false; + i++; + break; + } else if (array[i + 1] == '>') { + whereBuf.append(MARKER_NOT_EQUAL); + operatorPosition = false; + i++; + break; + } + } + operatorPosition = false; + whereBuf.append(MARKER_LESS); + break; + case '>': + if (i + 1 < array.length) { + if (array[i + 1] == '=') { + whereBuf.append(MARKER_GREATER_EQUALS); + operatorPosition = false; + i++; + break; + } + } + operatorPosition = false; + whereBuf.append(MARKER_GREATER); + break; + case 'i': + case 'I': + boolean whileI = true; + StringBuilder operI = new StringBuilder(); + operI.append(c); + while (whileI) { + i++; + if (i >= array.length) { + whileI = false; + } + + if (array[i] != ' ' && array[i] != '\n') { + operI.append(array[i]); + } else { + continue; + } + String tmp = operI.toString().toLowerCase(); + if (tmp.equals("in")) { + whereBuf.append(MARKER_IN); + listOperator = true; + whileI = false; + operatorPosition = false; + } else if (tmp.equals("isnull")) { + whereBuf.append(MARKER_NULL); + whileI = false; + operatorPosition = false; + } else if (tmp.equals("isnotnull")) { + whereBuf.append(MARKER_NOT_NULL); + whileI = false; + operatorPosition = false; + } + // longest 9 - isnotnull + if (tmp.length() > 8) { + whileI = false; + } + } + break; + case 'n': + case 'N': + boolean whileN = true; + StringBuilder operN = new StringBuilder(); + operN.append(c); + while (whileN) { + i++; + if (i >= array.length) { + whileN = false; + } + + if (array[i] != ' ' && array[i] != '\n') { + operN.append(array[i]); + } else { + continue; + } + + String tmp = operN.toString().toLowerCase(); + + if (tmp.equals("notin")) { + whereBuf.append(MARKER_NOT_IN); + listOperator = true; + whileN = false; + operatorPosition = false; + } + + // longest 5 - notin + if (tmp.length() > 4) { + whileN = false; + } + } + break; + default: + whereBuf.append(c); + } + } else { + whereBuf.append(c); + } + } else { + whereBuf.append(c); + } + } + } + } + + if (wherePart && StringUtils.isBlank(where)) { + throw new UniverseException("Incorrect block where"); + } + + UniverseQuery universeQuery = new UniverseQuery(select.toString().trim(), + where, universeInfo); + + if (!universeQuery.isCorrect()) { + throw new UniverseException("Incorrect query"); + } + + return universeQuery; + } + + private String parseWhere(String where, Map<String, UniverseNodeInfo> nodeInfos) + throws UniverseException { + List<String> out = new ArrayList<>(); + Stack<String> stack = new Stack<>(); + + where = where.replaceAll("\\s*", ""); + + Set<String> operationSymbols = new HashSet<>(OPERATIONS.keySet()); + operationSymbols.add(LEFT_BRACE); + operationSymbols.add(RIGHT_BRACE); + + int index = 0; + + boolean findNext = true; + while (findNext) { + int nextOperationIndex = where.length(); + String nextOperation = ""; + for (String operation : operationSymbols) { + int i = where.indexOf(operation, index); + if (i >= 0 && i < nextOperationIndex) { + nextOperation = operation; + nextOperationIndex = i; + } + } + if (nextOperationIndex == where.length()) { + findNext = false; + } else { + if (index != nextOperationIndex) { + out.add(where.substring(index, nextOperationIndex)); + } + if (nextOperation.equals(LEFT_BRACE)) { + stack.push(nextOperation); + } + else if (nextOperation.equals(RIGHT_BRACE)) { + while (!stack.peek().equals(LEFT_BRACE)) { + out.add(stack.pop()); + if (stack.empty()) { + throw new UniverseException("Unmatched brackets"); + } + } + stack.pop(); + } + else { + while (!stack.empty() && !stack.peek().equals(LEFT_BRACE) && + (OPERATIONS.get(nextOperation) >= OPERATIONS.get(stack.peek()))) { + out.add(stack.pop()); + } + stack.push(nextOperation); + } + index = nextOperationIndex + nextOperation.length(); + } + } + if (index != where.length()) { + out.add(where.substring(index)); + } + while (!stack.empty()) { + out.add(stack.pop()); + } + StringBuffer result = new StringBuffer(); + if (!out.isEmpty()) + result.append(out.remove(0)); + while (!out.isEmpty()) + result.append(" ").append(out.remove(0)); + + // result contains the reverse polish notation + return convertWhereToXml(result.toString(), nodeInfos); + } + + private String parseResultObj(String resultObj, Map<String, UniverseNodeInfo> nodeInfos) + throws UniverseException { + if (StringUtils.isNotBlank(resultObj)) { + UniverseNodeInfo nodeInfo = nodeInfos.get(resultObj.trim()); + if (nodeInfo != null) { + return String.format(RESULT_OBJ_TEMPLATE, nodeInfo.getNodePath(), nodeInfo.getId()); + } + throw new UniverseException(String.format("Not found information about: \"%s\"", + resultObj.trim())); + } + + return StringUtils.EMPTY; + } + + private String convertWhereToXml(String rpn, Map<String, UniverseNodeInfo> nodeInfos) + throws UniverseException { + StringTokenizer tokenizer = new StringTokenizer(rpn, " "); + + Stack<String> stack = new Stack(); + + while (tokenizer.hasMoreTokens()) { + StringBuilder tmp = new StringBuilder(); + String token = tokenizer.nextToken(); + if (!OPERATIONS.keySet().contains(token)) { + stack.push(token.trim()); + } else { + String rightOperand = revertReplace(stack.pop()); + String operator = token.replaceAll("^#|#$", ""); + + if (token.equalsIgnoreCase(MARKER_NOT_NULL) || token.equalsIgnoreCase(MARKER_NULL)) { + UniverseNodeInfo rightOperandInfo = nodeInfos.get(rightOperand); + stack.push(String.format(COMPARISON_FILTER, rightOperandInfo.getId(), + rightOperandInfo.getNodePath(), operator)); + continue; + } + + if (token.equalsIgnoreCase(MARKER_FILTER)) { + UniverseNodeInfo rightOperandInfo = nodeInfos.get(rightOperand); + stack.push(String.format(PREDEFINED_FILTER_TEMPLATE, rightOperandInfo.getNodePath(), + rightOperandInfo.getId())); + continue; + } + + String leftOperand = stack.empty() ? null : revertReplace(stack.pop()); + + if (token.equalsIgnoreCase(MARKER_AND) || token.equalsIgnoreCase(MARKER_OR)) { + if (rightOperand.matches("^\\[.*\\]$")) { + UniverseNodeInfo rightOperandInfo = nodeInfos.get(rightOperand); + if (rightOperandInfo == null) { + throw new UniverseException(String.format("Not found information about: \"%s\"", + rightOperand)); + } + rightOperand = String.format(PREDEFINED_FILTER_TEMPLATE, + rightOperandInfo.getNodePath(), rightOperandInfo.getId()); + } + if (leftOperand.matches("^\\[.*\\]$")) { + UniverseNodeInfo leftOperandInfo = nodeInfos.get(leftOperand); + if (leftOperandInfo == null) { + throw new UniverseException(String.format("Not found information about: \"%s\"", + leftOperand)); + } + leftOperand = String.format(PREDEFINED_FILTER_TEMPLATE, leftOperandInfo.getNodePath(), + leftOperandInfo.getId()); + } + tmp.append(String.format("<%s>\n", operator)); + tmp.append(leftOperand); + tmp.append("\n"); + tmp.append(rightOperand); + tmp.append("\n"); + tmp.append(String.format("</%s>\n", operator)); + stack.push(tmp.toString()); + continue; + } + + UniverseNodeInfo leftOperandInfo = nodeInfos.get(leftOperand); + if (leftOperandInfo == null) { + throw new UniverseException(String.format("Not found information about: \"%s\"", + leftOperand)); + } + if (token.equalsIgnoreCase(MARKER_IN) || token.equalsIgnoreCase(MARKER_NOT_IN)) { + String listValues = rightOperand.replaceAll("^\\(|\\)$", "").trim(); + boolean startItem = false; + List<String> values = new ArrayList<>(); + StringBuilder value = new StringBuilder(); + boolean isNumericList = false; + if (listValues.charAt(0) != '\'') { + isNumericList = true; + } + if (isNumericList) { + String[] nums = listValues.split(","); + for (String num : nums) { + values.add(num.trim()); + } + } else { + for (int i = 0; i < listValues.length(); i++) { + char c = listValues.charAt(i); + if (c == '\'' && (i == 0 || listValues.charAt(i - 1) != '\\')) { + startItem = !startItem; + if (!startItem) { + values.add(value.toString()); + value = new StringBuilder(); + } + continue; + } + if (startItem) { + value.append(c); + } + } + } + + if (!values.isEmpty()) { + tmp.append(String.format(COMPRASION_START_TEMPLATE, leftOperandInfo.getNodePath(), + operator, leftOperandInfo.getId())); + tmp.append(CONST_OPERAND_START_TEMPLATE); + String type = isNumericList ? "Numeric" : "String"; + for (String v : values) { + tmp.append(String.format(CONST_OPERAND_VALUE_TEMPLATE, type, v)); + } + tmp.append(CONST_OPERAND_END_TEMPLATE); + tmp.append(COMPRASION_END_TEMPLATE); + stack.push(tmp.toString()); + } + continue; + } + + // EqualTo, LessThanOrEqualTo, NotEqualTo, LessThan, GreaterThanOrEqualTo, GreaterThan + UniverseNodeInfo rightOperandInfo = null; + if (rightOperand.startsWith("[") && rightOperand.endsWith("]")) { + rightOperandInfo = nodeInfos.get(rightOperand); + if (rightOperandInfo == null) { + throw new UniverseException(String.format("Not found information about: \"%s\"", + rightOperand)); + } + } + if (OPERATIONS.containsKey(token)) { + if (rightOperandInfo != null) { + tmp.append(String.format(COMPRASION_START_TEMPLATE, leftOperandInfo.getNodePath(), + operator, leftOperandInfo.getId())); + tmp.append(String.format(OBJECT_OPERAND_TEMPLATE, rightOperandInfo.getId(), + rightOperandInfo.getNodePath())); + tmp.append(COMPRASION_END_TEMPLATE); + } else { + String type = rightOperand.startsWith("'") ? "String" : "Numeric"; + String value = rightOperand.replaceAll("^'|'$", ""); + tmp.append(String.format(COMPRASION_START_TEMPLATE, leftOperandInfo.getNodePath(), + operator, leftOperandInfo.getId())); + tmp.append(CONST_OPERAND_START_TEMPLATE); + tmp.append(String.format(CONST_OPERAND_VALUE_TEMPLATE, type, value)); + tmp.append(CONST_OPERAND_END_TEMPLATE); + tmp.append(COMPRASION_END_TEMPLATE); + } + stack.push(tmp.toString()); + continue; + } + throw new UniverseException(String.format("Incorrect syntax after: \"%s\"", leftOperand)); + } + } + + return stack.pop(); + } + + private String revertReplace(String s) { + return s.replaceAll(MARKER_BACKSPACE, " ") + .replaceAll(MARKER_LEFT_BRACE, "(") + .replaceAll(MARKER_RIGHT_BRACE, ")"); + } + + private boolean isFilter(String buf, String after) { + boolean result = false; + String[] parts = buf.trim().split("\\s"); + if (parts[parts.length - 1].matches("^\\[.*\\]$")) { + // check before + if (parts.length == 1) { + result = true; + } else { + int count = parts.length - 2; + Set<String> operations = new HashSet(OPERATIONS.keySet()); + operations.remove(MARKER_AND); + operations.remove(MARKER_OR); + while (count >= 0) { + String p = parts[count]; + if (StringUtils.isNotBlank(p)) { + if (!operations.contains(p)) { + result = true; + break; + } else { + return false; + } + } + count--; + } + } + after = after.trim(); + // check after + if (result && !after.startsWith("and") && !after.startsWith("or") && + !after.startsWith(";") && StringUtils.isNotBlank(after)) { + result = false; + } + } + + return result; + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/sap/src/main/resources/interpreter-setting.json ---------------------------------------------------------------------- diff --git a/sap/src/main/resources/interpreter-setting.json b/sap/src/main/resources/interpreter-setting.json new file mode 100644 index 0000000..cb5cf94 --- /dev/null +++ b/sap/src/main/resources/interpreter-setting.json @@ -0,0 +1,42 @@ +[ + { + "group": "sap", + "name": "universe", + "className": "org.apache.zeppelin.sap.UniverseInterpreter", + "defaultInterpreter": true, + "properties": { + "universe.api.url": { + "envName": null, + "propertyName": "universe.api.url", + "defaultValue": "http://localhost:6405/biprws", + "description": "API url of Universe", + "type": "url" + }, + "universe.user": { + "envName": null, + "propertyName": "universe.user", + "defaultValue": "", + "description": "Username for API of Universe", + "type": "string" + }, + "universe.password": { + "envName": null, + "propertyName": "universe.password", + "defaultValue": "", + "description": "Password for API of Universe", + "type": "password" + }, + "universe.authType": { + "envName": null, + "propertyName": "universe.password", + "defaultValue": "secEnterprise", + "description": "Type of authentication for API of Universe. Available values: secEnterprise, secLDAP, secWinAD, secSAPR3", + "type": "string" + } + }, + "editor": { + "editOnDblClick": false, + "completionKey": "TAB" + } + } +] http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/sap/src/main/resources/universe.keywords ---------------------------------------------------------------------- diff --git a/sap/src/main/resources/universe.keywords b/sap/src/main/resources/universe.keywords new file mode 100644 index 0000000..0811bfb --- /dev/null +++ b/sap/src/main/resources/universe.keywords @@ -0,0 +1 @@ +universe,select,where,and,or,is null,is not null,in \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java ---------------------------------------------------------------------- diff --git a/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java new file mode 100644 index 0000000..91a4217 --- /dev/null +++ b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java @@ -0,0 +1,134 @@ +/** + * 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.sap.universe; + +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.completer.CachedCompleter; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Universe completer unit tests + */ +public class UniverseCompleterTest { + + private UniverseCompleter universeCompleter; + private UniverseUtil universeUtil; + private UniverseClient universeClient; + + @Before + public void beforeTest() throws UniverseException { + universeCompleter = new UniverseCompleter(0); + universeUtil = new UniverseUtil(); + Map<String, UniverseInfo> universes = new HashMap<>(); + + universes.put("testUniverse", new UniverseInfo("1", "testUniverse", "uvx")); + universes.put("test with space", new UniverseInfo("2", "test with space", "uvx")); + universes.put("(GLOBAL) universe", new UniverseInfo("3", "(GLOBAL) universe", "uvx")); + UniverseInfo universeInfo = new UniverseInfo("1", "testUniverse", "uvx"); + Map<String, UniverseNodeInfo> testUniverseNodes = new HashMap<>(); + testUniverseNodes.put("[Dimension].[Test].[name1]", + new UniverseNodeInfo("name1id", "name1", "dimension", "Dimension\\Test", + "Dimension|folder\\Test|folder\\name1|dimension")); + testUniverseNodes.put("[Dimension].[Test].[name2]", + new UniverseNodeInfo("name2id", "name2", "dimension", "Dimension\\Test", + "Dimension|folder\\Test|folder\\name2|dimension")); + testUniverseNodes.put("[Filter].[name3]", + new UniverseNodeInfo("name3id", "name3", "filter", "Filter", + "Filter|folder\\name3|filter")); + testUniverseNodes.put("[Filter].[name4]", + new UniverseNodeInfo("name4id", "name4", "filter", "Filter", + "Filter|folder\\name4|filter")); + testUniverseNodes.put("[Measure].[name5]", + new UniverseNodeInfo("name5id", "name5", "measure", "Measure", + "Measure|folder\\name5|measure")); + + universeClient = mock(UniverseClient.class); + when(universeClient.getUniverseInfo(anyString())).thenReturn(universeInfo); + when(universeClient.getUniverseNodesInfo(anyString(), anyString())) + .thenReturn(testUniverseNodes); + when(universeClient.getUniversesMap()).thenReturn(universes); + } + + @Test + public void testCreateUniverseNameCompleter() { + String buffer = "universe ["; + List<CharSequence> candidates = new ArrayList<>(); + universeCompleter.createOrUpdate(universeClient, null, buffer, 9); + CachedCompleter completer = universeCompleter.getUniverseCompleter(); + assertNull(completer); + universeCompleter.createOrUpdate(universeClient, null, buffer, 10); + completer = universeCompleter.getUniverseCompleter(); + assertNotNull(completer); + + completer.getCompleter().complete(StringUtils.EMPTY, 0, candidates); + assertEquals(3, candidates.size()); + } + + @Test + public void testCreateUniverseNodesCompleter() { + String buffer = "universe [testUniverse]; select ["; + List<CharSequence> candidates = new ArrayList<>(); + universeCompleter.createOrUpdate(universeClient, null, buffer, 32); + Map<String, CachedCompleter> completerMap = universeCompleter.getUniverseInfoCompletersMap(); + assertFalse(completerMap.containsKey("testUniverse")); + universeCompleter.createOrUpdate(universeClient, null, buffer, 33); + completerMap = universeCompleter.getUniverseInfoCompletersMap(); + assertTrue(completerMap.containsKey("testUniverse")); + CachedCompleter completer = completerMap.get("testUniverse"); + + completer.getCompleter().complete(StringUtils.EMPTY, 0, candidates); + assertEquals(3, candidates.size()); + List<String> candidatesStrings = new ArrayList<>(); + for (Object o : candidates) { + UniverseNodeInfo info = (UniverseNodeInfo) o; + candidatesStrings.add(info.getName()); + } + List<String> expected = Arrays.asList("Filter", "Measure", "Dimension"); + Collections.sort(candidatesStrings); + Collections.sort(expected); + assertEquals(expected, candidatesStrings); + } + + @Test + public void testNestedUniverseNodes() { + String buffer = "universe [testUniverse]; select [Dimension].[Test].[n"; + List<CharSequence> candidates = new ArrayList<>(); + + universeCompleter.createOrUpdate(universeClient, null, buffer, 53); + Map<String, CachedCompleter> completerMap = universeCompleter.getUniverseInfoCompletersMap(); + assertTrue(completerMap.containsKey("testUniverse")); + CachedCompleter completer = completerMap.get("testUniverse"); + + completer.getCompleter().complete("[Dimension].[Test].[n", 21, candidates); + assertEquals(2, candidates.size()); + List<String> candidatesStrings = new ArrayList<>(); + for (Object o : candidates) { + UniverseNodeInfo info = (UniverseNodeInfo) o; + candidatesStrings.add(info.getName()); + } + List<String> expected = Arrays.asList("name1", "name2"); + Collections.sort(candidatesStrings); + Collections.sort(expected); + assertEquals(expected, candidatesStrings); + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java ---------------------------------------------------------------------- diff --git a/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java new file mode 100644 index 0000000..81a027e --- /dev/null +++ b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java @@ -0,0 +1,371 @@ +/* + * 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.sap.universe; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UniverseUtilTest { + + private UniverseClient universeClient; + private UniverseUtil universeUtil; + + @Before + public void beforeTest() throws UniverseException { + universeUtil = new UniverseUtil(); + UniverseInfo universeInfo = new UniverseInfo("1", "testUniverse", "uvx"); + Map<String, UniverseNodeInfo> testUniverseNodes = new HashMap<>(); + testUniverseNodes.put("[Dimension].[Test].[name1]", + new UniverseNodeInfo("name1id", "name1", "dimension", "Dimension\\Test", + "Dimension|folder\\Test|folder\\name1|dimension")); + testUniverseNodes.put("[Dimension].[Test].[name2]", + new UniverseNodeInfo("name2id", "name2", "dimension", "Filter\\Test", + "Dimension|folder\\Test|folder\\name2|dimension")); + testUniverseNodes.put("[Filter].[name3]", + new UniverseNodeInfo("name3id", "name3", "filter", "Filter", + "Filter|folder\\name3|filter")); + testUniverseNodes.put("[Filter].[name4]", + new UniverseNodeInfo("name4id", "name4", "filter", "Filter", + "Filter|folder\\name4|filter")); + testUniverseNodes.put("[Measure].[name5]", + new UniverseNodeInfo("name5id", "name5", "measure", "Measure", + "Measure|folder\\name5|measure")); + + universeClient = mock(UniverseClient.class); + when(universeClient.getUniverseInfo(anyString())).thenReturn(universeInfo); + when(universeClient.getUniverseNodesInfo(anyString(), anyString())) + .thenReturn(testUniverseNodes); + } + + @Test + public void testForConvert() throws UniverseException { + String request = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name3] and [Dimension].[Test].[name2] > 1;"; + UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null); + assertNotNull(universeQuery); + assertNotNull(universeQuery.getUniverseInfo()); + assertEquals("<resultObjects>\n" + + "<resultObject path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n" + + "</resultObjects>", universeQuery.getSelect()); + assertEquals("<and>\n" + + "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n" + + "\n<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" + + " operator=\"GreaterThan\" id=\"name2id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">1</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n", universeQuery.getWhere()); + assertEquals("testUniverse", universeQuery.getUniverseInfo().getName()); + } + + @Test + public void testConvertConditions() throws UniverseException { + String request = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name3] " + + "and [Dimension].[Test].[name2] >= 1 " + + "and [Dimension].[Test].[name2] < 20 " + + "and [Dimension].[Test].[name1] <> 'test' " + + "and [Dimension].[Test].[name1] is not null " + + "and [Measure].[name5] is null" + + "and [Dimension].[Test].[name1] in ('var1', 'v a r 2') " + + "and [Dimension].[Test].[name1] in ('var1','withoutspaces')" + + "and [Dimension].[Test].[name1] in ('one value')" + + "and [Dimension].[Test].[name2] in (1,3,4);"; + UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null); + assertNotNull(universeQuery); + assertEquals("<and>\n" + + "<and>\n" + + "<and>\n" + + "<and>\n" + + "<and>\n" + + "<and>\n" + + "<and>\n" + + "<and>\n" + + "<and>\n" + + "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" + + " operator=\"GreaterThanOrEqualTo\" id=\"name2id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">1</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" + + " operator=\"LessThan\" id=\"name2id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">20</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" + + " operator=\"NotEqualTo\" id=\"name1id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"String\">test</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n\n" + + "<comparisonFilter id=\"name1id\" path=\"Dimension|folder\\Test|folder\\name1|dimension\"" + + " operator=\"IsNotNull\"/>\n\n" + + "</and>\n\n" + + "<comparisonFilter id=\"name5id\" path=\"Measure|folder\\name5|measure\" operator=\"IsNull\"/>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" + + " operator=\"InList\" id=\"name1id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"String\">var1</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"String\">v a r 2</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" + + " operator=\"InList\" id=\"name1id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"String\">var1</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"String\">withoutspaces</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" + + " operator=\"InList\" id=\"name1id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"String\">one value</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" + + " operator=\"InList\" id=\"name2id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">1</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"Numeric\">3</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"Numeric\">4</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</and>\n", + universeQuery.getWhere()); + } + + @Test(expected = UniverseException.class) + public void testFailConvertWithoutUniverse() throws UniverseException { + String request = "universe ;\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name3] and [Dimension].[Test].[name2] > 1;"; + universeUtil.convertQuery(request, universeClient, null); + } + + @Test(expected = UniverseException.class) + public void testFailConvertWithIncorrectSelect() throws UniverseException { + String request = "universe [testUniverse];\n" + + "select [not].[exist];"; + universeUtil.convertQuery(request, universeClient, null); + } + + + @Test(expected = UniverseException.class) + public void testFailConvertWithIncorrectCondition() throws UniverseException { + String request = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name;"; + universeUtil.convertQuery(request, universeClient, null); + } + + @Test + public void testFiltersConditions() throws UniverseException { + String request1 = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name3];"; + String request2 = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Measure].[name5] > 2 and [Filter].[name3];"; + String request3 = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name3] or [Measure].[name5];"; + String request4 = "universe [testUniverse];\n" + + "select [Measure].[name5]\n" + + "where [Filter].[name3] and [Measure].[name5] is null;"; + UniverseQuery universeQuery = universeUtil.convertQuery(request1, universeClient, null); + assertEquals("<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n", + universeQuery.getWhere()); + universeQuery = universeUtil.convertQuery(request2, universeClient, null); + assertEquals("<and>\n" + + "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"GreaterThan\" id=\"name5id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">2</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" + + "</and>\n", + universeQuery.getWhere()); + universeQuery = universeUtil.convertQuery(request3, universeClient, null); + assertEquals("<or>\n" + + "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" + + "<predefinedFilter path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n\n" + + "</or>\n", + universeQuery.getWhere()); + universeQuery = universeUtil.convertQuery(request4, universeClient, null); + assertEquals("<and>\n" + + "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" + + "<comparisonFilter id=\"name5id\" path=\"Measure|folder\\name5|measure\" operator=\"IsNull\"/>\n\n" + + "</and>\n", + universeQuery.getWhere()); + } + + @Test + public void testNestedConditions() throws UniverseException { + String request = "universe [testUniverse];\n" + + "select [Dimension].[Test].[name2]\n" + + "where ([Measure].[name5] = 'text' or ([Dimension].[Test].[name1] in ('1','2', '3') and\n" + + "[Dimension].[Test].[name2] is not null)) and ([Filter].[name4] or [Measure].[name5] >=12)\n" + + "or [Dimension].[Test].[name2] not in (31, 65, 77);"; + UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null); + assertEquals("<or>\n" + + "<and>\n" + + "<or>\n" + + "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"EqualTo\" id=\"name5id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"String\">text</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "<and>\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\" operator=\"InList\" id=\"name1id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"String\">1</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"String\">2</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"String\">3</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "<comparisonFilter id=\"name2id\" path=\"Dimension|folder\\Test|folder\\name2|dimension\" operator=\"IsNotNull\"/>\n\n" + + "</and>\n\n" + + "</or>\n\n" + + "<or>\n" + + "<predefinedFilter path=\"Filter|folder\\name4|filter\" id=\"name4id\"/>\n\n" + + "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"GreaterThanOrEqualTo\" id=\"name5id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">12</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</or>\n\n" + + "</and>\n\n" + + "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\" operator=\"NotInList\" id=\"name2id\">\n" + + "<constantOperand>\n" + + "<value>\n" + + "<caption type=\"Numeric\">31</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"Numeric\">65</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"Numeric\">77</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "</or>\n", + universeQuery.getWhere()); + } + + @Test + public void testWithoutConditions() throws UniverseException { + String request = "universe [testUniverse];\n" + + "select [Dimension].[Test].[name2], [Measure].[name5],\n" + + "[Dimension].[Test].[name1] ;"; + UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null); + assertNull(universeQuery.getWhere()); + assertEquals("<resultObjects>\n" + + "<resultObject path=\"Dimension|folder\\Test|folder\\name2|dimension\" id=\"name2id\"/>\n" + + "<resultObject path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n" + + "<resultObject path=\"Dimension|folder\\Test|folder\\name1|dimension\" id=\"name1id\"/>\n" + + "</resultObjects>", + universeQuery.getSelect()); + } + + @Test + public void testCaseSensitive() throws UniverseException { + String request = "uniVersE [testUniverse];\n" + + "seLEct [Dimension].[Test].[name2], [Measure].[name5]\n" + + "whERE [Dimension].[Test].[name2] Is NULl Or [Measure].[name5] IN (1,2) aNd [Measure].[name5] is NOT nUll;"; + UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null); + assertEquals("<resultObjects>\n" + + "<resultObject path=\"Dimension|folder\\Test|folder\\name2|dimension\" id=\"name2id\"/>\n" + + "<resultObject path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n" + + "</resultObjects>", + universeQuery.getSelect()); + assertEquals("<or>\n" + + "<comparisonFilter id=\"name2id\" path=\"Dimension|folder\\Test|folder\\name2|dimension\" operator=\"IsNull\"/>\n\n" + + "<and>\n" + + "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"InList\" id=\"name5id\">\n" + + "<constantOperand>\n" + "<value>\n" + "<caption type=\"Numeric\">1</caption>\n" + + "</value>\n" + + "<value>\n" + + "<caption type=\"Numeric\">2</caption>\n" + + "</value>\n" + + "</constantOperand>\n" + + "</comparisonFilter>\n\n" + + "<comparisonFilter id=\"name5id\" path=\"Measure|folder\\name5|measure\" operator=\"IsNotNull\"/>\n\n" + + "</and>\n\n" + + "</or>\n", + universeQuery.getWhere()); + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java ---------------------------------------------------------------------- diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java index 20cceda..fc5f380 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java @@ -24,5 +24,6 @@ public enum CompletionType { setting, command, keyword, - path + path, + universe } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java ---------------------------------------------------------------------- diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index 725d94b..db70637 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -692,7 +692,8 @@ public class ZeppelinConfiguration extends XMLConfiguration { + "org.apache.zeppelin.beam.BeamInterpreter," + "org.apache.zeppelin.scio.ScioInterpreter," + "org.apache.zeppelin.groovy.GroovyInterpreter," - + "org.apache.zeppelin.neo4j.Neo4jCypherInterpreter" + + "org.apache.zeppelin.neo4j.Neo4jCypherInterpreter," + + "org.apache.zeppelin.sap.UniverseInterpreter" ), ZEPPELIN_INTERPRETER_JSON("zeppelin.interpreter.setting", "interpreter-setting.json"), ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"), http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e27e8d4f/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 971257c..ee17130 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -800,7 +800,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat name: v.name, value: v.value, meta: v.meta, - caption: computeCaption(v.value, v.meta), + caption: computeCaption(v.name, v.meta), score: 300, }); }