@@ -26,6 +26,8 @@ import { FileResourceFetcher } from '../shared/resourcefetcher/fileResourceFetch
2626import { isAmazonQ } from '../shared/extensionUtilities'
2727import { telemetry } from '../shared/telemetry/telemetry'
2828
29+ const logger = getLogger ( 'notifications' )
30+
2931/**
3032 * Handles fetching and maintaining the state of in-IDE notifications.
3133 * Notifications are constantly polled from a known endpoint and then stored in global state.
@@ -39,8 +41,6 @@ import { telemetry } from '../shared/telemetry/telemetry'
3941 * Emergency notifications - fetched at a regular interval.
4042 */
4143export class NotificationsController {
42- public static readonly suggestedPollIntervalMs = 1000 * 60 * 10 // 10 minutes
43-
4444 /** Internal memory state that is written to global state upon modification. */
4545 private readonly state : NotificationsState
4646
@@ -75,7 +75,7 @@ export class NotificationsController {
7575 this . readState ( )
7676 await this . fetchNotifications ( category )
7777 } catch ( err : any ) {
78- getLogger ( 'notifications' ) . error ( `Unable to fetch %s notifications: %s` , category , err )
78+ logger . error ( `Unable to fetch %s notifications: %s` , category , err )
7979 }
8080
8181 await this . displayNotifications ( ruleEngine )
@@ -93,25 +93,20 @@ export class NotificationsController {
9393
9494 await NotificationsNode . instance . setNotifications ( startUp , emergency )
9595
96- // Emergency notifications can't be dismissed, but if the user minimizes the panel then
97- // we don't want to focus it each time we set the notification nodes.
98- // So we store it in dismissed once a focus has been fired for it.
99- const newEmergencies = emergency . map ( ( n ) => n . id ) . filter ( ( id ) => ! dismissed . has ( id ) )
100- if ( newEmergencies . length > 0 ) {
101- this . state . dismissed = [ ...this . state . dismissed , ...newEmergencies ]
102- await this . writeState ( )
103- void this . notificationsNode . focusPanel ( )
104- }
105-
10696 // Process on-receive behavior for newly received notifications that passes rule engine
107- const newlyReceivedToDisplay = [ ...startUp , ...emergency ] . filter ( ( n ) => this . state . newlyReceived . includes ( n . id ) )
108- if ( newlyReceivedToDisplay . length > 0 ) {
109- await this . notificationsNode . onReceiveNotifications ( newlyReceivedToDisplay )
97+ const wasNewlyReceived = ( n : ToolkitNotification ) => this . state . newlyReceived . includes ( n . id )
98+ const newStartUp = startUp . filter ( wasNewlyReceived )
99+ const newEmergency = emergency . filter ( wasNewlyReceived )
100+ const newlyReceived = [ ...newStartUp , ...newEmergency ]
101+
102+ if ( newlyReceived . length > 0 ) {
103+ await this . notificationsNode . onReceiveNotifications ( newlyReceived )
110104 // remove displayed notifications from newlyReceived
111- this . state . newlyReceived = this . state . newlyReceived . filter (
112- ( id ) => ! newlyReceivedToDisplay . some ( ( n ) => n . id === id )
113- )
105+ this . state . newlyReceived = this . state . newlyReceived . filter ( ( id ) => ! newlyReceived . some ( ( n ) => n . id === id ) )
114106 await this . writeState ( )
107+ if ( newEmergency . length > 0 ) {
108+ void this . notificationsNode . focusPanel ( )
109+ }
115110 }
116111 }
117112
@@ -121,7 +116,9 @@ export class NotificationsController {
121116 * hide all notifications.
122117 */
123118 public async dismissNotification ( notificationId : string ) {
124- getLogger ( 'notifications' ) . debug ( 'Dismissing notification: %s' , notificationId )
119+ logger . debug ( 'Dismissing notification: %s' , notificationId )
120+
121+ this . readState ( ) // Don't overwrite dismissals from other windows
125122 this . state . dismissed . push ( notificationId )
126123 await this . writeState ( )
127124
@@ -134,7 +131,7 @@ export class NotificationsController {
134131 private async fetchNotifications ( category : NotificationType ) {
135132 const response = await this . fetcher . fetch ( category , this . state [ category ] . eTag )
136133 if ( ! response . content ) {
137- getLogger ( 'notifications' ) . verbose ( 'No new notifications for category: %s' , category )
134+ logger . verbose ( 'No new notifications for category: %s' , category )
138135 return
139136 }
140137 // Parse the notifications
@@ -149,7 +146,7 @@ export class NotificationsController {
149146 const addedNotifications = newNotifications . filter ( ( n : any ) => ! currentNotificationIds . has ( n . id ) )
150147
151148 if ( addedNotifications . length > 0 ) {
152- getLogger ( 'notifications' ) . verbose (
149+ logger . verbose (
153150 'New notifications received for category %s, ids: %s' ,
154151 category ,
155152 addedNotifications . map ( ( n : any ) => n . id ) . join ( ', ' )
@@ -161,7 +158,7 @@ export class NotificationsController {
161158 this . state [ category ] . eTag = response . eTag
162159 await this . writeState ( )
163160
164- getLogger ( 'notifications' ) . verbose (
161+ logger . verbose (
165162 "Fetched notifications JSON for category '%s' with schema version: %s. There were %d notifications." ,
166163 category ,
167164 this . state [ category ] . payload ?. schemaVersion ,
@@ -173,7 +170,7 @@ export class NotificationsController {
173170 * Write the latest memory state to global state.
174171 */
175172 private async writeState ( ) {
176- getLogger ( 'notifications' ) . debug ( 'NotificationsController: Updating notifications state at %s' , this . storageKey )
173+ logger . debug ( 'NotificationsController: Updating notifications state at %s' , this . storageKey )
177174
178175 // Clean out anything in 'dismissed' that doesn't exist anymore.
179176 const notifications = new Set (
@@ -199,7 +196,7 @@ export class NotificationsController {
199196 */
200197 private readState ( ) {
201198 const state = this . getDefaultState ( )
202- this . state . dismissed = state . dismissed
199+ this . state . dismissed = [ ... new Set ( [ ... this . state . dismissed , ... state . dismissed ] ) ]
203200 }
204201
205202 /**
@@ -239,7 +236,7 @@ function registerDismissCommand() {
239236 await NotificationsController . instance . dismissNotification ( notification . id )
240237 } )
241238 } else {
242- getLogger ( 'notifications' ) . error ( `${ name } : Cannot dismiss notification: item is not a vscode.TreeItem` )
239+ logger . error ( `${ name } : Cannot dismiss notification: item is not a vscode.TreeItem` )
243240 }
244241 } )
245242 )
@@ -277,17 +274,23 @@ export class RemoteFetcher implements NotificationFetcher {
277274 const fetcher = new HttpResourceFetcher ( endpoint , {
278275 showUrl : true ,
279276 } )
280- getLogger ( 'notifications' ) . verbose (
281- 'Attempting to fetch notifications for category: %s at endpoint: %s' ,
282- category ,
283- endpoint
277+ logger . verbose ( 'Attempting to fetch notifications for category: %s at endpoint: %s' , category , endpoint )
278+
279+ return withRetries (
280+ async ( ) => {
281+ try {
282+ return await fetcher . getNewETagContent ( versionTag )
283+ } catch ( err ) {
284+ logger . error ( 'Failed to fetch at endpoint: %s, err: %s' , endpoint , err )
285+ throw err
286+ }
287+ } ,
288+ {
289+ maxRetries : RemoteFetcher . retryNumber ,
290+ delay : RemoteFetcher . retryIntervalMs ,
291+ // No exponential backoff - necessary?
292+ }
284293 )
285-
286- return withRetries ( async ( ) => await fetcher . getNewETagContent ( versionTag ) , {
287- maxRetries : RemoteFetcher . retryNumber ,
288- delay : RemoteFetcher . retryIntervalMs ,
289- // No exponential backoff - necessary?
290- } )
291294 }
292295}
293296
@@ -309,11 +312,7 @@ export class LocalFetcher implements NotificationFetcher {
309312
310313 async fetch ( category : NotificationType , versionTag ?: string ) : Promise < ResourceResponse > {
311314 const uri = category === 'startUp' ? this . startUpLocalPath : this . emergencyLocalPath
312- getLogger ( 'notifications' ) . verbose (
313- 'Attempting to fetch notifications locally for category: %s at path: %s' ,
314- category ,
315- uri
316- )
315+ logger . verbose ( 'Attempting to fetch notifications locally for category: %s at path: %s' , category , uri )
317316
318317 return {
319318 content : await new FileResourceFetcher ( globals . context . asAbsolutePath ( uri ) ) . get ( ) ,
0 commit comments