@@ -7,7 +7,7 @@ import * as buffer from 'buffer';
7
7
import { ApolloQueryResult , DocumentNode , FetchResult , MutationOptions , NetworkStatus , QueryOptions } from 'apollo-boost' ;
8
8
import * as vscode from 'vscode' ;
9
9
import { AuthenticationError , AuthProvider , GitHubServerType , isSamlError } from '../common/authentication' ;
10
- import { COPILOT_ACCOUNTS } from '../common/comment' ;
10
+ import { COPILOT_ACCOUNTS , IComment , IReviewThread } from '../common/comment' ;
11
11
import { Disposable } from '../common/lifecycle' ;
12
12
import Logger from '../common/logger' ;
13
13
import { GitHubRemote , parseRemote } from '../common/remote' ;
@@ -69,6 +69,7 @@ import {
69
69
convertRESTPullRequestToRawPullRequest ,
70
70
getAvatarWithEnterpriseFallback ,
71
71
getOverrideBranch ,
72
+ insertNewCommitsSinceReview ,
72
73
isInCodespaces ,
73
74
parseAccount ,
74
75
parseCombinedTimelineEvents ,
@@ -1537,10 +1538,13 @@ export class GitHubRepository extends Disposable {
1537
1538
crossRefs . delete ( model . html_url ) ;
1538
1539
}
1539
1540
}
1541
+ const oldEvents = issueModel . timelineEvents ;
1542
+ issueModel . timelineEvents = events ;
1540
1543
if ( crossRefs . size > 0 ) {
1541
1544
this . _onDidChangePullRequests . fire ( ) ;
1545
+ } else if ( oldEvents . length !== events . length ) {
1546
+ this . _onDidChangePullRequests . fire ( ) ;
1542
1547
}
1543
-
1544
1548
return events ;
1545
1549
} catch ( e ) {
1546
1550
console . log ( e ) ;
@@ -1563,6 +1567,116 @@ export class GitHubRepository extends Disposable {
1563
1567
return CopilotWorkingStatus . NotCopilotIssue ;
1564
1568
}
1565
1569
1570
+ /**
1571
+ * Get the timeline events of a pull request, including comments, reviews, commits, merges, deletes, and assigns.
1572
+ */
1573
+ async getTimelineEvents ( pullRequestModel : PullRequestModel ) : Promise < Common . TimelineEvent [ ] > {
1574
+ const getTimelineEvents = async ( ) => {
1575
+ Logger . debug ( `Fetch timeline events of PR #${ pullRequestModel . number } - enter` , PullRequestModel . ID ) ;
1576
+ const { query, remote, schema } = await this . ensure ( ) ;
1577
+ try {
1578
+ const { data } = await query < TimelineEventsResponse > ( {
1579
+ query : schema . TimelineEvents ,
1580
+ variables : {
1581
+ owner : remote . owner ,
1582
+ name : remote . repositoryName ,
1583
+ number : pullRequestModel . number ,
1584
+ } ,
1585
+ } ) ;
1586
+
1587
+ if ( data . repository === null ) {
1588
+ Logger . error ( 'Unexpected null repository when fetching timeline' , PullRequestModel . ID ) ;
1589
+ }
1590
+ return data ;
1591
+ } catch ( e ) {
1592
+ Logger . error ( `Failed to get pull request timeline events: ${ e } ` , PullRequestModel . ID ) ;
1593
+ console . log ( e ) ;
1594
+ return undefined ;
1595
+ }
1596
+ } ;
1597
+
1598
+ const [ data , latestReviewCommitInfo , currentUser , reviewThreads ] = await Promise . all ( [
1599
+ getTimelineEvents ( ) ,
1600
+ pullRequestModel . getViewerLatestReviewCommit ( ) ,
1601
+ ( await this . getAuthenticatedUser ( ) ) . login ,
1602
+ pullRequestModel . getReviewThreads ( )
1603
+ ] ) ;
1604
+
1605
+
1606
+ const ret = data ?. repository ?. pullRequest . timelineItems . nodes ?? [ ] ;
1607
+ const events = await parseCombinedTimelineEvents ( ret , await this . getCopilotTimelineEvents ( pullRequestModel ) , this ) ;
1608
+
1609
+ this . addReviewTimelineEventComments ( events , reviewThreads ) ;
1610
+ insertNewCommitsSinceReview ( events , latestReviewCommitInfo ?. sha , currentUser , pullRequestModel . head ) ;
1611
+ Logger . debug ( `Fetch timeline events of PR #${ pullRequestModel . number } - done` , PullRequestModel . ID ) ;
1612
+ const oldEvents = pullRequestModel . timelineEvents ;
1613
+ pullRequestModel . timelineEvents = events ;
1614
+ if ( oldEvents . length !== events . length ) {
1615
+ this . _onDidChangePullRequests . fire ( ) ;
1616
+ }
1617
+ return events ;
1618
+ }
1619
+
1620
+ private addReviewTimelineEventComments ( events : Common . TimelineEvent [ ] , reviewThreads : IReviewThread [ ] ) : void {
1621
+ interface CommentNode extends IComment {
1622
+ childComments ?: CommentNode [ ] ;
1623
+ }
1624
+
1625
+ const reviewEvents = events . filter ( ( e ) : e is Common . ReviewEvent => e . event === Common . EventType . Reviewed ) ;
1626
+ const reviewComments = reviewThreads . reduce ( ( previous , current ) => ( previous as IComment [ ] ) . concat ( current . comments ) , [ ] ) ;
1627
+
1628
+ const reviewEventsById = reviewEvents . reduce ( ( index , evt ) => {
1629
+ index [ evt . id ] = evt ;
1630
+ evt . comments = [ ] ;
1631
+ return index ;
1632
+ } , { } as { [ key : number ] : Common . ReviewEvent } ) ;
1633
+
1634
+ const commentsById = reviewComments . reduce ( ( index , evt ) => {
1635
+ index [ evt . id ] = evt ;
1636
+ return index ;
1637
+ } , { } as { [ key : number ] : CommentNode } ) ;
1638
+
1639
+ const roots : CommentNode [ ] = [ ] ;
1640
+ let i = reviewComments . length ;
1641
+ while ( i -- > 0 ) {
1642
+ const c : CommentNode = reviewComments [ i ] ;
1643
+ if ( ! c . inReplyToId ) {
1644
+ roots . unshift ( c ) ;
1645
+ continue ;
1646
+ }
1647
+ const parent = commentsById [ c . inReplyToId ] ;
1648
+ parent . childComments = parent . childComments || [ ] ;
1649
+ parent . childComments = [ c , ...( c . childComments || [ ] ) , ...parent . childComments ] ;
1650
+ }
1651
+
1652
+ roots . forEach ( c => {
1653
+ const review = reviewEventsById [ c . pullRequestReviewId ! ] ;
1654
+ if ( review ) {
1655
+ review . comments = review . comments . concat ( c ) . concat ( c . childComments || [ ] ) ;
1656
+ }
1657
+ } ) ;
1658
+
1659
+ reviewThreads . forEach ( thread => {
1660
+ if ( ! thread . prReviewDatabaseId || ! reviewEventsById [ thread . prReviewDatabaseId ] ) {
1661
+ return ;
1662
+ }
1663
+ const prReviewThreadEvent = reviewEventsById [ thread . prReviewDatabaseId ] ;
1664
+ prReviewThreadEvent . reviewThread = {
1665
+ threadId : thread . id ,
1666
+ canResolve : thread . viewerCanResolve ,
1667
+ canUnresolve : thread . viewerCanUnresolve ,
1668
+ isResolved : thread . isResolved
1669
+ } ;
1670
+
1671
+ } ) ;
1672
+
1673
+ const pendingReview = reviewEvents . filter ( r => r . state ?. toLowerCase ( ) === 'pending' ) [ 0 ] ;
1674
+ if ( pendingReview ) {
1675
+ // Ensures that pending comments made in reply to other reviews are included for the pending review
1676
+ pendingReview . comments = reviewComments . filter ( c => c . isDraft ) ;
1677
+ }
1678
+ }
1679
+
1566
1680
/**
1567
1681
* Get the status checks of the pull request, those for the last commit.
1568
1682
*
0 commit comments