Skip to content

Commit c9a4814

Browse files
[DQL] Executed DQL services will now be grouped by tenant in the Services tab (#119)
* [DQL] Executed DQL services will now be grouped by tenant in the Services tab This helps to easily execute the same query on multiple tenants and compare values * [DQL] Fixing code reviews * [DQL] Fixing error when executing run configuration
1 parent d6cc73b commit c9a4814

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1302
-1129
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Adding predefined timeframes to the DQL execution toolbar (user can still define a custom one)
1212
- The DQL execution result will show an empty cell for a boolean column that had a `null` value (instead of the
1313
unchecked checkbox). This change helps to differentiate between `false` and `null` values.
14+
- Executed DQL queries will now be grouped by the tenant they were executed on in the Services tab
1415
- The "Show DQL query" option when executing DQL will now try to show the parsed query instead of the raw one, if
1516
possible.
1617
- Adding support for "Expression DQL" file (with `.edql` extensions), which allows to define a DQL expression without
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package pl.thedeem.intellij.common;
2+
3+
import com.intellij.openapi.util.IconLoader;
4+
5+
import javax.swing.*;
6+
7+
public interface Icons {
8+
Icon DYNATRACE_LOGO = IconLoader.getIcon("/icons/dynatrace.png", Icons.class);
9+
}

src/main/java/pl/thedeem/intellij/common/IntelliJUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.intellij.openapi.project.Project;
2121
import com.intellij.openapi.project.ProjectUtil;
2222
import com.intellij.openapi.ui.Messages;
23+
import com.intellij.openapi.util.io.FileUtil;
2324
import com.intellij.openapi.util.text.StringUtil;
2425
import com.intellij.openapi.vfs.VfsUtilCore;
2526
import com.intellij.openapi.vfs.VirtualFile;
@@ -88,6 +89,14 @@ public static void openRunConfiguration(@NotNull Project project) {
8889
return VfsUtilCore.getRelativePath(file, baseDir, '/');
8990
}
9091

92+
public static @Nullable VirtualFile getProjectRelativeFile(@NotNull String relativePath, @NotNull Project project) {
93+
VirtualFile baseDir = ProjectUtil.guessProjectDir(project);
94+
if (baseDir == null) {
95+
return null;
96+
}
97+
return baseDir.findFileByRelativePath(FileUtil.toSystemIndependentName(relativePath));
98+
}
99+
91100
public static class StandardEditorCustomization implements EditorCustomization {
92101
@Override
93102
public void customize(@NotNull EditorEx editor) {

src/main/java/pl/thedeem/intellij/common/StandardItemPresentation.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.intellij.navigation.ItemPresentation;
44
import com.intellij.psi.PsiElement;
5+
import org.jetbrains.annotations.NotNull;
56
import org.jetbrains.annotations.Nullable;
67

78
import javax.swing.*;
@@ -11,11 +12,11 @@ public class StandardItemPresentation implements ItemPresentation {
1112
private final PsiElement element;
1213
private final Icon icon;
1314

14-
public StandardItemPresentation(String name, Icon icon) {
15+
public StandardItemPresentation(@NotNull String name, @Nullable Icon icon) {
1516
this(name, null, icon);
1617
}
1718

18-
public StandardItemPresentation(String name, PsiElement element, Icon icon) {
19+
public StandardItemPresentation(@NotNull String name, @Nullable PsiElement element, @Nullable Icon icon) {
1920
this.name = name;
2021
this.element = element;
2122
this.icon = icon;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package pl.thedeem.intellij.common.components;
2+
3+
import com.intellij.icons.AllIcons;
4+
import com.intellij.openapi.ui.JBMenuItem;
5+
import com.intellij.openapi.ui.JBPopupMenu;
6+
import com.intellij.ui.DocumentAdapter;
7+
import com.intellij.ui.JBColor;
8+
import com.intellij.ui.components.JBTextField;
9+
import com.intellij.util.ui.JBUI;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
import pl.thedeem.intellij.dql.DQLBundle;
13+
import pl.thedeem.intellij.dql.DQLUtil;
14+
15+
import javax.swing.border.Border;
16+
import javax.swing.event.DocumentEvent;
17+
import javax.swing.text.AbstractDocument;
18+
import javax.swing.text.AttributeSet;
19+
import javax.swing.text.BadLocationException;
20+
import javax.swing.text.DocumentFilter;
21+
import java.awt.*;
22+
23+
public class ResizableTextField extends JBTextField {
24+
private final static int TEXT_FIELD_MIN_WIDTH = 75;
25+
private final static int TEXT_FIELD_MAX_WIDTH = 400;
26+
27+
public ResizableTextField() {
28+
getDocument().addDocumentListener(new DocumentAdapter() {
29+
@Override
30+
protected void textChanged(@NotNull DocumentEvent documentEvent) {
31+
resizeToFit();
32+
}
33+
});
34+
}
35+
36+
public void resizeToFit() {
37+
String text = getText();
38+
FontMetrics fm = getFontMetrics(getFont());
39+
40+
int textWidth = fm.stringWidth(text + "WW");
41+
int newWidth = Math.max(JBUI.scale(TEXT_FIELD_MIN_WIDTH), Math.min(JBUI.scale(TEXT_FIELD_MAX_WIDTH), textWidth));
42+
43+
Dimension dim = new Dimension(newWidth, getPreferredSize().height);
44+
setPreferredSize(dim);
45+
setMinimumSize(dim);
46+
47+
Container parent = getParent();
48+
if (parent != null) {
49+
parent.revalidate();
50+
parent.repaint();
51+
}
52+
}
53+
54+
public static @NotNull ResizableTextField createStandardField(@Nullable String placeholder, @Nullable String tooltip) {
55+
ResizableTextField result = new ResizableTextField();
56+
result.setToolTipText(tooltip);
57+
result.getEmptyText().setText(placeholder);
58+
result.setPreferredSize(new Dimension(result.getPreferredSize().width, JBUI.scale(25)));
59+
result.resizeToFit();
60+
return result;
61+
}
62+
63+
public static @NotNull ResizableTextField createTimeField(@Nullable String placeholder, @Nullable String tooltip) {
64+
ResizableTextField result = createStandardField(placeholder, tooltip);
65+
Border defaultBorder = result.getBorder();
66+
result.getDocument().addDocumentListener(new DocumentAdapter() {
67+
@Override
68+
protected void textChanged(@NotNull DocumentEvent documentEvent) {
69+
String error = validateTime(result.getText());
70+
if (error != null) {
71+
result.setToolTipText(error);
72+
result.setBorder(JBUI.Borders.compound(JBUI.Borders.customLineBottom(JBColor.RED), defaultBorder));
73+
} else {
74+
result.setToolTipText(tooltip);
75+
result.setBorder(defaultBorder);
76+
}
77+
result.revalidate();
78+
result.repaint();
79+
}
80+
});
81+
82+
JBPopupMenu popupMenu = new JBPopupMenu();
83+
JBMenuItem currentTimestampAction = new JBMenuItem(DQLBundle.message("components.queryExecution.actions.getCurrentTimestamp"));
84+
currentTimestampAction.setIcon(AllIcons.General.Inline_edit);
85+
currentTimestampAction.setBorder(ComponentsUtils.DEFAULT_BORDER);
86+
currentTimestampAction.addActionListener(e -> result.setText(DQLUtil.getCurrentTimeTimestamp()));
87+
popupMenu.add(currentTimestampAction);
88+
result.setComponentPopupMenu(popupMenu);
89+
return result;
90+
}
91+
92+
public static @NotNull ResizableTextField createNumericField(@Nullable String placeholder, @Nullable String tooltip) {
93+
ResizableTextField result = createStandardField(placeholder, tooltip);
94+
((AbstractDocument) result.getDocument()).setDocumentFilter(new NumericFilter());
95+
return result;
96+
}
97+
98+
private static @Nullable String validateTime(@NotNull String time) throws IllegalArgumentException {
99+
try {
100+
DQLUtil.parseUserTime(time.trim());
101+
return null;
102+
} catch (IllegalArgumentException ex) {
103+
return DQLBundle.message("components.queryRange.invalidDate", ex.getMessage());
104+
}
105+
}
106+
107+
private static class NumericFilter extends DocumentFilter {
108+
@Override
109+
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
110+
throws BadLocationException {
111+
if (string.matches("-?\\d*")) {
112+
super.insertString(fb, offset, string, attr);
113+
}
114+
}
115+
116+
@Override
117+
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
118+
throws BadLocationException {
119+
if (text.matches("-?\\d*")) {
120+
super.replace(fb, offset, length, text, attrs);
121+
}
122+
}
123+
}
124+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pl.thedeem.intellij.common.components;
2+
3+
import com.intellij.openapi.actionSystem.UiDataProvider;
4+
import com.intellij.util.ui.components.BorderLayoutPanel;
5+
6+
public abstract class SimpleDataProviderPanel extends BorderLayoutPanel implements UiDataProvider {
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package pl.thedeem.intellij.common.services;
2+
3+
import com.intellij.execution.services.ServiceViewDescriptor;
4+
import com.intellij.openapi.Disposable;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
import java.util.List;
8+
9+
public interface ManagedService extends ServiceViewDescriptor, Disposable {
10+
@NotNull String getServiceId();
11+
12+
@NotNull List<ManagedServiceGroup> getParentGroups();
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pl.thedeem.intellij.common.services;
2+
3+
import com.intellij.execution.services.ServiceViewDescriptor;
4+
import com.intellij.openapi.Disposable;
5+
6+
public interface ManagedServiceGroup extends ServiceViewDescriptor, Disposable {
7+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package pl.thedeem.intellij.common.services;
2+
3+
import com.intellij.execution.services.ServiceViewDescriptor;
4+
import com.intellij.execution.services.ServiceViewGroupingContributor;
5+
import com.intellij.execution.services.SimpleServiceViewDescriptor;
6+
import com.intellij.openapi.project.Project;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Unmodifiable;
9+
import pl.thedeem.intellij.common.Icons;
10+
11+
import java.util.List;
12+
13+
public class ManagedServiceViewContributor implements ServiceViewGroupingContributor<ManagedService, ManagedServiceGroup> {
14+
@Override
15+
public @NotNull ServiceViewDescriptor getViewDescriptor(@NotNull Project project) {
16+
return new SimpleServiceViewDescriptor("Dynatrace Query Language Services", Icons.DYNATRACE_LOGO);
17+
}
18+
19+
@Override
20+
public @NotNull @Unmodifiable List<ManagedService> getServices(@NotNull Project project) {
21+
return ProjectServicesManager.getInstance(project).getRegisteredServices();
22+
}
23+
24+
@Override
25+
public @NotNull ServiceViewDescriptor getServiceDescriptor(@NotNull Project project, @NotNull ManagedService service) {
26+
return service;
27+
}
28+
29+
@Override
30+
public @NotNull List<ManagedServiceGroup> getGroups(@NotNull ManagedService managedService) {
31+
return managedService.getParentGroups();
32+
}
33+
34+
@Override
35+
public @NotNull ServiceViewDescriptor getGroupDescriptor(@NotNull ManagedServiceGroup group) {
36+
return group;
37+
}
38+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package pl.thedeem.intellij.common.services;
2+
3+
import com.intellij.execution.services.ServiceEventListener;
4+
import com.intellij.execution.services.ServiceEventListener.ServiceEvent;
5+
import com.intellij.execution.services.ServiceViewManager;
6+
import com.intellij.openapi.Disposable;
7+
import com.intellij.openapi.components.Service;
8+
import com.intellij.openapi.project.Project;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.function.Predicate;
14+
15+
@Service(Service.Level.PROJECT)
16+
public final class ProjectServicesManager implements Disposable {
17+
private final List<ManagedService> children;
18+
19+
private final Project project;
20+
21+
public ProjectServicesManager(@NotNull Project project) {
22+
this.project = project;
23+
this.children = new ArrayList<>();
24+
}
25+
26+
public static @NotNull ProjectServicesManager getInstance(@NotNull Project project) {
27+
return project.getService(ProjectServicesManager.class);
28+
}
29+
30+
public @NotNull List<ManagedService> getRegisteredServices() {
31+
return children;
32+
}
33+
34+
@Override
35+
public void dispose() {
36+
for (ManagedService service : children) {
37+
service.dispose();
38+
}
39+
children.clear();
40+
}
41+
42+
public <T extends ManagedService> void registerService(@NotNull T service) {
43+
unregisterService(service);
44+
children.add(service);
45+
project.getMessageBus().syncPublisher(ServiceEventListener.TOPIC).handle(
46+
ServiceEventListener.ServiceEvent.createServiceAddedEvent(service, ManagedServiceViewContributor.class, null)
47+
);
48+
ServiceViewManager instance = ServiceViewManager.getInstance(project);
49+
instance.select(service, ManagedServiceViewContributor.class, true, true);
50+
}
51+
52+
public <T extends ManagedService> void unregisterService(@NotNull T service) {
53+
ManagedService existing = children.stream().filter(e -> e.equals(service)).findFirst().orElse(null);
54+
if (existing != null) {
55+
existing.dispose();
56+
children.remove(existing);
57+
project.getMessageBus().syncPublisher(ServiceEventListener.TOPIC).handle(
58+
ServiceEvent.createEvent(ServiceEventListener.EventType.SERVICE_REMOVED, existing, ManagedServiceViewContributor.class)
59+
);
60+
}
61+
}
62+
63+
public @NotNull List<ManagedService> find(@NotNull Predicate<? super ManagedService> filter) {
64+
return children.stream().filter(filter).toList();
65+
}
66+
}

0 commit comments

Comments
 (0)