Skip to content

Commit 813be15

Browse files
committed
implemented on-click and on-receive notification rendering
1 parent 42f49a8 commit 813be15

File tree

3 files changed

+153
-9
lines changed

3 files changed

+153
-9
lines changed

packages/core/src/notifications/controller.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class NotificationsController {
4242

4343
constructor(
4444
private readonly notificationsNode: NotificationsNode,
45-
private readonly fetcher: NotificationFetcher = new RemoteFetcher()
45+
private readonly fetcher: NotificationFetcher = new LocalFetcher()
4646
) {
4747
if (!NotificationsController.#instance) {
4848
// Register on first creation only.
@@ -63,6 +63,7 @@ export class NotificationsController {
6363
}
6464

6565
public pollForEmergencies(ruleEngine: RuleEngine) {
66+
getLogger().info('polling .....')
6667
return this.poll(ruleEngine, 'emergency')
6768
}
6869

@@ -121,8 +122,25 @@ export class NotificationsController {
121122
getLogger('notifications').verbose('No new notifications for category: %s', category)
122123
return
123124
}
124-
125-
getLogger('notifications').verbose('ETAG has changed for notifications category: %s', category)
125+
// Parse the notifications
126+
const newPayload = JSON.parse(response.content)
127+
const newNotifications = newPayload.notifications ?? []
128+
129+
// Get the current notifications
130+
const currentNotifications = this.state[category].payload?.notifications ?? []
131+
const currentNotificationIds = new Set(currentNotifications.map((n: any) => n.id))
132+
133+
// Compare and find if there's any notifications newly added
134+
const addedNotifications = newNotifications.filter((n: any) => !currentNotificationIds.has(n.id))
135+
136+
if (addedNotifications.length > 0) {
137+
getLogger('notifications').info(
138+
'New notifications received for category %s, ids: %s',
139+
category,
140+
addedNotifications.map((n: any) => n.id).join(', ')
141+
)
142+
await this.onReceiveNotifications(addedNotifications)
143+
}
126144

127145
this.state[category].payload = JSON.parse(response.content)
128146
this.state[category].eTag = response.eTag
@@ -136,6 +154,23 @@ export class NotificationsController {
136154
)
137155
}
138156

157+
private async onReceiveNotifications(notifications: ToolkitNotification[]) {
158+
for (const notification of notifications) {
159+
switch (notification.uiRenderInstructions.onRecieve) {
160+
case 'modal':
161+
// Handle modal case
162+
void this.notificationsNode.renderModal(notification)
163+
break
164+
case 'toast':
165+
// toast case, no user input needed
166+
void vscode.window.showInformationMessage(
167+
notification.uiRenderInstructions.content['en-US'].descriptionPreview ??
168+
notification.uiRenderInstructions.content['en-US'].title
169+
)
170+
break
171+
}
172+
}
173+
}
139174
/**
140175
* Write the latest memory state to global state.
141176
*/

packages/core/src/notifications/panelNode.ts

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import { contextKey, setContext } from '../shared/vscode/setContext'
1111
import { NotificationType, ToolkitNotification } from './types'
1212
import { ToolkitError } from '../shared/errors'
1313
import { isAmazonQ } from '../shared/extensionUtilities'
14+
import { getLogger } from '../shared/logger/logger'
15+
import { tempDirPath } from '../shared/filesystemUtilities'
16+
import path from 'path'
17+
import fs from '../shared/fs/fs'
1418

1519
/**
1620
* Controls the "Notifications" side panel/tree in each extension. It takes purely UX actions
@@ -129,12 +133,105 @@ export class NotificationsNode implements TreeNode {
129133
*
130134
* TODO: implement more rendering possibilites.
131135
*/
132-
private async openNotification(notification: ToolkitNotification) {
133-
await vscode.window.showTextDocument(
134-
await vscode.workspace.openTextDocument({
135-
content: notification.uiRenderInstructions.content['en-US'].description,
136-
})
136+
public async openNotification(notification: ToolkitNotification) {
137+
switch (notification.uiRenderInstructions.onClick.type) {
138+
case 'modal':
139+
// Handle modal case
140+
await this.renderModal(notification)
141+
break
142+
case 'openUrl':
143+
if (!notification.uiRenderInstructions.onClick.url) {
144+
throw new ToolkitError('No url provided for onclick open url')
145+
}
146+
// Handle openUrl case
147+
await vscode.env.openExternal(vscode.Uri.parse(notification.uiRenderInstructions.onClick.url))
148+
break
149+
case 'openTextDocument':
150+
// Handle openTextDocument case
151+
await this.showReadonlyTextDocument(notification.uiRenderInstructions.content['en-US'].description)
152+
break
153+
}
154+
}
155+
156+
/**
157+
* Shows a read only txt file for the contect of notification on a side column
158+
* It's read-only so that the "save" option doesn't appear when user closes the notification
159+
*/
160+
private async showReadonlyTextDocument(content: string): Promise<void> {
161+
getLogger('notifications').info('showing txt document ... ')
162+
try {
163+
const tempFilePath = path.join(tempDirPath, 'AWSToolkitNotifications.txt')
164+
165+
if (await fs.existsFile(tempFilePath)) {
166+
// If file exist, make sure it has write permission (0o644)
167+
await fs.chmod(tempFilePath, 0o644)
168+
}
169+
170+
await fs.writeFile(tempFilePath, content)
171+
172+
// Set the file permissions to read-only (0o444)
173+
await fs.chmod(tempFilePath, 0o444)
174+
175+
// Now, open the document
176+
const document = await vscode.workspace.openTextDocument(tempFilePath)
177+
178+
const options: vscode.TextDocumentShowOptions = {
179+
viewColumn: vscode.ViewColumn.Beside,
180+
preserveFocus: true,
181+
preview: true,
182+
}
183+
184+
await vscode.window.showTextDocument(document, options)
185+
} catch (error) {
186+
throw new ToolkitError(`Error showing text document: ${error}`)
187+
}
188+
}
189+
190+
public async renderModal(notification: ToolkitNotification) {
191+
getLogger('notifications').info('rendering modal ... ')
192+
if (!notification.uiRenderInstructions.buttons) {
193+
throw new ToolkitError('no button defined for modal')
194+
return
195+
}
196+
197+
const buttons = notification.uiRenderInstructions.buttons
198+
199+
const buttonLabels = buttons.map((buttons) => buttons.displayText['en-US'])
200+
201+
const detail = notification.uiRenderInstructions.content['en-US'].description
202+
203+
const selectedButton = await vscode.window.showInformationMessage(
204+
notification.uiRenderInstructions.content['en-US'].title,
205+
{ modal: true, detail },
206+
...buttonLabels
137207
)
208+
209+
if (selectedButton) {
210+
const buttons = notification.uiRenderInstructions.buttons.find(
211+
(buttons) => buttons.displayText['en-US'] === selectedButton
212+
)
213+
214+
if (buttons) {
215+
switch (buttons.type) {
216+
case 'updateAndReload':
217+
await this.updateAndReload(notification.displayIf.extensionId)
218+
break
219+
case 'openUrl':
220+
if (buttons.url) {
221+
await vscode.env.openExternal(vscode.Uri.parse(buttons.url))
222+
}
223+
break
224+
default:
225+
getLogger().warn(`Unhandled button type: ${buttons.type}`)
226+
}
227+
}
228+
}
229+
}
230+
231+
private async updateAndReload(id: string) {
232+
getLogger('notifications').info('Updating and reloading the extension...')
233+
await vscode.commands.executeCommand('workbench.extensions.installExtension', id)
234+
await vscode.commands.executeCommand('workbench.action.reloadWindow')
138235
}
139236

140237
/**

packages/core/src/notifications/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,21 @@ export interface UIRenderInstructions {
5151
[`en-US`]: {
5252
title: string
5353
description: string
54+
descriptionPreview?: string // optional property for toast
5455
}
5556
}
56-
// TODO actions
57+
onRecieve: string
58+
onClick: {
59+
type: string
60+
url?: string // optional property for 'openUrl'
61+
}
62+
buttons?: Array<{
63+
type: string
64+
displayText: {
65+
[`en-US`]: string
66+
}
67+
url?: string // optional property for 'openUrl'
68+
}>
5769
}
5870

5971
/** Condition/criteria section of a notification. */

0 commit comments

Comments
 (0)