@@ -7,7 +7,7 @@ import * as buffer from 'buffer';
77import { ApolloQueryResult , DocumentNode , FetchResult , MutationOptions , NetworkStatus , QueryOptions } from 'apollo-boost' ;
88import * as vscode from 'vscode' ;
99import { AuthenticationError , AuthProvider , GitHubServerType , isSamlError } from '../common/authentication' ;
10- import { COPILOT_ACCOUNTS } from '../common/comment' ;
10+ import { COPILOT_ACCOUNTS , IComment , IReviewThread } from '../common/comment' ;
1111import { Disposable } from '../common/lifecycle' ;
1212import Logger from '../common/logger' ;
1313import { GitHubRemote , parseRemote } from '../common/remote' ;
@@ -69,6 +69,7 @@ import {
6969 convertRESTPullRequestToRawPullRequest ,
7070 getAvatarWithEnterpriseFallback ,
7171 getOverrideBranch ,
72+ insertNewCommitsSinceReview ,
7273 isInCodespaces ,
7374 parseAccount ,
7475 parseCombinedTimelineEvents ,
@@ -1537,10 +1538,13 @@ export class GitHubRepository extends Disposable {
15371538 crossRefs . delete ( model . html_url ) ;
15381539 }
15391540 }
1541+ const oldEvents = issueModel . timelineEvents ;
1542+ issueModel . timelineEvents = events ;
15401543 if ( crossRefs . size > 0 ) {
15411544 this . _onDidChangePullRequests . fire ( ) ;
1545+ } else if ( oldEvents . length !== events . length ) {
1546+ this . _onDidChangePullRequests . fire ( ) ;
15421547 }
1543-
15441548 return events ;
15451549 } catch ( e ) {
15461550 console . log ( e ) ;
@@ -1563,6 +1567,116 @@ export class GitHubRepository extends Disposable {
15631567 return CopilotWorkingStatus . NotCopilotIssue ;
15641568 }
15651569
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+
15661680 /**
15671681 * Get the status checks of the pull request, those for the last commit.
15681682 *
0 commit comments