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
4 changes: 3 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ export namespace ExtensionName {
export const APP_MODERNIZATION_FOR_JAVA = "vscjava.migrate-java-to-azure";
// Java upgrade extension is merged into app modernization extension
export const APP_MODERNIZATION_UPGRADE_FOR_JAVA = APP_MODERNIZATION_FOR_JAVA;
export const APP_MODERNIZATION_EXTENSION_NAME = "GitHub Copilot app modernization";
export const APP_MODERNIZATION_EXTENSION_NAME = "GitHub Copilot modernization";
}

export namespace Upgrade {
export const PACKAGE_ID_FOR_JAVA_RUNTIME = "java:*";
/** Minimum version of the appmod extension that supports gotoAgentMode command */
export const MIN_APPMOD_VERSION = "1.15.0";
}

/**
Expand Down
44 changes: 37 additions & 7 deletions src/upgrade/display/notificationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// Licensed under the MIT license.

import { commands, ExtensionContext, extensions, window } from "vscode";
import * as semver from "semver";
import { UpgradeReason, type IUpgradeIssuesRenderer, type UpgradeIssue } from "../type";
import { buildCVENotificationMessage, buildFixPrompt, buildNotificationMessage } from "../utility";
import { buildCVENotificationMessage, buildFixPrompt, buildNotificationMessage, type ExtensionState } from "../utility";
import { Commands } from "../../commands";
import { Settings } from "../../settings";
import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper";
import { ExtensionName } from "../../constants";
import { ExtensionName, Upgrade } from "../../constants";
import { CveUpgradeIssue } from "../cve";

const KEY_PREFIX = 'javaupgrade.notificationManager';
Expand All @@ -17,6 +18,8 @@ const BUTTON_TEXT_UPGRADE = "Upgrade Now";
const BUTTON_TEXT_FIX_CVE = "Fix Now";
const BUTTON_TEXT_INSTALL_AND_UPGRADE = "Install Extension and Upgrade";
const BUTTON_TEXT_INSTALL_AND_FIX_CVE = "Install Extension and Fix";
const BUTTON_TEXT_UPDATE_AND_UPGRADE = "Update Extension and Upgrade";
const BUTTON_TEXT_UPDATE_AND_FIX_CVE = "Update Extension and Fix";
const BUTTON_TEXT_NOT_NOW = "Not Now";

const SECONDS_IN_A_DAY = 24 * 60 * 60;
Expand All @@ -26,6 +29,18 @@ function getNowTs() {
return Number(new Date()) / 1000;
}

function getExtensionState(): ExtensionState {
const ext = extensions.getExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA);
if (!ext) {
return "not-installed";
}
const version = ext.packageJSON?.version;
if (version && semver.gte(version, Upgrade.MIN_APPMOD_VERSION)) {
return "up-to-date";
}
return "outdated";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If version is undefined, it will go to this condition.

}

class NotificationManager implements IUpgradeIssuesRenderer {
private hasShown = false;
private context?: ExtensionContext;
Expand Down Expand Up @@ -61,18 +76,33 @@ class NotificationManager implements IUpgradeIssuesRenderer {
}
this.hasShown = true;

const hasExtension = !!extensions.getExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA);
const extensionState = getExtensionState();
const prompt = buildFixPrompt(issue);

let notificationMessage = "";

if (hasCVEIssue) {
notificationMessage = buildCVENotificationMessage(cveIssues, hasExtension);
notificationMessage = buildCVENotificationMessage(cveIssues, extensionState);
} else {
notificationMessage = buildNotificationMessage(issue, hasExtension);
notificationMessage = buildNotificationMessage(issue, extensionState);
}

let upgradeButtonText: string;
let fixCVEButtonText: string;
switch (extensionState) {
case "up-to-date":
upgradeButtonText = BUTTON_TEXT_UPGRADE;
fixCVEButtonText = BUTTON_TEXT_FIX_CVE;
break;
case "outdated":
upgradeButtonText = BUTTON_TEXT_UPDATE_AND_UPGRADE;
fixCVEButtonText = BUTTON_TEXT_UPDATE_AND_FIX_CVE;
break;
case "not-installed":
upgradeButtonText = BUTTON_TEXT_INSTALL_AND_UPGRADE;
fixCVEButtonText = BUTTON_TEXT_INSTALL_AND_FIX_CVE;
break;
}
const upgradeButtonText = hasExtension ? BUTTON_TEXT_UPGRADE : BUTTON_TEXT_INSTALL_AND_UPGRADE;
const fixCVEButtonText = hasExtension ? BUTTON_TEXT_FIX_CVE : BUTTON_TEXT_INSTALL_AND_FIX_CVE;
sendInfo(operationId, {
operationName: "java.dependency.upgradeNotification.show",
});
Expand Down
43 changes: 37 additions & 6 deletions src/upgrade/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,20 @@ function findEolDate(currentVersion: string, eolDate: Record<string, string>): s
return null;
}

export function buildNotificationMessage(issue: UpgradeIssue, hasExtension: boolean): string {
export type ExtensionState = "up-to-date" | "outdated" | "not-installed";

function getActionWord(extensionState: ExtensionState, verb: string): string {
switch (extensionState) {
case "up-to-date":
return verb;
case "outdated":
return `update ${ExtensionName.APP_MODERNIZATION_EXTENSION_NAME} extension and ${verb}`;
case "not-installed":
return `install ${ExtensionName.APP_MODERNIZATION_EXTENSION_NAME} extension and ${verb}`;
}
}

export function buildNotificationMessage(issue: UpgradeIssue, extensionState: ExtensionState): string {
const {
packageId,
currentVersion,
Expand All @@ -31,7 +44,7 @@ export function buildNotificationMessage(issue: UpgradeIssue, hasExtension: bool
packageDisplayName
} = issue;

const upgradeWord = hasExtension ? "upgrade" : `install ${ExtensionName.APP_MODERNIZATION_EXTENSION_NAME} extension and upgrade`;
const upgradeWord = getActionWord(extensionState, "upgrade");

if (packageId === Upgrade.PACKAGE_ID_FOR_JAVA_RUNTIME) {
return `This project is using an older Java runtime (${currentVersion}). Would you like to ${upgradeWord} it to the latest LTS version?`;
Expand All @@ -51,7 +64,7 @@ export function buildNotificationMessage(issue: UpgradeIssue, hasExtension: bool
}
}

export function buildCVENotificationMessage(issues: CveUpgradeIssue[], hasExtension: boolean): string {
export function buildCVENotificationMessage(issues: CveUpgradeIssue[], extensionState: ExtensionState): string {

if (issues.length === 0) {
return "No CVE issues found.";
Expand Down Expand Up @@ -81,7 +94,7 @@ export function buildCVENotificationMessage(issues: CveUpgradeIssue[], hasExtens
CVESeverityDistribution: severityText,
});

const fixWord = hasExtension ? "fix" : `install ${ExtensionName.APP_MODERNIZATION_EXTENSION_NAME} extension and fix`;
const fixWord = getActionWord(extensionState, "fix");

if (issues.length === 1) {
return `${severityText} CVE vulnerability is detected in this project. Would you like to ${fixWord} it now?`;
Expand Down Expand Up @@ -155,10 +168,28 @@ export async function checkOrPopupToInstallAppModExtensionForModernization(

export async function checkOrInstallAppModExtensionForUpgrade(
extensionIdToCheck: string): Promise<void> {
if (extensions.getExtension(extensionIdToCheck)) {
return;
const ext = extensions.getExtension(extensionIdToCheck);

if (ext) {
const installedVersion = ext.packageJSON?.version;
if (installedVersion && semver.gte(installedVersion, Upgrade.MIN_APPMOD_VERSION)) {
return;
}
}

await commands.executeCommand("workbench.extensions.installExtension", ExtensionName.APP_MODERNIZATION_FOR_JAVA);

if (ext) {
// Extension was updated (not freshly installed) — reload required
const reload = await window.showInformationMessage(
"GitHub Copilot modernization extension has been updated. Reload VS Code to start the upgrade experience.",
"Reload Now"
);
if (reload === "Reload Now") {
await commands.executeCommand("workbench.action.reloadWindow");
}
return;
}

await checkOrPromptToEnableAppModExtension("upgrade");
Comment on lines 180 to 194
}
Loading