Skip to content

Commit fcc0b0e

Browse files
authored
Better error messages in Gremlin (#145)
Better error message display
1 parent 689653c commit fcc0b0e

File tree

10 files changed

+208
-36
lines changed

10 files changed

+208
-36
lines changed

database/opencypher/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/opencypher/gremlin/OpenCypherGremlinDatabase.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultRow;
1010
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryResultColumn;
1111
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryResultRow;
12+
import com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions.OpenCypherGremlinException;
1213
import com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.query.OpenCypherGremlinQueryResult;
1314
import org.apache.log4j.Level;
1415
import org.apache.log4j.LogManager;
@@ -17,14 +18,14 @@
1718
import org.apache.tinkerpop.gremlin.driver.Client;
1819
import org.apache.tinkerpop.gremlin.driver.Cluster;
1920
import org.apache.tinkerpop.gremlin.driver.Result;
20-
import org.neo4j.driver.v1.exceptions.ClientException;
2121
import org.opencypher.gremlin.client.CypherGremlinClient;
2222

2323
import java.net.URI;
2424
import java.util.*;
2525
import java.util.concurrent.ExecutionException;
2626
import java.util.stream.Collectors;
2727

28+
import static com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions.ExceptionWrapper.*;
2829
import static java.util.Collections.*;
2930
import static java.util.stream.Collectors.toList;
3031
/**
@@ -102,7 +103,8 @@ public GraphQueryResult execute(String query, Map<String, Object> statementParam
102103
if (query.toUpperCase().startsWith("EXPLAIN")) {
103104
return new OpenCypherGremlinQueryResult(0, emptyList(), emptyList(), emptyList(), emptyList());
104105
} else {
105-
throw new ClientException(e.getMessage());
106+
String exceptionMessage = wrapExceptionInMeaningMessage(e);
107+
throw new OpenCypherGremlinException(exceptionMessage, e);
106108
}
107109
}
108110
}
@@ -150,7 +152,8 @@ public GraphMetadata metadata() {
150152

151153
return new OpenCypherGremlinGraphMetadata(labelResult, relResult, vertexPropResult, edgePropResult);
152154
} catch (InterruptedException | ExecutionException e) {
153-
throw new RuntimeException(e);
155+
String exceptionMessage = wrapExceptionInMeaningMessage(e);
156+
throw new OpenCypherGremlinException(exceptionMessage, e);
154157
} finally {
155158
gremlinClient.close();
156159
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions;
2+
3+
public enum ExceptionErrorMessages {
4+
ERROR_OCCURRED("Error occurred."),
5+
SYNTAX_WARNING("Please note that Cypher query is translated to Gremlin and may fail" +
6+
" because of translation or database specifics. Make sure that flavor is properly configured" +
7+
" in database connection configuration."),
8+
SERIALIZER_EXCEPTION("Wrong serializer selected. Please check connection configuration."),
9+
RESPONSE_EXCEPTION("Database connection failed. Please check database configuration" +
10+
" (including username and password) and retry to connect."),
11+
CONNECTION_EXCEPTION("Database connection failed. Please check database configuration" +
12+
" (including username and password) and retry to connect.");
13+
14+
private final String description;
15+
16+
ExceptionErrorMessages(String description) {
17+
this.description = description;
18+
}
19+
20+
public String getDescription() {
21+
return description;
22+
}
23+
24+
@Override
25+
public String toString() {
26+
return description;
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions;
2+
3+
import java.io.PrintWriter;
4+
import java.io.StringWriter;
5+
6+
public class ExceptionWrapper {
7+
public static final int SHORT_STRING_LENGTH = 150;
8+
private static final String NON_THIN_CHARS = "[^iIl1\\.,']";
9+
10+
private static int textWidth(String str) {
11+
return str.length() - str.replaceAll(NON_THIN_CHARS, "").length() / 2;
12+
}
13+
14+
public static String ellipseString(String text, int targetLength) {
15+
if (textWidth(text) <= targetLength) {
16+
return text;
17+
}
18+
int end = text.lastIndexOf(' ', targetLength - 3);
19+
if (end == -1) {
20+
return text.substring(0, targetLength - 3) + "...";
21+
}
22+
int newEnd = end;
23+
do {
24+
end = newEnd;
25+
newEnd = text.indexOf(' ', end + 1);
26+
if (newEnd == -1) {
27+
newEnd = text.length();
28+
}
29+
30+
} while (textWidth(text.substring(0, newEnd) + "...") < targetLength);
31+
32+
return text.substring(0, end) + "...";
33+
}
34+
35+
public static String getCause(Exception exception) {
36+
StringBuilder exceptionCauses = new StringBuilder();
37+
Throwable cause = exception.getCause();
38+
int counter = 0;
39+
while (cause != null && counter <= 50) {
40+
exceptionCauses.append(cause.getMessage()).append(System.lineSeparator());
41+
cause = cause.getCause();
42+
counter++;
43+
}
44+
return exceptionCauses.toString();
45+
}
46+
47+
public static String getStackTrace(final Throwable throwable) {
48+
final StringWriter sw = new StringWriter();
49+
final PrintWriter pw = new PrintWriter(sw, true);
50+
throwable.printStackTrace(pw);
51+
return sw.getBuffer().toString();
52+
}
53+
54+
public static String wrapExceptionInMeaningMessage(Exception exception) {
55+
String exceptionMessage = exception.getMessage();
56+
if (exceptionMessage != null) {
57+
if (exceptionMessage.contains("SerializationException")) {
58+
return ExceptionErrorMessages.SERIALIZER_EXCEPTION.getDescription();
59+
}
60+
if (exceptionMessage.contains("ResponseException")) {
61+
return ExceptionErrorMessages.RESPONSE_EXCEPTION.getDescription();
62+
}
63+
if (exceptionMessage.contains("ConnectionException")) {
64+
return ExceptionErrorMessages.CONNECTION_EXCEPTION.getDescription();
65+
}
66+
if (exceptionMessage.length() > SHORT_STRING_LENGTH) {
67+
return ellipseString(exceptionMessage, SHORT_STRING_LENGTH);
68+
}
69+
return exceptionMessage;
70+
} else {
71+
return ExceptionErrorMessages.ERROR_OCCURRED.getDescription();
72+
}
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions;
2+
3+
public class OpenCypherGremlinException extends RuntimeException {
4+
public OpenCypherGremlinException(String message, Throwable cause) {
5+
super(message, cause);
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.neueda.jetbrains.plugin.graphdb.test.integration.opencypher.gremlin;
2+
3+
import com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions.ExceptionWrapper;
4+
import org.junit.Test;
5+
6+
import static org.junit.Assert.*;
7+
8+
public class ExceptionWrapperTest {
9+
@Test
10+
public void shouldEllipseString() throws Exception {
11+
String shortString = "DataSource[Cosmos] - metadata refresh failed. Reason: java.util.concurrent.ExecutionException:" +
12+
"org.apache.tinkerpop.gremlin.driver.exception.ResponseException:...";
13+
String longString = "DataSource[Cosmos] - metadata refresh failed. Reason: java.util.concurrent.ExecutionException:" +
14+
"org.apache.tinkerpop.gremlin.driver.exception.ResponseException: ActivityId : d0015df6-09d2-48a6-b3ab-32d026213a42";
15+
String truncatedString = ExceptionWrapper.ellipseString(longString, ExceptionWrapper.SHORT_STRING_LENGTH);
16+
assertEquals(shortString, truncatedString);
17+
}
18+
}

testing/integration-tinkerpop/src/test/java/com/neueda/jetbrains/plugin/graphdb/test/integration/opencypher/gremlin/OpenCypherGremlinDatabaseTestSecure.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public void wrongCredentials() throws Exception {
4040
OpenCypherGremlinDatabase database = new OpenCypherGremlinDatabase(config);
4141

4242
assertThatThrownBy(() -> database.execute("RETURN 1"))
43-
.hasMessageContaining("Username and/or password are incorrect");
43+
.hasMessageContaining("Database connection failed. Please check database configuration" +
44+
" (including username and password) and retry to connect.");
4445
}
4546

4647
@Test

ui/jetbrains/src/main/java/com/neueda/jetbrains/plugin/graphdb/jetbrains/ui/console/log/LogPanel.java

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
import com.intellij.execution.filters.TextConsoleBuilderFactory;
44
import com.intellij.execution.ui.ConsoleView;
55
import com.intellij.execution.ui.ConsoleViewContentType;
6+
import com.intellij.icons.AllIcons;
67
import com.intellij.openapi.Disposable;
78
import com.intellij.openapi.project.Project;
9+
import com.intellij.openapi.ui.popup.IconButton;
10+
import com.intellij.openapi.ui.popup.JBPopupFactory;
811
import com.intellij.openapi.util.Disposer;
12+
import com.intellij.ui.components.JBScrollPane;
913
import com.intellij.util.messages.MessageBus;
14+
import com.intellij.util.ui.JBUI;
1015
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult;
16+
import com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions.ExceptionErrorMessages;
17+
import com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions.OpenCypherGremlinException;
1118
import com.neueda.jetbrains.plugin.graphdb.jetbrains.actions.execute.ExecuteQueryPayload;
1219
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.metadata.DataSourceMetadata;
1320
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.state.DataSourceApi;
@@ -17,12 +24,16 @@
1724
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.datasource.metadata.MetadataRetrieveEvent;
1825
import org.jetbrains.annotations.Nullable;
1926

27+
import javax.swing.*;
2028
import java.awt.*;
2129
import java.util.Map;
2230

23-
import static com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryParametersRetrievalErrorEvent.PARAMS_ERROR_COMMON_MSG;
31+
import static com.neueda.jetbrains.plugin.graphdb.database.opencypher.gremlin.exceptions.ExceptionWrapper.*;
32+
import static com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryParametersRetrievalErrorEvent.*;
33+
import static com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.datasource.interactions.DataSourceDialog.*;
2434

2535
public class LogPanel implements Disposable {
36+
private static final String SHOW_DETAILS = "Details...";
2637

2738
private ConsoleView log;
2839

@@ -107,7 +118,8 @@ public void metadataRefreshSucceed(DataSourceApi nodeDataSource, DataSourceMetad
107118

108119
@Override
109120
public void metadataRefreshFailed(DataSourceApi nodeDataSource, Exception exception) {
110-
error(String.format("DataSource[%s] - metadata refresh failed. Reason: ", nodeDataSource.getName()));
121+
String prefix = String.format("DataSource[%s] - metadata refresh failed. Reason: ", nodeDataSource.getName());
122+
error(prefix);
111123
printException(exception);
112124
newLine();
113125
}
@@ -120,19 +132,19 @@ public void metadataRefreshFailed(DataSourceApi nodeDataSource, Exception except
120132
});
121133
}
122134

123-
public void userInput(String message) {
135+
private void userInput(String message) {
124136
log.print(message, ConsoleViewContentType.USER_INPUT);
125137
}
126138

127-
public void printParametersMap(Map<String, Object> parameters) {
139+
private void printParametersMap(Map<String, Object> parameters) {
128140
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
129141
String message = String.format("%s: %s", entry.getKey(), entry.getValue());
130142
log.print(message, ConsoleViewContentType.USER_INPUT);
131143
newLine();
132144
}
133145
}
134146

135-
public void info(String message) {
147+
private void info(String message) {
136148
log.print(message, ConsoleViewContentType.NORMAL_OUTPUT);
137149
}
138150

@@ -142,26 +154,53 @@ public void error(@Nullable String message) {
142154
}
143155
}
144156

145-
public void printException(Exception exception) {
157+
private String printException(Exception exception) {
158+
String errorMessage;
146159
if (exception.getMessage() != null) {
147-
error(exception.getMessage());
160+
errorMessage = exception.getMessage();
148161
} else {
149-
error(exception.toString());
162+
errorMessage = exception.toString();
150163
}
164+
error(errorMessage);
165+
String newLine = System.lineSeparator();
166+
String details = getCause(exception) + newLine + getStackTrace(exception);
167+
log.printHyperlink(" " + SHOW_DETAILS, p -> showPopup("Error details", details, exception));
151168
newLine();
152-
153-
Throwable cause = exception.getCause();
154-
while (cause != null) {
155-
error(cause.getMessage());
156-
newLine();
157-
cause = cause.getCause();
158-
}
169+
return errorMessage;
159170
}
160171

161-
public void newLine() {
172+
private void newLine() {
162173
log.print("\n", ConsoleViewContentType.NORMAL_OUTPUT);
163174
}
164175

176+
private void showPopup(String title, String details, Exception exception) {
177+
JPanel popupPanel = new JPanel(new BorderLayout());
178+
popupPanel.setBorder(JBUI.Borders.empty(THICKNESS));
179+
180+
JTextArea exceptionDetails = new JTextArea();
181+
exceptionDetails.setLineWrap(false);
182+
exceptionDetails.append(details);
183+
JLabel jLabel = new JLabel(exception.getMessage(), AllIcons.Process.State.RedExcl, JLabel.LEFT);
184+
185+
JBScrollPane scrollPane = new JBScrollPane(exceptionDetails);
186+
scrollPane.setPreferredSize(new Dimension(-1, HEIGHT));
187+
popupPanel.add(jLabel, BorderLayout.NORTH);
188+
popupPanel.add(scrollPane, BorderLayout.CENTER);
189+
String gremlinTranslationWarning = exception instanceof OpenCypherGremlinException ? ExceptionErrorMessages.SYNTAX_WARNING.getDescription() : "";
190+
191+
JBPopupFactory.getInstance()
192+
.createComponentPopupBuilder(
193+
popupPanel,
194+
log.getComponent())
195+
.setTitle(title)
196+
.setAdText(gremlinTranslationWarning)
197+
.setResizable(true)
198+
.setMovable(true)
199+
.setCancelButton(new IconButton("Close", AllIcons.Actions.Close, AllIcons.Actions.CloseHovered))
200+
.createPopup()
201+
.showInFocusCenter();
202+
}
203+
165204
@Override
166205
public void dispose() {
167206
log.dispose();

ui/jetbrains/src/main/java/com/neueda/jetbrains/plugin/graphdb/jetbrains/ui/console/params/ParametersPanel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import javax.swing.*;
3333
import java.awt.*;
3434

35-
import static com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryParametersRetrievalErrorEvent.PARAMS_ERROR_COMMON_MSG;
35+
import static com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryParametersRetrievalErrorEvent.*;
3636

3737
public class ParametersPanel implements ParametersProvider {
3838

0 commit comments

Comments
 (0)