Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ public static void showInstallConfirmation(@Nonnull Project project, boolean for
: "Install this plugin to automate migrating your apps to Azure with Copilot.";
} else {
message = forUpgrade
? "To upgrade your apps, you'll need two plugins: GitHub Copilot and app modernization."
: "To migrate to Azure, you'll need two plugins: GitHub Copilot and app modernization.";
? "To upgrade your apps, you'll need to install GitHub Copilot modernization."
: "To migrate to Azure, you'll need to install GitHub Copilot modernization.";
}
AppModUtils.logTelemetryEvent("plugin." + action + ".install-prompt-shown", Map.of("copilotInstalled", String.valueOf(copilotInstalled)));
if (Messages.showOkCancelDialog(project, message, title, "Install", "Cancel", Messages.getQuestionIcon()) == Messages.OK) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.ProjectActivity;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.extensions.PluginId;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.dao.JavaUpgradeIssue;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesCache;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaVersionNotificationService;
Expand All @@ -32,6 +35,7 @@ public class JavaUpgradeCheckStartupActivity implements ProjectActivity, DumbAwa

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

@Override
public Object execute(@Nonnull Project project, @Nonnull Continuation<? super Unit> continuation) {
Expand Down Expand Up @@ -67,6 +71,12 @@ private void performJavaUpgradeCheck(@Nonnull Project project) {
if (project.isDisposed()) {
return;
}

// Warm up Copilot's lazy chat-mode indexing once per project open. Copilot only scans
// custom agents from .github/agents/ when its chat-mode registry is explicitly refreshed
// (otherwise not until the chat panel is first opened), so we trigger that refresh now —
// before the user clicks a fix action — so the agent is resolvable on the very first click.
warmUpCopilotChatModes(project);

// Refresh the cache (this populates JDK and dependency issues for use by inspections)
final JavaUpgradeIssuesCache cache = JavaUpgradeIssuesCache.getInstance(project);
Expand Down Expand Up @@ -101,4 +111,20 @@ private void performJavaUpgradeCheck(@Nonnull Project project) {
log.error("Error performing Java upgrade check for project: {}", project.getName(), e);
}
}

private void warmUpCopilotChatModes(@Nonnull Project project) {
try {
final IdeaPluginDescriptor copilot = PluginManagerCore.getPlugin(PluginId.getId(COPILOT_PLUGIN_ID));
if (copilot == null || !copilot.isEnabled() || copilot.getPluginClassLoader() == null) {
return;
}
// Actively trigger Copilot to (re)scan custom agents so its chat-mode registry is populated
// before the first fix-action click. Merely reading the chatModes StateFlow does NOT populate
// it — only refreshChatModes() does — which is why a cold first click previously missed the agent.
JavaVersionNotificationService.triggerChatModesRefresh(project, copilot.getPluginClassLoader());
} catch (Throwable e) {
// Best effort only; the fix action still falls back to its own URI path.
log.warn("Failed to warm up Copilot Chat modes: {}", project.getName(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.intellij.openapi.vfs.VirtualFile;
import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.dao.VulnerabilityInfo;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesCache;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaVersionNotificationService;
import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.ProblemsViewUtils;
import com.microsoft.azure.toolkit.intellij.appmod.utils.AppModUtils;
Expand Down Expand Up @@ -47,13 +46,15 @@ public void actionPerformed(@NotNull AnActionEvent e) {
if (vulnerabilityInfo == null) {
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(
project,
SCAN_AND_RESOLVE_CVES_PROMPT
SCAN_AND_RESOLVE_CVES_PROMPT,
APPMOD_CVE_AGENT_NAME
);
} else {
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(
project,
String.format(FIX_VULNERABLE_DEPENDENCY_WITH_COPILOT_PROMPT,
vulnerabilityInfo.getDependencyCoordinate())
vulnerabilityInfo.getDependencyCoordinate()),
APPMOD_CVE_AGENT_NAME
);
}
AppModUtils.logTelemetryEvent("openCopilotChatForCveFixDependencyInProblemsViewAction", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
Expand Down Expand Up @@ -82,19 +83,17 @@ public void update(@NotNull AnActionEvent e) {
final VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE);
final boolean isBuildFile = isBuildFile(file);

if (!isBuildFile || !isCVEIssue(description)) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
final var issue = JavaUpgradeIssuesCache.getInstance(project).findCveIssue(vulnerabilityInfo.getGroupId() + ":" + vulnerabilityInfo.getArtifactId());
if (issue == null) {
if (!isBuildFile || !isCVEIssue(description) || vulnerabilityInfo == null) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setEnabledAndVisible(true);
// e.getPresentation().setText(SCAN_AND_RESOLVE_CVES_WITH_COPILOT_DISPLAY_NAME);
final String baseText = getTemplatePresentation().getText();
if (!AppModPluginInstaller.isAppModPluginInstalled()) {
e.getPresentation().setText(e.getPresentation().getText() + AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN);
e.getPresentation().setText(baseText + AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN);
} else {
e.getPresentation().setText(baseText);
}
} catch (Throwable ex) {
// In case of any error, hide the action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws

// Try to extract dependency information from the current context
final String prompt = buildPromptFromContext(editor, file);
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_CVE_AGENT_NAME);
AppModUtils.logTelemetryEvent("openCveFixDependencyCopilotChatFromIntentionAction");
} catch (Throwable e) {
log.error("Failed to invoke CveFixDependencyIntentionAction: ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {

JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(
project,
SCAN_AND_RESOLVE_CVES_PROMPT
SCAN_AND_RESOLVE_CVES_PROMPT,
APPMOD_CVE_AGENT_NAME
);
AppModUtils.logTelemetryEvent("openCopilotChatForCveFixInProblemsViewAction", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
} catch (Throwable ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.util.Map;

import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_CVE_AGENT_NAME;
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.SCAN_AND_RESOLVE_CVES_PROMPT;
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.SCAN_AND_RESOLVE_CVES_WITH_COPILOT_DISPLAY_NAME;

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

// Try to extract dependency information from the current context
final String prompt = buildPromptFromContext();
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_CVE_AGENT_NAME);
AppModUtils.logTelemetryEvent("openCveFixCopilotChatFromIntentionAction", Map.of("AppModPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
} catch (Throwable e) {
log.error("Failed to invoke CveFixIntentionAction: ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import static com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN;
import static com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller.isAppModPluginInstalled;
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_UPGRADE_AGENT_NAME;
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.UPGRADE_JAVA_AND_FRAMEWORK_PROMPT;

/**
Expand Down Expand Up @@ -56,8 +57,11 @@ public void update(@NotNull AnActionEvent e) {
isMavenBuildFile(file) ||
isGradleBuildFile(file);
}
final String baseText = getTemplatePresentation().getText();
if (!isAppModPluginInstalled()) {
e.getPresentation().setText(e.getPresentation().getText() + TO_INSTALL_APP_MODE_PLUGIN);
e.getPresentation().setText(baseText + TO_INSTALL_APP_MODE_PLUGIN);
} else {
e.getPresentation().setText(baseText);
}
if (visible){
AppModUtils.logTelemetryEvent("showJavaUpgradeContextMenuAction", Map.of("appmodPluginInstalled", String.valueOf(isAppModPluginInstalled())));
Expand All @@ -81,7 +85,7 @@ public void actionPerformed(@NotNull AnActionEvent e) {
String prompt = buildUpgradePrompt();

// Open Copilot chat with the upgrade prompt
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME);
AppModUtils.logTelemetryEvent("openJavaUpgradeCopilotChatFromContextMenu", Map.of("appmodPluginInstalled", String.valueOf(isAppModPluginInstalled())));
} catch (Throwable ex) {
// Log error but do not crash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Map;

import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_UPGRADE_AGENT_NAME;
import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.UPGRADE_JAVA_FRAMEWORK_PROMPT;

/**
Expand Down Expand Up @@ -55,7 +56,7 @@ public String getName() {
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
try {
String prompt = buildPromptForIssue(issue);
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt);
JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME);
AppModUtils.logTelemetryEvent("openCopilotChatForJavaUpgradeQuickFix", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled())));
} catch (Throwable ex) {
log.error("Failed to apply Java upgrade quick fix", ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
package com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.action;

import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.ex.ActionPopupMenuListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.ProjectActivity;
import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller;
Expand All @@ -21,6 +26,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* Registers the Upgrade action into the GitHub Copilot context menu at runtime.
* This is needed because the Copilot plugin creates its context menu groups dynamically.
Expand All @@ -31,17 +38,67 @@ public class UpgradeActionRegistrar implements ProjectActivity {
private static final String UPGRADE_ACTION_ID = "AzureToolkit.JavaUpgradeContextMenu";
private static final String PROJECT_VIEW_POPUP_MENU = "ProjectViewPopupMenu";

// Application-level guard so we install the popup listener only once per IDE process,
// even if multiple projects are opened (this ProjectActivity runs per-project).
private static final AtomicBoolean POPUP_LISTENER_INSTALLED = new AtomicBoolean(false);

@Nullable
@Override
public Object execute(@NotNull Project project, @NotNull Continuation<? super Unit> continuation) {
try{
// Eager attempt: works on 2nd+ project open within the same IDE process,
// after the GitHub Copilot plugin has populated its dynamic submenu.
discoverAndRegisterAction();
// Lazy fallback (fixes the first-open race): re-attempt the registration
// every time the Project View popup is created. The Copilot submenu is
// guaranteed to exist by the time the user right-clicks, and the call
// is cheap + idempotent thanks to the containsAction guard.
installLazyRegistrationListener();
} catch (Throwable e) {
log.error("Failed to register Upgrade action in Copilot context menu.", e);
}
return Unit.INSTANCE;
}

/**
* Installs an application-scoped {@link ActionPopupMenuListener} (only once per IDE
* process) that re-runs {@link #discoverAndRegisterAction()} whenever the Project
* View popup menu is opened. This is the lazy fallback for the first project open
* after IDE launch, where {@link ProjectActivity}s from us and from the GitHub Copilot
* plugin race and our discovery can miss Copilot's not-yet-created submenu.
*/
private void installLazyRegistrationListener() {
if (!POPUP_LISTENER_INSTALLED.compareAndSet(false, true)) {
return;
}
try {
ActionManagerEx.getInstanceEx().addActionPopupMenuListener(new ActionPopupMenuListener() {
@Override
public void actionPopupMenuCreated(@NotNull ActionPopupMenu menu) {
// Only react to the Project View right-click popup; ignore all
// other popups (editor, tool windows, etc.) to keep this cheap.
if (!ActionPlaces.PROJECT_VIEW_POPUP.equals(menu.getPlace())) {
return;
}
try {
discoverAndRegisterAction();
} catch (Throwable ex) {
log.warn("Lazy registration of Upgrade action into Copilot submenu failed.", ex);
}
}

@Override
public void actionPopupMenuReleased(@NotNull ActionPopupMenu menu) {
// no-op
}
}, ApplicationManager.getApplication());
} catch (Throwable e) {
// Roll back the flag so a later project open can try installing again.
POPUP_LISTENER_INSTALLED.set(false);
log.warn("Failed to install lazy registration listener for Upgrade action.", e);
}
}

private void discoverAndRegisterAction() {
// Only proceed if Copilot plugin is installed
if (!AppModPluginInstaller.isCopilotInstalled()) {
Expand Down
Loading