Skip to content

Commit 86b4347

Browse files
Merge pull request #12179 from fangjian0423/jimmy/fix-issue-322-324-325
Fix appmod plugin: Copilot modernization branding, CVE fix actions, and menu hover
2 parents 2dde6c9 + 1389f71 commit 86b4347

11 files changed

Lines changed: 481 additions & 24 deletions

File tree

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/common/AppModPluginInstaller.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ public static void showInstallConfirmation(@Nonnull Project project, boolean for
107107
: "Install this plugin to automate migrating your apps to Azure with Copilot.";
108108
} else {
109109
message = forUpgrade
110-
? "To upgrade your apps, you'll need two plugins: GitHub Copilot and app modernization."
111-
: "To migrate to Azure, you'll need two plugins: GitHub Copilot and app modernization.";
110+
? "To upgrade your apps, you'll need to install GitHub Copilot modernization."
111+
: "To migrate to Azure, you'll need to install GitHub Copilot modernization.";
112112
}
113113
AppModUtils.logTelemetryEvent("plugin." + action + ".install-prompt-shown", Map.of("copilotInstalled", String.valueOf(copilotInstalled)));
114114
if (Messages.showOkCancelDialog(project, message, title, "Install", "Cancel", Messages.getQuestionIcon()) == Messages.OK) {

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/JavaUpgradeCheckStartupActivity.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import com.intellij.openapi.project.DumbService;
1111
import com.intellij.openapi.project.Project;
1212
import com.intellij.openapi.startup.ProjectActivity;
13+
import com.intellij.ide.plugins.IdeaPluginDescriptor;
14+
import com.intellij.ide.plugins.PluginManagerCore;
15+
import com.intellij.openapi.extensions.PluginId;
1316
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.dao.JavaUpgradeIssue;
1417
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesCache;
1518
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaVersionNotificationService;
@@ -32,6 +35,7 @@ public class JavaUpgradeCheckStartupActivity implements ProjectActivity, DumbAwa
3235

3336
// Additional delay after smart mode to ensure Maven/Gradle sync is complete
3437
private static final long POST_INDEXING_DELAY_SECONDS = 3;
38+
private static final String COPILOT_PLUGIN_ID = "com.github.copilot";
3539

3640
@Override
3741
public Object execute(@Nonnull Project project, @Nonnull Continuation<? super Unit> continuation) {
@@ -67,6 +71,12 @@ private void performJavaUpgradeCheck(@Nonnull Project project) {
6771
if (project.isDisposed()) {
6872
return;
6973
}
74+
75+
// Warm up Copilot's lazy chat-mode indexing once per project open. Copilot only scans
76+
// custom agents from .github/agents/ when its chat-mode registry is explicitly refreshed
77+
// (otherwise not until the chat panel is first opened), so we trigger that refresh now —
78+
// before the user clicks a fix action — so the agent is resolvable on the very first click.
79+
warmUpCopilotChatModes(project);
7080

7181
// Refresh the cache (this populates JDK and dependency issues for use by inspections)
7282
final JavaUpgradeIssuesCache cache = JavaUpgradeIssuesCache.getInstance(project);
@@ -101,4 +111,20 @@ private void performJavaUpgradeCheck(@Nonnull Project project) {
101111
log.error("Error performing Java upgrade check for project: {}", project.getName(), e);
102112
}
103113
}
114+
115+
private void warmUpCopilotChatModes(@Nonnull Project project) {
116+
try {
117+
final IdeaPluginDescriptor copilot = PluginManagerCore.getPlugin(PluginId.getId(COPILOT_PLUGIN_ID));
118+
if (copilot == null || !copilot.isEnabled() || copilot.getPluginClassLoader() == null) {
119+
return;
120+
}
121+
// Actively trigger Copilot to (re)scan custom agents so its chat-mode registry is populated
122+
// before the first fix-action click. Merely reading the chatModes StateFlow does NOT populate
123+
// it — only refreshChatModes() does — which is why a cold first click previously missed the agent.
124+
JavaVersionNotificationService.triggerChatModesRefresh(project, copilot.getPluginClassLoader());
125+
} catch (Throwable e) {
126+
// Best effort only; the fix action still falls back to its own URI path.
127+
log.warn("Failed to warm up Copilot Chat modes: {}", project.getName(), e);
128+
}
129+
}
104130
}

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyInProblemsViewAction.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.intellij.openapi.vfs.VirtualFile;
1212
import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller;
1313
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.dao.VulnerabilityInfo;
14-
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesCache;
1514
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaVersionNotificationService;
1615
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.ProblemsViewUtils;
1716
import com.microsoft.azure.toolkit.intellij.appmod.utils.AppModUtils;
@@ -47,13 +46,15 @@ public void actionPerformed(@NotNull AnActionEvent e) {
4746
if (vulnerabilityInfo == null) {
4847
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(
4948
project,
50-
SCAN_AND_RESOLVE_CVES_PROMPT
49+
SCAN_AND_RESOLVE_CVES_PROMPT,
50+
APPMOD_CVE_AGENT_NAME
5151
);
5252
} else {
5353
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(
5454
project,
5555
String.format(FIX_VULNERABLE_DEPENDENCY_WITH_COPILOT_PROMPT,
56-
vulnerabilityInfo.getDependencyCoordinate())
56+
vulnerabilityInfo.getDependencyCoordinate()),
57+
APPMOD_CVE_AGENT_NAME
5758
);
5859
}
5960
AppModUtils.logTelemetryEvent("openCopilotChatForCveFixDependencyInProblemsViewAction", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
@@ -82,19 +83,17 @@ public void update(@NotNull AnActionEvent e) {
8283
final VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE);
8384
final boolean isBuildFile = isBuildFile(file);
8485

85-
if (!isBuildFile || !isCVEIssue(description)) {
86-
e.getPresentation().setEnabledAndVisible(false);
87-
return;
88-
}
89-
final var issue = JavaUpgradeIssuesCache.getInstance(project).findCveIssue(vulnerabilityInfo.getGroupId() + ":" + vulnerabilityInfo.getArtifactId());
90-
if (issue == null) {
86+
if (!isBuildFile || !isCVEIssue(description) || vulnerabilityInfo == null) {
9187
e.getPresentation().setEnabledAndVisible(false);
9288
return;
9389
}
9490
e.getPresentation().setEnabledAndVisible(true);
9591
// e.getPresentation().setText(SCAN_AND_RESOLVE_CVES_WITH_COPILOT_DISPLAY_NAME);
92+
final String baseText = getTemplatePresentation().getText();
9693
if (!AppModPluginInstaller.isAppModPluginInstalled()) {
97-
e.getPresentation().setText(e.getPresentation().getText() + AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN);
94+
e.getPresentation().setText(baseText + AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN);
95+
} else {
96+
e.getPresentation().setText(baseText);
9897
}
9998
} catch (Throwable ex) {
10099
// In case of any error, hide the action

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyIntentionAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws
103103

104104
// Try to extract dependency information from the current context
105105
final String prompt = buildPromptFromContext(editor, file);
106-
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
106+
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_CVE_AGENT_NAME);
107107
AppModUtils.logTelemetryEvent("openCveFixDependencyCopilotChatFromIntentionAction");
108108
} catch (Throwable e) {
109109
log.error("Failed to invoke CveFixDependencyIntentionAction: ", e);

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixInProblemsViewAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
4646

4747
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(
4848
project,
49-
SCAN_AND_RESOLVE_CVES_PROMPT
49+
SCAN_AND_RESOLVE_CVES_PROMPT,
50+
APPMOD_CVE_AGENT_NAME
5051
);
5152
AppModUtils.logTelemetryEvent("openCopilotChatForCveFixInProblemsViewAction", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
5253
} catch (Throwable ex) {

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixIntentionAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import java.util.Map;
2626

27+
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_CVE_AGENT_NAME;
2728
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.SCAN_AND_RESOLVE_CVES_PROMPT;
2829
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.SCAN_AND_RESOLVE_CVES_WITH_COPILOT_DISPLAY_NAME;
2930

@@ -107,7 +108,7 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws
107108

108109
// Try to extract dependency information from the current context
109110
final String prompt = buildPromptFromContext();
110-
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
111+
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_CVE_AGENT_NAME);
111112
AppModUtils.logTelemetryEvent("openCveFixCopilotChatFromIntentionAction", Map.of("AppModPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
112113
} catch (Throwable e) {
113114
log.error("Failed to invoke CveFixIntentionAction: ", e);

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeContextMenuAction.java

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

2222
import static com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN;
2323
import static com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller.isAppModPluginInstalled;
24+
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_UPGRADE_AGENT_NAME;
2425
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.UPGRADE_JAVA_AND_FRAMEWORK_PROMPT;
2526

2627
/**
@@ -56,8 +57,11 @@ public void update(@NotNull AnActionEvent e) {
5657
isMavenBuildFile(file) ||
5758
isGradleBuildFile(file);
5859
}
60+
final String baseText = getTemplatePresentation().getText();
5961
if (!isAppModPluginInstalled()) {
60-
e.getPresentation().setText(e.getPresentation().getText() + TO_INSTALL_APP_MODE_PLUGIN);
62+
e.getPresentation().setText(baseText + TO_INSTALL_APP_MODE_PLUGIN);
63+
} else {
64+
e.getPresentation().setText(baseText);
6165
}
6266
if (visible){
6367
AppModUtils.logTelemetryEvent("showJavaUpgradeContextMenuAction", Map.of("appmodPluginInstalled", String.valueOf(isAppModPluginInstalled())));
@@ -81,7 +85,7 @@ public void actionPerformed(@NotNull AnActionEvent e) {
8185
String prompt = buildUpgradePrompt();
8286

8387
// Open Copilot chat with the upgrade prompt
84-
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
88+
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME);
8589
AppModUtils.logTelemetryEvent("openJavaUpgradeCopilotChatFromContextMenu", Map.of("appmodPluginInstalled", String.valueOf(isAppModPluginInstalled())));
8690
} catch (Throwable ex) {
8791
// Log error but do not crash

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeQuickFix.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Map;
2020

21+
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_UPGRADE_AGENT_NAME;
2122
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.UPGRADE_JAVA_FRAMEWORK_PROMPT;
2223

2324
/**
@@ -55,7 +56,7 @@ public String getName() {
5556
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
5657
try {
5758
String prompt = buildPromptForIssue(issue);
58-
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
59+
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME);
5960
AppModUtils.logTelemetryEvent("openCopilotChatForJavaUpgradeQuickFix", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
6061
} catch (Throwable ex) {
6162
log.error("Failed to apply Java upgrade quick fix", ex);

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/UpgradeActionRegistrar.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
package com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.action;
77

88
import com.intellij.openapi.actionSystem.ActionManager;
9+
import com.intellij.openapi.actionSystem.ActionPlaces;
10+
import com.intellij.openapi.actionSystem.ActionPopupMenu;
911
import com.intellij.openapi.actionSystem.AnAction;
1012
import com.intellij.openapi.actionSystem.DefaultActionGroup;
1113
import com.intellij.openapi.actionSystem.Presentation;
1214
import com.intellij.openapi.actionSystem.Separator;
15+
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
16+
import com.intellij.openapi.actionSystem.ex.ActionPopupMenuListener;
17+
import com.intellij.openapi.application.ApplicationManager;
1318
import com.intellij.openapi.project.Project;
1419
import com.intellij.openapi.startup.ProjectActivity;
1520
import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller;
@@ -21,6 +26,8 @@
2126
import org.jetbrains.annotations.NotNull;
2227
import org.jetbrains.annotations.Nullable;
2328

29+
import java.util.concurrent.atomic.AtomicBoolean;
30+
2431
/**
2532
* Registers the Upgrade action into the GitHub Copilot context menu at runtime.
2633
* This is needed because the Copilot plugin creates its context menu groups dynamically.
@@ -31,17 +38,67 @@ public class UpgradeActionRegistrar implements ProjectActivity {
3138
private static final String UPGRADE_ACTION_ID = "AzureToolkit.JavaUpgradeContextMenu";
3239
private static final String PROJECT_VIEW_POPUP_MENU = "ProjectViewPopupMenu";
3340

41+
// Application-level guard so we install the popup listener only once per IDE process,
42+
// even if multiple projects are opened (this ProjectActivity runs per-project).
43+
private static final AtomicBoolean POPUP_LISTENER_INSTALLED = new AtomicBoolean(false);
44+
3445
@Nullable
3546
@Override
3647
public Object execute(@NotNull Project project, @NotNull Continuation<? super Unit> continuation) {
3748
try{
49+
// Eager attempt: works on 2nd+ project open within the same IDE process,
50+
// after the GitHub Copilot plugin has populated its dynamic submenu.
3851
discoverAndRegisterAction();
52+
// Lazy fallback (fixes the first-open race): re-attempt the registration
53+
// every time the Project View popup is created. The Copilot submenu is
54+
// guaranteed to exist by the time the user right-clicks, and the call
55+
// is cheap + idempotent thanks to the containsAction guard.
56+
installLazyRegistrationListener();
3957
} catch (Throwable e) {
4058
log.error("Failed to register Upgrade action in Copilot context menu.", e);
4159
}
4260
return Unit.INSTANCE;
4361
}
4462

63+
/**
64+
* Installs an application-scoped {@link ActionPopupMenuListener} (only once per IDE
65+
* process) that re-runs {@link #discoverAndRegisterAction()} whenever the Project
66+
* View popup menu is opened. This is the lazy fallback for the first project open
67+
* after IDE launch, where {@link ProjectActivity}s from us and from the GitHub Copilot
68+
* plugin race and our discovery can miss Copilot's not-yet-created submenu.
69+
*/
70+
private void installLazyRegistrationListener() {
71+
if (!POPUP_LISTENER_INSTALLED.compareAndSet(false, true)) {
72+
return;
73+
}
74+
try {
75+
ActionManagerEx.getInstanceEx().addActionPopupMenuListener(new ActionPopupMenuListener() {
76+
@Override
77+
public void actionPopupMenuCreated(@NotNull ActionPopupMenu menu) {
78+
// Only react to the Project View right-click popup; ignore all
79+
// other popups (editor, tool windows, etc.) to keep this cheap.
80+
if (!ActionPlaces.PROJECT_VIEW_POPUP.equals(menu.getPlace())) {
81+
return;
82+
}
83+
try {
84+
discoverAndRegisterAction();
85+
} catch (Throwable ex) {
86+
log.warn("Lazy registration of Upgrade action into Copilot submenu failed.", ex);
87+
}
88+
}
89+
90+
@Override
91+
public void actionPopupMenuReleased(@NotNull ActionPopupMenu menu) {
92+
// no-op
93+
}
94+
}, ApplicationManager.getApplication());
95+
} catch (Throwable e) {
96+
// Roll back the flag so a later project open can try installing again.
97+
POPUP_LISTENER_INSTALLED.set(false);
98+
log.warn("Failed to install lazy registration listener for Upgrade action.", e);
99+
}
100+
}
101+
45102
private void discoverAndRegisterAction() {
46103
// Only proceed if Copilot plugin is installed
47104
if (!AppModPluginInstaller.isCopilotInstalled()) {

0 commit comments

Comments
 (0)