Skip to content

Commit f81c637

Browse files
committed
Integrate azd to Azure Explorer
1 parent 5cf8cb9 commit f81c637

File tree

4 files changed

+527
-3
lines changed

4 files changed

+527
-3
lines changed

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-service-explorer/src/main/java/com/microsoft/azure/toolkit/intellij/explorer/AzureExplorer.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
import com.microsoft.azure.toolkit.ide.common.favorite.Favorites;
2929
import com.microsoft.azure.toolkit.ide.common.genericresource.GenericResourceActionsContributor;
3030
import com.microsoft.azure.toolkit.ide.common.genericresource.GenericResourceNode;
31+
import com.microsoft.azure.toolkit.ide.common.icon.AzureIcon;
3132
import com.microsoft.azure.toolkit.ide.common.icon.AzureIcons;
33+
import com.microsoft.azure.toolkit.intellij.common.TerminalUtils;
3234
import com.microsoft.azure.toolkit.intellij.common.component.Tree;
3335
import com.microsoft.azure.toolkit.intellij.common.component.TreeUtils;
36+
import com.microsoft.azure.toolkit.intellij.explorer.azd.AzdInitializeFromTemplates;
3437
import com.microsoft.azure.toolkit.lib.Azure;
3538
import com.microsoft.azure.toolkit.lib.auth.AzureAccount;
3639
import com.microsoft.azure.toolkit.lib.auth.IAccountActions;
@@ -68,15 +71,17 @@ public class AzureExplorer extends Tree {
6871
public static final AzureExplorerNodeProviderManager manager = new AzureExplorerNodeProviderManager();
6972
public static final String AZURE_ICON = AzureIcons.Common.AZURE.getIconPath();
7073

71-
private AzureExplorer() {
74+
private AzureExplorer(Project project) {
7275
super();
7376
this.putClientProperty(PLACE, ResourceCommonActionsContributor.AZURE_EXPLORER);
7477
this.root = new Node<>("Azure")
7578
.withChildrenLoadLazily(false)
7679
.addChild(buildFavoriteRoot())
7780
.addChild(buildAppGroupedResourcesRoot())
7881
.addChild(buildTypeGroupedResourcesRoot())
79-
.addChildren(buildNonAzServiceNodes());
82+
.addChildren(buildNonAzServiceNodes())
83+
.addChildren(buildAzdGroup(project));
84+
8085
this.init(this.root);
8186
this.setRootVisible(false);
8287
//noinspection UnstableApiUsage
@@ -102,6 +107,52 @@ private AzureExplorer() {
102107
}));
103108
}
104109

