Skip to content

Commit d28b92d

Browse files
committed
Dynamically created tabs are shown on explain and profile queries
1 parent e728938 commit d28b92d

File tree

11 files changed

+501
-0
lines changed

11 files changed

+501
-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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,10 @@ public interface GraphQueryResult {
2020
List<GraphRelationship> getRelationships();
2121

2222
List<GraphQueryNotification> getNotifications();
23+
24+
boolean hasPlan();
25+
26+
boolean hasProfile();
27+
28+
GraphQueryPlan getPlan();
2329
}

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 hasProfile() {
118+
return Optional.ofNullable(resultSummary)
119+
.map(ResultSummary::hasProfile)
120+
.orElse(false);
121+
}
122+
123+
public GraphQueryPlan getQueryPlan() {
124+
if (!hasPlan()) {
125+
return null;
126+
}
127+
128+
Plan plan = resultSummary.plan();
129+
return 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 hasProfile() {
160+
return buffer.hasProfile();
161+
}
162+
163+
@Override
164+
public 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: 76 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,17 +26,29 @@
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.MouseEvent;
45+
import java.awt.event.MouseListener;
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

@@ -66,6 +82,15 @@ public class GraphConsoleView implements Disposable {
6682
private LogPanel logPanel;
6783
private ParametersPanel parametersPanel;
6884

85+
private static final DateTimeFormatter QUERY_PLAN_TIME_FORMAT = new DateTimeFormatterBuilder()
86+
.appendValue(HOUR_OF_DAY, 2)
87+
.appendLiteral(':')
88+
.appendValue(MINUTE_OF_HOUR, 2)
89+
.optionalStart()
90+
.appendLiteral(':')
91+
.appendValue(SECOND_OF_MINUTE, 2)
92+
.toFormatter();
93+
6994
public GraphConsoleView() {
7095
initialized = false;
7196

@@ -106,6 +131,10 @@ public void initToolWindow(Project project, ToolWindow toolWindow) {
106131
return callback;
107132
});
108133

134+
AtomicInteger tabId = new AtomicInteger(0);
135+
project.getMessageBus().connect().subscribe(QueryPlanEvent.QUERY_PLAN_EVENT,
136+
(query, result) -> createNewQueryPlanTab(query, result, tabId.incrementAndGet()));
137+
109138
// Actions
110139
final ActionGroup consoleActionGroup = (ActionGroup)
111140
ActionManager.getInstance().getAction(GraphConstants.Actions.CONSOLE_ACTIONS);
@@ -123,6 +152,31 @@ private void createUIComponents() {
123152
graphCanvas = new JPanel(new GridLayout(0, 1));
124153
consoleTabsPane = new JBTabsPaneImpl(null, SwingConstants.TOP, this);
125154
consoleTabs = (JBTabsImpl) consoleTabsPane.getTabs();
155+
156+
consoleTabs.addTabMouseListener(new MouseListener() {
157+
@Override
158+
public void mouseReleased(MouseEvent e) {
159+
if (UIUtil.isCloseClick(e, MouseEvent.MOUSE_RELEASED)) {
160+
final TabInfo info = consoleTabs.findInfo(e);
161+
if (info != null && info.getText().startsWith("Query ")) {
162+
IdeEventQueue.getInstance().blockNextEvents(e);
163+
consoleTabs.removeTab(info);
164+
}
165+
}
166+
}
167+
168+
@Override
169+
public void mouseClicked(MouseEvent e) {}
170+
171+
@Override
172+
public void mousePressed(MouseEvent e) {}
173+
174+
@Override
175+
public void mouseEntered(MouseEvent e) {}
176+
177+
@Override
178+
public void mouseExited(MouseEvent e) {}
179+
});
126180
}
127181

128182
private void updateLookAndFeel() {
@@ -146,6 +200,28 @@ private void initializeWidgets(Project project) {
146200
statusBar.addWidget(executionStatusBarWidget, "before Position");
147201
}
148202

203+
private void createNewQueryPlanTab(String originalQuery,
204+
GraphQueryResult result, int tabId) {
205+
JPanel panel = new JPanel();
206+
panel.setLayout(new BorderLayout(0, 3));
207+
208+
QueryPlanPanel qpPanel = new QueryPlanPanel(originalQuery, result);
209+
qpPanel.initialize(panel);
210+
211+
TabInfo tabInfo = new TabInfo(panel);
212+
DefaultActionGroup tabActions = new DefaultActionGroup(new QueryPlanPanel.CloseTab() {
213+
@Override
214+
public void actionPerformed(AnActionEvent e) {
215+
consoleTabs.removeTab(tabInfo);
216+
}
217+
});
218+
tabInfo.setTabLabelActions(tabActions, ActionPlaces.EDITOR_TAB);
219+
220+
String planType = result.hasProfile() ? "Profile" : "Explain";
221+
consoleTabs.addTab(tabInfo.setText(String.format("%1s %2d - %3s", planType, tabId,
222+
LocalDateTime.now().format(QUERY_PLAN_TIME_FORMAT))));
223+
}
224+
149225
public TablePanel getTablePanel() {
150226
return tablePanel;
151227
}
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)