Repository: microsoft/vscode (TypeScript)
Task type: Bug Fix

Issue:
Codespaces progress notification makes links not clickable

https://user-images.githubusercontent.com/35271042/119018546-08705080-b951-11eb-8505-0e063aa81629.mp4

1. Create a new codespaces using the vscode repo
2. Try clicking on the logs link
3. :bug: Unable to click on the link

Upon inspecting the DOM, it appears that elements are being added/removed/updated every second so that may be blocking the link from being clicked on

Codespaces Extension: v0.10.2
Version: 1.57.0-insider
Commit: 29c61570a5b9a669f777bb28b5acd5c37d99edbe
Date: 2021-05-20T05:11:47.260Z
Electron: 12.0.7
Chrome: 89.0.4389.128
Node.js: 14.16.0
V8: 8.9.255.25-electron.0
OS: Darwin x64 20.3.0
Codespaces progress notification makes links not clickable

https://user-images.githubusercontent.com/35271042/119018546-08705080-b951-11eb-8505-0e063aa81629.mp4

1. Create a new codespaces using the vscode repo
2. Try clicking on the logs link
3. :bug: Unable to click on the link

Upon inspecting the DOM, it appears that elements are being added/removed/updated every second so that may be blocking the link from being clicked on

Codespaces Extension: v0.10.2
Version: 1.57.0-insider
Commit: 29c61570a5b9a669f777bb28b5acd5c37d99edbe
Date: 2021-05-20T05:11:47.260Z
Electron: 12.0.7
Chrome: 89.0.4389.128
Node.js: 14.16.0
V8: 8.9.255.25-electron.0
OS: Darwin x64 20.3.0


Discussion:
Following up upstream first...
@bpasero what do you think about not rerendering the notification message if the content didn't change between calls to report? In the codespaces extension I send fake progress updates quickly to smooth out the progress bar, and you can't click a link if it updates at the moment you are clicking it
@roblourens good catch, indeed we should not update the DOM when contents have not changed. Is this something you would be interested in doing a PR for?

I see to locations that could benefit of a check to not emit change events when nothing changed:
* [`updateSeverity`](https://github.com/microsoft/vscode/blob/be6a9027041c6136dfdf2571fa7a5092cbbf31d2/src/vs/workbench/common/notifications.ts#L621-L621)
* [`updateMessage`](https://github.com/microsoft/vscode/blob/be6a9027041c6136dfdf2571fa7a5092cbbf31d2/src/vs/workbench/common/notifications.ts#L626-L626)

I would think for the latter, a simple check for `.raw === .raw` would be sufficient.
Sure
Following up upstream first...
@bpasero what do you think about not rerendering the notification message if the content didn't change between calls to report? In the codespaces extension I send fake progress updates quickly to smooth out the progress bar, and you can't click a link if it updates at the moment you are clicking it
@roblourens good catch, indeed we should not update the DOM when contents have not changed. Is this something you would be interested in doing a PR for?

I see to locations that could benefit of a check to not emit change events when nothing changed:
* [`updateSeverity`](https://github.com/microsoft/vscode/blob/be6a9027041c6136dfdf2571fa7a5092cbbf31d2/src/vs/workbench/common/notifications.ts#L621-L621)
* [`updateMessage`](https://github.com/microsoft/vscode/blob/be6a9027041c6136dfdf2571fa7a5092cbbf31d2/src/vs/workbench/common/notifications.ts#L626-L626)

I would think for the latter, a simple check for `.raw === .raw` would be sufficient.
Sure

Solution patch:
diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts
--- a/src/vs/workbench/common/notifications.ts
+++ b/src/vs/workbench/common/notifications.ts
@@ -619,13 +619,17 @@ export class NotificationViewItem extends Disposable implements INotificationVie
 	}
 
 	updateSeverity(severity: Severity): void {
+		if (severity === this._severity) {
+			return;
+		}
+
 		this._severity = severity;
 		this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.SEVERITY });
 	}
 
 	updateMessage(input: NotificationMessage): void {
 		const message = NotificationViewItem.parseNotificationMessage(input);
-		if (!message) {
+		if (!message || message.raw === this._message.raw) {
 			return;
 		}
 


Test patch:
diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts
--- a/src/vs/workbench/test/common/notifications.test.ts
+++ b/src/vs/workbench/test/common/notifications.test.ts
@@ -10,6 +10,7 @@ import { INotification, Severity, NotificationsFilter } from 'vs/platform/notifi
 import { createErrorWithActions } from 'vs/base/common/errors';
 import { NotificationService } from 'vs/workbench/services/notification/common/notificationService';
 import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
+import { timeout } from 'vs/base/common/async';
 
 suite('Notifications', () => {
 
@@ -143,6 +144,23 @@ suite('Notifications', () => {
 		assert.strictEqual(item11.silent, true);
 	});
 
+	test('Items - does not fire changed when message did not change (content, severity)', async () => {
+		const item1 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' })!;
+
+		let fired = false;
+		item1.onDidChangeContent(() => {
+			fired = true;
+		});
+
+		item1.updateMessage('Error Message');
+		await timeout(0);
+		assert.ok(!fired, 'Expected onDidChangeContent to not be fired');
+
+		item1.updateSeverity(Severity.Error);
+		await timeout(0);
+		assert.ok(!fired, 'Expected onDidChangeContent to not be fired');
+	});
+
 	test('Model', () => {
 		const model = new NotificationsModel();
 
@@ -167,11 +185,11 @@ suite('Notifications', () => {
 		assert.strictEqual(lastNotificationEvent.index, 0);
 		assert.strictEqual(lastNotificationEvent.kind, NotificationChangeType.ADD);
 
-		item1Handle.updateMessage('Error Message');
+		item1Handle.updateMessage('Different Error Message');
 		assert.strictEqual(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
 		assert.strictEqual(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.MESSAGE);
 
-		item1Handle.updateSeverity(Severity.Error);
+		item1Handle.updateSeverity(Severity.Warning);
 		assert.strictEqual(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
 		assert.strictEqual(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.SEVERITY);
 
@@ -205,8 +223,8 @@ suite('Notifications', () => {
 		item1Handle.close();
 		assert.strictEqual(called, 1);
 		assert.strictEqual(model.notifications.length, 2);
-		assert.strictEqual(lastNotificationEvent.item.severity, item1.severity);
-		assert.strictEqual(lastNotificationEvent.item.message.linkedText.toString(), item1.message);
+		assert.strictEqual(lastNotificationEvent.item.severity, Severity.Warning);
+		assert.strictEqual(lastNotificationEvent.item.message.linkedText.toString(), 'Different Error Message');
 		assert.strictEqual(lastNotificationEvent.index, 2);
 		assert.strictEqual(lastNotificationEvent.kind, NotificationChangeType.REMOVE);