diff --git a/aws-toolkit-vscode.code-workspace b/aws-toolkit-vscode.code-workspace index f03cfefd48d..86ba263428b 100644 --- a/aws-toolkit-vscode.code-workspace +++ b/aws-toolkit-vscode.code-workspace @@ -1,19 +1,24 @@ { - "folders": [ - { - "path": "." - }, - { - "path": "packages/toolkit" - }, - { - "path": "packages/core" - }, - { - "path": "packages/amazonq" - } - ], - "settings": { - "typescript.tsdk": "node_modules/typescript/lib" - } -} \ No newline at end of file + "folders": [ + { + "path": ".", + }, + { + "path": "packages/toolkit", + }, + { + "path": "packages/core", + }, + { + "path": "packages/amazonq", + }, + ], + "settings": { + "typescript.tsdk": "node_modules/typescript/lib", + "workbench.colorCustomizations": { + "activityBar.background": "#0E350E", + "titleBar.activeBackground": "#134A14", + "titleBar.activeForeground": "#F3FCF3", + }, + }, +} diff --git a/package-lock.json b/package-lock.json index b6a588cc3a4..780b446c1bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20160,7 +20160,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.33.0-SNAPSHOT", + "version": "3.33.0-g3ca78ba", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 37cf850d4fb..0d1b7e13812 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -425,6 +425,48 @@ "category": "%AWS.amazonq.title%", "enablement": "aws.codewhisperer.connected" }, + { + "command": "aws.amazonq.notifications.clear", + "title": "DEBUG Notifications: Clear notifications", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications.debug" + }, + { + "command": "aws.amazonq.notifications.reset", + "title": "DEBUG Notifications: Reset notifications", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications" + }, + { + "command": "aws.amazonq.notifications.startup1", + "title": "DEBUG Notifications: Send text changelog startup", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications.debug" + }, + { + "command": "aws.amazonq.notifications.startup2", + "title": "DEBUG Notifications: Send direct link startup", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications.debug" + }, + { + "command": "aws.amazonq.notifications.emergency1", + "title": "DEBUG Notifications: Send workaround emergency", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications.debug" + }, + { + "command": "aws.amazonq.notifications.emergency2", + "title": "DEBUG Notifications: Send update and reload emergency", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications.debug" + }, + { + "command": "aws.amazonq.notifications.emergency3", + "title": "DEBUG Notifications: Send critical emergency", + "category": "%AWS.amazonq.title%", + "enablement": "aws.amazonq.notifications.debug" + }, { "command": "aws.amazonq.security.scan", "title": "%AWS.command.amazonq.security.scan%", diff --git a/packages/core/src/notifications/activation.ts b/packages/core/src/notifications/activation.ts index a563880d46f..cce34f87627 100644 --- a/packages/core/src/notifications/activation.ts +++ b/packages/core/src/notifications/activation.ts @@ -4,16 +4,17 @@ */ import * as vscode from 'vscode' -import { DevSettings } from '../shared/settings' import { NotificationsController } from './controller' import { NotificationsNode } from './panelNode' import { RuleEngine, getRuleContext } from './rules' import globals from '../shared/extensionGlobals' import { AuthState } from './types' +import { setContext } from '../shared/vscode/setContext' +import { isAmazonQ } from '../shared/extensionUtilities' import { getLogger } from '../shared/logger/logger' /** Time in MS to poll for emergency notifications */ -const emergencyPollTime = 1000 * 10 * 60 +const emergencyPollTime = 1000 * 6 /** * Activate the in-IDE notifications module and begin receiving notifications. @@ -28,13 +29,14 @@ export async function activate( authStateFn: () => Promise ) { // TODO: Currently gated behind feature-flag. - if (!DevSettings.instance.get('notifications', false)) { - return - } + // if (!DevSettings.instance.get('notifications', false)) { + // return + // } const panelNode = NotificationsNode.instance panelNode.registerView(context) + await setContext(isAmazonQ() ? 'aws.amazonq.notifications' : 'aws.toolkit.notifications', true) const controller = new NotificationsController(panelNode) const engine = new RuleEngine(await getRuleContext(context, initialState)) diff --git a/packages/core/src/notifications/controller.ts b/packages/core/src/notifications/controller.ts index 9d1636c7a24..767c4aeec86 100644 --- a/packages/core/src/notifications/controller.ts +++ b/packages/core/src/notifications/controller.ts @@ -9,6 +9,7 @@ import globals from '../shared/extensionGlobals' import { globalKey } from '../shared/globalState' import { getNotificationTelemetryId, + Notifications, NotificationsState, NotificationsStateConstructor, NotificationType, @@ -23,7 +24,9 @@ import { TreeNode } from '../shared/treeview/resourceTreeDataProvider' import { withRetries } from '../shared/utilities/functionUtils' import { FileResourceFetcher } from '../shared/resourcefetcher/fileResourceFetcher' import { isAmazonQ } from '../shared/extensionUtilities' +import { randomUUID } from '../shared/crypto' import { telemetry } from '../shared/telemetry/telemetry' +import { setContext } from '../shared/vscode/setContext' /** * Handles fetching and maintaining the state of in-IDE notifications. @@ -64,6 +67,11 @@ export class NotificationsController { dismissed: [], newlyReceived: [], }) + + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.reset' : 'aws.toolkit.notifications.reset', + async () => await globals.globalState.update(NotificationsController.instance.storageKey, {}) + ) } public pollForStartUp(ruleEngine: RuleEngine) { @@ -239,9 +247,9 @@ export class RemoteFetcher implements NotificationFetcher { public static readonly retryIntervalMs = 30000 private readonly startUpEndpoint: string = - 'https://idetoolkits-hostedfiles.amazonaws.com/Notifications/VSCode/startup/1.x.json' + 'https://idetoolkits-hostedfiles.amazonaws.com/Notifications/integ/VSCode/startup/1.x.json' private readonly emergencyEndpoint: string = - 'https://idetoolkits-hostedfiles.amazonaws.com/Notifications/VSCode/emergency/1.x.json' + 'https://idetoolkits-hostedfiles.amazonaws.com/Notifications/integ/VSCode/emergency/1.x.json' constructor(startUpPath?: string, emergencyPath?: string) { this.startUpEndpoint = startUpPath ?? this.startUpEndpoint @@ -297,3 +305,247 @@ export class LocalFetcher implements NotificationFetcher { } } } + +export class DevFetcher implements NotificationFetcher { + private startupNotifications: Notifications = { schemaVersion: '0', notifications: [] } + private emergencyNotifications: Notifications = { schemaVersion: '0', notifications: [] } + + constructor() { + void setContext(isAmazonQ() ? 'aws.amazonq.notifications.debug' : 'aws.toolkit.notifications.debug', true) + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.clear' : 'aws.toolkit.notifications.clearStartUp', + async () => { + this.startupNotifications.notifications = [] + this.emergencyNotifications.notifications = [] + await globals.globalState.update(NotificationsController.instance.storageKey, {}) + await NotificationsController.instance.pollForStartUp(new RuleEngine()) + await NotificationsController.instance.pollForEmergencies(new RuleEngine()) + } + ) + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.startup1' : 'aws.toolkit.notifications.startup1', + async () => { + const id = `startup${randomUUID()}` + this.startupNotifications.notifications.push({ + id, + displayIf: { + extensionId: globals.context.extension.id, + }, + uiRenderInstructions: { + content: { + 'en-US': { + title: 'New Amazon Q Chat features', + description: + "You can now use Amazon Q inline in your IDE, without ever touching the mouse or using copy and paste. \nPress ⌘+I (Ctrl+I on Windows) to trigger inline chat. \nDescribe a function or feature you'd like to develop and Amazon Q will generate and display a code diff that inserts new code at the cursor position. \nPress Enter to accept and apply the diff, or Escape to reject it. \nAlternatively you select a block of code (maybe even the entire file) then press ⌘+I (Ctrl+I on Windows) to provide instructions on how to refactor the selected code. \nYou will see a diff against the selected code and can press Enter to accept and apply the diff.\n\nLearn more at https://aws.amazon.com/developer/generative-ai/amazon-q/change-log/", + toastPreview: 'New Amazon Q features available: inline chat', + }, + }, + onRecieve: 'toast', + onClick: { + type: 'openTextDocument', + }, + actions: [ + { + type: 'openTxt', + displayText: { + 'en-US': 'Learn more', + }, + }, + ], + }, + }) + // eslint-disable-next-line aws-toolkits/no-json-stringify-in-log + getLogger().info( + JSON.stringify( + this.startupNotifications.notifications[this.startupNotifications.notifications.length - 1], + undefined, + 4 + ) + ) + await NotificationsController.instance.pollForStartUp(new RuleEngine()) + } + ) + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.startup2' : 'aws.toolkit.notifications.startup2', + async () => { + const id = `startup${randomUUID()}` + this.startupNotifications.notifications.push({ + id, + displayIf: { + extensionId: globals.context.extension.id, + }, + uiRenderInstructions: { + content: { + 'en-US': { + title: "What's New", + description: 'New Amazon Q Chat features available!', + toastPreview: 'New Amazon Q features are available!', + }, + }, + onRecieve: 'toast', + onClick: { + type: 'openUrl', + url: 'https://aws.amazon.com/developer/generative-ai/amazon-q/change-log/', + }, + actions: [ + { + type: 'openUrl', + url: 'https://aws.amazon.com/developer/generative-ai/amazon-q/change-log/', + displayText: { + 'en-US': 'Learn more', + }, + }, + ], + }, + }) + // eslint-disable-next-line aws-toolkits/no-json-stringify-in-log + getLogger().info( + JSON.stringify( + this.startupNotifications.notifications[this.startupNotifications.notifications.length - 1], + undefined, + 4 + ) + ) + await NotificationsController.instance.pollForStartUp(new RuleEngine()) + } + ) + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.emergency1' : 'aws.toolkit.notifications.emergency1', + async () => { + const id = `emergency${randomUUID()}` + this.emergencyNotifications.notifications.push({ + id, + displayIf: { + extensionId: globals.context.extension.id, + }, + uiRenderInstructions: { + content: { + 'en-US': { + title: "Can't sign in to Amazon Q", + description: + 'There is currently a bug that is preventing users from signing into Amazon Q. If this impacts you, please try this workaround:\n\n 1. Reload your IDE\n 2. Run the command in the command palette:: `Amazon Q: Reset State`.\n 3. Set your default region to `us-east-3`.\n 4. Try to sign into Amazon Q with your desired region in the dropdown.\n\nWe are currently working on releasing a fix so that this workaround is not required.\nPlease reach out on our github issues with any questions.', + toastPreview: + 'Signing into Amazon Q is broken, please try this workaround while we work on releasing a fix.', + }, + }, + onRecieve: 'toast', + onClick: { + type: 'openTextDocument', + }, + actions: [ + { + type: 'openTxt', + displayText: { + 'en-US': 'Learn more', + }, + }, + ], + }, + }) + // eslint-disable-next-line aws-toolkits/no-json-stringify-in-log + getLogger().info( + JSON.stringify( + this.emergencyNotifications.notifications[this.emergencyNotifications.notifications.length - 1], + undefined, + 4 + ) + ) + await NotificationsController.instance.pollForEmergencies(new RuleEngine()) + } + ) + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.emergency2' : 'aws.toolkit.notifications.emergency2', + async () => { + const id = `emergency${randomUUID()}` + this.emergencyNotifications.notifications.push({ + id, + displayIf: { + extensionId: globals.context.extension.id, + }, + uiRenderInstructions: { + content: { + 'en-US': { + title: 'Update Amazon Q to avoid breaking bugs', + description: + 'There is currently a bug that prevents Amazon Q from responding to chat requests. It is fixed in the latest version. Please update your Amazon Q now.', + toastPreview: + 'This version of Amazon Q is currently broken, please update to avoid issues.', + }, + }, + onRecieve: 'toast', + onClick: { + type: 'modal', + }, + actions: [ + { + type: 'updateAndReload', + displayText: { + 'en-US': 'Update and Reload', + }, + }, + ], + }, + }) + // eslint-disable-next-line aws-toolkits/no-json-stringify-in-log + getLogger().info( + JSON.stringify( + this.emergencyNotifications.notifications[this.emergencyNotifications.notifications.length - 1], + undefined, + 4 + ) + ) + await NotificationsController.instance.pollForEmergencies(new RuleEngine()) + } + ) + Commands.register( + isAmazonQ() ? 'aws.amazonq.notifications.emergency3' : 'aws.toolkit.notifications.emergency3', + async () => { + const id = `emergency${randomUUID()}` + this.emergencyNotifications.notifications.push({ + id, + displayIf: { + extensionId: 'amazonwebservices.amazon-q-vscode', + additionalCriteria: [{ type: 'AuthState', values: ['connected'] }], + }, + uiRenderInstructions: { + content: { + 'en-US': { + title: 'Amazon Q may delete user data', + description: + 'Amazon Q is erroneously deleting user data! Please sign out and sign back into Amazon Q immediately to avoid data loss, or update Amazon Q now.', + }, + }, + onRecieve: 'modal', + onClick: { + type: 'modal', + }, + actions: [ + { + type: 'updateAndReload', + displayText: { + 'en-US': 'Update and Reload', + }, + }, + ], + }, + }) + // eslint-disable-next-line aws-toolkits/no-json-stringify-in-log + getLogger().info( + JSON.stringify( + this.emergencyNotifications.notifications[this.emergencyNotifications.notifications.length - 1], + undefined, + 4 + ) + ) + await NotificationsController.instance.pollForEmergencies(new RuleEngine()) + } + ) + } + + async fetch(category: NotificationType, versionTag?: string): Promise { + return { + content: JSON.stringify(category === 'startUp' ? this.startupNotifications : this.emergencyNotifications), + eTag: 'DEVMODE', + } + } +} diff --git a/packages/core/src/notifications/panelNode.ts b/packages/core/src/notifications/panelNode.ts index 66a29930bcb..82c987f98c7 100644 --- a/packages/core/src/notifications/panelNode.ts +++ b/packages/core/src/notifications/panelNode.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode' import { ResourceTreeDataProvider, TreeNode } from '../shared/treeview/resourceTreeDataProvider' import { Command, Commands } from '../shared/vscode/commands2' -import { getIcon } from '../shared/icons' +import { Icon, getIcon } from '../shared/icons' import { contextKey, setContext } from '../shared/vscode/setContext' import { NotificationType, ToolkitNotification, getNotificationTelemetryId } from './types' import { ToolkitError } from '../shared/errors' @@ -34,6 +34,7 @@ export class NotificationsNode implements TreeNode { private readonly showContextStr: contextKey private readonly startUpNodeContext: string private readonly emergencyNodeContext: string + private view: vscode.TreeView | undefined static #instance: NotificationsNode @@ -78,10 +79,19 @@ export class NotificationsNode implements TreeNode { public getChildren() { const buildNode = (n: ToolkitNotification, type: NotificationType) => { + let icon: Icon & { color?: vscode.ThemeColor } + if (type === 'startUp') { + icon = getIcon('vscode-question') as Icon + } else { + icon = getIcon('vscode-alert') as Icon + icon.color = new vscode.ThemeColor('errorForeground') + } + return this.openNotificationCmd.build(n).asTreeNode({ label: n.uiRenderInstructions.content['en-US'].title, - iconPath: type === 'startUp' ? getIcon('vscode-question') : getIcon('vscode-alert'), + iconPath: icon, contextValue: type === 'startUp' ? this.startUpNodeContext : this.emergencyNodeContext, + tooltip: 'Click to open', }) } @@ -240,6 +250,10 @@ export class NotificationsNode implements TreeNode { return undefined } + get visible() { + return this.view?.visible + } + static get instance() { if (this.#instance === undefined) { this.#instance = new NotificationsNode() diff --git a/packages/core/src/notifications/rules.ts b/packages/core/src/notifications/rules.ts index ab661c80930..f39b49866b4 100644 --- a/packages/core/src/notifications/rules.ts +++ b/packages/core/src/notifications/rules.ts @@ -61,13 +61,17 @@ function isValidVersion(version: string, condition: ConditionalClause): boolean * */ export class RuleEngine { - constructor(private readonly context: RuleContext) {} + constructor(private readonly context?: RuleContext) {} public shouldDisplayNotification(payload: ToolkitNotification) { return this.evaluate(payload.displayIf) } private evaluate(condition: DisplayIf): boolean { + if (!this.context) { + return true + } + const currentExt = globals.context.extension.id if (condition.extensionId !== currentExt) { return false @@ -96,6 +100,10 @@ export class RuleEngine { } private evaluateRule(criteria: CriteriaCondition) { + if (!this.context) { + return true + } + const expected = criteria.values const expectedSet = new Set(expected) diff --git a/packages/core/src/shared/vscode/setContext.ts b/packages/core/src/shared/vscode/setContext.ts index 57450126412..715f3cd4dd8 100644 --- a/packages/core/src/shared/vscode/setContext.ts +++ b/packages/core/src/shared/vscode/setContext.ts @@ -25,6 +25,10 @@ export type contextKey = | 'aws.toolkit.amazonq.dismissed' | 'aws.toolkit.amazonqInstall.dismissed' | 'aws.toolkit.notifications.show' + | 'aws.amazonq.notifications' + | 'aws.amazonq.notifications.debug' + | 'aws.toolkit.notifications' + | 'aws.toolkit.notifications.debug' // Deprecated/legacy names. New keys should start with "aws.". | 'codewhisperer.activeLine' | 'gumby.isPlanAvailable' diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 02fb6c277f5..b81409f3eaf 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -2,7 +2,7 @@ "name": "aws-toolkit-vscode", "displayName": "AWS Toolkit", "description": "Including CodeCatalyst, Infrastructure Composer, and support for Lambda, S3, CloudWatch Logs, CloudFormation, and many other services.", - "version": "3.33.0-SNAPSHOT", + "version": "3.33.0-g3ca78ba", "extensionKind": [ "workspace" ], @@ -2314,6 +2314,42 @@ } } }, + { + "command": "aws.toolkit.notifications.clear", + "title": "DEBUG Notifications: Clear notifications", + "category": "%AWS.title%", + "enablement": "aws.toolkit.notifications" + }, + { + "command": "aws.toolkit.notifications.startup1", + "title": "DEBUG Notifications: Send text changelog startup", + "category": "%AWS.title%", + "enablement": "aws.toolkit.notifications" + }, + { + "command": "aws.toolkit.notifications.startup2", + "title": "DEBUG Notifications: Send direct link startup", + "category": "%AWS.title%", + "enablement": "aws.toolkit.notifications" + }, + { + "command": "aws.toolkit.notifications.emergency1", + "title": "DEBUG Notifications: Send workaround emergency", + "category": "%AWS.title%", + "enablement": "aws.toolkit.notifications" + }, + { + "command": "aws.toolkit.notifications.emergency2", + "title": "DEBUG Notifications: Send update and reload emergency", + "category": "%AWS.title%", + "enablement": "aws.toolkit.notifications" + }, + { + "command": "aws.toolkit.notifications.emergency3", + "title": "DEBUG Notifications: Send critical emergency", + "category": "%AWS.title%", + "enablement": "aws.toolkit.notifications" + }, { "command": "aws.codecatalyst.openOrg", "title": "%AWS.command.codecatalyst.openOrg%",