110+
private List<Node<?>> buildAzdGroup(Project project) {
111+
final Node<String> azd = new Node<>("Azure Developer (AZD)");
112+
azd.addChild(getInitializeFromTemplatesNode(project));
113+
azd.addChild(getInitializeFromSourceNode(project));
114+
azd.addChild(getProvisionResourcesNode(project));
115+
azd.addChild(getDeployToAzureNode(project));
116+
azd.addChild(getProvisionAndDeployToAzureNode(project));
117+
return List.of(azd);
118+
}
119+
120+
@Nonnull
121+
private static Node<String> getProvisionAndDeployToAzureNode(Project project) {
122+
123+
return new Node<>("Provision and Deploy to Azure")
124+
.withIcon(AzureIcons.Action.START)
125+
.onClicked(e -> TerminalUtils.executeInTerminal(project, "azd up", "azd"));
126+
}
127+
128+
@Nonnull
129+
private static Node<String> getDeployToAzureNode(Project project) {
130+
return new Node<>("Deploy to Azure")
131+
.withIcon(AzureIcons.Action.DEPLOY)
132+
.onClicked(e -> TerminalUtils.executeInTerminal(project, "azd deploy", "azd"));
133+
}
134+
135+
@Nonnull
136+
private static Node<String> getProvisionResourcesNode(Project project) {
137+
return new Node<>("Provision resources")
138+
.withIcon(AzureIcons.Action.EXPORT)
139+
.onClicked(e -> TerminalUtils.executeInTerminal(project, "azd provision", "azd"));
140+
}
141+
142+
@Nonnull
143+
private static Node<String> getInitializeFromSourceNode(Project project) {
144+
return new Node<>("Initialize from source")
145+
.withIcon(AzureIcons.Action.EDIT)
146+
.onClicked(e -> TerminalUtils.executeInTerminal(project, "azd init", "azd"));
147+
}
148+
149+
@Nonnull
150+
private static Node<String> getInitializeFromTemplatesNode(Project project) {
151+
return new Node<>("Initialize from templates")
152+
.withIcon(AzureIcons.Common.CREATE)
153+
.onClicked(e -> AzdInitializeFromTemplates.showToolPopup(project));
154+
}
155+
105156
@Override
106157
public String getToolTipText(MouseEvent event) {
107158
// add tooltip for hover actions
@@ -172,7 +223,7 @@ public void refreshAll() {
172223
public static class ToolWindowFactory implements com.intellij.openapi.wm.ToolWindowFactory, DumbAware {
173224
public void createToolWindowContent(@Nonnull Project project, @Nonnull ToolWindow toolWindow) {
174225
final SimpleToolWindowPanel windowPanel = new SimpleToolWindowPanel(true, true);
175-
final AzureExplorer explorer = new AzureExplorer();
226+
final AzureExplorer explorer = new AzureExplorer(project);
176227
final JBScrollPane scrollPane = new JBScrollPane(explorer);
177228
explorer.putClientProperty(KEY_SCROLL_PANE, scrollPane);
178229
windowPanel.setContent(scrollPane);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package com.microsoft.azure.toolkit.intellij.explorer.azd;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.core.type.TypeReference;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.intellij.icons.AllIcons;
8+
import com.intellij.ide.BrowserUtil;
9+
import com.intellij.openapi.application.ApplicationManager;
10+
import com.intellij.openapi.progress.ProgressIndicator;
11+
import com.intellij.openapi.progress.ProgressManager;
12+
import com.intellij.openapi.progress.Task;
13+
import com.intellij.openapi.project.Project;
14+
import com.intellij.openapi.ui.DialogWrapper;
15+
import com.intellij.openapi.ui.VerticalFlowLayout;
16+
import com.intellij.openapi.ui.popup.JBPopup;
17+
import com.intellij.ui.HyperlinkLabel;
18+
import com.intellij.ui.ScrollPaneFactory;
19+
import com.intellij.ui.components.JBCheckBox;
20+
import com.intellij.ui.components.JBLabel;
21+
import com.intellij.ui.components.JBPanel;
22+
import com.intellij.util.ui.JBUI;
23+
import com.microsoft.azure.toolkit.intellij.common.AzureActionButton;
24+
import com.microsoft.azure.toolkit.intellij.common.TerminalUtils;
25+
import org.jdesktop.swingx.HorizontalLayout;
26+
import org.jetbrains.annotations.NotNull;
27+
import org.jetbrains.annotations.Nullable;
28+
29+
import javax.swing.*;
30+
import javax.swing.event.HyperlinkEvent;
31+
import java.awt.*;
32+
import java.io.IOException;
33+
import java.net.URL;
34+
import java.util.ArrayList;
35+
import java.util.Collections;
36+
import java.util.HashMap;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.stream.Collectors;
40+
41+
public class AzdAvailableTemplatesPopup extends JPanel {
42+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
43+
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
44+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
45+
private final Color activeColor = new Color(100, 150, 255);
46+
private final Color inactiveColor = Color.GRAY;
47+
private final Project project;
48+
private List<AzdTemplate> templates = new ArrayList<>();
49+
private List<JBCheckBox> tagButtons = new ArrayList<>();
50+
private JBPopup popup;
51+
52+
public AzdAvailableTemplatesPopup(Project project) {
53+
this.project = project;
54+
setLayout(new BorderLayout());
55+
setBorder(JBUI.Borders.empty(10));
56+
this.templates = readFromGitHub("https://raw.githubusercontent.com/Azure/awesome-azd/refs/heads/main/website/static/templates.json");
57+
58+
final List<String> allTags = topKTags(templates, 10);
59+
60+
final JPanel tilesPanel = new JPanel();
61+
final JPanel filterTagsPanel = new JPanel(new HorizontalLayout(JBUI.scale(10)));
62+
filterTagsPanel.setBorder(JBUI.Borders.emptyBottom(10)); // Adds 10px space below the panel
63+
final JBLabel filterLabel = new JBLabel("Filter by tags:");
64+
filterTagsPanel.add(filterLabel);
65+
66+
// Create scroll pane
67+
addFilters(filterTagsPanel, tilesPanel, allTags);
68+
69+
final JScrollPane tagsScrollPane = ScrollPaneFactory.createScrollPane(filterTagsPanel);
70+
add(tagsScrollPane, BorderLayout.NORTH);
71+
72+
// Create a scroll pane for the tiles
73+
tilesPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.MIDDLE, true, true));
74+
75+
tilesPanel.setBorder(JBUI.Borders.empty(10));
76+
77+
// Add scroll pane with tiles panel
78+
final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(tilesPanel);
79+
scrollPane.setBorder(JBUI.Borders.empty());
80+
add(scrollPane, BorderLayout.CENTER);
81+
82+
83+
// Load data
84+
loadData(tilesPanel, Collections.emptyList());
85+
}
86+
87+
public static List<AzdTemplate> readFromGitHub(String githubUrl) {
88+
try {
89+
// Read JSON from URL directly into the Repository model
90+
return OBJECT_MAPPER.readValue(new URL(githubUrl), new TypeReference<List<AzdTemplate>>() {});
91+
} catch (IOException e) {
92+
System.err.println("Error reading JSON from GitHub URL: " + e.getMessage());
93+
return null;
94+
}
95+
}
96+
97+
private void addFilters(JPanel filterTagsPanel, JPanel tilesPanel, List<String> allTags) {
98+
allTags.forEach(tag -> {
99+
final JBCheckBox tagCheckbox = new JBCheckBox(tag);
100+
tagCheckbox.addActionListener(e -> {
101+
final List<String> selectedTags = tagButtons.stream()
102+
.filter(AbstractButton::isSelected)
103+
.map(AbstractButton::getText)
104+
.collect(Collectors.toList());
105+
106+
loadData(tilesPanel, selectedTags);
107+
});
108+
filterTagsPanel.add(tagCheckbox);
109+
tagButtons.add(tagCheckbox);
110+
});
111+
}
112+
113+
/**
114+
* Load data from command execution and create tiles
115+
*/
116+
private void loadData(JPanel tilesPanel, List<String> tags) {
117+
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Loading Tool Data", false) {
118+
@Override
119+
public void run(@NotNull ProgressIndicator indicator) {
120+
indicator.setText("Executing command to load templates...");
121+
final List<AzdTemplate> javaTemplates = templates.stream()
122+
.filter(template -> template.getLanguages() != null && template.getLanguages().contains("java"))
123+
.toList();
124+
125+
final List<AzdTemplate> tagTemplates = javaTemplates.stream()
126+
.filter(template -> tags == null || tags.isEmpty() || template.getTags().containsAll(tags))
127+
.toList();
128+
129+
ApplicationManager.getApplication().invokeLater(() -> {
130+
tilesPanel.removeAll();
131+
132+
for (final AzdTemplate item : tagTemplates) {
133+
createToolTile(item, tilesPanel);
134+
}
135+
136+
tilesPanel.revalidate();
137+
tilesPanel.repaint();
138+
});
139+
}
140+
});
141+
}
142+
143+
private void createToolTile(AzdTemplate item, JPanel tilesPanel) {
144+
// Title at the top
145+
final JBLabel titleLabel = new JBLabel(item.getTitle());
146+
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, titleLabel.getFont().getSize() + 2));
147+
titleLabel.setBorder(JBUI.Borders.empty(0, 0, 3, 0));
148+
tilesPanel.add(titleLabel);
149+
150+
final JBLabel label = new JBLabel("<html><p style='width: 900px;'>" + item.getDescription() + "<p></html>");
151+
tilesPanel.add(label);
152+
153+
final HyperlinkLabel githubLink = new HyperlinkLabel(item.getAuthorUrl());
154+
githubLink.addHyperlinkListener(e -> BrowserUtil.browse(((HyperlinkLabel) ((HyperlinkEvent) e).getSource()).getText()));
155+
githubLink.setBorder(JBUI.Borders.empty(2, 0, 3, 0));
156+
157+
tilesPanel.add(githubLink);
158+
159+
final JBPanel commandPanel = new JBPanel(new HorizontalLayout(JBUI.scale(5)));
160+
commandPanel.setBorder(JBUI.Borders.emptyBottom(15));
161+
commandPanel.add(new JLabel((AllIcons.Debugger.Console)));
162+
163+
final JBLabel commandLabel = new JBLabel("Command: ");
164+
commandPanel.add(commandLabel);
165+
final JTextField textBox = new JTextField(100);
166+
textBox.setMaximumSize(new Dimension(200, 30));
167+
textBox.setText("azd init -t " + item.getSource());
168+
169+
textBox.setFont(textBox.getFont().deriveFont(Font.TRUETYPE_FONT, textBox.getFont().getSize()));
170+
commandPanel.add(textBox);
171+
172+
final AzureActionButton<Void> runButton = new AzureActionButton<>();
173+
runButton.setText("Run");
174+
runButton.requestFocusInWindow();
175+
runButton.addActionListener(e -> {
176+
final String command = textBox.getText();
177+
if (command.isEmpty()) {
178+
JOptionPane.showMessageDialog(this, "Command cannot be empty", "Error", JOptionPane.ERROR_MESSAGE);
179+
return;
180+
}
181+
// Show confirmation dialog
182+
RunConfirmationDialog dialog = new RunConfirmationDialog(command);
183+
dialog.setTitle("Confirm Run");
184+
dialog.show();
185+
});
186+
commandPanel.add(runButton);
187+
188+
tilesPanel.add(commandPanel);
189+
190+
final JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL);
191+
tilesPanel.add(separator);
192+
}
193+
194+
/**
195+
* Dialog to show confirmation for running a command
196+
*/
197+
private class RunConfirmationDialog extends DialogWrapper {
198+
private final String command;
199+
200+
public RunConfirmationDialog(String command) {
201+
super(project, false);
202+
this.command = command;
203+
setTitle("Confirm Run");
204+
init();
205+
}
206+
207+
@Override
208+
protected @Nullable JComponent createCenterPanel() {
209+
final JPanel panel = new JPanel(new BorderLayout());
210+
panel.add(new JLabel("Run command: " + command + "?"), BorderLayout.CENTER);
211+
return panel;
212+
}
213+
@Override
214+
protected void doOKAction() {
215+
super.doOKAction();
216+
TerminalUtils.executeInTerminal(project, command, "azd");
217+
}
218+
}
219+
220+
public void setPopup(JBPopup popup) {
221+
this.popup = popup;
222+
}
223+
224+
225+
public static List<String> topKTags(List<AzdTemplate> input, int k) {
226+
// Count occurrences
227+
final Map<String, Integer> frequencyMap = new HashMap<>();
228+
final List<String> tags = input.stream()
229+
.filter(template -> template.getLanguages() != null && template.getLanguages().contains("java"))
230+
.flatMap(template -> template.getTags().stream())
231+
.toList();
232+
233+
for (final String str : tags) {
234+
frequencyMap.put(str, frequencyMap.getOrDefault(str, 0) + 1);
235+
}
236+
237+
// Sort by frequency in descending order
238+
return frequencyMap.entrySet().stream()
239+
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
240+
.limit(k)
241+
.map(Map.Entry::getKey)
242+
.collect(Collectors.toList());
243+
}
244+
}

0 commit comments

Comments
 (0)