Skip to content

Commit 1f903e2

Browse files
MetaurFylmTM
authored andcommitted
Dynamically created tabs are shown on explain and profile queries (#23)
* Dynamically created tabs are shown on explain and profile queries * Checkstyle fixes * Fixed close tab by middle mouse button click * Review fixes
1 parent e728938 commit 1f903e2

File tree

11 files changed

+510
-0
lines changed

11 files changed

+510
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.api.query;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
public interface GraphQueryPlan {
7+
8+
String getOperatorType();
9+
10+
Map<String, Object> getArguments();
11+
12+
List<String> getIdentifiers();
13+
14+
List<? extends GraphQueryPlan> children();
15+
16+
}

database/api/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/api/query/GraphQueryResult.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphRelationship;
55

66
import java.util.List;
7+
import java.util.Optional;
78

89
public interface GraphQueryResult {
910

@@ -20,4 +21,10 @@ public interface GraphQueryResult {
2021
List<GraphRelationship> getRelationships();
2122

2223
List<GraphQueryNotification> getNotifications();
24+
25+
boolean hasPlan();
26+
27+
boolean isProfilePlan();
28+
29+
Optional<GraphQueryPlan> getPlan();
2330
}

database/neo4j/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/neo4j/bolt/Neo4jBoltBuffer.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphNode;
44
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphRelationship;
55
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryNotification;
6+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryPlan;
67
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultColumn;
78
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultRow;
89
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryNotification;
10+
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryPlan;
911
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryResultColumn;
1012
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryResultRow;
1113
import org.neo4j.driver.v1.summary.InputPosition;
14+
import org.neo4j.driver.v1.summary.Plan;
1215
import org.neo4j.driver.v1.summary.ResultSummary;
1316

1417
import java.util.ArrayList;
@@ -18,6 +21,7 @@
1821
import java.util.Optional;
1922

2023
import static java.util.stream.Collectors.toList;
24+
import static java.util.stream.Collectors.toMap;
2125

2226
public class Neo4jBoltBuffer {
2327

@@ -103,4 +107,39 @@ public List<GraphQueryNotification> getNotifications() {
103107

104108
return notifications;
105109
}
110+
111+
public boolean hasPlan() {
112+
return Optional.ofNullable(resultSummary)
113+
.map(ResultSummary::hasPlan)
114+
.orElse(false);
115+
}
116+
117+
public boolean isProfilePlan() {
118+
return Optional.ofNullable(resultSummary)
119+
.map(ResultSummary::hasProfile)
120+
.orElse(false);
121+
}
122+
123+
public Optional<GraphQueryPlan> getQueryPlan() {
124+
if (!hasPlan()) {
125+
return Optional.empty();
126+
}
127+
128+
Plan plan = resultSummary.plan();
129+
return Optional.of(new Neo4jBoltQueryPlan(plan.operatorType(), getArguments(plan), plan.identifiers(),
130+
getPlanChildren(plan.children())));
131+
}
132+
133+
private static List<Neo4jBoltQueryPlan> getPlanChildren(List<? extends Plan> childrenPlans) {
134+
return childrenPlans.stream()
135+
.map(plan -> new Neo4jBoltQueryPlan(plan.operatorType(), getArguments(plan), plan.identifiers(),
136+
getPlanChildren(plan.children())))
137+
.collect(toList());
138+
}
139+
140+
private static Map<String, Object> getArguments(Plan plan) {
141+
return plan.arguments().entrySet().stream()
142+
.collect(toMap(Map.Entry::getKey, e -> e.getValue().asObject()));
143+
}
144+
106145
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data;
2+
3+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryPlan;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
public class Neo4jBoltQueryPlan implements GraphQueryPlan {
9+
10+
private String operatorType;
11+
private Map<String, Object> arguments;
12+
private List<String> identifiers;
13+
private List<Neo4jBoltQueryPlan> children;
14+
15+
public Neo4jBoltQueryPlan(String operatorType, Map<String, Object> arguments, List<String> identifiers,
16+
List<Neo4jBoltQueryPlan> children) {
17+
this.operatorType = operatorType;
18+
this.arguments = arguments;
19+
this.identifiers = identifiers;
20+
this.children = children;
21+
}
22+
23+
@Override
24+
public String getOperatorType() {
25+
return operatorType;
26+
}
27+
28+
@Override
29+
public Map<String, Object> getArguments() {
30+
return arguments;
31+
}
32+
33+
@Override
34+
public List<String> getIdentifiers() {
35+
return identifiers;
36+
}
37+
38+
@Override
39+
public List<Neo4jBoltQueryPlan> children() {
40+
return children;
41+
}
42+
}

database/neo4j/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/neo4j/bolt/query/Neo4jBoltQueryResult.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphNode;
44
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphRelationship;
55
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryNotification;
6+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryPlan;
67
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult;
78
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultColumn;
89
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultRow;
@@ -149,6 +150,21 @@ public List<GraphQueryNotification> getNotifications() {
149150
return buffer.getNotifications();
150151
}
151152

153+
@Override
154+
public boolean hasPlan() {
155+
return buffer.hasPlan();
156+
}
157+
158+
@Override
159+
public boolean isProfilePlan() {
160+
return buffer.isProfilePlan();
161+
}
162+
163+
@Override
164+
public Optional<GraphQueryPlan> getPlan() {
165+
return buffer.getQueryPlan();
166+
}
167+
152168
private Optional<GraphNode> findNodeById(List<GraphNode> nodes, String id) {
153169
return nodes.stream().filter((node) -> node.getId().equals(id)).findFirst();
154170
}

ui/jetbrains/src/main/java/com/neueda/jetbrains/plugin/graphdb/jetbrains/database/QueryExecutionService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.analytics.Analytics;
1010
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.state.DataSourceApi;
1111
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryExecutionProcessEvent;
12+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryPlanEvent;
1213
import com.neueda.jetbrains.plugin.graphdb.jetbrains.util.Notifier;
1314

1415
import java.util.concurrent.Future;
@@ -61,6 +62,11 @@ private synchronized void executeInBackground(DataSourceApi dataSource, ExecuteQ
6162
event.resultReceived(payload, result);
6263
event.postResultReceived(payload);
6364
event.executionCompleted(payload);
65+
66+
if (result.hasPlan()) {
67+
QueryPlanEvent queryPlanEvent = messageBus.syncPublisher(QueryPlanEvent.QUERY_PLAN_EVENT);
68+
queryPlanEvent.queryPlanReceived(payload.getContent(), result);
69+
}
6470
});
6571
} catch (Exception e) {
6672
ApplicationManager.getApplication().invokeLater(() -> {

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console;
22

3+
import com.intellij.ide.IdeEventQueue;
34
import com.intellij.openapi.Disposable;
45
import com.intellij.openapi.actionSystem.ActionGroup;
56
import com.intellij.openapi.actionSystem.ActionManager;
7+
import com.intellij.openapi.actionSystem.ActionPlaces;
68
import com.intellij.openapi.actionSystem.ActionToolbar;
9+
import com.intellij.openapi.actionSystem.AnActionEvent;
10+
import com.intellij.openapi.actionSystem.DefaultActionGroup;
711
import com.intellij.openapi.components.ServiceManager;
812
import com.intellij.openapi.project.Project;
913
import com.intellij.openapi.util.ActionCallback;
@@ -22,20 +26,34 @@
2226
import com.intellij.ui.tabs.TabInfo;
2327
import com.intellij.ui.tabs.impl.JBTabsImpl;
2428
import com.intellij.ui.treeStructure.Tree;
29+
import com.intellij.util.ui.UIUtil;
30+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult;
2531
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.analytics.Analytics;
32+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event.QueryPlanEvent;
2633
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.graph.GraphPanel;
2734
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.log.LogPanel;
2835
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.params.ParametersPanel;
36+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.plan.QueryPlanPanel;
2937
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.status.ExecutionStatusBarWidget;
3038
import com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.table.TablePanel;
3139
import com.neueda.jetbrains.plugin.graphdb.platform.GraphConstants;
3240
import com.neueda.jetbrains.plugin.graphdb.visualization.services.LookAndFeelService;
3341

3442
import javax.swing.*;
3543
import java.awt.*;
44+
import java.awt.event.MouseAdapter;
45+
import java.awt.event.MouseEvent;
46+
import java.time.LocalDateTime;
47+
import java.time.format.DateTimeFormatter;
48+
import java.time.format.DateTimeFormatterBuilder;
49+
import java.util.concurrent.atomic.AtomicInteger;
50+
51+
import static java.time.temporal.ChronoField.*;
3652

3753
public class GraphConsoleView implements Disposable {
3854

55+
public static final String PROFILE_PLAN_TITLE = "Profile";
56+
public static final String EXPLAIN_PLAN_TITLE = "Explain";
3957
private boolean initialized;
4058

4159
private ExecutionStatusBarWidget executionStatusBarWidget;
@@ -66,6 +84,15 @@ public class GraphConsoleView implements Disposable {
6684
private LogPanel logPanel;
6785
private ParametersPanel parametersPanel;
6886

87+
private static final DateTimeFormatter QUERY_PLAN_TIME_FORMAT = new DateTimeFormatterBuilder()
88+
.appendValue(HOUR_OF_DAY, 2)
89+
.appendLiteral(':')
90+
.appendValue(MINUTE_OF_HOUR, 2)
91+
.optionalStart()
92+
.appendLiteral(':')
93+
.appendValue(SECOND_OF_MINUTE, 2)
94+
.toFormatter();
95+
6996
public GraphConsoleView() {
7097
initialized = false;
7198

@@ -106,6 +133,10 @@ public void initToolWindow(Project project, ToolWindow toolWindow) {
106133
return callback;
107134
});
108135

136+
AtomicInteger tabId = new AtomicInteger(0);
137+
project.getMessageBus().connect().subscribe(QueryPlanEvent.QUERY_PLAN_EVENT,
138+
(query, result) -> createNewQueryPlanTab(query, result, tabId.incrementAndGet()));
139+
109140
// Actions
110141
final ActionGroup consoleActionGroup = (ActionGroup)
111142
ActionManager.getInstance().getAction(GraphConstants.Actions.CONSOLE_ACTIONS);
@@ -123,6 +154,22 @@ private void createUIComponents() {
123154
graphCanvas = new JPanel(new GridLayout(0, 1));
124155
consoleTabsPane = new JBTabsPaneImpl(null, SwingConstants.TOP, this);
125156
consoleTabs = (JBTabsImpl) consoleTabsPane.getTabs();
157+
158+
consoleTabs.addTabMouseListener(new MouseAdapter() {
159+
@Override
160+
public void mouseReleased(MouseEvent e) {
161+
if (UIUtil.isCloseClick(e, MouseEvent.MOUSE_RELEASED)) {
162+
final TabInfo info = consoleTabs.findInfo(e);
163+
if (info != null) {
164+
String tabTitle = info.getText();
165+
if (tabTitle.startsWith(PROFILE_PLAN_TITLE) || tabTitle.startsWith(EXPLAIN_PLAN_TITLE)) {
166+
IdeEventQueue.getInstance().blockNextEvents(e);
167+
consoleTabs.removeTab(info);
168+
}
169+
}
170+
}
171+
}
172+
});
126173
}
127174

128175
private void updateLookAndFeel() {
@@ -146,6 +193,29 @@ private void initializeWidgets(Project project) {
146193
statusBar.addWidget(executionStatusBarWidget, "before Position");
147194
}
148195

196+
private void createNewQueryPlanTab(String originalQuery,
197+
GraphQueryResult result, int tabId) {
198+
JPanel panel = new JPanel();
199+
panel.setLayout(new BorderLayout(0, 3));
200+
201+
QueryPlanPanel qpPanel = new QueryPlanPanel(originalQuery, result);
202+
qpPanel.initialize(panel);
203+
204+
TabInfo tabInfo = new TabInfo(panel);
205+
DefaultActionGroup tabActions = new DefaultActionGroup(new QueryPlanPanel.CloseTab() {
206+
@Override
207+
public void actionPerformed(AnActionEvent e) {
208+
super.actionPerformed(e);
209+
consoleTabs.removeTab(tabInfo);
210+
}
211+
});
212+
tabInfo.setTabLabelActions(tabActions, ActionPlaces.EDITOR_TAB);
213+
214+
String planType = result.isProfilePlan() ? PROFILE_PLAN_TITLE : EXPLAIN_PLAN_TITLE;
215+
consoleTabs.addTab(tabInfo.setText(String.format("%1s %2d - %3s", planType, tabId,
216+
LocalDateTime.now().format(QUERY_PLAN_TIME_FORMAT))));
217+
}
218+
149219
public TablePanel getTablePanel() {
150220
return tablePanel;
151221
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.neueda.jetbrains.plugin.graphdb.jetbrains.ui.console.event;
2+
3+
import com.intellij.util.messages.Topic;
4+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult;
5+
6+
public interface QueryPlanEvent {
7+
8+
Topic<QueryPlanEvent> QUERY_PLAN_EVENT = Topic.create("GraphDatabaseConsole.QueryPlanEvent", QueryPlanEvent.class);
9+
10+
void queryPlanReceived(String query, GraphQueryResult result);
11+
}

0 commit comments

Comments
 (0)