44 */
55
66import * as vscode from 'vscode'
7+ import * as nls from 'vscode-nls'
78import { ResourceTreeDataProvider , TreeNode } from '../shared/treeview/resourceTreeDataProvider'
89import { Command , Commands } from '../shared/vscode/commands2'
9- import { Icon , IconPath , getIcon } from '../shared/icons'
10+ import { Icon , getIcon } from '../shared/icons'
1011import { contextKey , setContext } from '../shared/vscode/setContext'
11- import { NotificationType , ToolkitNotification , getNotificationTelemetryId } from './types'
12+ import { NotificationType , OnReceiveType , ToolkitNotification , getNotificationTelemetryId } from './types'
1213import { ToolkitError } from '../shared/errors'
1314import { isAmazonQ } from '../shared/extensionUtilities'
1415import { getLogger } from '../shared/logger/logger'
1516import { registerToolView } from '../awsexplorer/activationShared'
1617import { readonlyDocument } from '../shared/utilities/textDocumentUtilities'
1718import { openUrl } from '../shared/utilities/vsCodeUtils'
1819import { telemetry } from '../shared/telemetry/telemetry'
20+ import { globals } from '../shared'
21+
22+ const localize = nls . loadMessageBundle ( )
23+ const logger = getLogger ( 'notifications' )
1924
2025/**
2126 * Controls the "Notifications" side panel/tree in each extension. It takes purely UX actions
2227 * and does not determine what notifications to dispaly or how to fetch and store them.
2328 */
2429export class NotificationsNode implements TreeNode {
30+ public static readonly title = localize ( 'AWS.notifications.title' , 'Notifications' )
31+
2532 public readonly id = 'notifications'
2633 public readonly resource = this
2734 public provider ?: ResourceTreeDataProvider
@@ -34,6 +41,7 @@ export class NotificationsNode implements TreeNode {
3441 private readonly showContextStr : contextKey
3542 private readonly startUpNodeContext : string
3643 private readonly emergencyNodeContext : string
44+ private view : vscode . TreeView < TreeNode > | undefined
3745
3846 static #instance: NotificationsNode
3947
@@ -62,31 +70,49 @@ export class NotificationsNode implements TreeNode {
6270 }
6371
6472 public getTreeItem ( ) {
65- const item = new vscode . TreeItem ( 'Notifications' )
73+ const item = new vscode . TreeItem ( NotificationsNode . title )
6674 item . collapsibleState = vscode . TreeItemCollapsibleState . Collapsed
6775 item . contextValue = 'notifications'
6876
6977 return item
7078 }
7179
72- public refresh ( ) : void {
73- const hasNotifications = this . startUpNotifications . length > 0 || this . emergencyNotifications . length > 0
74- void setContext ( this . showContextStr , hasNotifications )
80+ public refresh ( ) {
81+ const totalNotifications = this . notificationCount ( )
82+ if ( this . view ) {
83+ if ( totalNotifications > 0 ) {
84+ this . view . badge = {
85+ tooltip : `${ totalNotifications } notification${ totalNotifications > 1 ? 's' : '' } ` ,
86+ value : totalNotifications ,
87+ }
88+ this . view . title = `${ NotificationsNode . title } (${ totalNotifications } )`
89+ } else {
90+ this . view . badge = undefined
91+ this . view . title = NotificationsNode . title
92+ }
93+ } else {
94+ logger . warn ( 'NotificationsNode was refreshed but the view was not initialized!' )
95+ }
7596
7697 this . provider ?. refresh ( )
98+ return setContext ( this . showContextStr , totalNotifications > 0 )
7799 }
78100
79101 public getChildren ( ) {
80102 const buildNode = ( n : ToolkitNotification , type : NotificationType ) => {
81- const icon : Icon | IconPath =
82- type === 'startUp'
83- ? getIcon ( 'vscode-question' )
84- : { ...getIcon ( 'vscode-alert' ) , color : new vscode . ThemeColor ( 'errorForeground' ) }
103+ const icon : Icon =
104+ type === 'emergency'
105+ ? Object . assign ( getIcon ( 'vscode-alert' ) as Icon , {
106+ color : new vscode . ThemeColor ( 'errorForeground' ) ,
107+ } )
108+ : ( getIcon ( 'vscode-question' ) as Icon )
109+
110+ const title = n . uiRenderInstructions . content [ 'en-US' ] . title
85111 return this . openNotificationCmd . build ( n ) . asTreeNode ( {
86- label : n . uiRenderInstructions . content [ 'en-US' ] . title ,
112+ label : title ,
113+ tooltip : title ,
87114 iconPath : icon ,
88115 contextValue : type === 'startUp' ? this . startUpNodeContext : this . emergencyNodeContext ,
89- tooltip : 'Click to open' ,
90116 } )
91117 }
92118
@@ -100,10 +126,10 @@ export class NotificationsNode implements TreeNode {
100126 * Sets the current list of notifications. Nodes are generated for each notification.
101127 * No other processing is done, see NotificationController.
102128 */
103- public setNotifications ( startUp : ToolkitNotification [ ] , emergency : ToolkitNotification [ ] ) {
129+ public async setNotifications ( startUp : ToolkitNotification [ ] , emergency : ToolkitNotification [ ] ) {
104130 this . startUpNotifications = startUp
105131 this . emergencyNotifications = emergency
106- this . refresh ( )
132+ await this . refresh ( )
107133 }
108134
109135 /**
@@ -112,9 +138,9 @@ export class NotificationsNode implements TreeNode {
112138 *
113139 * Only dismisses startup notifications.
114140 */
115- public dismissStartUpNotification ( id : string ) {
141+ public async dismissStartUpNotification ( id : string ) {
116142 this . startUpNotifications = this . startUpNotifications . filter ( ( n ) => n . id !== id )
117- this . refresh ( )
143+ await this . refresh ( )
118144 }
119145
120146 /**
@@ -124,6 +150,10 @@ export class NotificationsNode implements TreeNode {
124150 return vscode . commands . executeCommand ( this . focusCmdStr )
125151 }
126152
153+ private notificationCount ( ) {
154+ return this . startUpNotifications . length + this . emergencyNotifications . length
155+ }
156+
127157 /**
128158 * Fired when a notification is clicked on in the panel. It will run any rendering
129159 * instructions included in the notification. See {@link ToolkitNotification.uiRenderInstructions}.
@@ -132,23 +162,23 @@ export class NotificationsNode implements TreeNode {
132162 switch ( notification . uiRenderInstructions . onClick . type ) {
133163 case 'modal' :
134164 // Render blocking modal
135- getLogger ( 'notifications' ) . verbose ( `rendering modal for notificaiton: ${ notification . id } ...` )
165+ logger . verbose ( `rendering modal for notificaiton: ${ notification . id } ...` )
136166 await this . showInformationWindow ( notification , 'modal' , false )
137167 break
138168 case 'openUrl' :
139169 // Show open url option
140170 if ( ! notification . uiRenderInstructions . onClick . url ) {
141171 throw new ToolkitError ( 'No url provided for onclick open url' )
142172 }
143- getLogger ( 'notifications' ) . verbose ( `opening url for notification: ${ notification . id } ...` )
173+ logger . verbose ( `opening url for notification: ${ notification . id } ...` )
144174 await openUrl (
145175 vscode . Uri . parse ( notification . uiRenderInstructions . onClick . url ) ,
146176 getNotificationTelemetryId ( notification )
147177 )
148178 break
149179 case 'openTextDocument' :
150180 // Display read-only txt document
151- getLogger ( 'notifications' ) . verbose ( `showing txt document for notification: ${ notification . id } ...` )
181+ logger . verbose ( `showing txt document for notification: ${ notification . id } ...` )
152182 await telemetry . toolkit_invokeAction . run ( async ( ) => {
153183 telemetry . record ( { source : getNotificationTelemetryId ( notification ) , action : 'openTxt' } )
154184 await readonlyDocument . show (
@@ -165,7 +195,11 @@ export class NotificationsNode implements TreeNode {
165195 * Can be either a blocking modal or a bottom-right corner toast
166196 * Handles the button click actions based on the button type.
167197 */
168- private showInformationWindow ( notification : ToolkitNotification , type : string = 'toast' , passive : boolean = false ) {
198+ private showInformationWindow (
199+ notification : ToolkitNotification ,
200+ type : OnReceiveType = 'toast' ,
201+ passive : boolean = false
202+ ) {
169203 const isModal = type === 'modal'
170204
171205 // modal has to have defined actions (buttons)
@@ -203,7 +237,10 @@ export class NotificationsNode implements TreeNode {
203237 )
204238 break
205239 case 'updateAndReload' :
206- await this . updateAndReload ( notification . displayIf . extensionId )
240+ // Give things time to finish executing.
241+ globals . clock . setTimeout ( ( ) => {
242+ return this . updateAndReload ( notification . displayIf . extensionId )
243+ } , 1000 )
207244 break
208245 case 'openUrl' :
209246 if ( selectedButton . url ) {
@@ -228,7 +265,11 @@ export class NotificationsNode implements TreeNode {
228265 }
229266
230267 private async updateAndReload ( id : string ) {
231- getLogger ( 'notifications' ) . verbose ( 'Updating and reloading the extension...' )
268+ logger . verbose ( 'Updating and reloading the extension...' )
269+
270+ // Publish pending telemetry before it is lost to the window reload.
271+ await globals . telemetry . flushRecords ( )
272+
232273 await vscode . commands . executeCommand ( 'workbench.extensions.installExtension' , id )
233274 await vscode . commands . executeCommand ( 'workbench.action.reloadWindow' )
234275 }
@@ -258,14 +299,13 @@ export class NotificationsNode implements TreeNode {
258299 }
259300
260301 registerView ( context : vscode . ExtensionContext ) {
261- const view = registerToolView (
302+ this . view = registerToolView (
262303 {
263304 nodes : [ this ] ,
264305 view : isAmazonQ ( ) ? 'aws.amazonq.notifications' : 'aws.toolkit.notifications' ,
265306 refreshCommands : [ ( provider : ResourceTreeDataProvider ) => this . registerProvider ( provider ) ] ,
266307 } ,
267308 context
268309 )
269- view . message = `New feature announcements and emergency notifications for ${ isAmazonQ ( ) ? 'Amazon Q' : 'AWS Toolkit' } will appear here.`
270310 }
271311}
0 commit comments