Skip to content

Commit d127008

Browse files
Merge pull request #108 from dynatrace-oss/feature/dql/table-columns-reordering
[DQL] Adding an option to reorder & change the visibility for columns
2 parents cd01d89 + de48e7b commit d127008

26 files changed

+588
-334
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Small UI improvements:
88
- DQL fragment selected during the query execution will now have a green border instead of a selection background
9+
- Adding data toolbar for DQL execution results opened as a new editor tab
10+
- Adding an option to change the DQL execution results column order and visibility
11+
- The "Show DQL query" option when executing DQL will now try to show the parsed query instead of the raw one, if
12+
possible.
913
- Adding support for "Expression DQL" file (with `.edql` extensions), which allows to define a DQL expression without
1014
the command context.
1115
Such expression are commonly used in as-a-code DQL queries or in OpenPipeline configurations.

docs/wiki/DQL.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Given the following `dql-variables.json` file:
141141

142142
and the following DQL query:
143143

144-
```dql
144+
```text
145145
data record(
146146
number = $number,
147147
string = $string,
@@ -171,6 +171,8 @@ data record(
171171

172172
When a Dynatrace tenant connection is configured, you can execute DQL queries directly from the IDE.
173173

174+
![An example result](images/dql-execution.png)
175+
174176
#### DQL query selection
175177

176178
You can select a part of the DQL query to be executed. If the plugin discovers a part of the query is selected, a popup
@@ -223,15 +225,14 @@ the query metadata (details like execution time, number of returned records, etc
223225
editor tab. Additionally, you can save the JSON result into a file.
224226

225227
Inside the tabular view, double-clicking a cell will open its content in a new editor tab, allowing you to explore
226-
complex values like nested records or long strings easily.
228+
complex values like nested records or long strings easily. You can change the order and visibility of displayed columns
229+
by clicking on the option button in the toolbar.
227230

228231
In case when the DQL query execution fails, the plugin will display the error message returned by the Dynatrace tenant.
229232

230233
If Dynatrace returns notifications about the executed query (like a warning about limited records number), they will be
231234
displayed at the bottom part of the result view.
232235

233-
![An example result](images/dql-execution.png)
234-
235236
### Partial DQL
236237

237238
In some cases (especially when storing DQLs as-a-code in your repository) you might want to create DQL files containing

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

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,39 @@
1010
import com.intellij.ide.DataManager;
1111
import com.intellij.lang.Language;
1212
import com.intellij.openapi.actionSystem.DataContext;
13-
import com.intellij.openapi.diagnostic.Logger;
1413
import com.intellij.openapi.editor.EditorSettings;
1514
import com.intellij.openapi.editor.colors.EditorColorsManager;
1615
import com.intellij.openapi.editor.ex.EditorEx;
16+
import com.intellij.openapi.fileChooser.FileChooserFactory;
17+
import com.intellij.openapi.fileChooser.FileSaverDescriptor;
18+
import com.intellij.openapi.fileChooser.FileSaverDialog;
1719
import com.intellij.openapi.fileTypes.PlainTextLanguage;
1820
import com.intellij.openapi.project.Project;
1921
import com.intellij.openapi.project.ProjectUtil;
22+
import com.intellij.openapi.ui.Messages;
23+
import com.intellij.openapi.util.text.StringUtil;
2024
import com.intellij.openapi.vfs.VfsUtilCore;
2125
import com.intellij.openapi.vfs.VirtualFile;
26+
import com.intellij.openapi.vfs.VirtualFileWrapper;
2227
import com.intellij.ui.*;
2328
import com.intellij.util.IconUtil;
2429
import com.intellij.util.ui.JBUI;
2530
import org.jetbrains.annotations.NotNull;
2631
import org.jetbrains.annotations.Nullable;
32+
import pl.thedeem.intellij.dql.DQLBundle;
2733
import pl.thedeem.intellij.dql.exec.runConfiguration.ExecuteDQLRunConfiguration;
2834

2935
import javax.swing.*;
36+
import java.io.File;
37+
import java.io.FileWriter;
38+
import java.lang.reflect.Constructor;
3039
import java.util.List;
3140
import java.util.Objects;
41+
import java.util.concurrent.Callable;
3242
import java.util.concurrent.ExecutionException;
3343
import java.util.concurrent.TimeoutException;
3444

3545
public class IntelliJUtils {
36-
private static final Logger logger = Logger.getInstance(IntelliJUtils.class);
3746
private static final ObjectMapper mapper = new ObjectMapper();
3847

3948
public static @NotNull EditorTextField createEditorPanel(@NotNull Project project, @Nullable Language language, boolean isViewer, @NotNull List<EditorCustomization> customizations) {
@@ -102,20 +111,16 @@ public void customize(@NotNull EditorEx editor) {
102111
}
103112
}
104113

105-
public static @Nullable String prettyPrintJson(@Nullable Object json) {
114+
public static @Nullable String prettyPrintJson(@Nullable Object json) throws JsonProcessingException {
106115
if (json == null) {
107116
return "";
108117
}
109-
try {
110-
DefaultIndenter indenter = new DefaultIndenter(" ", DefaultIndenter.SYS_LF);
111-
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
112-
printer.indentObjectsWith(indenter);
113-
printer.indentArraysWith(indenter);
114-
return mapper.writer(printer).writeValueAsString(json);
115-
} catch (JsonProcessingException e) {
116-
logger.warn("Failed to pretty print JSON", e);
117-
return null;
118-
}
118+
119+
DefaultIndenter indenter = new DefaultIndenter(" ", DefaultIndenter.SYS_LF);
120+
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
121+
printer.indentObjectsWith(indenter);
122+
printer.indentArraysWith(indenter);
123+
return mapper.writer(printer).writeValueAsString(json);
119124
}
120125

121126
public static Icon scaleToBottomRight(@NotNull Icon base, @NotNull Icon original, float scale) {
@@ -131,4 +136,59 @@ public static Icon scaleToBottomRight(@NotNull Icon base, @NotNull Icon original
131136
);
132137
}};
133138
}
139+
140+
public static void openSaveFileDialog(
141+
@NotNull String title,
142+
@NotNull String description,
143+
@NotNull String fileName,
144+
@NotNull Callable<String> contentCallback,
145+
@NotNull Project project
146+
) {
147+
FileSaverDescriptor descriptor = getFileSaver(title, description);
148+
if (descriptor == null) {
149+
Messages.showErrorDialog(
150+
DQLBundle.message("components.actions.saveAsFile.error.incompatibleVersion"),
151+
DQLBundle.message("components.actions.saveAsFile.error.title")
152+
);
153+
return;
154+
}
155+
FileSaverDialog dialog = FileChooserFactory.getInstance().createSaveFileDialog(descriptor, project);
156+
VirtualFileWrapper fileWrapper = dialog.save(fileName);
157+
try {
158+
String content = contentCallback.call();
159+
if (fileWrapper != null && StringUtil.isNotEmpty(content)) {
160+
File file = fileWrapper.getFile();
161+
try (FileWriter writer = new FileWriter(file)) {
162+
writer.write(content);
163+
}
164+
}
165+
} catch (Exception ex) {
166+
Messages.showErrorDialog(
167+
DQLBundle.message("components.actions.saveAsFile.error.description", ex.getMessage()),
168+
DQLBundle.message("components.actions.saveAsFile.error.title")
169+
);
170+
}
171+
}
172+
173+
public static @Nullable FileSaverDescriptor getFileSaver(@NotNull String title, @NotNull String description) {
174+
try {
175+
Constructor<FileSaverDescriptor> ctor = FileSaverDescriptor.class.getConstructor(String.class, String.class, String.class);
176+
return ctor.newInstance(
177+
title,
178+
description,
179+
"json"
180+
);
181+
} catch (Exception ignored) {
182+
try {
183+
Constructor<FileSaverDescriptor> ctor = FileSaverDescriptor.class.getConstructor(String.class, String.class, String[].class);
184+
return ctor.newInstance(
185+
title,
186+
description,
187+
new String[]{"json"}
188+
);
189+
} catch (Exception ignored2) {
190+
return null;
191+
}
192+
}
193+
}
134194
}

src/main/java/pl/thedeem/intellij/common/components/FormattedLanguageText.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public void run(@NotNull ProgressIndicator progressIndicator) {
5353
resultText = content.call();
5454
} catch (Exception e) {
5555
progressIndicator.cancel();
56+
resultText = null;
5657
}
5758
}
5859

src/main/java/pl/thedeem/intellij/common/components/CommonTable.java renamed to src/main/java/pl/thedeem/intellij/common/components/table/CommonTable.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
package pl.thedeem.intellij.common.components;
1+
package pl.thedeem.intellij.common.components.table;
22

3+
import com.intellij.openapi.ui.popup.JBPopup;
4+
import com.intellij.openapi.ui.popup.JBPopupFactory;
35
import com.intellij.ui.TableSpeedSearch;
46
import com.intellij.ui.table.JBTable;
7+
import org.jetbrains.annotations.NotNull;
8+
import pl.thedeem.intellij.common.components.ComponentsUtils;
9+
import pl.thedeem.intellij.common.components.table.rendering.CommonTableCellRenderer;
10+
import pl.thedeem.intellij.common.components.table.rendering.CommonTableHeaderRenderer;
11+
import pl.thedeem.intellij.common.components.table.reordering.TableColumnReorderUtil;
512

613
import javax.swing.*;
714
import javax.swing.table.TableModel;
815
import java.awt.*;
916
import java.awt.event.MouseAdapter;
1017
import java.awt.event.MouseEvent;
18+
import java.util.Set;
1119

1220
public class CommonTable extends JBTable {
13-
public CommonTable(TableModel model) {
21+
public CommonTable(@NotNull TableModel model) {
1422
super(model);
1523
setLayout(new BorderLayout());
1624
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
@@ -43,4 +51,16 @@ public void setColumnPreferredWidthInCharacters(int column, int characters) {
4351
}
4452
getColumnModel().getColumn(column).setPreferredWidth(getFontMetrics(getFont()).charWidth('0') * characters + 10);
4553
}
54+
55+
public @NotNull JBPopup createColumnsReorderPopup(@NotNull Set<String> allColumns) {
56+
JComponent content = new TableColumnReorderUtil().createColumnReorderingComponent(this, allColumns);
57+
return JBPopupFactory.getInstance()
58+
.createComponentPopupBuilder(content, this)
59+
.setRequestFocus(true)
60+
.setResizable(true)
61+
.setMovable(true)
62+
.setCancelOnClickOutside(true)
63+
.setCancelOnOtherWindowOpen(true)
64+
.createPopup();
65+
}
4666
}

src/main/java/pl/thedeem/intellij/common/components/RowCountTable.java renamed to src/main/java/pl/thedeem/intellij/common/components/table/RowCountTable.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package pl.thedeem.intellij.common.components;
1+
package pl.thedeem.intellij.common.components.table;
22

33
import com.intellij.ui.table.JBTable;
4+
import pl.thedeem.intellij.common.components.ComponentsUtils;
45

56
import javax.swing.*;
67
import javax.swing.table.AbstractTableModel;

src/main/java/pl/thedeem/intellij/common/components/CommonTableCellRenderer.java renamed to src/main/java/pl/thedeem/intellij/common/components/table/rendering/CommonTableCellRenderer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package pl.thedeem.intellij.common.components;
1+
package pl.thedeem.intellij.common.components.table.rendering;
22

33
import com.intellij.util.ui.JBUI;
4+
import pl.thedeem.intellij.common.components.ComponentsUtils;
45

56
import javax.swing.*;
67
import javax.swing.table.DefaultTableCellRenderer;

src/main/java/pl/thedeem/intellij/common/components/CommonTableHeaderRenderer.java renamed to src/main/java/pl/thedeem/intellij/common/components/table/rendering/CommonTableHeaderRenderer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package pl.thedeem.intellij.common.components;
1+
package pl.thedeem.intellij.common.components.table.rendering;
22

33
import com.intellij.util.ui.JBUI;
4+
import pl.thedeem.intellij.common.components.ComponentsUtils;
45

56
import javax.swing.*;
67
import javax.swing.table.DefaultTableCellRenderer;

src/main/java/pl/thedeem/intellij/common/components/MultiLineCellRenderer.java renamed to src/main/java/pl/thedeem/intellij/common/components/table/rendering/MultiLineCellRenderer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package pl.thedeem.intellij.common.components;
1+
package pl.thedeem.intellij.common.components.table.rendering;
22

33
import com.intellij.util.ui.JBUI;
4+
import pl.thedeem.intellij.common.components.ComponentsUtils;
45

56
import javax.swing.*;
67
import javax.swing.table.TableCellRenderer;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package pl.thedeem.intellij.common.components.table.reordering;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import javax.swing.*;
6+
import java.awt.datatransfer.DataFlavor;
7+
import java.awt.datatransfer.StringSelection;
8+
import java.awt.datatransfer.Transferable;
9+
10+
class ReorderableListTransferHandler extends TransferHandler {
11+
private static final DataFlavor FLAVOR = DataFlavor.stringFlavor;
12+
private int fromIndex = -1;
13+
14+
@Override
15+
public int getSourceActions(JComponent c) {
16+
return MOVE;
17+
}
18+
19+
@Override
20+
protected Transferable createTransferable(@NotNull JComponent c) {
21+
if (c instanceof JList<?> list) {
22+
fromIndex = list.getSelectedIndex();
23+
if (list.getSelectedValue() instanceof TableColumnItem item) {
24+
return new StringSelection(item.name());
25+
}
26+
}
27+
return new StringSelection("");
28+
}
29+
30+
@Override
31+
public boolean canImport(TransferSupport support) {
32+
return support.isDrop() && support.isDataFlavorSupported(FLAVOR);
33+
}
34+
35+
@Override
36+
@SuppressWarnings("unchecked")
37+
public boolean importData(TransferSupport support) {
38+
if (!canImport(support)) {
39+
return false;
40+
}
41+
42+
if (support.getComponent() instanceof JList<?> list && support.getDropLocation() instanceof JList.DropLocation dropLocation) {
43+
if (list.getModel() instanceof DefaultListModel<?> model) {
44+
int toIndex = dropLocation.getIndex();
45+
if (fromIndex < 0 || toIndex < 0) {
46+
return false;
47+
}
48+
if (toIndex > model.getSize()) {
49+
toIndex = model.getSize();
50+
}
51+
52+
if (toIndex == fromIndex || toIndex == fromIndex + 1) {
53+
return false;
54+
}
55+
56+
if (model.get(fromIndex) instanceof TableColumnItem tableItem) {
57+
model.remove(fromIndex);
58+
if (toIndex > fromIndex) {
59+
toIndex--;
60+
}
61+
((DefaultListModel<Object>) model).add(toIndex, tableItem);
62+
list.setSelectedIndex(toIndex);
63+
return true;
64+
}
65+
}
66+
}
67+
return false;
68+
}
69+
}

0 commit comments

Comments
 (0)