|
5 | 5 |
|
6 | 6 | import * as vscode from 'vscode'; |
7 | 7 | import { Disposable } from '../common/lifecycle'; |
| 8 | +import { EXPERIMENTAL_NOTIFICATIONS_MARK_PRS } from '../common/settingKeys'; |
| 9 | +import { EventType, TimelineEvent } from '../common/timelineEvent'; |
8 | 10 | import { toNotificationUri } from '../common/uri'; |
| 11 | +import { CredentialStore } from '../github/credentials'; |
9 | 12 | import { NotificationSubjectType } from '../github/interface'; |
10 | 13 | import { IssueModel } from '../github/issueModel'; |
11 | 14 | import { PullRequestModel } from '../github/pullRequestModel'; |
@@ -38,7 +41,7 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP |
38 | 41 | private _sortingMethod: NotificationsSortMethod = NotificationsSortMethod.Timestamp; |
39 | 42 | get sortingMethod(): NotificationsSortMethod { return this._sortingMethod; } |
40 | 43 |
|
41 | | - constructor(private readonly _notificationProvider: NotificationsProvider) { |
| 44 | + constructor(private readonly _notificationProvider: NotificationsProvider, private readonly _credentialStore: CredentialStore) { |
42 | 45 | super(); |
43 | 46 | this._register(this._onDidChangeTreeData); |
44 | 47 | this._register(this._onDidChangeNotifications); |
@@ -233,6 +236,73 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP |
233 | 236 | } |
234 | 237 | } |
235 | 238 |
|
| 239 | + private _getMeaningfulEventTime(event: TimelineEvent, currentUser: string, isCurrentUser: boolean): Date | undefined { |
| 240 | + const userCheck = (testUser?: string) => { |
| 241 | + if (isCurrentUser) { |
| 242 | + return testUser === currentUser; |
| 243 | + } else if (!isCurrentUser) { |
| 244 | + return testUser !== currentUser; |
| 245 | + } |
| 246 | + }; |
| 247 | + |
| 248 | + if (event.event === EventType.Committed) { |
| 249 | + if (userCheck(event.author.login)) { |
| 250 | + return new Date(event.authoredDate); |
| 251 | + } |
| 252 | + } else if (event.event === EventType.Commented) { |
| 253 | + if (userCheck(event.user?.login)) { |
| 254 | + return new Date(event.createdAt); |
| 255 | + } |
| 256 | + } else if (event.event === EventType.Reviewed) { |
| 257 | + // We only count empty reviews as meaningful if the user is the current user |
| 258 | + if (isCurrentUser || (event.comments.length > 0 || event.body.length > 0)) { |
| 259 | + if (userCheck(event.user?.login)) { |
| 260 | + return new Date(event.submittedAt); |
| 261 | + } |
| 262 | + } |
| 263 | + } |
| 264 | + } |
| 265 | + |
| 266 | + public async markMergedPullRequestAsRead(): Promise<void> { |
| 267 | + const markAsDone = vscode.workspace.getConfiguration('githubPullRequests').get<'markAsRead' | 'markAsDone'>(EXPERIMENTAL_NOTIFICATIONS_MARK_PRS, 'markAsRead') === 'markAsDone'; |
| 268 | + const filteredNotifications = Array.from(this._notifications.values()).filter(notification => notification.notification.subject.type === NotificationSubjectType.PullRequest && notification.model.isMerged); |
| 269 | + const timlines = await Promise.all(filteredNotifications.map(notification => (notification.model as PullRequestModel).getTimelineEvents())); |
| 270 | + |
| 271 | + const markPromises: Promise<void>[] = []; |
| 272 | + |
| 273 | + for (const [index, notification] of filteredNotifications.entries()) { |
| 274 | + const currentUser = await this._credentialStore.getCurrentUser(notification.model.remote.authProviderId); |
| 275 | + |
| 276 | + // Check that there have been no comments, reviews, or commits, since last read |
| 277 | + const timeline = timlines[index]; |
| 278 | + let userLastEvent: Date | undefined = undefined; |
| 279 | + let nonUserLastEvent: Date | undefined = undefined; |
| 280 | + for (let i = timeline.length - 1; i >= 0; i--) { |
| 281 | + const event = timeline[i]; |
| 282 | + if (!userLastEvent) { |
| 283 | + userLastEvent = this._getMeaningfulEventTime(event, currentUser.login, true); |
| 284 | + } |
| 285 | + if (!nonUserLastEvent) { |
| 286 | + nonUserLastEvent = this._getMeaningfulEventTime(event, currentUser.login, false); |
| 287 | + } |
| 288 | + if (userLastEvent && nonUserLastEvent) { |
| 289 | + break; |
| 290 | + } |
| 291 | + } |
| 292 | + |
| 293 | + if (!nonUserLastEvent || (userLastEvent && (userLastEvent.getTime() > nonUserLastEvent.getTime()))) { |
| 294 | + if (markAsDone) { |
| 295 | + markPromises.push(this._notificationProvider.markAsDone({ threadId: notification.notification.id, notificationKey: notification.notification.key })); |
| 296 | + } else { |
| 297 | + markPromises.push(this._notificationProvider.markAsRead({ threadId: notification.notification.id, notificationKey: notification.notification.key })); |
| 298 | + } |
| 299 | + this._notifications.delete(notification.notification.key); |
| 300 | + } |
| 301 | + } |
| 302 | + await Promise.all(markPromises); |
| 303 | + this.refresh(); |
| 304 | + } |
| 305 | + |
236 | 306 | public sortNotifications(method: NotificationsSortMethod): void { |
237 | 307 | if (this._sortingMethod === method) { |
238 | 308 | return; |
|
0 commit comments