diff --git a/lib/Octokit.GraphQL.0.0.2-alpha.nupkg b/lib/Octokit.GraphQL.0.0.2-alpha.nupkg
deleted file mode 100644
index 88686b7729..0000000000
Binary files a/lib/Octokit.GraphQL.0.0.2-alpha.nupkg and /dev/null differ
diff --git a/lib/Octokit.GraphQL.0.0.4-alpha.nupkg b/lib/Octokit.GraphQL.0.0.4-alpha.nupkg
new file mode 100644
index 0000000000..1a994c026d
Binary files /dev/null and b/lib/Octokit.GraphQL.0.0.4-alpha.nupkg differ
diff --git a/src/GitHub.Api/GitHub.Api.csproj b/src/GitHub.Api/GitHub.Api.csproj
index 58f63e5ef0..f9128fd0cc 100644
--- a/src/GitHub.Api/GitHub.Api.csproj
+++ b/src/GitHub.Api/GitHub.Api.csproj
@@ -51,12 +51,12 @@
..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
True
diff --git a/src/GitHub.Api/packages.config b/src/GitHub.Api/packages.config
index 6356b0e89c..67e299e22d 100644
--- a/src/GitHub.Api/packages.config
+++ b/src/GitHub.Api/packages.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/GitHub.App/Factories/ModelServiceFactory.cs b/src/GitHub.App/Factories/ModelServiceFactory.cs
index 5ac1d26cdc..a5ef967749 100644
--- a/src/GitHub.App/Factories/ModelServiceFactory.cs
+++ b/src/GitHub.App/Factories/ModelServiceFactory.cs
@@ -16,7 +16,6 @@ namespace GitHub.Factories
public sealed class ModelServiceFactory : IModelServiceFactory, IDisposable
{
readonly IApiClientFactory apiClientFactory;
- readonly IGraphQLClientFactory graphQLClientFactory;
readonly IHostCacheFactory hostCacheFactory;
readonly IAvatarProvider avatarProvider;
readonly Dictionary cache = new Dictionary();
@@ -25,12 +24,10 @@ public sealed class ModelServiceFactory : IModelServiceFactory, IDisposable
[ImportingConstructor]
public ModelServiceFactory(
IApiClientFactory apiClientFactory,
- IGraphQLClientFactory graphQLClientFactory,
IHostCacheFactory hostCacheFactory,
IAvatarProvider avatarProvider)
{
this.apiClientFactory = apiClientFactory;
- this.graphQLClientFactory = graphQLClientFactory;
this.hostCacheFactory = hostCacheFactory;
this.avatarProvider = avatarProvider;
}
@@ -47,7 +44,6 @@ public async Task CreateAsync(IConnection connection)
{
result = new ModelService(
await apiClientFactory.Create(connection.HostAddress),
- await graphQLClientFactory.CreateConnection(connection.HostAddress),
await hostCacheFactory.Create(connection.HostAddress),
avatarProvider);
result.InsertUser(AccountCacheItem.Create(connection.User));
diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj
index 9011acecaf..56edeb3562 100644
--- a/src/GitHub.App/GitHub.App.csproj
+++ b/src/GitHub.App/GitHub.App.csproj
@@ -145,12 +145,12 @@
..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
True
@@ -209,12 +209,10 @@
-
-
+
-
@@ -225,6 +223,7 @@
+
@@ -256,7 +255,7 @@
-
+
@@ -268,8 +267,6 @@
-
-
True
True
diff --git a/src/GitHub.App/Models/IssueCommentModel.cs b/src/GitHub.App/Models/IssueCommentModel.cs
deleted file mode 100644
index 5031aa2bd4..0000000000
--- a/src/GitHub.App/Models/IssueCommentModel.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-
-namespace GitHub.Models
-{
- public class IssueCommentModel : ICommentModel
- {
- public int Id { get; set; }
- public string NodeId { get; set; }
- public string Body { get; set; }
- public DateTimeOffset CreatedAt { get; set; }
- public IAccount User { get; set; }
- }
-}
diff --git a/src/GitHub.App/Models/PullRequestFileModel.cs b/src/GitHub.App/Models/PullRequestFileModel.cs
deleted file mode 100644
index f8a90c0e30..0000000000
--- a/src/GitHub.App/Models/PullRequestFileModel.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-
-namespace GitHub.Models
-{
- public class PullRequestFileModel : IPullRequestFileModel
- {
- public PullRequestFileModel(string fileName, string sha, PullRequestFileStatus status)
- {
- FileName = fileName;
- Sha = sha;
- Status = status;
- }
-
- public string FileName { get; }
- public string Sha { get; }
- public PullRequestFileStatus Status { get; }
- }
-}
diff --git a/src/GitHub.App/Models/PullRequestModel.cs b/src/GitHub.App/Models/PullRequestModel.cs
index 8e9d6439d7..6a560fa2ea 100644
--- a/src/GitHub.App/Models/PullRequestModel.cs
+++ b/src/GitHub.App/Models/PullRequestModel.cs
@@ -162,32 +162,6 @@ public string Body
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
public IAccount Author { get; set; }
- public IReadOnlyList ChangedFiles { get; set; } = new IPullRequestFileModel[0];
- public IReadOnlyList Comments { get; set; } = new ICommentModel[0];
-
- IReadOnlyList reviews = new IPullRequestReviewModel[0];
- public IReadOnlyList Reviews
- {
- get { return reviews; }
- set
- {
- Guard.ArgumentNotNull(value, nameof(value));
- reviews = value;
- this.RaisePropertyChange();
- }
- }
-
- IReadOnlyList reviewComments = new IPullRequestReviewCommentModel[0];
- public IReadOnlyList ReviewComments
- {
- get { return reviewComments; }
- set
- {
- Guard.ArgumentNotNull(value, nameof(value));
- reviewComments = value;
- this.RaisePropertyChange();
- }
- }
IAccount assignee;
public IAccount Assignee
diff --git a/src/GitHub.App/Models/PullRequestReviewCommentModel.cs b/src/GitHub.App/Models/PullRequestReviewCommentModel.cs
deleted file mode 100644
index 7ae45548e6..0000000000
--- a/src/GitHub.App/Models/PullRequestReviewCommentModel.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-
-namespace GitHub.Models
-{
- public class PullRequestReviewCommentModel : IPullRequestReviewCommentModel
- {
- public int Id { get; set; }
- public string NodeId { get; set; }
- public int PullRequestReviewId { get; set; }
- public string Path { get; set; }
- public int? Position { get; set; }
- public int? OriginalPosition { get; set; }
- public string CommitId { get; set; }
- public string OriginalCommitId { get; set; }
- public string DiffHunk { get; set; }
- public IAccount User { get; set; }
- public string Body { get; set; }
- public DateTimeOffset CreatedAt { get; set; }
- public bool IsPending { get; set; }
- }
-}
diff --git a/src/GitHub.App/Models/PullRequestReviewModel.cs b/src/GitHub.App/Models/PullRequestReviewModel.cs
deleted file mode 100644
index aab24ab076..0000000000
--- a/src/GitHub.App/Models/PullRequestReviewModel.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-
-namespace GitHub.Models
-{
- public class PullRequestReviewModel : IPullRequestReviewModel
- {
- public long Id { get; set; }
- public string NodeId { get; set; }
- public IAccount User { get; set; }
- public string Body { get; set; }
- public PullRequestReviewState State { get; set; }
- public string CommitId { get; set; }
- public DateTimeOffset? SubmittedAt { get; set; }
- }
-}
diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs
index a4bd06b8a0..a174c3e299 100644
--- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs
+++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs
@@ -35,13 +35,12 @@ public PullRequestDetailViewModelDesigner()
{
var repoPath = @"C:\Repo";
- Model = new PullRequestModel(419,
- "Error handling/bubbling from viewmodels to views to viewhosts",
- new AccountDesigner { Login = "shana", IsUser = true },
- DateTime.Now.Subtract(TimeSpan.FromDays(3)))
+ Model = new PullRequestDetailModel
{
- State = PullRequestStateEnum.Open,
- CommitCount = 9,
+ Number = 419,
+ Title = "Error handling/bubbling from viewmodels to views to viewhosts",
+ Author = new ActorModel { Login = "shana" },
+ UpdatedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3)),
};
SourceBranchDisplayName = "shana/error-handling";
@@ -71,22 +70,22 @@ public PullRequestDetailViewModelDesigner()
{
new PullRequestReviewSummaryViewModel
{
- Id = 2,
- User = new AccountDesigner { Login = "grokys", IsUser = true },
+ Id = "id1",
+ User = new ActorViewModel { Login = "grokys" },
State = PullRequestReviewState.Pending,
FileCommentCount = 0,
},
new PullRequestReviewSummaryViewModel
{
- Id = 1,
- User = new AccountDesigner { Login = "jcansdale", IsUser = true },
+ Id = "id",
+ User = new ActorViewModel { Login = "jcansdale" },
State = PullRequestReviewState.Approved,
FileCommentCount = 5,
},
new PullRequestReviewSummaryViewModel
{
- Id = 2,
- User = new AccountDesigner { Login = "shana", IsUser = true },
+ Id = "id3",
+ User = new ActorViewModel { Login = "shana" },
State = PullRequestReviewState.ChangesRequested,
FileCommentCount = 5,
},
@@ -98,11 +97,12 @@ public PullRequestDetailViewModelDesigner()
Files = new PullRequestFilesViewModelDesigner();
}
- public IPullRequestModel Model { get; }
+ public PullRequestDetailModel Model { get; }
public IPullRequestSession Session { get; }
public ILocalRepositoryModel LocalRepository { get; }
public string RemoteRepositoryOwner { get; }
public int Number { get; set; }
+ public IActorViewModel Author { get; set; }
public string SourceBranchDisplayName { get; set; }
public string TargetBranchDisplayName { get; set; }
public int CommentCount { get; set; }
diff --git a/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs
index 9b5c512756..796e275901 100644
--- a/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs
+++ b/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs
@@ -18,6 +18,7 @@ public class PullRequestListViewModelDesigner : PanePageViewModelBase, IPullRequ
public PullRequestListViewModelDesigner()
{
var prs = new TrackingCollection(Observable.Empty());
+
prs.Add(new PullRequestModel(399, "Let's try doing this differently",
new AccountDesigner { Login = "shana", IsUser = true },
DateTimeOffset.Now - TimeSpan.FromDays(1))
@@ -58,7 +59,7 @@ public PullRequestListViewModelDesigner()
public IReadOnlyList Repositories { get; }
public IRemoteRepositoryModel SelectedRepository { get; set; }
- public IPullRequestModel CheckedOutPullRequest { get; set; }
+ public PullRequestDetailModel CheckedOutPullRequest { get; set; }
public ITrackingCollection PullRequests { get; set; }
public IPullRequestModel SelectedPullRequest { get; set; }
diff --git a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs
index a13faba254..15c6e68f57 100644
--- a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs
+++ b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs
@@ -12,11 +12,13 @@ public class PullRequestReviewAuthoringViewModelDesigner : PanePageViewModelBase
{
public PullRequestReviewAuthoringViewModelDesigner()
{
- PullRequestModel = new PullRequestModel(
- 419,
- "Fix a ton of potential crashers, odd code and redundant calls in ModelService",
- new AccountDesigner { Login = "Haacked", IsUser = true },
- DateTimeOffset.Now - TimeSpan.FromDays(2));
+ PullRequestModel = new PullRequestDetailModel
+ {
+ Number = 419,
+ Title = "Fix a ton of potential crashers, odd code and redundant calls in ModelService",
+ Author = new ActorModel { Login = "Haacked" },
+ UpdatedAt = DateTimeOffset.Now - TimeSpan.FromDays(2),
+ };
Files = new PullRequestFilesViewModelDesigner();
@@ -42,10 +44,10 @@ public PullRequestReviewAuthoringViewModelDesigner()
public IReadOnlyList FileComments { get; }
public IPullRequestFilesViewModel Files { get; }
public ILocalRepositoryModel LocalRepository { get; set; }
- public IPullRequestReviewModel Model { get; set; }
+ public PullRequestReviewModel Model { get; set; }
public ReactiveCommand
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
True
diff --git a/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs b/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs
index 4619d3c545..8ead806ddb 100644
--- a/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs
+++ b/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs
@@ -30,7 +30,7 @@ class InlineCommentThreadModel : ReactiveObject, IInlineCommentThreadModel
string relativePath,
string commitSha,
IList diffMatch,
- IEnumerable comments)
+ IEnumerable comments)
{
Guard.ArgumentNotNull(relativePath, nameof(relativePath));
Guard.ArgumentNotNull(commitSha, nameof(commitSha));
@@ -41,10 +41,15 @@ class InlineCommentThreadModel : ReactiveObject, IInlineCommentThreadModel
DiffLineType = diffMatch[0].Type;
CommitSha = commitSha;
RelativePath = relativePath;
+
+ foreach (var comment in comments)
+ {
+ comment.Thread = this;
+ }
}
///
- public IReadOnlyList Comments { get; }
+ public IReadOnlyList Comments { get; }
///
public IList DiffMatch { get; }
diff --git a/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs
index 11432ac83f..eac1dca542 100644
--- a/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs
+++ b/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs
@@ -1,9 +1,10 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
+using System.Reactive;
using GitHub.InlineReviews.ViewModels;
using GitHub.Models;
-using GitHub.SampleData;
+using GitHub.ViewModels;
using ReactiveUI;
namespace GitHub.InlineReviews.SampleData
@@ -14,16 +15,11 @@ class CommentThreadViewModelDesigner : ICommentThreadViewModel
public ObservableCollection Comments { get; }
= new ObservableCollection();
- public IAccount CurrentUser { get; set; }
- = new AccountDesigner { Login = "shana", IsUser = true };
+ public IActorViewModel CurrentUser { get; set; }
+ = new ActorViewModel { Login = "shana" };
- public ReactiveCommand PostComment { get; }
- public ReactiveCommand EditComment { get; }
- public ReactiveCommand DeleteComment { get; }
-
- public Uri GetCommentUrl(int id)
- {
- throw new NotImplementedException();
- }
+ public ReactiveCommand PostComment { get; }
+ public ReactiveCommand EditComment { get; }
+ public ReactiveCommand DeleteComment { get; }
}
}
diff --git a/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs
index 4c735d5111..dca3732639 100644
--- a/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs
+++ b/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs
@@ -2,10 +2,8 @@
using System.Reactive;
using System.Diagnostics.CodeAnalysis;
using GitHub.InlineReviews.ViewModels;
-using GitHub.Models;
-using GitHub.SampleData;
-using GitHub.UI;
using ReactiveUI;
+using GitHub.ViewModels;
namespace GitHub.InlineReviews.SampleData
{
@@ -14,11 +12,10 @@ class CommentViewModelDesigner : ReactiveObject, ICommentViewModel
{
public CommentViewModelDesigner()
{
- User = new AccountDesigner { Login = "shana", IsUser = true };
+ Author = new ActorViewModel { Login = "shana" };
}
- public int Id { get; set; }
- public string NodeId { get; set; }
+ public string Id { get; set; }
public string Body { get; set; }
public string ErrorMessage { get; set; }
public CommentEditState EditState { get; set; }
@@ -27,7 +24,8 @@ public CommentViewModelDesigner()
public bool CanDelete { get; } = true;
public ICommentThreadViewModel Thread { get; }
public DateTimeOffset UpdatedAt => DateTime.Now.Subtract(TimeSpan.FromDays(3));
- public IAccount User { get; set; }
+ public IActorViewModel Author { get; set; }
+ public Uri WebUrl { get; }
public ReactiveCommand BeginEdit { get; }
public ReactiveCommand CancelEdit { get; }
diff --git a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs
index 6f0d7ce542..d816271980 100644
--- a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs
+++ b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs
@@ -3,6 +3,7 @@
using System.Reactive.Subjects;
using System.Threading.Tasks;
using GitHub.Models;
+using GitHub.Primitives;
using Microsoft.VisualStudio.Text;
using Octokit;
@@ -54,7 +55,7 @@ public interface IPullRequestSessionService
/// A collection of objects with updated line numbers.
///
IReadOnlyList BuildCommentThreads(
- IPullRequestModel pullRequest,
+ PullRequestDetailModel pullRequest,
string relativePath,
IReadOnlyList diff,
string headSha);
@@ -134,6 +135,26 @@ public interface IPullRequestSessionService
///
Task ReadFileAsync(string path);
+ ///
+ /// Reads a for a specified pull request.
+ ///
+ /// The host address.
+ /// The repository owner.
+ /// The repository name.
+ /// The pull request number.
+ /// A task returning the pull request model.
+ Task ReadPullRequestDetail(HostAddress address, string owner, string name, int number);
+
+ ///
+ /// Reads the current viewer for the specified address..
+ ///
+ /// The host address.
+ /// A task returning the viewer.
+ ///
+ /// A "Viewer" is the GraphQL term for the currently authenticated user.
+ ///
+ Task ReadViewer(HostAddress address);
+
///
/// Find the merge base for a pull request.
///
@@ -142,7 +163,7 @@ public interface IPullRequestSessionService
///
/// The merge base SHA for the PR.
///
- Task GetPullRequestMergeBase(ILocalRepositoryModel repository, IPullRequestModel pullRequest);
+ Task GetPullRequestMergeBase(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest);
///
/// Gets the GraphQL ID for a pull request.
@@ -176,10 +197,9 @@ public interface IPullRequestSessionService
/// The local repository.
/// The user posting the review.
/// The GraphQL ID of the pull request.
- ///
- Task CreatePendingReview(
+ /// The updated state of the pull request.
+ Task CreatePendingReview(
ILocalRepositoryModel localRepository,
- IAccount user,
string pullRequestId);
///
@@ -187,7 +207,8 @@ public interface IPullRequestSessionService
///
/// The local repository.
/// The GraphQL ID of the review.
- Task CancelPendingReview(
+ /// The updated state of the pull request.
+ Task CancelPendingReview(
ILocalRepositoryModel localRepository,
string reviewId);
@@ -195,17 +216,14 @@ public interface IPullRequestSessionService
/// Posts PR review with no comments.
///
/// The local repository.
- /// The owner of the repository fork to post to.
- /// The user posting the review.
- /// The pull request number.
+ /// The GraphQL ID of the pull request.
/// The SHA of the commit being reviewed.
/// The review body.
/// The review event.
- Task PostReview(
+ /// The updated state of the pull request.
+ Task PostReview(
ILocalRepositoryModel localRepository,
- string remoteRepositoryOwner,
- IAccount user,
- int number,
+ string pullRequestId,
string commitId,
string body,
PullRequestReviewEvent e);
@@ -214,13 +232,12 @@ public interface IPullRequestSessionService
/// Submits a pending PR review.
///
/// The local repository.
- /// The user posting the review.
/// The GraphQL ID of the pending review.
/// The review body.
/// The review event.
- Task SubmitPendingReview(
+ /// The updated state of the pull request.
+ Task SubmitPendingReview(
ILocalRepositoryModel localRepository,
- IAccount user,
string pendingReviewId,
string body,
PullRequestReviewEvent e);
@@ -229,29 +246,38 @@ public interface IPullRequestSessionService
/// Posts a new pending PR review comment.
///
/// The local repository.
- /// The user posting the comment.
/// The GraphQL ID of the pending review.
/// The comment body.
/// THe SHA of the commit to comment on.
/// The relative path of the file to comment on.
/// The line index in the diff to comment on.
- /// A model representing the posted comment.
+ /// The updated state of the pull request.
///
- /// The method posts a new pull request comment to a pending review started by
- /// .
+ /// This method posts a new pull request comment to a pending review started by
+ /// .
///
- Task PostPendingReviewComment(
+ Task PostPendingReviewComment(
ILocalRepositoryModel localRepository,
- IAccount user,
string pendingReviewId,
string body,
string commitId,
string path,
int position);
- Task PostPendingReviewCommentReply(
+ ///
+ /// Posts a new pending PR review comment reply.
+ ///
+ /// The local repository.
+ /// The GraphQL ID of the pending review.
+ /// The comment body.
+ /// The GraphQL ID of the comment to reply to.
+ /// The updated state of the pull request.
+ ///
+ /// The method posts a new pull request comment to a pending review started by
+ /// .
+ ///
+ Task PostPendingReviewCommentReply(
ILocalRepositoryModel localRepository,
- IAccount user,
string pendingReviewId,
string body,
string inReplyTo);
@@ -260,20 +286,19 @@ public interface IPullRequestSessionService
/// Posts a new standalone PR review comment.
///
/// The local repository.
- /// The user posting the comment.
- /// The pull request node id.
+ /// The GraphQL ID of the pull request.
/// The comment body.
/// THe SHA of the commit to comment on.
/// The relative path of the file to comment on.
/// The line index in the diff to comment on.
- /// A model representing the posted comment.
+ /// The updated state of the pull request.
///
/// The method posts a new standalone pull request comment that is not attached to a pending
/// pull request review.
///
- Task PostStandaloneReviewComment(ILocalRepositoryModel localRepository,
- IAccount user,
- string pullRequestNodeId,
+ Task PostStandaloneReviewComment(
+ ILocalRepositoryModel localRepository,
+ string pullRequestId,
string body,
string commitId,
string path,
@@ -283,16 +308,15 @@ public interface IPullRequestSessionService
/// Posts a PR review comment reply.
///
/// The local repository.
- /// The user posting the comment.
- /// The pull request node id.
+ /// The GraphQL ID of the pull request.
/// The comment body.
- /// The comment node id to reply to.
- /// A model representing the posted comment.
- Task PostStandaloneReviewCommentReply(ILocalRepositoryModel localRepository,
- IAccount user,
- string pullRequestNodeId,
+ /// The GraphQL ID of the comment to reply to.
+ /// The updated state of the pull request.
+ Task PostStandaloneReviewCommentReply(
+ ILocalRepositoryModel localRepository,
+ string pullRequestId,
string body,
- string inReplyToNodeId);
+ string inReplyTo);
///
/// Delete a PR review comment.
@@ -301,11 +325,10 @@ public interface IPullRequestSessionService
/// The owner of the repository fork to delete from.
/// The user deleting the comment.
/// The pull request comment number.
- ///
- Task DeleteComment(
+ /// The updated state of the pull request.
+ Task DeleteComment(
ILocalRepositoryModel localRepository,
string remoteRepositoryOwner,
- IAccount user,
int number);
///
@@ -316,10 +339,9 @@ public interface IPullRequestSessionService
/// The user deleting the comment.
/// The pull request comment node id.
/// The replacement comment body.
- /// A model representing the edited comment.
- Task EditComment(ILocalRepositoryModel localRepository,
+ /// The updated state of the pull request.
+ Task EditComment(ILocalRepositoryModel localRepository,
string remoteRepositoryOwner,
- IAccount user,
string commentNodeId,
string body);
}
diff --git a/src/GitHub.InlineReviews/Services/PullRequestSession.cs b/src/GitHub.InlineReviews/Services/PullRequestSession.cs
index e62a0af729..5076ab45e6 100644
--- a/src/GitHub.InlineReviews/Services/PullRequestSession.cs
+++ b/src/GitHub.InlineReviews/Services/PullRequestSession.cs
@@ -11,6 +11,7 @@
using System.Threading;
using System.Reactive.Subjects;
using static System.FormattableString;
+using GitHub.Primitives;
namespace GitHub.InlineReviews.Services
{
@@ -32,16 +33,15 @@ public class PullRequestSession : ReactiveObject, IPullRequestSession
bool isCheckedOut;
string mergeBase;
IReadOnlyList files;
- IPullRequestModel pullRequest;
+ PullRequestDetailModel pullRequest;
string pullRequestNodeId;
- Subject pullRequestChanged = new Subject();
+ Subject pullRequestChanged = new Subject();
bool hasPendingReview;
- string pendingReviewNodeId { get; set; }
public PullRequestSession(
IPullRequestSessionService service,
- IAccount user,
- IPullRequestModel pullRequest,
+ ActorModel user,
+ PullRequestDetailModel pullRequest,
ILocalRepositoryModel localRepository,
string repositoryOwner,
bool isCheckedOut)
@@ -127,41 +127,35 @@ public string GetRelativePath(string path)
}
///
- public async Task PostReviewComment(
+ public async Task PostReviewComment(
string body,
string commitId,
string path,
IReadOnlyList diff,
int position)
{
- IPullRequestReviewCommentModel model;
-
if (!HasPendingReview)
{
- var pullRequestNodeId = await GetPullRequestNodeId();
- model = await service.PostStandaloneReviewComment(
+ var model = await service.PostStandaloneReviewComment(
LocalRepository,
- User,
- pullRequestNodeId,
+ PullRequest.Id,
body,
commitId,
path,
position);
+ await Update(model);
}
else
{
- model = await service.PostPendingReviewComment(
+ var model = await service.PostPendingReviewComment(
LocalRepository,
- User,
- pendingReviewNodeId,
+ PendingReviewId,
body,
commitId,
path,
position);
+ await Update(model);
}
-
- await AddComment(model);
- return model;
}
///
@@ -171,59 +165,48 @@ public string GetRelativePath(string path)
await service.DeleteComment(
LocalRepository,
RepositoryOwner,
- User,
number);
-
- await RemoveComment(number);
}
///
- public async Task EditComment(string commentNodeId, string body)
+ public async Task EditComment(string commentNodeId, string body)
{
var model = await service.EditComment(
LocalRepository,
RepositoryOwner,
- User,
commentNodeId,
body);
- await ReplaceComment(model);
- return model;
+ await Update(model);
}
///
- public async Task PostReviewComment(
+ public async Task PostReviewComment(
string body,
- string inReplyToNodeId)
+ string inReplyTo)
{
- IPullRequestReviewCommentModel model;
-
if (!HasPendingReview)
{
- var pullRequestNodeId = await GetPullRequestNodeId();
- model = await service.PostStandaloneReviewCommentReply(
+ var model = await service.PostStandaloneReviewCommentReply(
LocalRepository,
- User,
- pullRequestNodeId,
+ PullRequest.Id,
body,
- inReplyToNodeId);
+ inReplyTo);
+ await Update(model);
}
else
{
- model = await service.PostPendingReviewCommentReply(
+ var model = await service.PostPendingReviewCommentReply(
LocalRepository,
- User,
- pendingReviewNodeId,
+ PendingReviewId,
body,
- inReplyToNodeId);
+ inReplyTo);
+ await Update(model);
}
-
- await AddComment(model);
- return model;
}
///
- public async Task StartReview()
+ public async Task StartReview()
{
if (HasPendingReview)
{
@@ -232,11 +215,9 @@ public async Task StartReview()
var model = await service.CreatePendingReview(
LocalRepository,
- User,
await GetPullRequestNodeId());
- await AddReview(model);
- return model;
+ await Update(model);
}
///
@@ -247,31 +228,26 @@ public async Task CancelReview()
throw new InvalidOperationException("There is no pending review to cancel.");
}
- await service.CancelPendingReview(LocalRepository, pendingReviewNodeId);
+ await service.CancelPendingReview(LocalRepository, PendingReviewId);
PullRequest.Reviews = PullRequest.Reviews
- .Where(x => x.NodeId != pendingReviewNodeId)
- .ToList();
- PullRequest.ReviewComments = PullRequest.ReviewComments
- .Where(x => x.PullRequestReviewId != PendingReviewId)
+ .Where(x => x.Id != PendingReviewId)
.ToList();
await Update(PullRequest);
}
///
- public async Task PostReview(string body, Octokit.PullRequestReviewEvent e)
+ public async Task PostReview(string body, Octokit.PullRequestReviewEvent e)
{
- IPullRequestReviewModel model;
+ PullRequestDetailModel model;
- if (pendingReviewNodeId == null)
+ if (PendingReviewId == null)
{
model = await service.PostReview(
LocalRepository,
- RepositoryOwner,
- User,
- PullRequest.Number,
- PullRequest.Head.Sha,
+ PullRequest.Id,
+ PullRequest.HeadRefSha,
body,
e);
}
@@ -279,18 +255,28 @@ public async Task PostReview(string body, Octokit.PullR
{
model = await service.SubmitPendingReview(
LocalRepository,
- User,
- pendingReviewNodeId,
+ PendingReviewId,
body,
e);
}
- await AddReview(model);
- return model;
+ await Update(model);
+ }
+
+ ///
+ public async Task Refresh()
+ {
+ var address = HostAddress.Create(LocalRepository.CloneUrl);
+ var model = await service.ReadPullRequestDetail(
+ address,
+ RepositoryOwner,
+ LocalRepository.Name,
+ PullRequest.Number);
+ await Update(model);
}
///
- public async Task Update(IPullRequestModel pullRequestModel)
+ async Task Update(PullRequestDetailModel pullRequestModel)
{
PullRequest = pullRequestModel;
mergeBase = null;
@@ -304,58 +290,27 @@ public async Task Update(IPullRequestModel pullRequestModel)
pullRequestChanged.OnNext(pullRequestModel);
}
- async Task AddComment(IPullRequestReviewCommentModel comment)
- {
- PullRequest.ReviewComments = PullRequest.ReviewComments
- .Concat(new[] { comment })
- .ToList();
- await Update(PullRequest);
- }
-
- async Task ReplaceComment(IPullRequestReviewCommentModel comment)
+ async Task AddComment(PullRequestReviewCommentModel comment)
{
- PullRequest.ReviewComments = PullRequest.ReviewComments
- .Select(model => model.Id == comment.Id ? comment: model)
- .ToList();
-
- await Update(PullRequest);
- }
-
- async Task RemoveComment(int commentId)
- {
- PullRequest.ReviewComments = PullRequest.ReviewComments
- .Where(model => model.Id != commentId)
- .ToList();
-
- await Update(PullRequest);
- }
-
- async Task AddReview(IPullRequestReviewModel review)
- {
- PullRequest.Reviews = PullRequest.Reviews
- .Where(x => x.NodeId != review.NodeId)
- .Concat(new[] { review })
- .ToList();
+ var review = PullRequest.Reviews.FirstOrDefault(x => x.Id == PendingReviewId);
- if (review.State != PullRequestReviewState.Pending)
+ if (review == null)
{
- foreach (var comment in PullRequest.ReviewComments)
- {
- if (comment.PullRequestReviewId == review.Id)
- {
- comment.IsPending = false;
- }
- }
+ throw new KeyNotFoundException("Could not find pending review.");
}
+ review.Comments = review.Comments
+ .Concat(new[] { comment })
+ .ToList();
await Update(PullRequest);
}
async Task UpdateFile(PullRequestSessionFile file)
{
+ await Task.Delay(0);
var mergeBaseSha = await GetMergeBase();
- file.BaseSha = PullRequest.Base.Sha;
- file.CommitSha = file.IsTrackingHead ? PullRequest.Head.Sha : file.CommitSha;
+ file.BaseSha = PullRequest.BaseRefSha;
+ file.CommitSha = file.IsTrackingHead ? PullRequest.HeadRefSha : file.CommitSha;
file.Diff = await service.Diff(LocalRepository, mergeBaseSha, file.CommitSha, file.RelativePath);
file.InlineCommentThreads = service.BuildCommentThreads(PullRequest, file.RelativePath, file.Diff, file.CommitSha);
}
@@ -363,19 +318,17 @@ async Task UpdateFile(PullRequestSessionFile file)
void UpdatePendingReview()
{
var pendingReview = PullRequest.Reviews
- .FirstOrDefault(x => x.State == PullRequestReviewState.Pending && x.User.Login == User.Login);
+ .FirstOrDefault(x => x.State == PullRequestReviewState.Pending && x.Author.Login == User.Login);
if (pendingReview != null)
{
HasPendingReview = true;
- pendingReviewNodeId = pendingReview.NodeId;
PendingReviewId = pendingReview.Id;
}
else
{
HasPendingReview = false;
- pendingReviewNodeId = null;
- PendingReviewId = 0;
+ PendingReviewId = null;
}
}
@@ -428,10 +381,10 @@ public bool IsCheckedOut
}
///
- public IAccount User { get; }
+ public ActorModel User { get; }
///
- public IPullRequestModel PullRequest
+ public PullRequestDetailModel PullRequest
{
get { return pullRequest; }
private set
@@ -449,7 +402,7 @@ private set
}
///
- public IObservable PullRequestChanged => pullRequestChanged;
+ public IObservable PullRequestChanged => pullRequestChanged;
///
public ILocalRepositoryModel LocalRepository { get; }
@@ -465,7 +418,7 @@ public bool HasPendingReview
}
///
- public long PendingReviewId { get; private set; }
+ public string PendingReviewId { get; private set; }
IEnumerable FilePaths
{
diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs
index 50e5b0dbba..56109be303 100644
--- a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs
+++ b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs
@@ -13,6 +13,7 @@
using GitHub.InlineReviews.Models;
using GitHub.Logging;
using GitHub.Models;
+using GitHub.Primitives;
using GitHub.Services;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@@ -32,8 +33,6 @@ public class PullRequestSessionManager : ReactiveObject, IPullRequestSessionMana
static readonly ILogger log = LogManager.ForContext();
readonly IPullRequestService service;
readonly IPullRequestSessionService sessionService;
- readonly IConnectionManager connectionManager;
- readonly IModelServiceFactory modelServiceFactory;
readonly Dictionary, WeakReference> sessions =
new Dictionary, WeakReference>();
TaskCompletionSource initialized;
@@ -45,27 +44,19 @@ public class PullRequestSessionManager : ReactiveObject, IPullRequestSessionMana
///
/// The PR service to use.
/// The PR session service to use.
- /// The connectionManager to use.
- /// The ModelService factory.
/// The team explorer service to use.
[ImportingConstructor]
public PullRequestSessionManager(
IPullRequestService service,
IPullRequestSessionService sessionService,
- IConnectionManager connectionManager,
- IModelServiceFactory modelServiceFactory,
ITeamExplorerContext teamExplorerContext)
{
Guard.ArgumentNotNull(service, nameof(service));
Guard.ArgumentNotNull(sessionService, nameof(sessionService));
- Guard.ArgumentNotNull(connectionManager, nameof(connectionManager));
- Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory));
Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext));
this.service = service;
this.sessionService = sessionService;
- this.connectionManager = connectionManager;
- this.modelServiceFactory = modelServiceFactory;
initialized = new TaskCompletionSource(null);
Observable.FromEventPattern(teamExplorerContext, nameof(teamExplorerContext.StatusChanged))
@@ -155,11 +146,11 @@ public string GetRelativePath(ITextBuffer buffer)
}
///
- public async Task GetSession(IPullRequestModel pullRequest)
+ public async Task GetSession(string owner, string name, int number)
{
- Guard.ArgumentNotNull(pullRequest, nameof(pullRequest));
+ var session = await GetSessionInternal(owner, name, number);
- if (await service.EnsureLocalBranchesAreMarkedAsPullRequests(repository, pullRequest))
+ if (await service.EnsureLocalBranchesAreMarkedAsPullRequests(repository, session.PullRequest))
{
// The branch for the PR was not previously marked with the PR number in the git
// config so we didn't pick up that the current branch is a PR branch. That has
@@ -167,7 +158,7 @@ public async Task GetSession(IPullRequestModel pullRequest)
await StatusChanged();
}
- return await GetSessionInternal(pullRequest);
+ return session;
}
///
@@ -215,19 +206,14 @@ async Task StatusChanged()
if (pr != null)
{
var changePR =
- pr.Item1 != (session?.PullRequest.Base.RepositoryCloneUrl.Owner) ||
+ pr.Item1 != (session?.PullRequest.BaseRepositoryOwner) ||
pr.Item2 != (session?.PullRequest.Number);
if (changePR)
{
- var modelService = await connectionManager.GetModelService(repository, modelServiceFactory);
- var pullRequest = await modelService?.GetPullRequest(pr.Item1, repository.Name, pr.Item2);
- if (pullRequest != null)
- {
- var newSession = await GetSessionInternal(pullRequest);
- if (newSession != null) newSession.IsCheckedOut = true;
- session = newSession;
- }
+ var newSession = await GetSessionInternal(pr.Item1, repository.Name, pr.Item2);
+ if (newSession != null) newSession.IsCheckedOut = true;
+ session = newSession;
}
}
else
@@ -244,11 +230,11 @@ async Task StatusChanged()
}
}
- async Task GetSessionInternal(IPullRequestModel pullRequest)
+ async Task GetSessionInternal(string owner, string name, int number)
{
PullRequestSession session = null;
WeakReference weakSession;
- var key = Tuple.Create(pullRequest.Base.RepositoryCloneUrl.Owner, pullRequest.Number);
+ var key = Tuple.Create(owner, number);
if (sessions.TryGetValue(key, out weakSession))
{
@@ -257,23 +243,17 @@ async Task GetSessionInternal(IPullRequestModel pullRequest)
if (session == null)
{
- var modelService = await connectionManager.GetModelService(repository, modelServiceFactory);
-
- if (modelService != null)
- {
- session = new PullRequestSession(
- sessionService,
- await modelService.GetCurrentUser(),
- pullRequest,
- repository,
- key.Item1,
- false);
- sessions[key] = new WeakReference(session);
- }
- }
- else
- {
- await session.Update(pullRequest);
+ var address = HostAddress.Create(repository.CloneUrl);
+ var pullRequest = await sessionService.ReadPullRequestDetail(address, owner, name, number);
+
+ session = new PullRequestSession(
+ sessionService,
+ await sessionService.ReadViewer(address),
+ pullRequest,
+ repository,
+ key.Item1,
+ false);
+ sessions[key] = new WeakReference(session);
}
return session;
@@ -287,12 +267,12 @@ async Task UpdateLiveFile(PullRequestSessionLiveFile file, bool rebuildThreads)
{
var mergeBase = await session.GetMergeBase();
var contents = sessionService.GetContents(file.TextBuffer);
- file.BaseSha = session.PullRequest.Base.Sha;
+ file.BaseSha = session.PullRequest.BaseRefSha;
file.CommitSha = await CalculateCommitSha(session, file, contents);
file.Diff = await sessionService.Diff(
session.LocalRepository,
mergeBase,
- session.PullRequest.Head.Sha,
+ session.PullRequest.HeadRefSha,
file.RelativePath,
contents);
@@ -302,7 +282,7 @@ async Task UpdateLiveFile(PullRequestSessionLiveFile file, bool rebuildThreads)
session.PullRequest,
file.RelativePath,
file.Diff,
- session.PullRequest.Head.Sha);
+ session.PullRequest.HeadRefSha);
}
else
{
diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs
index f9d71cbcf4..0e0eb3eb39 100644
--- a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs
+++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs
@@ -23,6 +23,7 @@
using ReactiveUI;
using Serilog;
using PullRequestReviewEvent = Octokit.PullRequestReviewEvent;
+using static Octokit.GraphQL.Variable;
// GraphQL DatabaseId field are marked as deprecated, but we need them for interop with REST.
#pragma warning disable CS0618
@@ -36,6 +37,8 @@ namespace GitHub.InlineReviews.Services
public class PullRequestSessionService : IPullRequestSessionService
{
static readonly ILogger log = LogManager.ForContext();
+ static ICompiledQuery readPullRequest;
+ static ICompiledQuery readViewer;
readonly IGitService gitService;
readonly IGitClient gitClient;
@@ -43,7 +46,6 @@ public class PullRequestSessionService : IPullRequestSessionService
readonly IApiClientFactory apiClientFactory;
readonly IGraphQLClientFactory graphqlFactory;
readonly IUsageTracker usageTracker;
-
readonly IDictionary, string> mergeBaseCache;
[ImportingConstructor]
@@ -85,22 +87,22 @@ public virtual async Task> Diff(ILocalRepositoryModel r
///
public IReadOnlyList BuildCommentThreads(
- IPullRequestModel pullRequest,
+ PullRequestDetailModel pullRequest,
string relativePath,
IReadOnlyList diff,
string headSha)
{
relativePath = relativePath.Replace("\\", "/");
- var commentsByPosition = pullRequest.ReviewComments
- .Where(x => x.Path == relativePath && x.OriginalPosition.HasValue)
+ var threadsByPosition = pullRequest.Threads
+ .Where(x => x.Path == relativePath && !x.IsOutdated)
.OrderBy(x => x.Id)
- .GroupBy(x => Tuple.Create(x.OriginalCommitId, x.OriginalPosition.Value));
+ .GroupBy(x => Tuple.Create(x.OriginalCommitSha, x.OriginalPosition));
var threads = new List();
- foreach (var comments in commentsByPosition)
+ foreach (var thread in threadsByPosition)
{
- var hunk = comments.First().DiffHunk;
+ var hunk = thread.First().DiffHunk;
var chunks = DiffUtilities.ParseFragment(hunk);
var chunk = chunks.Last();
var diffLines = chunk.Lines.Reverse().Take(5).ToList();
@@ -111,12 +113,16 @@ public virtual async Task> Diff(ILocalRepositoryModel r
continue;
}
- var thread = new InlineCommentThreadModel(
+ var inlineThread = new InlineCommentThreadModel(
relativePath,
headSha,
diffLines,
- comments);
- threads.Add(thread);
+ thread.SelectMany(t => t.Comments.Select(c => new InlineCommentModel
+ {
+ Comment = c,
+ Review = pullRequest.Reviews.FirstOrDefault(x => x.Comments.Contains(c)),
+ })));
+ threads.Add(inlineThread);
}
UpdateCommentThreads(threads, diff);
@@ -124,11 +130,11 @@ public virtual async Task> Diff(ILocalRepositoryModel r
}
///
- public IReadOnlyList> UpdateCommentThreads(
+ public IReadOnlyList> UpdateCommentThreads(
IReadOnlyList threads,
IReadOnlyList diff)
{
- var changedLines = new List>();
+ var changedLines = new List>();
foreach (var thread in threads)
{
@@ -151,7 +157,7 @@ public virtual async Task> Diff(ILocalRepositoryModel r
if (changed)
{
- var side = thread.DiffLineType == DiffChangeType.Delete ? DiffSide.Left : DiffSide.Right;
+ var side = thread.DiffLineType == DiffChangeType.Delete ? GitHub.Models.DiffSide.Left : GitHub.Models.DiffSide.Right;
if (oldLineNumber != -1) changedLines.Add(Tuple.Create(oldLineNumber, side));
if (newLineNumber != -1 && newLineNumber != oldLineNumber) changedLines.Add(Tuple.Create(newLineNumber, side));
}
@@ -260,6 +266,108 @@ public async Task ReadFileAsync(string path)
return null;
}
+ public virtual async Task ReadPullRequestDetail(HostAddress address, string owner, string name, int number)
+ {
+ if (readPullRequest == null)
+ {
+ readPullRequest = new Query()
+ .Repository(Var(nameof(owner)), Var(nameof(name)))
+ .PullRequest(Var(nameof(number)))
+ .Select(pr => new PullRequestDetailModel
+ {
+ Id = pr.Id.Value,
+ Number = pr.Number,
+ Author = new ActorModel
+ {
+ Login = pr.Author.Login,
+ AvatarUrl = pr.Author.AvatarUrl(null),
+ },
+ Title = pr.Title,
+ Body = pr.Body,
+ BaseRefSha = pr.BaseRefOid,
+ BaseRefName = pr.BaseRefName,
+ BaseRepositoryOwner = pr.Repository.Owner.Login,
+ HeadRefName = pr.HeadRefName,
+ HeadRefSha = pr.HeadRefOid,
+ HeadRepositoryOwner = pr.HeadRepositoryOwner != null ? pr.HeadRepositoryOwner.Login : null,
+ State = (PullRequestStateEnum)pr.State,
+ UpdatedAt = pr.UpdatedAt,
+ Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new PullRequestReviewModel
+ {
+ Id = review.Id.Value,
+ Body = review.Body,
+ CommitId = review.Commit.Oid,
+ State = (GitHub.Models.PullRequestReviewState)review.State,
+ SubmittedAt = review.SubmittedAt,
+ Author = new ActorModel
+ {
+ Login = review.Author.Login,
+ AvatarUrl = review.Author.AvatarUrl(null),
+ },
+ Comments = review.Comments(null, null, null, null).AllPages().Select(comment => new CommentAdapter
+ {
+ Id = comment.Id.Value,
+ Author = new ActorModel
+ {
+ Login = comment.Author.Login,
+ AvatarUrl = comment.Author.AvatarUrl(null),
+ },
+ Body = comment.Body,
+ Path = comment.Path,
+ CommitSha = comment.Commit.Oid,
+ DiffHunk = comment.DiffHunk,
+ Position = comment.Position,
+ OriginalPosition = comment.OriginalPosition,
+ OriginalCommitId = comment.OriginalCommit.Oid,
+ ReplyTo = comment.ReplyTo != null ? comment.ReplyTo.Id.Value : null,
+ CreatedAt = comment.CreatedAt,
+ Url = comment.Url,
+ }).ToList(),
+ }).ToList(),
+ }).Compile();
+ }
+
+ var vars = new Dictionary
+ {
+ { nameof(owner), owner },
+ { nameof(name), name },
+ { nameof(number), number },
+ };
+
+ var connection = await graphqlFactory.CreateConnection(address);
+ var result = await connection.Run(readPullRequest, vars);
+
+ var apiClient = await apiClientFactory.Create(address);
+ var files = await apiClient.GetPullRequestFiles(owner, name, number).ToList();
+
+ result.ChangedFiles = files.Select(file => new PullRequestFileModel
+ {
+ FileName = file.FileName,
+ Sha = file.Sha,
+ Status = (PullRequestFileStatus)Enum.Parse(typeof(PullRequestFileStatus), file.Status, true),
+ }).ToList();
+
+ BuildPullRequestThreads(result);
+ return result;
+ }
+
+ public virtual async Task ReadViewer(HostAddress address)
+ {
+ if (readViewer == null)
+ {
+ readViewer = new Query()
+ .Viewer
+ .Select(x => new ActorModel
+ {
+ Login = x.Login,
+ AvatarUrl = x.AvatarUrl(null),
+ }).Compile();
+ }
+
+ var connection = await graphqlFactory.CreateConnection(address);
+ return await connection.Run(readViewer);
+ }
+
public async Task GetGraphQLPullRequestId(
ILocalRepositoryModel localRepository,
string repositoryOwner,
@@ -273,14 +381,14 @@ public async Task ReadFileAsync(string path)
.PullRequest(number)
.Select(x => x.Id);
- return await graphql.Run(query);
+ return (await graphql.Run(query)).Value;
}
///
- public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel repository, IPullRequestModel pullRequest)
+ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest)
{
- var baseSha = pullRequest.Base.Sha;
- var headSha = pullRequest.Head.Sha;
+ var baseSha = pullRequest.BaseRefSha;
+ var headSha = pullRequest.HeadRefSha;
var key = new Tuple(baseSha, headSha);
string mergeBase;
@@ -291,9 +399,9 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
using (var repo = await GetRepository(repository))
{
- var targetUrl = pullRequest.Base.RepositoryCloneUrl;
- var headUrl = pullRequest.Head.RepositoryCloneUrl;
- var baseRef = pullRequest.Base.Ref;
+ var targetUrl = repository.CloneUrl.WithOwner(pullRequest.BaseRepositoryOwner);
+ var headUrl = repository.CloneUrl.WithOwner(pullRequest.HeadRepositoryOwner);
+ var baseRef = pullRequest.BaseRefName;
var pullNumber = pullRequest.Number;
try
{
@@ -320,9 +428,8 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
}
///
- public async Task CreatePendingReview(
+ public async Task CreatePendingReview(
ILocalRepositoryModel localRepository,
- IAccount user,
string pullRequestId)
{
var address = HostAddress.Create(localRepository.CloneUrl.Host);
@@ -330,28 +437,24 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
var review = new AddPullRequestReviewInput
{
- PullRequestId = pullRequestId,
+ PullRequestId = new ID(pullRequestId),
};
- var addReview = new Mutation()
+ var mutation = new Mutation()
.AddPullRequestReview(review)
- .Select(x => new PullRequestReviewModel
+ .Select(x => new
{
- Id = x.PullRequestReview.DatabaseId.Value,
- Body = x.PullRequestReview.Body,
- CommitId = x.PullRequestReview.Commit.Oid,
- NodeId = x.PullRequestReview.Id,
- State = FromGraphQL(x.PullRequestReview.State),
- User = user,
+ x.PullRequestReview.Repository.Owner.Login,
+ x.PullRequestReview.PullRequest.Number
});
- var result = await graphql.Run(addReview);
+ var result = await graphql.Run(mutation);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentStartReview);
- return result;
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
///
- public async Task CancelPendingReview(
+ public async Task CancelPendingReview(
ILocalRepositoryModel localRepository,
string reviewId)
{
@@ -360,52 +463,55 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
var delete = new DeletePullRequestReviewInput
{
- PullRequestReviewId = reviewId,
+ PullRequestReviewId = new ID(reviewId),
};
- var deleteReview = new Mutation()
+ var mutation = new Mutation()
.DeletePullRequestReview(delete)
- .Select(x => x.ClientMutationId);
+ .Select(x => new
+ {
+ x.PullRequestReview.Repository.Owner.Login,
+ x.PullRequestReview.PullRequest.Number
+ });
- await graphql.Run(deleteReview);
+ var result = await graphql.Run(mutation);
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
///
- public async Task PostReview(
+ public async Task PostReview(
ILocalRepositoryModel localRepository,
- string remoteRepositoryOwner,
- IAccount user,
- int number,
+ string pullRequestId,
string commitId,
string body,
PullRequestReviewEvent e)
{
var address = HostAddress.Create(localRepository.CloneUrl.Host);
- var apiClient = await apiClientFactory.Create(address);
-
- var result = await apiClient.PostPullRequestReview(
- remoteRepositoryOwner,
- localRepository.Name,
- number,
- commitId,
- body,
- e);
-
- await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts);
+ var graphql = await graphqlFactory.CreateConnection(address);
- return new PullRequestReviewModel
+ var addReview = new AddPullRequestReviewInput
{
- Id = result.Id,
- Body = result.Body,
- CommitId = result.CommitId,
- State = (GitHub.Models.PullRequestReviewState)result.State.Value,
- User = user,
+ Body = body,
+ CommitOID = commitId,
+ Event = ToGraphQl(e),
+ PullRequestId = new ID(pullRequestId),
};
+
+ var mutation = new Mutation()
+ .AddPullRequestReview(addReview)
+ .Select(x => new
+ {
+ x.PullRequestReview.Repository.Owner.Login,
+ x.PullRequestReview.PullRequest.Number
+ });
+
+ var result = await graphql.Run(mutation);
+ await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts);
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
- public async Task SubmitPendingReview(
+ public async Task SubmitPendingReview(
ILocalRepositoryModel localRepository,
- IAccount user,
string pendingReviewId,
string body,
PullRequestReviewEvent e)
@@ -417,30 +523,25 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
{
Body = body,
Event = ToGraphQl(e),
- PullRequestReviewId = pendingReviewId,
+ PullRequestReviewId = new ID(pendingReviewId),
};
var mutation = new Mutation()
.SubmitPullRequestReview(submit)
- .Select(x => new PullRequestReviewModel
+ .Select(x => new
{
- Body = body,
- CommitId = x.PullRequestReview.Commit.Oid,
- Id = x.PullRequestReview.DatabaseId.Value,
- NodeId = x.PullRequestReview.Id,
- State = (GitHub.Models.PullRequestReviewState)x.PullRequestReview.State,
- User = user,
+ x.PullRequestReview.Repository.Owner.Login,
+ x.PullRequestReview.PullRequest.Number
});
var result = await graphql.Run(mutation);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts);
- return result;
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
///
- public async Task PostPendingReviewComment(
+ public async Task PostPendingReviewComment(
ILocalRepositoryModel localRepository,
- IAccount user,
string pendingReviewId,
string body,
string commitId,
@@ -456,37 +557,25 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
CommitOID = commitId,
Path = path,
Position = position,
- PullRequestReviewId = pendingReviewId,
+ PullRequestReviewId = new ID(pendingReviewId),
};
var addComment = new Mutation()
.AddPullRequestReviewComment(comment)
- .Select(x => new PullRequestReviewCommentModel
+ .Select(x => new
{
- Id = x.Comment.DatabaseId.Value,
- NodeId = x.Comment.Id,
- Body = x.Comment.Body,
- CommitId = x.Comment.Commit.Oid,
- Path = x.Comment.Path,
- Position = x.Comment.Position,
- CreatedAt = x.Comment.CreatedAt.Value,
- DiffHunk = x.Comment.DiffHunk,
- OriginalPosition = x.Comment.OriginalPosition,
- OriginalCommitId = x.Comment.OriginalCommit.Oid,
- PullRequestReviewId = x.Comment.PullRequestReview.DatabaseId.Value,
- User = user,
- IsPending = true,
+ x.Comment.Repository.Owner.Login,
+ x.Comment.PullRequest.Number
});
var result = await graphql.Run(addComment);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost);
- return result;
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
///
- public async Task PostPendingReviewCommentReply(
+ public async Task PostPendingReviewCommentReply(
ILocalRepositoryModel localRepository,
- IAccount user,
string pendingReviewId,
string body,
string inReplyTo)
@@ -497,39 +586,27 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
var comment = new AddPullRequestReviewCommentInput
{
Body = body,
- InReplyTo = inReplyTo,
- PullRequestReviewId = pendingReviewId,
+ InReplyTo = new ID(inReplyTo),
+ PullRequestReviewId = new ID(pendingReviewId),
};
var addComment = new Mutation()
.AddPullRequestReviewComment(comment)
- .Select(x => new PullRequestReviewCommentModel
+ .Select(x => new
{
- Id = x.Comment.DatabaseId.Value,
- NodeId = x.Comment.Id,
- Body = x.Comment.Body,
- CommitId = x.Comment.Commit.Oid,
- Path = x.Comment.Path,
- Position = x.Comment.Position,
- CreatedAt = x.Comment.CreatedAt.Value,
- DiffHunk = x.Comment.DiffHunk,
- OriginalPosition = x.Comment.OriginalPosition,
- OriginalCommitId = x.Comment.OriginalCommit.Oid,
- PullRequestReviewId = x.Comment.PullRequestReview.DatabaseId.Value,
- User = user,
- IsPending = true,
+ x.Comment.Repository.Owner.Login,
+ x.Comment.PullRequest.Number
});
var result = await graphql.Run(addComment);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost);
- return result;
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
///
- public async Task PostStandaloneReviewComment(
+ public async Task PostStandaloneReviewComment(
ILocalRepositoryModel localRepository,
- IAccount user,
- string pullRequestNodeId,
+ string pullRequestId,
string body,
string commitId,
string path,
@@ -543,7 +620,7 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
Body = body,
CommitOID = commitId,
Event = Octokit.GraphQL.Model.PullRequestReviewEvent.Comment,
- PullRequestId = pullRequestNodeId,
+ PullRequestId = new ID(pullRequestId),
Comments = new[]
{
new DraftPullRequestReviewComment
@@ -557,51 +634,33 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
var mutation = new Mutation()
.AddPullRequestReview(addReview)
- .Select(payload =>
- payload.PullRequestReview
- .Comments(1, null, null, null)
- .Nodes.Select(x => new PullRequestReviewCommentModel
- {
- Id = x.DatabaseId.Value,
- NodeId = x.Id,
- Body = x.Body,
- CommitId = x.Commit.Oid,
- Path = x.Path,
- Position = x.Position,
- CreatedAt = x.CreatedAt.Value,
- DiffHunk = x.DiffHunk,
- OriginalPosition = x.OriginalPosition,
- OriginalCommitId = x.OriginalCommit.Oid,
- PullRequestReviewId = x.PullRequestReview.DatabaseId.Value,
- User = user,
- IsPending = false
- }));
-
- var result = (await graphql.Run(mutation)).First();
+ .Select(x => new
+ {
+ x.PullRequestReview.Repository.Owner.Login,
+ x.PullRequestReview.PullRequest.Number
+ });
+
+ var result = await graphql.Run(mutation);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost);
- return result;
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
///
- public async Task PostStandaloneReviewCommentReply(
+ public async Task PostStandaloneReviewCommentReply(
ILocalRepositoryModel localRepository,
- IAccount user,
- string pullRequestNodeId,
+ string pullRequestId,
string body,
- string inReplyToNodeId)
+ string inReplyTo)
{
- var review = await CreatePendingReview(localRepository, user, pullRequestNodeId);
- var comment = await PostPendingReviewCommentReply(localRepository, user, review.NodeId, body, inReplyToNodeId);
- await SubmitPendingReview(localRepository, user, review.NodeId, null, PullRequestReviewEvent.Comment);
- comment.IsPending = false;
- return comment;
+ var review = await CreatePendingReview(localRepository, pullRequestId);
+ var comment = await PostPendingReviewCommentReply(localRepository, review.Id, body, inReplyTo);
+ return await SubmitPendingReview(localRepository, review.Id, null, PullRequestReviewEvent.Comment);
}
///
- public async Task DeleteComment(
+ public async Task DeleteComment(
ILocalRepositoryModel localRepository,
string remoteRepositoryOwner,
- IAccount user,
int number)
{
var address = HostAddress.Create(localRepository.CloneUrl.Host);
@@ -613,12 +672,12 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
number);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentDelete);
+ return await ReadPullRequestDetail(address, remoteRepositoryOwner, localRepository.Name, number);
}
///
- public async Task EditComment(ILocalRepositoryModel localRepository,
+ public async Task EditComment(ILocalRepositoryModel localRepository,
string remoteRepositoryOwner,
- IAccount user,
string commentNodeId,
string body)
{
@@ -628,30 +687,19 @@ public virtual async Task GetPullRequestMergeBase(ILocalRepositoryModel
var updatePullRequestReviewCommentInput = new UpdatePullRequestReviewCommentInput
{
Body = body,
- PullRequestReviewCommentId = commentNodeId
+ PullRequestReviewCommentId = new ID(commentNodeId),
};
var editComment = new Mutation().UpdatePullRequestReviewComment(updatePullRequestReviewCommentInput)
- .Select(x => new PullRequestReviewCommentModel
+ .Select(x => new
{
- Id = x.PullRequestReviewComment.DatabaseId.Value,
- NodeId = x.PullRequestReviewComment.Id,
- Body = x.PullRequestReviewComment.Body,
- CommitId = x.PullRequestReviewComment.Commit.Oid,
- Path = x.PullRequestReviewComment.Path,
- Position = x.PullRequestReviewComment.Position,
- CreatedAt = x.PullRequestReviewComment.CreatedAt.Value,
- DiffHunk = x.PullRequestReviewComment.DiffHunk,
- OriginalPosition = x.PullRequestReviewComment.OriginalPosition,
- OriginalCommitId = x.PullRequestReviewComment.OriginalCommit.Oid,
- PullRequestReviewId = x.PullRequestReviewComment.PullRequestReview.DatabaseId.Value,
- User = user,
- IsPending = !x.PullRequestReviewComment.PublishedAt.HasValue,
+ x.PullRequestReviewComment.Repository.Owner.Login,
+ x.PullRequestReviewComment.PullRequest.Number
});
var result = await graphql.Run(editComment);
await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost);
- return result;
+ return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number);
}
int GetUpdatedLineNumber(IInlineCommentThreadModel thread, IEnumerable diff)
@@ -673,6 +721,65 @@ Task GetRepository(ILocalRepositoryModel repository)
return Task.Factory.StartNew(() => gitService.GetRepository(repository.LocalPath));
}
+ static void BuildPullRequestThreads(PullRequestDetailModel model)
+ {
+ var commentsByReplyId = new Dictionary>();
+
+ // Get all comments that are not replies.
+ foreach (CommentAdapter comment in model.Reviews.SelectMany(x => x.Comments))
+ {
+ if (comment.ReplyTo == null)
+ {
+ commentsByReplyId.Add(comment.Id, new List { comment });
+ }
+ }
+
+ // Get the comments that are replies and place them into the relevant list.
+ foreach (CommentAdapter comment in model.Reviews.SelectMany(x => x.Comments))
+ {
+ if (comment.ReplyTo != null)
+ {
+ List thread = null;
+
+ if (commentsByReplyId.TryGetValue(comment.ReplyTo, out thread))
+ {
+ thread.Add(comment);
+ break;
+ }
+ }
+ }
+
+ // Build a collection of threads for the information collected above.
+ var threads = new List();
+
+ foreach (var threadSource in commentsByReplyId)
+ {
+ var adapter = threadSource.Value[0];
+
+ var thread = new PullRequestReviewThreadModel
+ {
+ Comments = threadSource.Value,
+ CommitSha = adapter.CommitSha,
+ DiffHunk = adapter.DiffHunk,
+ Id = adapter.Id,
+ IsOutdated = adapter.Position == null,
+ OriginalCommitSha = adapter.OriginalCommitId,
+ OriginalPosition = adapter.OriginalPosition,
+ Path = adapter.Path,
+ Position = adapter.Position,
+ };
+
+ // Set a reference to the thread in the comment.
+ foreach (var comment in threadSource.Value)
+ {
+ comment.Thread = thread;
+ }
+
+ threads.Add(thread);
+ }
+
+ model.Threads = threads;
+ }
static GitHub.Models.PullRequestReviewState FromGraphQL(Octokit.GraphQL.Model.PullRequestReviewState s)
{
@@ -693,5 +800,16 @@ static Octokit.GraphQL.Model.PullRequestReviewEvent ToGraphQl(Octokit.PullReques
throw new NotSupportedException();
}
}
+
+ class CommentAdapter : PullRequestReviewCommentModel
+ {
+ public string Path { get; set; }
+ public string CommitSha { get; set; }
+ public string DiffHunk { get; set; }
+ public int? Position { get; set; }
+ public int OriginalPosition { get; set; }
+ public string OriginalCommitId { get; set; }
+ public string ReplyTo { get; set; }
+ }
}
}
diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs b/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs
index b0d8ffcb47..3891003d63 100644
--- a/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs
+++ b/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs
@@ -151,6 +151,7 @@ async Task Initialize()
}
else
{
+ side = DiffSide.Right;
await InitializeLiveFile();
sessionManagerSubscription = sessionManager
.WhenAnyValue(x => x.CurrentSession)
diff --git a/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs
index 016a5b1de7..84f35c4695 100644
--- a/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.ObjectModel;
+using System.Reactive;
using GitHub.Extensions;
using GitHub.Models;
+using GitHub.ViewModels;
using ReactiveUI;
namespace GitHub.InlineReviews.ViewModels
@@ -11,27 +13,27 @@ namespace GitHub.InlineReviews.ViewModels
///
public abstract class CommentThreadViewModel : ReactiveObject, ICommentThreadViewModel
{
- ReactiveCommand postComment;
- private ReactiveCommand editComment;
- private ReactiveCommand deleteComment;
+ ReactiveCommand postComment;
+ ReactiveCommand editComment;
+ ReactiveCommand deleteComment;
///
/// Intializes a new instance of the class.
///
/// The current user.
- protected CommentThreadViewModel(IAccount currentUser)
+ protected CommentThreadViewModel(ActorModel currentUser)
{
Guard.ArgumentNotNull(currentUser, nameof(currentUser));
Comments = new ObservableCollection();
- CurrentUser = currentUser;
+ CurrentUser = new ActorViewModel(currentUser);
}
///
public ObservableCollection Comments { get; }
///
- public ReactiveCommand PostComment
+ public ReactiveCommand PostComment
{
get { return postComment; }
protected set
@@ -45,7 +47,7 @@ protected set
}
}
- public ReactiveCommand EditComment
+ public ReactiveCommand EditComment
{
get { return editComment; }
protected set
@@ -57,7 +59,7 @@ protected set
}
}
- public ReactiveCommand DeleteComment
+ public ReactiveCommand DeleteComment
{
get { return deleteComment; }
protected set
@@ -70,9 +72,6 @@ protected set
}
///
- public IAccount CurrentUser { get; }
-
- ///
- public abstract Uri GetCommentUrl(int id);
+ public IActorViewModel CurrentUser { get; }
}
}
diff --git a/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs
index 14f5848a42..89276e7693 100644
--- a/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs
@@ -6,6 +6,7 @@
using GitHub.Extensions;
using GitHub.Logging;
using GitHub.Models;
+using GitHub.ViewModels;
using ReactiveUI;
using Serilog;
@@ -31,39 +32,37 @@ public class CommentViewModel : ReactiveObject, ICommentViewModel
///
/// The thread that the comment is a part of.
/// The current user.
- /// The ID of the comment.
- /// The GraphQL ID of the comment.
+ /// The GraphQL ID of the comment.
/// The comment body.
/// The comment edit state.
- /// The author of the comment.
+ /// The author of the comment.
/// The modified date of the comment.
protected CommentViewModel(
ICommentThreadViewModel thread,
- IAccount currentUser,
- int commentId,
- string commentNodeId,
+ IActorViewModel currentUser,
+ string commentId,
string body,
CommentEditState state,
- IAccount user,
- DateTimeOffset updatedAt)
+ IActorViewModel author,
+ DateTimeOffset updatedAt,
+ Uri webUrl)
{
Guard.ArgumentNotNull(thread, nameof(thread));
Guard.ArgumentNotNull(currentUser, nameof(currentUser));
- Guard.ArgumentNotNull(body, nameof(body));
- Guard.ArgumentNotNull(user, nameof(user));
+ Guard.ArgumentNotNull(author, nameof(author));
Thread = thread;
CurrentUser = currentUser;
Id = commentId;
- NodeId = commentNodeId;
Body = body;
EditState = state;
- User = user;
+ Author = author;
UpdatedAt = updatedAt;
+ WebUrl = webUrl;
var canDeleteObservable = this.WhenAnyValue(
x => x.EditState,
- x => x == CommentEditState.None && user.Login.Equals(currentUser.Login));
+ x => x == CommentEditState.None && author.Login == currentUser.Login);
canDelete = canDeleteObservable.ToProperty(this, x => x.CanDelete);
@@ -71,7 +70,7 @@ public class CommentViewModel : ReactiveObject, ICommentViewModel
var canEdit = this.WhenAnyValue(
x => x.EditState,
- x => x == CommentEditState.Placeholder || (x == CommentEditState.None && user.Login.Equals(currentUser.Login)));
+ x => x == CommentEditState.Placeholder || (x == CommentEditState.None && author.Login == currentUser.Login));
BeginEdit = ReactiveCommand.Create(canEdit);
BeginEdit.Subscribe(DoBeginEdit);
@@ -90,7 +89,7 @@ public class CommentViewModel : ReactiveObject, ICommentViewModel
CancelEdit.Subscribe(DoCancelEdit);
AddErrorHandler(CancelEdit);
- OpenOnGitHub = ReactiveCommand.Create(this.WhenAnyValue(x => x.Id, x => x != 0));
+ OpenOnGitHub = ReactiveCommand.Create(this.WhenAnyValue(x => x.Id).Select(x => x != null));
}
///
@@ -101,9 +100,17 @@ public class CommentViewModel : ReactiveObject, ICommentViewModel
/// The comment model.
protected CommentViewModel(
ICommentThreadViewModel thread,
- IAccount currentUser,
- ICommentModel model)
- : this(thread, currentUser, model.Id, model.NodeId, model.Body, CommentEditState.None, model.User, model.CreatedAt)
+ ActorModel currentUser,
+ CommentModel model)
+ : this(
+ thread,
+ new ActorViewModel(currentUser),
+ model.Id,
+ model.Body,
+ CommentEditState.None,
+ new ActorViewModel(model.Author),
+ model.CreatedAt,
+ new Uri(model.Url))
{
}
@@ -161,21 +168,14 @@ async Task DoCommitEdit(object unused)
ErrorMessage = null;
IsSubmitting = true;
- ICommentModel model;
-
- if (Id == 0)
+ if (Id == null)
{
- model = await Thread.PostComment.ExecuteAsyncTask(Body);
+ await Thread.PostComment.ExecuteAsyncTask(Body);
}
else
{
- model = await Thread.EditComment.ExecuteAsyncTask(new Tuple(NodeId, Body));
+ await Thread.EditComment.ExecuteAsyncTask(new Tuple(Id, Body));
}
-
- Id = model.Id;
- NodeId = model.NodeId;
- EditState = CommentEditState.None;
- UpdatedAt = DateTimeOffset.Now;
}
catch (Exception e)
{
@@ -190,10 +190,7 @@ async Task DoCommitEdit(object unused)
}
///
- public int Id { get; private set; }
-
- ///
- public string NodeId { get; private set; }
+ public string Id { get; private set; }
///
public string Body
@@ -243,13 +240,16 @@ public DateTimeOffset UpdatedAt
}
///
- public IAccount CurrentUser { get; }
+ public IActorViewModel CurrentUser { get; }
///
public ICommentThreadViewModel Thread { get; }
///
- public IAccount User { get; }
+ public IActorViewModel Author { get; }
+
+ ///
+ public Uri WebUrl { get; }
///
public ReactiveCommand BeginEdit { get; }
diff --git a/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs
index b24e28ea50..b17290de59 100644
--- a/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.ObjectModel;
+using System.Reactive;
using GitHub.Models;
+using GitHub.ViewModels;
using ReactiveUI;
namespace GitHub.InlineReviews.ViewModels
@@ -10,13 +12,6 @@ namespace GitHub.InlineReviews.ViewModels
///
public interface ICommentThreadViewModel
{
- ///
- /// Gets the browser URI for a comment in the thread.
- ///
- /// The ID of the comment.
- /// The URI.
- Uri GetCommentUrl(int id);
-
///
/// Gets the comments in the thread.
///
@@ -25,21 +20,21 @@ public interface ICommentThreadViewModel
///
/// Gets the current user under whos account new comments will be created.
///
- IAccount CurrentUser { get; }
+ IActorViewModel CurrentUser { get; }
///
/// Called by a comment in the thread to post itself as a new comment to the API.
///
- ReactiveCommand PostComment { get; }
+ ReactiveCommand PostComment { get; }
///
/// Called by a comment in the thread to post itself as an edit to a comment to the API.
///
- ReactiveCommand EditComment { get; }
+ ReactiveCommand EditComment { get; }
///
/// Called by a comment in the thread to send a delete of the comment to the API.
///
- ReactiveCommand DeleteComment { get; }
+ ReactiveCommand DeleteComment { get; }
}
}
diff --git a/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs
index fdd0fb2733..8f5e2d292c 100644
--- a/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs
@@ -18,15 +18,10 @@ public enum CommentEditState
///
public interface ICommentViewModel : IViewModel
{
- ///
- /// Gets the ID of the comment.
- ///
- int Id { get; }
-
///
/// Gets the GraphQL ID of the comment.
///
- string NodeId { get; }
+ string Id { get; }
///
/// Gets or sets the body of the comment.
@@ -67,13 +62,18 @@ public interface ICommentViewModel : IViewModel
///
/// Gets the author of the comment.
///
- IAccount User { get; }
+ IActorViewModel Author { get; }
///
/// Gets the thread that the comment is a part of.
///
ICommentThreadViewModel Thread { get; }
+ ///
+ /// Gets the URL of the comment on the web.
+ ///
+ Uri WebUrl { get; }
+
///
/// Gets a command which will begin editing of the comment.
///
diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs
index 9e3e7bd76a..2ada559882 100644
--- a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs
@@ -124,6 +124,7 @@ public async Task Initialize()
else
{
relativePath = sessionManager.GetRelativePath(buffer);
+ side = DiffSide.Right;
file = await sessionManager.GetLiveFile(relativePath, peekSession.TextView, buffer);
await SessionChanged(sessionManager.CurrentSession);
sessionSubscription = sessionManager.WhenAnyValue(x => x.CurrentSession)
diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs
index 598655d42f..7ec86f0768 100644
--- a/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs
@@ -3,11 +3,9 @@
using System.Globalization;
using System.Reactive.Linq;
using System.Threading.Tasks;
-using GitHub.Api;
using GitHub.Extensions;
using GitHub.Models;
using GitHub.Services;
-using Octokit;
using ReactiveUI;
namespace GitHub.InlineReviews.ViewModels
@@ -24,7 +22,7 @@ public class InlineCommentThreadViewModel : CommentThreadViewModel
/// The comments to display in this inline review.
public InlineCommentThreadViewModel(
IPullRequestSession session,
- IEnumerable comments)
+ IEnumerable comments)
: base(session.User)
{
Guard.ArgumentNotNull(session, nameof(session));
@@ -45,7 +43,12 @@ public class InlineCommentThreadViewModel : CommentThreadViewModel
foreach (var comment in comments)
{
- Comments.Add(new PullRequestReviewCommentViewModel(session, this, CurrentUser, comment));
+ Comments.Add(new PullRequestReviewCommentViewModel(
+ session,
+ this,
+ CurrentUser,
+ comment.Review,
+ comment.Comment));
}
Comments.Add(PullRequestReviewCommentViewModel.CreatePlaceholder(session, this, CurrentUser));
@@ -56,42 +59,29 @@ public class InlineCommentThreadViewModel : CommentThreadViewModel
///
public IPullRequestSession Session { get; }
- ///
- public override Uri GetCommentUrl(int id)
- {
- return new Uri(string.Format(
- CultureInfo.InvariantCulture,
- "{0}/pull/{1}#discussion_r{2}",
- Session.LocalRepository.CloneUrl.ToRepositoryUrl(Session.RepositoryOwner),
- Session.PullRequest.Number,
- id));
- }
-
- async Task DoPostComment(object parameter)
+ async Task DoPostComment(object parameter)
{
Guard.ArgumentNotNull(parameter, nameof(parameter));
var body = (string)parameter;
- var nodeId = Comments[0].NodeId;
- return await Session.PostReviewComment(body, nodeId);
+ var replyId = Comments[0].Id;
+ await Session.PostReviewComment(body, replyId);
}
- async Task DoEditComment(object parameter)
+ async Task DoEditComment(object parameter)
{
Guard.ArgumentNotNull(parameter, nameof(parameter));
var item = (Tuple)parameter;
- return await Session.EditComment(item.Item1, item.Item2);
+ await Session.EditComment(item.Item1, item.Item2);
}
- async Task DoDeleteComment(object parameter)
+ async Task DoDeleteComment(object parameter)
{
Guard.ArgumentNotNull(parameter, nameof(parameter));
var number = (int)parameter;
await Session.DeleteComment(number);
-
- return new object();
}
}
}
diff --git a/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs
index a0ca7a2f98..17096ae220 100644
--- a/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs
@@ -45,11 +45,11 @@ public class NewInlineCommentThreadViewModel : CommentThreadViewModel
this.WhenAnyValue(x => x.NeedsPush, x => !x),
DoPostComment);
- EditComment = ReactiveCommand.CreateAsyncTask(
+ EditComment = ReactiveCommand.CreateAsyncTask(
Observable.Return(false),
o => null);
- DeleteComment = ReactiveCommand.CreateAsyncTask(
+ DeleteComment = ReactiveCommand.CreateAsyncTask(
Observable.Return(false),
o => null);
@@ -91,13 +91,7 @@ public bool NeedsPush
private set { this.RaiseAndSetIfChanged(ref needsPush, value); }
}
- ///
- public override Uri GetCommentUrl(int id)
- {
- throw new NotSupportedException("Cannot navigate to a non-posted comment.");
- }
-
- async Task DoPostComment(object parameter)
+ async Task DoPostComment(object parameter)
{
Guard.ArgumentNotNull(parameter, nameof(parameter));
@@ -115,14 +109,12 @@ async Task DoPostComment(object parameter)
}
var body = (string)parameter;
- var model = await Session.PostReviewComment(
+ await Session.PostReviewComment(
body,
File.CommitSha,
File.RelativePath.Replace("\\", "/"),
File.Diff,
diffPosition.DiffLineNumber);
-
- return model;
}
}
}
diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs
index cce40609e6..a311582bbb 100644
--- a/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs
+++ b/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs
@@ -8,6 +8,7 @@
using GitHub.Logging;
using GitHub.Models;
using GitHub.Services;
+using GitHub.ViewModels;
using GitHub.VisualStudio.UI;
using ReactiveUI;
using Serilog;
@@ -29,25 +30,24 @@ public class PullRequestReviewCommentViewModel : CommentViewModel, IPullRequestR
/// The pull request session.
/// The thread that the comment is a part of.
/// The current user.
- /// The REST ID of the comment.
- /// The GraphQL ID of the comment.
+ /// The GraphQL ID of the comment.
/// The comment body.
/// The comment edit state.
- /// The author of the comment.
+ /// The author of the comment.
/// The modified date of the comment.
/// Whether this is a pending comment.
public PullRequestReviewCommentViewModel(
IPullRequestSession session,
ICommentThreadViewModel thread,
- IAccount currentUser,
- int commentId,
- string commentNodeId,
+ IActorViewModel currentUser,
+ string commentId,
string body,
CommentEditState state,
- IAccount user,
+ IActorViewModel author,
DateTimeOffset updatedAt,
- bool isPending)
- : base(thread, currentUser, commentId, commentNodeId, body, state, user, updatedAt)
+ bool isPending,
+ Uri webUrl)
+ : base(thread, currentUser, commentId, body, state, author, updatedAt, webUrl)
{
Guard.ArgumentNotNull(session, nameof(session));
@@ -56,7 +56,7 @@ public class PullRequestReviewCommentViewModel : CommentViewModel, IPullRequestR
var pendingReviewAndIdObservable = Observable.CombineLatest(
session.WhenAnyValue(x => x.HasPendingReview, x => !x),
- this.WhenAnyValue(model => model.Id, i => i == 0),
+ this.WhenAnyValue(model => model.Id).Select(i => i == null),
(hasPendingReview, isNewComment) => new { hasPendingReview, isNewComment });
canStartReview = pendingReviewAndIdObservable
@@ -83,9 +83,20 @@ public class PullRequestReviewCommentViewModel : CommentViewModel, IPullRequestR
public PullRequestReviewCommentViewModel(
IPullRequestSession session,
ICommentThreadViewModel thread,
- IAccount currentUser,
- IPullRequestReviewCommentModel model)
- : this(session, thread, currentUser, model.Id, model.NodeId, model.Body, CommentEditState.None, model.User, model.CreatedAt, model.IsPending)
+ IActorViewModel currentUser,
+ PullRequestReviewModel review,
+ PullRequestReviewCommentModel model)
+ : this(
+ session,
+ thread,
+ currentUser,
+ model.Id,
+ model.Body,
+ CommentEditState.None,
+ new ActorViewModel(model.Author),
+ model.CreatedAt,
+ review.State == PullRequestReviewState.Pending,
+ model.Url != null ? new Uri(model.Url) : null)
{
}
@@ -99,19 +110,19 @@ public class PullRequestReviewCommentViewModel : CommentViewModel, IPullRequestR
public static CommentViewModel CreatePlaceholder(
IPullRequestSession session,
ICommentThreadViewModel thread,
- IAccount currentUser)
+ IActorViewModel currentUser)
{
return new PullRequestReviewCommentViewModel(
session,
thread,
currentUser,
- 0,
null,
string.Empty,
CommentEditState.Placeholder,
currentUser,
DateTimeOffset.MinValue,
- false);
+ false,
+ null);
}
///
diff --git a/src/GitHub.InlineReviews/Views/CommentView.xaml b/src/GitHub.InlineReviews/Views/CommentView.xaml
index 5491c741a0..084f4d4409 100644
--- a/src/GitHub.InlineReviews/Views/CommentView.xaml
+++ b/src/GitHub.InlineReviews/Views/CommentView.xaml
@@ -12,7 +12,7 @@
xmlns:views="clr-namespace:GitHub.InlineReviews.Views"
mc:Ignorable="d" d:DesignWidth="300">
-
+
You can use a `CompositeDisposable` type here, it's designed to handle disposables in an optimal way (you can just call `Dispose()` on it and it will handle disposing everything it holds).
@@ -54,7 +54,7 @@
+ Account="{Binding Author}"/>
-
+
diff --git a/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs b/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs
index d272f9fd77..84b000c8e6 100644
--- a/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs
+++ b/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs
@@ -106,6 +106,11 @@ protected FormattedText FormattedText
protected override Size MeasureOverride(Size availableSize)
{
+ if (Text == null)
+ {
+ return new Size();
+ }
+
var parts = Text
.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar })
.ToList();
diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs
index b1629a71a4..7570da7e40 100644
--- a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs
+++ b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs
@@ -11,7 +11,7 @@ public partial class AccountAvatar : UserControl, ICommandSource
public static readonly DependencyProperty AccountProperty =
DependencyProperty.Register(
nameof(Account),
- typeof(IAccount),
+ typeof(object),
typeof(AccountAvatar));
public static readonly DependencyProperty CommandProperty =
ButtonBase.CommandProperty.AddOwner(typeof(AccountAvatar));
@@ -25,9 +25,9 @@ public AccountAvatar()
InitializeComponent();
}
- public IAccount Account
+ public object Account
{
- get { return (IAccount)GetValue(AccountProperty); }
+ get { return GetValue(AccountProperty); }
set { SetValue(AccountProperty, value); }
}
diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj
index 5edeed4588..0539195cc3 100644
--- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj
+++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj
@@ -253,12 +253,12 @@
..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.dll
True
-
- ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
+
+ ..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll
True
@@ -372,6 +372,7 @@
OptionsControl.xaml
+
ForkRepositoryExecuteView.xaml
diff --git a/src/GitHub.VisualStudio/Views/ActorAvatarView.cs b/src/GitHub.VisualStudio/Views/ActorAvatarView.cs
new file mode 100644
index 0000000000..12b4b4d38f
--- /dev/null
+++ b/src/GitHub.VisualStudio/Views/ActorAvatarView.cs
@@ -0,0 +1,181 @@
+using System;
+using System.ComponentModel;
+using System.ComponentModel.Composition;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using GitHub.Exports;
+using GitHub.ViewModels;
+
+namespace GitHub.VisualStudio.Views
+{
+ [ExportViewFor(typeof(IActorViewModel))]
+ [PartCreationPolicy(CreationPolicy.NonShared)]
+ public class ActorAvatarView : FrameworkElement, ICommandSource
+ {
+ public static readonly DependencyProperty CommandProperty =
+ ButtonBase.CommandProperty.AddOwner(typeof(ActorAvatarView));
+ public static readonly DependencyProperty CommandParameterProperty =
+ ButtonBase.CommandParameterProperty.AddOwner(typeof(ActorAvatarView));
+ public static readonly DependencyProperty CommandTargetProperty =
+ ButtonBase.CommandTargetProperty.AddOwner(typeof(ActorAvatarView));
+
+ public static readonly DependencyProperty IdleUpdateProperty =
+ DependencyProperty.Register(
+ nameof(IdleUpdate),
+ typeof(bool),
+ typeof(ActorAvatarView),
+ new FrameworkPropertyMetadata(false));
+
+ public static readonly DependencyProperty ViewModelProperty =
+ DependencyProperty.Register(
+ nameof(ViewModel),
+ typeof(IActorViewModel),
+ typeof(ActorAvatarView),
+ new FrameworkPropertyMetadata(HandleViewModelChanged));
+
+ IActorViewModel toRender;
+ Geometry clip;
+
+ static ActorAvatarView()
+ {
+ WidthProperty.OverrideMetadata(typeof(ActorAvatarView), new FrameworkPropertyMetadata(30.0));
+ HeightProperty.OverrideMetadata(typeof(ActorAvatarView), new FrameworkPropertyMetadata(30.0));
+ }
+
+ public ActorAvatarView()
+ {
+ }
+
+ public bool IdleUpdate
+ {
+ get { return (bool)GetValue(IdleUpdateProperty); }
+ set { SetValue(IdleUpdateProperty, value); }
+ }
+
+ public IActorViewModel ViewModel
+ {
+ get { return (IActorViewModel)GetValue(ViewModelProperty); }
+ set { SetValue(ViewModelProperty, value); }
+ }
+
+ public ICommand Command
+ {
+ get { return (ICommand)GetValue(CommandProperty); }
+ set { SetValue(CommandProperty, value); }
+ }
+
+ public object CommandParameter
+ {
+ get { return GetValue(CommandParameterProperty); }
+ set { SetValue(CommandParameterProperty, value); }
+ }
+
+ public IInputElement CommandTarget
+ {
+ get { return (IInputElement)GetValue(CommandTargetProperty); }
+ set { SetValue(CommandTargetProperty, value); }
+ }
+
+ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+ {
+ this.CaptureMouse();
+ }
+
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ if (this.IsMouseCaptured)
+ {
+ if (this.InputHitTest(e.GetPosition(this)) != null &&
+ Command?.CanExecute(CommandParameter) == true)
+ {
+ Command.Execute(CommandParameter);
+ }
+
+ this.ReleaseMouseCapture();
+ }
+ }
+
+ protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
+ {
+ clip = null;
+ }
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ if (toRender != null)
+ {
+ if (clip == null)
+ {
+ clip = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight), 3, 3);
+ }
+
+ drawingContext.PushClip(clip);
+ drawingContext.DrawImage(toRender.Avatar, new Rect(0, 0, ActualWidth, ActualHeight));
+ drawingContext.Pop();
+ }
+ }
+
+ void ViewModelChanged(DependencyPropertyChangedEventArgs e)
+ {
+ var oldValue = e.OldValue as IActorViewModel;
+ var newValue = e.NewValue as IActorViewModel;
+
+ if (oldValue != null)
+ {
+ oldValue.PropertyChanged -= ViewModelPropertyChanged;
+ }
+
+ if (IdleUpdate)
+ {
+ QueueIdleUpdate();
+ }
+ else
+ {
+ toRender = newValue;
+
+ if (toRender != null)
+ {
+ toRender.PropertyChanged += ViewModelPropertyChanged;
+ }
+ }
+
+ InvalidateVisual();
+ }
+
+ void QueueIdleUpdate()
+ {
+ toRender = null;
+ ComponentDispatcher.ThreadIdle += OnIdle;
+ }
+
+ void OnIdle(object sender, EventArgs e)
+ {
+ toRender = ViewModel;
+
+ if (toRender != null)
+ {
+ toRender.PropertyChanged += ViewModelPropertyChanged;
+ }
+
+ InvalidateVisual();
+ ComponentDispatcher.ThreadIdle -= OnIdle;
+ }
+
+ void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(IActorViewModel.Avatar))
+ {
+ QueueIdleUpdate();
+ }
+ }
+
+ static void HandleViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ (d as ActorAvatarView)?.ViewModelChanged(e);
+ }
+ }
+}
diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml
index 34d16abf5c..8813d36270 100644
--- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml
+++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml
@@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ghfvs="https://github.com/github/VisualStudio"
xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane"
+ xmlns:v="clr-namespace:GitHub.VisualStudio.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI"
xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"
@@ -212,10 +213,11 @@
-
+
diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml
index 80e4b85d52..30197716b2 100644
--- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml
+++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml
@@ -4,7 +4,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane"
+ xmlns:ghfvs="https://github.com/github/VisualStudio"
xmlns:c="clr-namespace:GitHub.VisualStudio.UI.Controls;assembly=GitHub.VisualStudio.UI"
+ xmlns:v="clr-namespace:GitHub.VisualStudio.Views"
xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"
xmlns:vm="clr-namespace:GitHub.ViewModels.GitHubPane;assembly=GitHub.App"
xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI"
@@ -16,7 +18,7 @@
-
+
@@ -33,7 +35,7 @@
+ Visibility="{Binding Id, Converter={ui:NotEqualsToVisibilityConverter {x:Null}}}">