From 9738c2fdc1aa94887353b095d8103a5de3540453 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Feb 2018 15:12:30 +0100 Subject: [PATCH 01/78] Whitespace change. As GitHub won't let us create empty PRs. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4ffa2e1416..258111cdc2 100644 --- a/README.md +++ b/README.md @@ -98,3 +98,4 @@ Visit the [Contributor Guidelines](CONTRIBUTING.md) for details on how to contri Copyright 2015 - 2017 GitHub, Inc. Licensed under the [MIT License](LICENSE.md) + From 69a61262e5c42f8ccda5f47d7f48364016b3fd41 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 12:24:29 +0100 Subject: [PATCH 02/78] Removed unused InlineReviews code. There was code left over from the now punted "conversation" view. Remove this code because by the time we get back to it, it will be out of date anyway. --- .../GitHub.InlineReviews.csproj | 18 +--- .../InlineReviewsPackage.cs | 2 - .../DiffCommentThreadViewModelDesigner.cs | 15 --- .../PullRequestCommentsViewModelDesigner.cs | 19 ---- .../ViewModels/DiffCommentThreadViewModel.cs | 25 ----- .../ViewModels/IDiffCommentThreadViewModel.cs | 10 -- .../ViewModels/IInlineCommentViewModel.cs | 21 ---- .../IPullRequestCommentsViewModel.cs | 15 --- .../ViewModels/InlineCommentViewModel.cs | 62 ------------ .../ViewModels/IssueCommentThreadViewModel.cs | 29 ------ .../PullRequestCommentsViewModel.cs | 99 ------------------- .../Views/DiffCommentThreadView.xaml | 57 ----------- .../Views/DiffCommentThreadView.xaml.cs | 28 ------ src/GitHub.InlineReviews/Views/DiffView.cs | 66 ------------- .../Views/PullRequestCommentsPane.cs | 49 --------- .../Views/PullRequestCommentsView.xaml | 99 ------------------- .../Views/PullRequestCommentsView.xaml.cs | 14 --- 17 files changed, 1 insertion(+), 627 deletions(-) delete mode 100644 src/GitHub.InlineReviews/SampleData/DiffCommentThreadViewModelDesigner.cs delete mode 100644 src/GitHub.InlineReviews/SampleData/PullRequestCommentsViewModelDesigner.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/DiffCommentThreadViewModel.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/IDiffCommentThreadViewModel.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/IInlineCommentViewModel.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/IPullRequestCommentsViewModel.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/InlineCommentViewModel.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/IssueCommentThreadViewModel.cs delete mode 100644 src/GitHub.InlineReviews/ViewModels/PullRequestCommentsViewModel.cs delete mode 100644 src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml delete mode 100644 src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml.cs delete mode 100644 src/GitHub.InlineReviews/Views/DiffView.cs delete mode 100644 src/GitHub.InlineReviews/Views/PullRequestCommentsPane.cs delete mode 100644 src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml delete mode 100644 src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml.cs diff --git a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj index cd778f5bd5..67616b6c1c 100644 --- a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj +++ b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj @@ -72,6 +72,7 @@ + @@ -93,8 +94,6 @@ - - @@ -103,11 +102,8 @@ - - - @@ -125,10 +121,6 @@ InlineCommentPeekView.xaml - - - PullRequestCommentsView.xaml - @@ -423,10 +415,6 @@ Designer true - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -435,10 +423,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - MSBuild:Compile Designer diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.cs b/src/GitHub.InlineReviews/InlineReviewsPackage.cs index 2d90e89b6f..3c5aab0848 100644 --- a/src/GitHub.InlineReviews/InlineReviewsPackage.cs +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.cs @@ -4,7 +4,6 @@ using System.Threading; using GitHub.Helpers; using GitHub.Commands; -using GitHub.InlineReviews.Views; using GitHub.Services.Vssdk.Commands; using GitHub.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; @@ -17,7 +16,6 @@ namespace GitHub.InlineReviews [Guid(Guids.InlineReviewsPackageId)] [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] [ProvideMenuResource("Menus.ctmenu", 1)] - [ProvideToolWindow(typeof(PullRequestCommentsPane), DocumentLikeTool = true)] public class InlineReviewsPackage : AsyncPackage { protected override async Task InitializeAsync( diff --git a/src/GitHub.InlineReviews/SampleData/DiffCommentThreadViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/DiffCommentThreadViewModelDesigner.cs deleted file mode 100644 index ebf8dc18df..0000000000 --- a/src/GitHub.InlineReviews/SampleData/DiffCommentThreadViewModelDesigner.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using GitHub.InlineReviews.ViewModels; - -namespace GitHub.InlineReviews.SampleData -{ - [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] - class DiffCommentThreadViewModelDesigner : IDiffCommentThreadViewModel - { - public string DiffHunk { get; set; } - public int LineNumber { get; set; } - public string Path { get; set; } - public ICommentThreadViewModel Comments { get; set; } - } -} diff --git a/src/GitHub.InlineReviews/SampleData/PullRequestCommentsViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/PullRequestCommentsViewModelDesigner.cs deleted file mode 100644 index bf1799d22e..0000000000 --- a/src/GitHub.InlineReviews/SampleData/PullRequestCommentsViewModelDesigner.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using GitHub.InlineReviews.ViewModels; -using GitHub.Models; -using ReactiveUI; - -namespace GitHub.InlineReviews.SampleData -{ - [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] - class PullRequestCommentsViewModelDesigner : IPullRequestCommentsViewModel - { - public IRepositoryModel Repository { get; set; } - public int Number { get; set; } - public string Title { get; set; } - public ICommentThreadViewModel Conversation { get; set; } - public IReactiveList FileComments { get; } - = new ReactiveList(); - } -} diff --git a/src/GitHub.InlineReviews/ViewModels/DiffCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/DiffCommentThreadViewModel.cs deleted file mode 100644 index e30b0d0a8a..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/DiffCommentThreadViewModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using ReactiveUI; - -namespace GitHub.InlineReviews.ViewModels -{ - class DiffCommentThreadViewModel : ReactiveObject, IDiffCommentThreadViewModel - { - public DiffCommentThreadViewModel( - string diffHunk, - int lineNumber, - string path, - InlineCommentThreadViewModel comments) - { - DiffHunk = diffHunk; - LineNumber = lineNumber; - Path = path; - Comments = comments; - } - - public string DiffHunk { get; } - public int LineNumber { get; } - public string Path { get; } - public ICommentThreadViewModel Comments { get; } - } -} diff --git a/src/GitHub.InlineReviews/ViewModels/IDiffCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/IDiffCommentThreadViewModel.cs deleted file mode 100644 index 808f10d5e0..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/IDiffCommentThreadViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GitHub.InlineReviews.ViewModels -{ - interface IDiffCommentThreadViewModel - { - string DiffHunk { get; } - int LineNumber { get; } - string Path { get; } - ICommentThreadViewModel Comments { get; } - } -} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/ViewModels/IInlineCommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/IInlineCommentViewModel.cs deleted file mode 100644 index 036cb5eefd..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/IInlineCommentViewModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace GitHub.InlineReviews.ViewModels -{ - /// - /// View model for an inline comment (aka Pull Request Review Comment). - /// - interface IInlineCommentViewModel : ICommentViewModel - { - /// - /// Gets the SHA of the commit that the comment was left on. - /// - string CommitSha { get; } - - /// - /// Gets the line on the diff between PR.Base and that - /// the comment was left on. - /// - int DiffLine { get; } - } -} diff --git a/src/GitHub.InlineReviews/ViewModels/IPullRequestCommentsViewModel.cs b/src/GitHub.InlineReviews/ViewModels/IPullRequestCommentsViewModel.cs deleted file mode 100644 index 441b4fc93d..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/IPullRequestCommentsViewModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.ObjectModel; -using GitHub.Models; -using ReactiveUI; - -namespace GitHub.InlineReviews.ViewModels -{ - interface IPullRequestCommentsViewModel - { - IRepositoryModel Repository { get; } - int Number { get; } - string Title { get; } - ICommentThreadViewModel Conversation { get; } - IReactiveList FileComments { get; } - } -} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentViewModel.cs deleted file mode 100644 index c7e1e65c75..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/InlineCommentViewModel.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using GitHub.Extensions; -using GitHub.Models; - -namespace GitHub.InlineReviews.ViewModels -{ - /// - /// View model for an inline comment (aka Pull Request Review Comment). - /// - public class InlineCommentViewModel : CommentViewModel, IInlineCommentViewModel - { - /// - /// Initializes a new instance of the class. - /// - /// The thread that the comment is a part of. - /// The current user. - /// The ID of the comment. - /// The comment body. - /// The comment edit state. - /// The author of the comment. - /// The modified date of the comment. - public InlineCommentViewModel( - ICommentThreadViewModel thread, - IAccount currentUser, - int commentId, - string body, - CommentEditState state, - IAccount user, - DateTimeOffset updatedAt, - string commitSha, - int diffLine) - : base(thread, currentUser, commentId, body, state, user, updatedAt) - { - Guard.ArgumentNotNull(commitSha, nameof(commitSha)); - - CommitSha = commitSha; - DiffLine = diffLine; - } - - /// - /// Initializes a new instance of the class. - /// - /// The thread that the comment is a part of. - /// The current user. - /// The comment model. - public InlineCommentViewModel( - ICommentThreadViewModel thread, - IAccount currentUser, - IPullRequestReviewCommentModel model) - : base(thread, currentUser, model) - { - CommitSha = model.OriginalCommitId; - DiffLine = model.OriginalPosition.Value; - } - - /// - public string CommitSha { get; } - - /// - public int DiffLine { get; } - } -} diff --git a/src/GitHub.InlineReviews/ViewModels/IssueCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/IssueCommentThreadViewModel.cs deleted file mode 100644 index 31f8123ae7..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/IssueCommentThreadViewModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; -using GitHub.Models; -using ReactiveUI; - -namespace GitHub.InlineReviews.ViewModels -{ - class IssueCommentThreadViewModel : CommentThreadViewModel - { - public IssueCommentThreadViewModel( - IRepositoryModel repository, - int number, - IAccount currentUser) - : base(currentUser) - { - Repository = repository; - Number = number; - } - - /// - public override Uri GetCommentUrl(int id) - { - throw new NotImplementedException(); - } - - public IRepositoryModel Repository { get; } - public int Number { get; } - } -} diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestCommentsViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestCommentsViewModel.cs deleted file mode 100644 index 9babec2f20..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/PullRequestCommentsViewModel.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using GitHub.Api; -using GitHub.Models; -using GitHub.Services; -using ReactiveUI; - -namespace GitHub.InlineReviews.ViewModels -{ - class PullRequestCommentsViewModel : ReactiveObject, IPullRequestCommentsViewModel, IDisposable - { - readonly IPullRequestSession session; - - public PullRequestCommentsViewModel( - IPullRequestSession session) - { - this.session = session; - - Repository = session.LocalRepository; - Number = session.PullRequest.Number; - Title = session.PullRequest.Title; - - Conversation = new IssueCommentThreadViewModel(Repository, Number, session.User); - - foreach (var comment in session.PullRequest.Comments) - { - Conversation.Comments.Add(new CommentViewModel( - Conversation, - session.User, - comment)); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - bool disposed = false; - - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - disposed = true; - - if (disposing) - { - (Conversation as IDisposable)?.Dispose(); - } - } - } - - public IRepositoryModel Repository { get; } - public int Number { get; } - public string Title { get; } - public ICommentThreadViewModel Conversation { get; } - public IReactiveList FileComments { get; } - = new ReactiveList(); - - public async Task Initialize() - { - var files = await session.GetAllFiles(); - - foreach (var file in files) - { - foreach (var thread in file.InlineCommentThreads) - { - var threadViewModel = new InlineCommentThreadViewModel( - session, - thread.Comments); - - FileComments.Add(new DiffCommentThreadViewModel( - ToString(thread.DiffMatch), - thread.LineNumber, - file.RelativePath, - threadViewModel)); - } - } - } - - private string ToString(IList diffMatch) - { - var b = new StringBuilder(); - - for (var i = diffMatch.Count - 1; i >= 0; --i) - { - b.AppendLine(diffMatch[i].Content); - } - - return b.ToString(); - } - } -} diff --git a/src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml b/src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml deleted file mode 100644 index c77a0030ed..0000000000 --- a/src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - .Subscribe(a => UpdateFilter(SelectedState, SelectedAssignee, a)); - -+ this.WhenAny(x => x.SelectedSortOrder, x => x.Value) -+ .Where(x => pullRequests != null) - - - - - - - @StanleyGoldman Ooops, I missed a x != null check here, and it's breaking the tests. Do you want to add it or shall I? - - - - - - - - - - - - - - - - - - - - - - - : - - - - - - - - - diff --git a/src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml.cs b/src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml.cs deleted file mode 100644 index 83952c4938..0000000000 --- a/src/GitHub.InlineReviews/Views/DiffCommentThreadView.xaml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -namespace GitHub.InlineReviews.Views -{ - /// - /// Interaction logic for DiffCommentThreadView.xaml - /// - public partial class DiffCommentThreadView : UserControl - { - public DiffCommentThreadView() - { - InitializeComponent(); - } - } -} diff --git a/src/GitHub.InlineReviews/Views/DiffView.cs b/src/GitHub.InlineReviews/Views/DiffView.cs deleted file mode 100644 index f0233bf970..0000000000 --- a/src/GitHub.InlineReviews/Views/DiffView.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.IO; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace GitHub.InlineReviews.Views -{ - public class DiffView : StackPanel - { - static readonly Brush AddedBrush = new SolidColorBrush(Color.FromRgb(0xD7, 0xE3, 0xBC)); - static readonly Brush DeletedBrush = new SolidColorBrush(Color.FromRgb(0xFF, 0x99, 0x99)); - - public static readonly DependencyProperty DiffProperty = - DependencyProperty.Register( - nameof(Diff), - typeof(string), - typeof(DiffView), - new PropertyMetadata(DiffChanged)); - - public string Diff - { - get { return (string)GetValue(DiffProperty); } - set { SetValue(DiffProperty, value); } - } - - void UpdateContents() - { - Children.Clear(); - - if (Diff != null) - { - using (var reader = new StringReader(Diff)) - { - string line; - - while ((line = reader.ReadLine()) != null) - { - var textBlock = new TextBlock(); - textBlock.Text = line; - - if (line.Length > 0) - { - switch (line[0]) - { - case '+': - textBlock.Background = AddedBrush; - break; - case '-': - textBlock.Background = DeletedBrush; - break; - } - } - - Children.Add(textBlock); - } - } - } - } - - static void DiffChanged(object sender, DependencyPropertyChangedEventArgs e) - { - ((DiffView)sender).UpdateContents(); - } - } -} diff --git a/src/GitHub.InlineReviews/Views/PullRequestCommentsPane.cs b/src/GitHub.InlineReviews/Views/PullRequestCommentsPane.cs deleted file mode 100644 index 3bc70a5a5f..0000000000 --- a/src/GitHub.InlineReviews/Views/PullRequestCommentsPane.cs +++ /dev/null @@ -1,49 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Company. All rights reserved. -// -//------------------------------------------------------------------------------ - -using System.Runtime.InteropServices; -using GitHub.Api; -using GitHub.Extensions; -using GitHub.InlineReviews.ViewModels; -using GitHub.Services; -using Microsoft.VisualStudio.Shell; -using Task = System.Threading.Tasks.Task; - -namespace GitHub.InlineReviews.Views -{ - [Guid("aa280a78-f2fa-49cd-b2f9-21426b40501f")] - public class PullRequestCommentsPane : ToolWindowPane - { - readonly PullRequestCommentsView view; - IPullRequestSession session; - - /// - /// Initializes a new instance of the class. - /// - public PullRequestCommentsPane() : base(null) - { - this.Caption = "Pull Request Comments"; - this.Content = view = new PullRequestCommentsView(); - } - - public async Task Initialize( - IPullRequestSession pullRequestSession, - IApiClient apiClient) - { - Guard.ArgumentNotNull(pullRequestSession, nameof(pullRequestSession)); - Guard.ArgumentNotNull(apiClient, nameof(apiClient)); - - if (this.session != null) - return; - - this.session = pullRequestSession; - - var viewModel = new PullRequestCommentsViewModel(pullRequestSession); - await viewModel.Initialize(); - view.DataContext = viewModel; - } - } -} diff --git a/src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml b/src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml deleted file mode 100644 index 025200f53a..0000000000 --- a/src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - Thanks @StanleyGoldman! Might also be nice to also be able to sort by PR number? - - - - - - - - - .Subscribe(a => UpdateFilter(SelectedState, SelectedAssignee, a)); - -+ this.WhenAny(x => x.SelectedSortOrder, x => x.Value) -+ .Where(x => pullRequests != null) - - - - - - - @StanleyGoldman Ooops, I missed a x != null check here, and it's breaking the tests. Do you want to add it or shall I? - - - - - - - - - - - - - - - - - - - - - - - - - - / - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml.cs b/src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml.cs deleted file mode 100644 index b95d9e7d5e..0000000000 --- a/src/GitHub.InlineReviews/Views/PullRequestCommentsView.xaml.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Windows.Controls; -using GitHub.VisualStudio.UI.Helpers; - -namespace GitHub.InlineReviews -{ - public partial class PullRequestCommentsView : UserControl - { - public PullRequestCommentsView() - { - this.InitializeComponent(); - PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; - } - } -} \ No newline at end of file From 9dd597641859a3a4f25ff560271229c6f02c2f9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 16 Mar 2018 11:20:26 +0100 Subject: [PATCH 03/78] Removed unused IInlineCommentModel. And removed missing files. --- src/GitHub.Exports/GitHub.Exports.csproj | 1 - .../Models/IInlineCommentModel.cs | 26 ------------------- .../GitHub.InlineReviews.csproj | 9 ------- 3 files changed, 36 deletions(-) delete mode 100644 src/GitHub.Exports/Models/IInlineCommentModel.cs diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index ea23e9f106..43bc622b3b 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -165,7 +165,6 @@ - diff --git a/src/GitHub.Exports/Models/IInlineCommentModel.cs b/src/GitHub.Exports/Models/IInlineCommentModel.cs deleted file mode 100644 index 40bbcb09c4..0000000000 --- a/src/GitHub.Exports/Models/IInlineCommentModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace GitHub.Models -{ - /// - /// Represents an pull request review comment that can be displayed inline in a code editor. - /// - public interface IInlineCommentModel - { - /// - /// Gets the 0-based line number of the comment. - /// - int LineNumber { get; } - - /// - /// Gets a value indicating whether the model is stale due to a change in the underlying - /// file. - /// - bool IsStale { get; } - - /// - /// Gets the original pull request review comment. - /// - IPullRequestReviewCommentModel Original { get; } - } -} diff --git a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj index 67616b6c1c..ec62ea2a23 100644 --- a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj +++ b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj @@ -72,7 +72,6 @@ - @@ -106,15 +105,7 @@ - - - - - - DiffCommentThreadView.xaml - - GlyphMarginGrid.xaml From f72df0c79900f21e0ee6093079c4f34fde1d347c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Feb 2018 15:05:57 +0100 Subject: [PATCH 04/78] Moved changed files tree into its own view. Ported from #1415. Moves the PR details changed files tree into its own view so that it can be shared by the PR reviews view. --- src/GitHub.App/GitHub.App.csproj | 57 ++- .../PullRequestDetailViewModelDesigner.cs | 12 +- .../PullRequestFilesViewModelDesigner.cs | 47 +++ .../Services/PullRequestEditorService.cs | 266 ++++++++++++- .../GitHubPane/PullRequestDetailViewModel.cs | 111 +----- .../GitHubPane/PullRequestDirectoryNode.cs | 10 +- .../GitHubPane/PullRequestFileNode.cs | 16 +- .../GitHubPane/PullRequestFilesViewModel.cs | 202 ++++++++++ src/GitHub.App/packages.config | 19 +- .../GitHub.Exports.Reactive.csproj | 2 + .../Services/IPullRequestEditorService.cs | 41 +- .../Services/PullRequestSessionExtensions.cs | 60 +++ .../GitHubPane/IPullRequestChangeNode.cs | 5 +- .../GitHubPane/IPullRequestDetailViewModel.cs | 25 +- .../GitHubPane/IPullRequestFilesViewModel.cs | 63 ++++ .../GitHub.VisualStudio.csproj | 7 + .../GitHubPane/PullRequestDetailView.xaml | 198 +++------- .../GitHubPane/PullRequestDetailView.xaml.cs | 357 ------------------ .../GitHubPane/PullRequestFilesView.xaml | 138 +++++++ .../GitHubPane/PullRequestFilesView.xaml.cs | 87 +++++ .../PullRequestDetailViewModelTests.cs | 99 +---- .../PullRequestFilesViewModelTests.cs | 128 +++++++ .../Services/PullRequestEditorServiceTests.cs | 12 +- test/UnitTests/UnitTests.csproj | 5 + test/UnitTests/packages.config | 1 + 25 files changed, 1200 insertions(+), 768 deletions(-) create mode 100644 src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs create mode 100644 src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs create mode 100644 test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 815b12f40b..f6c490a239 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -61,8 +61,16 @@ ..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll True + + ..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll + True + - ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll + ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6071\lib\Microsoft.VisualStudio.OLE.Interop.dll True @@ -74,21 +82,60 @@ True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6072\lib\net11\Microsoft.VisualStudio.Shell.Interop.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30320\lib\net20\Microsoft.VisualStudio.Shell.Interop.10.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61031\lib\net20\Microsoft.VisualStudio.Shell.Interop.11.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30111\lib\net20\Microsoft.VisualStudio.Shell.Interop.12.0.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.Shell.Interop.8.0.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll True - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll + ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6071\lib\net11\Microsoft.VisualStudio.TextManager.Interop.dll True - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.TextManager.Interop.8.0.dll True False ..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll + + ..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll + True + False ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll @@ -152,6 +199,7 @@ + @@ -172,6 +220,7 @@ + diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs index 64126dc3bd..63f163f42a 100644 --- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -31,8 +31,6 @@ public class PullRequestUpdateStateDesigner : IPullRequestUpdateState [ExcludeFromCodeCoverage] public class PullRequestDetailViewModelDesigner : PanePageViewModelBase, IPullRequestDetailViewModel { - private List changedFilesTree; - public PullRequestDetailViewModelDesigner() { var repoPath = @"C:\Repo"; @@ -69,8 +67,7 @@ public PullRequestDetailViewModelDesigner() modelsDir.Files.Add(oldBranchModel); gitHubDir.Directories.Add(modelsDir); - changedFilesTree = new List(); - changedFilesTree.Add(gitHubDir); + Files = new PullRequestFilesViewModelDesigner(); } public IPullRequestModel Model { get; } @@ -84,7 +81,7 @@ public PullRequestDetailViewModelDesigner() public bool IsCheckedOut { get; } public bool IsFromFork { get; } public string Body { get; } - public IReadOnlyList ChangedFilesTree => changedFilesTree; + public IPullRequestFilesViewModel Files { get; set; } public IPullRequestCheckoutState CheckoutState { get; set; } public IPullRequestUpdateState UpdateState { get; set; } public string OperationError { get; set; } @@ -94,12 +91,7 @@ public PullRequestDetailViewModelDesigner() public ReactiveCommand Checkout { get; } public ReactiveCommand Pull { get; } public ReactiveCommand Push { get; } - public ReactiveCommand SyncSubmodules { get; } public ReactiveCommand OpenOnGitHub { get; } - public ReactiveCommand DiffFile { get; } - public ReactiveCommand DiffFileWithWorkingDirectory { get; } - public ReactiveCommand OpenFileInWorkingDirectory { get; } - public ReactiveCommand ViewFile { get; } public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int number) => Task.CompletedTask; diff --git a/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs new file mode 100644 index 0000000000..c37398067a --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestFilesViewModelDesigner : PanePageViewModelBase, IPullRequestFilesViewModel + { + public PullRequestFilesViewModelDesigner() + { + Items = new[] + { + new PullRequestDirectoryNode("src") + { + Files = + { + new PullRequestFileNode("x", "src/File1.cs", "x", PullRequestFileStatus.Added, null), + new PullRequestFileNode("x", "src/File2.cs", "x", PullRequestFileStatus.Modified, null), + new PullRequestFileNode("x", "src/File3.cs", "x", PullRequestFileStatus.Removed, null), + new PullRequestFileNode("x", "src/File4.cs", "x", PullRequestFileStatus.Renamed, "src/Old.cs"), + } + } + }; + ChangedFilesCount = 4; + } + + public int ChangedFilesCount { get; set; } + public IReadOnlyList Items { get; } + public ReactiveCommand DiffFile { get; } + public ReactiveCommand ViewFile { get; } + public ReactiveCommand DiffFileWithWorkingDirectory { get; } + public ReactiveCommand OpenFileInWorkingDirectory { get; } + public ReactiveCommand OpenFirstComment { get; } + + public Task InitializeAsync( + IPullRequestSession session, + Func commentFilter = null) + { + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index b8c758fa39..4e318239a6 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -1,25 +1,195 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.VisualStudio; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; -using GitHub.Models; +using Task = System.Threading.Tasks.Task; namespace GitHub.Services { + /// + /// Services for opening views of pull request files in Visual Studio. + /// [Export(typeof(IPullRequestEditorService))] + [PartCreationPolicy(CreationPolicy.Shared)] public class PullRequestEditorService : IPullRequestEditorService { - readonly IGitHubServiceProvider serviceProvider; - // If the target line doesn't have a unique match, search this number of lines above looking for a match. public const int MatchLinesAboveTarget = 4; + readonly IGitHubServiceProvider serviceProvider; + readonly IPullRequestService pullRequestService; + readonly IVsEditorAdaptersFactoryService vsEditorAdaptersFactory; + readonly IStatusBarNotificationService statusBar; + readonly IUsageTracker usageTracker; + [ImportingConstructor] - public PullRequestEditorService(IGitHubServiceProvider serviceProvider) + public PullRequestEditorService( + IGitHubServiceProvider serviceProvider, + IPullRequestService pullRequestService, + IVsEditorAdaptersFactoryService vsEditorAdaptersFactory, + IStatusBarNotificationService statusBar, + IUsageTracker usageTracker) { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(pullRequestService, nameof(pullRequestService)); + Guard.ArgumentNotNull(vsEditorAdaptersFactory, nameof(vsEditorAdaptersFactory)); + Guard.ArgumentNotNull(statusBar, nameof(statusBar)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + this.serviceProvider = serviceProvider; + this.pullRequestService = pullRequestService; + this.vsEditorAdaptersFactory = vsEditorAdaptersFactory; + this.statusBar = statusBar; + this.usageTracker = usageTracker; + } + + /// + public async Task OpenFile( + IPullRequestSession session, + string relativePath, + bool workingDirectory) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + + try + { + var file = await session.GetFile(relativePath); + var fullPath = GetAbsolutePath(session, file); + var fileName = workingDirectory ? fullPath : await ExtractFile(session, file, true); + + using (workingDirectory ? null : OpenInProvisionalTab()) + { + var window = VisualStudio.Services.Dte.ItemOperations.OpenFile(fileName); + window.Document.ReadOnly = !workingDirectory; + + var buffer = GetBufferAt(fileName); + + if (!workingDirectory) + { + AddBufferTag(buffer, session, fullPath, null); + } + } + + if (workingDirectory) + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); + else + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error opening file", e); + } + } + + /// + public async Task OpenDiff( + IPullRequestSession session, + string relativePath, + bool workingDirectory) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + + try + { + var file = await session.GetFile(relativePath); + var rightPath = file.RelativePath; + var leftPath = await GetBaseFileName(session, file); + var rightFile = workingDirectory ? GetAbsolutePath(session, file) : await ExtractFile(session, file, true); + var leftFile = await ExtractFile(session, file, false); + var leftLabel = $"{leftPath};{session.GetBaseBranchDisplay()}"; + var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {session.PullRequest.Number}"; + var caption = $"Diff - {Path.GetFileName(file.RelativePath)}"; + var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | + __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; + + if (!workingDirectory) + { + options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; + } + + IVsWindowFrame frame; + using (OpenInProvisionalTab()) + { + var tooltip = $"{leftLabel}\nvs.\n{rightLabel}"; + + // Diff window will open in provisional (right hand) tab until document is touched. + frame = VisualStudio.Services.DifferenceService.OpenComparisonWindow2( + leftFile, + rightFile, + caption, + tooltip, + leftLabel, + rightLabel, + string.Empty, + string.Empty, + (uint)options); + } + + object docView; + frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); + var diffViewer = ((IVsDifferenceCodeWindow)docView).DifferenceViewer; + + AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, DiffSide.Left); + + if (!workingDirectory) + { + AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, DiffSide.Right); + } + + if (workingDirectory) + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsCompareWithSolution); + else + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewChanges); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error opening file", e); + } + } + + /// + public async Task OpenDiff( + IPullRequestSession session, + string relativePath, + IInlineCommentThreadModel thread) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + Guard.ArgumentNotNull(thread, nameof(thread)); + + await OpenDiff(session, relativePath, false); + + // HACK: We need to wait here for the diff view to set itself up and move its cursor + // to the first changed line. There must be a better way of doing this. + await Task.Delay(1500); + + var param = (object)new InlineCommentNavigationParams + { + FromLine = thread.LineNumber - 1, + }; + + VisualStudio.Services.Dte.Commands.Raise( + Guids.CommandSetString, + PkgCmdIDList.NextInlineCommentId, + ref param, + null); } public IVsTextView NavigateToEquivalentPosition(IVsTextView sourceView, string targetFile) @@ -180,6 +350,92 @@ IVsTextView OpenDocument(string fullPath) return view; } + void ShowErrorInStatusBar(string message, Exception e) + { + statusBar.ShowMessage(message + ": " + e.Message); + } + + void AddBufferTag(ITextBuffer buffer, IPullRequestSession session, string path, DiffSide? side) + { + buffer.Properties.GetOrCreateSingletonProperty( + typeof(PullRequestTextBufferInfo), + () => new PullRequestTextBufferInfo(session, path, side)); + + var projection = buffer as IProjectionBuffer; + + if (projection != null) + { + foreach (var source in projection.SourceBuffers) + { + AddBufferTag(source, session, path, side); + } + } + } + + async Task ExtractFile(IPullRequestSession session, IPullRequestSessionFile file, bool head) + { + var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); + var relativePath = head ? file.RelativePath : await GetBaseFileName(session, file); + + return await pullRequestService.ExtractFile( + session.LocalRepository, + session.PullRequest, + relativePath, + head, + encoding).ToTask(); + } + + ITextBuffer GetBufferAt(string filePath) + { + IVsUIHierarchy uiHierarchy; + uint itemID; + IVsWindowFrame windowFrame; + + if (VsShellUtilities.IsDocumentOpen( + serviceProvider, + filePath, + Guid.Empty, + out uiHierarchy, + out itemID, + out windowFrame)) + { + IVsTextView view = VsShellUtilities.GetTextView(windowFrame); + IVsTextLines lines; + if (view.GetBuffer(out lines) == 0) + { + var buffer = lines as IVsTextBuffer; + if (buffer != null) + return vsEditorAdaptersFactory.GetDataBuffer(buffer); + } + } + + return null; + } + + async Task GetBaseFileName(IPullRequestSession session, IPullRequestSessionFile file) + { + using (var changes = await pullRequestService.GetTreeChanges( + session.LocalRepository, + session.PullRequest)) + { + var fileChange = changes.FirstOrDefault(x => x.Path == file.RelativePath); + return fileChange?.Status == LibGit2Sharp.ChangeKind.Renamed ? + fileChange.OldPath : file.RelativePath; + } + } + + static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFile file) + { + return Path.Combine(session.LocalRepository.LocalPath, file.RelativePath); + } + + static IDisposable OpenInProvisionalTab() + { + return new NewDocumentStateScope( + __VSNEWDOCUMENTSTATE.NDS_Provisional, + VSConstants.NewDocumentStateReason.SolutionExplorer); + } + static IList ReadLines(string text) { var lines = new List(); diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index ad4c66f0c0..f75616cc16 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -42,7 +42,6 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq string targetBranchDisplayName; int commentCount; string body; - IReadOnlyList changedFilesTree; IPullRequestCheckoutState checkoutState; IPullRequestUpdateState updateState; string operationError; @@ -69,7 +68,8 @@ public PullRequestDetailViewModel( IModelServiceFactory modelServiceFactory, IUsageTracker usageTracker, ITeamExplorerContext teamExplorerContext, - IStatusBarNotificationService statusBarNotificationService) + IStatusBarNotificationService statusBarNotificationService, + IPullRequestFilesViewModel files) { Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService)); Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); @@ -84,6 +84,7 @@ public PullRequestDetailViewModel( this.usageTracker = usageTracker; this.teamExplorerContext = teamExplorerContext; this.statusBarNotificationService = statusBarNotificationService; + Files = files; Checkout = ReactiveCommand.CreateAsyncObservable( this.WhenAnyValue(x => x.CheckoutState) @@ -116,10 +117,6 @@ public PullRequestDetailViewModel( SubscribeOperationError(SyncSubmodules); OpenOnGitHub = ReactiveCommand.Create(); - DiffFile = ReactiveCommand.Create(); - DiffFileWithWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut)); - OpenFileInWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut)); - ViewFile = ReactiveCommand.Create(); } /// @@ -248,13 +245,9 @@ public string OperationError } /// - /// Gets the changed files as a tree. + /// Gets the pull request's changed files. /// - public IReadOnlyList ChangedFilesTree - { - get { return changedFilesTree; } - private set { this.RaiseAndSetIfChanged(ref changedFilesTree, value); } - } + public IPullRequestFilesViewModel Files { get; } /// /// Gets the web URL for the pull request. @@ -290,27 +283,6 @@ public Uri WebUrl /// public ReactiveCommand OpenOnGitHub { get; } - /// - /// Gets a command that diffs an between BASE and HEAD. - /// - public ReactiveCommand DiffFile { get; } - - /// - /// Gets a command that diffs an between the version in - /// the working directory and HEAD. - /// - public ReactiveCommand DiffFileWithWorkingDirectory { get; } - - /// - /// Gets a command that opens an from disk. - /// - public ReactiveCommand OpenFileInWorkingDirectory { get; } - - /// - /// Gets a command that opens an as it appears in the PR. - /// - public ReactiveCommand ViewFile { get; } - /// /// Initializes the view model. /// @@ -381,9 +353,7 @@ public async Task Load(IPullRequestModel pullRequest) TargetBranchDisplayName = GetBranchDisplayName(IsFromFork, pullRequest.Base?.Label); CommentCount = pullRequest.Comments.Count + pullRequest.ReviewComments.Count; Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; - - var changes = await pullRequestsService.GetTreeChanges(LocalRepository, pullRequest); - ChangedFilesTree = (await CreateChangedFilesTree(pullRequest, changes)).Children.ToList(); + await Files.InitializeAsync(Session); var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList(); @@ -507,15 +477,15 @@ public override async Task Refresh() /// The path to a temporary file. public Task ExtractFile(IPullRequestFileNode file, bool head) { - var relativePath = Path.Combine(file.DirectoryPath, file.FileName); - var encoding = pullRequestsService.GetEncoding(LocalRepository, relativePath); + var path = file.RelativePath; + var encoding = pullRequestsService.GetEncoding(LocalRepository, path); if (!head && file.OldPath != null) { - relativePath = file.OldPath; + path = file.OldPath; } - return pullRequestsService.ExtractFile(LocalRepository, model, relativePath, head, encoding).ToTask(); + return pullRequestsService.ExtractFile(LocalRepository, model, path, head, encoding).ToTask(); } /// @@ -525,7 +495,7 @@ public Task ExtractFile(IPullRequestFileNode file, bool head) /// The full path to the file in the working directory. public string GetLocalFilePath(IPullRequestFileNode file) { - return Path.Combine(LocalRepository.LocalPath, file.DirectoryPath, file.FileName); + return Path.Combine(LocalRepository.LocalPath, file.RelativePath); } /// @@ -560,54 +530,6 @@ void SubscribeOperationError(ReactiveCommand command) command.IsExecuting.Select(x => x).Subscribe(x => OperationError = null); } - async Task CreateChangedFilesTree(IPullRequestModel pullRequest, TreeChanges changes) - { - var dirs = new Dictionary - { - { string.Empty, new PullRequestDirectoryNode(string.Empty) } - }; - - foreach (var changedFile in pullRequest.ChangedFiles) - { - var node = new PullRequestFileNode( - LocalRepository.LocalPath, - changedFile.FileName, - changedFile.Sha, - changedFile.Status, - GetOldFileName(changedFile, changes)); - - var file = await Session.GetFile(changedFile.FileName); - var fileCommentCount = file?.WhenAnyValue(x => x.InlineCommentThreads) - .Subscribe(x => node.CommentCount = x.Count(y => y.LineNumber != -1)); - - var dir = GetDirectory(node.DirectoryPath, dirs); - dir.Files.Add(node); - } - - return dirs[string.Empty]; - } - - static PullRequestDirectoryNode GetDirectory(string path, Dictionary dirs) - { - PullRequestDirectoryNode dir; - - if (!dirs.TryGetValue(path, out dir)) - { - var parentPath = Path.GetDirectoryName(path); - var parentDir = GetDirectory(parentPath, dirs); - - dir = new PullRequestDirectoryNode(path); - - if (!parentDir.Directories.Any(x => x.DirectoryName == dir.DirectoryName)) - { - parentDir.Directories.Add(dir); - dirs.Add(path, dir); - } - } - - return dir; - } - static string GetBranchDisplayName(bool isFromFork, string targetBranchLabel) { if (targetBranchLabel != null) @@ -620,17 +542,6 @@ static string GetBranchDisplayName(bool isFromFork, string targetBranchLabel) } } - string GetOldFileName(IPullRequestFileModel file, TreeChanges changes) - { - if (file.Status == PullRequestFileStatus.Renamed) - { - var fileName = file.FileName.Replace("/", "\\"); - return changes?.Renamed.FirstOrDefault(x => x.Path == fileName)?.OldPath; - } - - return null; - } - IObservable DoCheckout(object unused) { return Observable.Defer(async () => diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs index 26b12b1dad..6a9d46ebde 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs @@ -13,10 +13,10 @@ public class PullRequestDirectoryNode : IPullRequestDirectoryNode /// Initializes a new instance of the class. /// /// The path to the directory, relative to the repository. - public PullRequestDirectoryNode(string fullPath) + public PullRequestDirectoryNode(string relativePath) { - DirectoryName = System.IO.Path.GetFileName(fullPath); - DirectoryPath = fullPath; + DirectoryName = System.IO.Path.GetFileName(relativePath); + RelativePath = relativePath.Replace("/", "\\"); Directories = new List(); Files = new List(); } @@ -27,9 +27,9 @@ public PullRequestDirectoryNode(string fullPath) public string DirectoryName { get; } /// - /// Gets the full directory path, relative to the root of the repository. + /// Gets the path to the directory, relative to the root of the repository. /// - public string DirectoryPath { get; } + public string RelativePath { get; } /// /// Gets the directory children of the node. diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs index ed9246612c..f9e1e4e164 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs @@ -18,7 +18,7 @@ public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode /// Initializes a new instance of the class. /// /// The absolute path to the repository. - /// The path to the file, relative to the repository. + /// The path to the file, relative to the repository. /// The SHA of the file. /// The way the file was changed. /// The string to display in the [message] box next to the filename. @@ -28,17 +28,17 @@ public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode /// public PullRequestFileNode( string repositoryPath, - string path, + string relativePath, string sha, PullRequestFileStatus status, string oldPath) { Guard.ArgumentNotEmptyString(repositoryPath, nameof(repositoryPath)); - Guard.ArgumentNotEmptyString(path, nameof(path)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); Guard.ArgumentNotEmptyString(sha, nameof(sha)); - FileName = Path.GetFileName(path); - DirectoryPath = Path.GetDirectoryName(path); + FileName = Path.GetFileName(relativePath); + RelativePath = relativePath.Replace("/", "\\"); Sha = sha; Status = status; OldPath = oldPath; @@ -51,7 +51,7 @@ public PullRequestFileNode( { if (oldPath != null) { - StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(path) ? + StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(relativePath) ? Path.GetFileName(oldPath) : oldPath; } else @@ -67,9 +67,9 @@ public PullRequestFileNode( public string FileName { get; } /// - /// Gets the path to the file's directory, relative to the root of the repository. + /// Gets the path to the file, relative to the root of the repository. /// - public string DirectoryPath { get; } + public string RelativePath { get; } /// /// Gets the old path of a moved/renamed file, relative to the root of the repository. diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs new file mode 100644 index 0000000000..bad6947451 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using ReactiveUI; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// View model displaying a tree of changed files in a pull request. + /// + [Export(typeof(IPullRequestFilesViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class PullRequestFilesViewModel : ViewModelBase, IPullRequestFilesViewModel + { + readonly IPullRequestService service; + readonly BehaviorSubject isBranchCheckedOut = new BehaviorSubject(false); + + IPullRequestSession pullRequestSession; + Func commentFilter; + int changedFilesCount; + IReadOnlyList items; + CompositeDisposable subscriptions; + + [ImportingConstructor] + public PullRequestFilesViewModel( + IPullRequestService service, + IPullRequestEditorService editorService) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(editorService, nameof(editorService)); + + this.service = service; + + DiffFile = ReactiveCommand.CreateAsyncTask(x => + editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, false)); + ViewFile = ReactiveCommand.CreateAsyncTask(x => + editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, false)); + DiffFileWithWorkingDirectory = ReactiveCommand.CreateAsyncTask( + isBranchCheckedOut, + x => editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, true)); + OpenFileInWorkingDirectory = ReactiveCommand.CreateAsyncTask( + isBranchCheckedOut, + x => editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, true)); + + OpenFirstComment = ReactiveCommand.CreateAsyncTask(async x => + { + var file = (IPullRequestFileNode)x; + var thread = await GetFirstCommentThread(file); + + if (thread != null) + { + await editorService.OpenDiff(pullRequestSession, file.RelativePath, thread); + } + }); + } + + /// + public int ChangedFilesCount + { + get { return changedFilesCount; } + private set { this.RaiseAndSetIfChanged(ref changedFilesCount, value); } + } + + /// + public IReadOnlyList Items + { + get { return items; } + private set { this.RaiseAndSetIfChanged(ref items, value); } + } + + /// + public void Dispose() + { + subscriptions?.Dispose(); + subscriptions = null; + } + + /// + public async Task InitializeAsync( + IPullRequestSession session, + Func filter = null) + { + Guard.ArgumentNotNull(session, nameof(session)); + + subscriptions?.Dispose(); + this.pullRequestSession = session; + this.commentFilter = filter; + subscriptions = new CompositeDisposable(); + subscriptions.Add(session.WhenAnyValue(x => x.IsCheckedOut).Subscribe(isBranchCheckedOut)); + + var dirs = new Dictionary + { + { string.Empty, new PullRequestDirectoryNode(string.Empty) } + }; + + using (var changes = await service.GetTreeChanges(session.LocalRepository, session.PullRequest)) + { + foreach (var changedFile in session.PullRequest.ChangedFiles) + { + var node = new PullRequestFileNode( + session.LocalRepository.LocalPath, + changedFile.FileName, + changedFile.Sha, + changedFile.Status, + GetOldFileName(changedFile, changes)); + var file = await session.GetFile(changedFile.FileName); + + if (file != null) + { + subscriptions.Add(file.WhenAnyValue(x => x.InlineCommentThreads) + .Subscribe(x => node.CommentCount = CountComments(x, filter))); + } + + var dir = GetDirectory(Path.GetDirectoryName(node.RelativePath), dirs); + dir.Files.Add(node); + } + } + + ChangedFilesCount = session.PullRequest.ChangedFiles.Count; + Items = dirs[string.Empty].Children.ToList(); + } + + /// + public ReactiveCommand DiffFile { get; } + + /// + public ReactiveCommand ViewFile { get; } + + /// + public ReactiveCommand DiffFileWithWorkingDirectory { get; } + + /// + public ReactiveCommand OpenFileInWorkingDirectory { get; } + + /// + public ReactiveCommand OpenFirstComment { get; } + + static int CountComments( + IEnumerable thread, + Func commentFilter) + { + return thread.Count(x => x.LineNumber != -1 && (commentFilter?.Invoke(x) ?? true)); + } + + static PullRequestDirectoryNode GetDirectory(string path, Dictionary dirs) + { + PullRequestDirectoryNode dir; + + if (!dirs.TryGetValue(path, out dir)) + { + var parentPath = Path.GetDirectoryName(path); + var parentDir = GetDirectory(parentPath, dirs); + + dir = new PullRequestDirectoryNode(path); + + if (!parentDir.Directories.Any(x => x.DirectoryName == dir.DirectoryName)) + { + parentDir.Directories.Add(dir); + dirs.Add(path, dir); + } + } + + return dir; + } + + static string GetOldFileName(IPullRequestFileModel file, TreeChanges changes) + { + if (file.Status == PullRequestFileStatus.Renamed) + { + var fileName = file.FileName.Replace("/", "\\"); + return changes?.Renamed.FirstOrDefault(x => x.Path == fileName)?.OldPath; + } + + return null; + } + + async Task GetFirstCommentThread(IPullRequestFileNode file) + { + var sessionFile = await pullRequestSession.GetFile(file.RelativePath); + var threads = sessionFile.InlineCommentThreads.AsEnumerable(); + + if (commentFilter != null) + { + threads = threads.Where(commentFilter); + } + + return threads.FirstOrDefault(); + } + } +} diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index dfe0b889c6..03a098bbfb 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -3,12 +3,23 @@ - + + + - - - + + + + + + + + + + + + diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 8b399b9220..f653adf5dc 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -182,6 +182,7 @@ + @@ -194,6 +195,7 @@ + diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs index 0f5f779c8c..b0371575af 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs @@ -1,9 +1,48 @@ -using Microsoft.VisualStudio.TextManager.Interop; +using System.Threading.Tasks; +using GitHub.Models; +using Microsoft.VisualStudio.TextManager.Interop; namespace GitHub.Services { + /// + /// Services for opening views of pull request files in Visual Studio. + /// public interface IPullRequestEditorService { + /// + /// Opens an editor for a file in a pull request. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// + /// If true opens the file in the working directory, if false opens the file in the HEAD + /// commit of the pull request. + /// + /// A task tracking the operation. + Task OpenFile(IPullRequestSession session, string relativePath, bool workingDirectory); + + /// + /// Opens an diff viewer for a file in a pull request. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// + /// If true the right hand side of the diff will be the current state of the file in the + /// working directory, if false it will be the HEAD commit of the pull request. + /// + /// A task tracking the operation. + Task OpenDiff(IPullRequestSession session, string relativePath, bool workingDirectory); + + /// + /// Opens an diff viewer for a file in a pull request with the specified inline comment + /// thread open. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// The thread to open + /// A task tracking the operation. + Task OpenDiff(IPullRequestSession session, string relativePath, IInlineCommentThreadModel thread); + /// /// Find the active text view. /// diff --git a/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs b/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs new file mode 100644 index 0000000000..4d8f1dca8f --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using GitHub.Extensions; + +namespace GitHub.Services +{ + /// + /// Extension methods for . + /// + public static class PullRequestSessionExtensions + { + /// + /// Gets the head (source) branch label for a pull request, stripping the owner if the pull + /// request is not from a fork. + /// + /// The pull request session. + /// The head branch label + public static string GetHeadBranchDisplay(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + return GetBranchDisplay(session.IsPullRequestFromFork(), session.PullRequest?.Head?.Label); + } + + /// + /// Gets the head (target) branch label for a pull request, stripping the owner if the pull + /// request is not from a fork. + /// + /// The pull request session. + /// The head branch label + public static string GetBaseBranchDisplay(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + return GetBranchDisplay(session.IsPullRequestFromFork(), session.PullRequest?.Base?.Label); + } + + /// + /// Returns a value that determines whether the pull request comes from a fork. + /// + /// The pull request session. + /// True if the pull request is from a fork, otherwise false. + public static bool IsPullRequestFromFork(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + + var headUrl = session.PullRequest.Head.RepositoryCloneUrl?.ToRepositoryUrl(); + var localUrl = session.LocalRepository.CloneUrl?.ToRepositoryUrl(); + return headUrl != null && localUrl != null ? headUrl != localUrl : false; + } + + static string GetBranchDisplay(bool fork, string label) + { + if (label != null) + { + return fork ? label : label.Split(':').Last(); + } + + return "[invalid]"; + } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs index 2661367609..a47f4889d1 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs @@ -8,9 +8,8 @@ namespace GitHub.ViewModels.GitHubPane public interface IPullRequestChangeNode { /// - /// Gets the path to the file (not including the filename) or directory, relative to the - /// root of the repository. + /// Gets the path to the file or directory, relative to the root of the repository. /// - string DirectoryPath { get; } + string RelativePath { get; } } } \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs index 078692d948..6d6a4262ac 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -126,9 +126,9 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse string Body { get; } /// - /// Gets the changed files as a tree. + /// Gets the pull request's changed files. /// - IReadOnlyList ChangedFilesTree { get; } + IPullRequestFilesViewModel Files { get; } /// /// Gets the state associated with the command. @@ -165,27 +165,6 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse /// ReactiveCommand OpenOnGitHub { get; } - /// - /// Gets a command that diffs an between BASE and HEAD. - /// - ReactiveCommand DiffFile { get; } - - /// - /// Gets a command that diffs an between the version in - /// the working directory and HEAD. - /// - ReactiveCommand DiffFileWithWorkingDirectory { get; } - - /// - /// Gets a command that opens an from disk. - /// - ReactiveCommand OpenFileInWorkingDirectory { get; } - - /// - /// Gets a command that opens an as it appears in the PR. - /// - ReactiveCommand ViewFile { get; } - /// /// Initializes the view model. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs new file mode 100644 index 0000000000..6953271341 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Represents a tree of changed files in a pull request. + /// + public interface IPullRequestFilesViewModel : IViewModel, IDisposable + { + /// + /// Gets the number of changed files in the pull request. + /// + int ChangedFilesCount { get; } + + /// + /// Gets the root nodes of the tree. + /// + IReadOnlyList Items { get; } + + /// + /// Gets a command that diffs an between BASE and HEAD. + /// + ReactiveCommand DiffFile { get; } + + /// + /// Gets a command that opens an as it appears in the PR. + /// + ReactiveCommand ViewFile { get; } + + /// + /// Gets a command that diffs an between the version in + /// the working directory and HEAD. + /// + ReactiveCommand DiffFileWithWorkingDirectory { get; } + + /// + /// Gets a command that opens an from disk. + /// + ReactiveCommand OpenFileInWorkingDirectory { get; } + + /// + /// Gets a command that opens the first comment for a in + /// the diff viewer. + /// + ReactiveCommand OpenFirstComment { get; } + + /// + /// Initializes the view model. + /// + /// The pull request session. + /// An optional review comment filter. + Task InitializeAsync( + IPullRequestSession session, + Func commentFilter = null); + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 8f0a917e97..522f30c4c2 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -374,6 +374,9 @@ GitHubPaneView.xaml + + PullRequestFilesView.xaml + PullRequestListView.xaml @@ -527,6 +530,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml index b5f1be0547..8ae87e52a2 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -2,36 +2,41 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:controls="clr-namespace:GitHub.VisualStudio.UI.Controls;assembly=GitHub.VisualStudio.UI" xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:uir="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" + xmlns:vm="clr-namespace:GitHub.ViewModels.GitHubPane;assembly=GitHub.App" xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" - xmlns:vsui="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.14.0" Background="{DynamicResource GitHubVsToolWindowBackground}" Foreground="{DynamicResource GitHubVsWindowText}" DataContext="{Binding ViewModel}" d:DesignWidth="356" d:DesignHeight="800" - mc:Ignorable="d"> + mc:Ignorable="d" + xmlns:vsui="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.14.0"> - - - - - + + + + Unable to connect to the internets over here! - - + + @@ -39,10 +44,10 @@ - - - - + + + + - + @@ -125,7 +130,7 @@ - + @@ -145,54 +150,54 @@ + Text="{Binding Model.UpdatedAt, StringFormat={x:Static prop:Resources.UpdatedFormat}, Converter={ui:DurationToStringConverter}, Mode=OneWay}"/> - - + Visibility="{Binding UpdateState.UpToDate, FallbackValue=Collapsed, Converter={ui:BooleanToInverseVisibilityConverter}}"> + - - + - - + - + + Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ui:BooleanToVisibilityConverter}}" /> + Visibility="{Binding UpdateState.UpToDate, FallbackValue=Collapsed, Converter={ui:BooleanToVisibilityConverter}}"> - + @@ -225,7 +230,7 @@ VerticalAlignment="Center" TextWrapping="Wrap" Style="{StaticResource FlatReadOnlyTextBox}" - Visibility="{Binding OperationError, Converter={ghfvs:NullToVisibilityConverter}}"/> + Visibility="{Binding OperationError, Converter={ui:NullToVisibilityConverter}}"/> @@ -249,7 +254,7 @@ - @@ -267,125 +272,18 @@ Markdown="{Binding Body}"/> - + View conversation on GitHub - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs index 038160144a..542a62b614 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs @@ -1,32 +1,17 @@ using System; using System.ComponentModel.Composition; using System.Globalization; -using System.Linq; using System.Reactive.Linq; using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; using System.Windows.Input; -using System.Windows.Media; -using GitHub.Commands; using GitHub.Exports; using GitHub.Extensions; -using GitHub.Models; using GitHub.Services; using GitHub.UI; using GitHub.UI.Helpers; using GitHub.ViewModels.GitHubPane; using GitHub.VisualStudio.UI.Helpers; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Projection; -using Microsoft.VisualStudio.TextManager.Interop; using ReactiveUI; -using Task = System.Threading.Tasks.Task; namespace GitHub.VisualStudio.Views.GitHubPane { @@ -47,38 +32,12 @@ public PullRequestDetailView() this.WhenActivated(d => { d(ViewModel.OpenOnGitHub.Subscribe(_ => DoOpenOnGitHub())); - d(ViewModel.DiffFile.Subscribe(x => DoDiffFile((IPullRequestFileNode)x, false).Forget())); - d(ViewModel.ViewFile.Subscribe(x => DoOpenFile((IPullRequestFileNode)x, false).Forget())); - d(ViewModel.DiffFileWithWorkingDirectory.Subscribe(x => DoDiffFile((IPullRequestFileNode)x, true).Forget())); - d(ViewModel.OpenFileInWorkingDirectory.Subscribe(x => DoOpenFile((IPullRequestFileNode)x, true).Forget())); }); - - bodyGrid.RequestBringIntoView += BodyFocusHack; } - [Import] - ITeamExplorerServiceHolder TeamExplorerServiceHolder { get; set; } - [Import] IVisualStudioBrowser VisualStudioBrowser { get; set; } - [Import] - IEditorOptionsFactoryService EditorOptionsFactoryService { get; set; } - - [Import] - IUsageTracker UsageTracker { get; set; } - - [Import] - IPullRequestEditorService NavigationService { get; set; } - - [Import] - IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; } - - protected override void OnVisualParentChanged(DependencyObject oldParent) - { - base.OnVisualParentChanged(oldParent); - } - void DoOpenOnGitHub() { var browser = VisualStudioBrowser; @@ -93,315 +52,6 @@ static Uri ToPullRequestUrl(string host, string owner, string repositoryName, in return new Uri(url); } - async Task DoOpenFile(IPullRequestFileNode file, bool workingDirectory) - { - try - { - var fullPath = ViewModel.GetLocalFilePath(file); - var fileName = workingDirectory ? fullPath : await ViewModel.ExtractFile(file, true); - - using (workingDirectory ? null : OpenInProvisionalTab()) - { - var window = GitHub.VisualStudio.Services.Dte.ItemOperations.OpenFile(fileName); - window.Document.ReadOnly = !workingDirectory; - - var buffer = GetBufferAt(fileName); - - if (!workingDirectory) - { - AddBufferTag(buffer, ViewModel.Session, fullPath, null); - - var textView = NavigationService.FindActiveView(); - EnableNavigateToEditor(textView, file); - } - } - - if (workingDirectory) - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); - else - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); - } - catch (Exception e) - { - ShowErrorInStatusBar("Error opening file", e); - } - } - - async Task DoNavigateToEditor(IPullRequestFileNode file) - { - try - { - if (!ViewModel.IsCheckedOut) - { - ShowInfoMessage("Checkout PR branch before opening file in solution."); - return; - } - - var fullPath = ViewModel.GetLocalFilePath(file); - - var activeView = NavigationService.FindActiveView(); - if (activeView == null) - { - ShowErrorInStatusBar("Couldn't find active view"); - return; - } - - NavigationService.NavigateToEquivalentPosition(activeView, fullPath); - - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsNavigateToEditor); - } - catch (Exception e) - { - ShowErrorInStatusBar("Error navigating to editor", e); - } - } - - static void ShowInfoMessage(string message) - { - ErrorHandler.ThrowOnFailure(VsShellUtilities.ShowMessageBox( - Services.GitHubServiceProvider, message, null, - OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST)); - } - - async Task DoDiffFile(IPullRequestFileNode file, bool workingDirectory) - { - try - { - var rightPath = System.IO.Path.Combine(file.DirectoryPath, file.FileName); - var leftPath = file.OldPath ?? rightPath; - var rightFile = workingDirectory ? ViewModel.GetLocalFilePath(file) : await ViewModel.ExtractFile(file, true); - var leftFile = await ViewModel.ExtractFile(file, false); - var leftLabel = $"{leftPath};{ViewModel.TargetBranchDisplayName}"; - var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {ViewModel.Model.Number}"; - var caption = $"Diff - {file.FileName}"; - var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | - __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; - - if (!workingDirectory) - { - options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; - } - - IVsWindowFrame frame; - using (OpenInProvisionalTab()) - { - var tooltip = $"{leftLabel}\nvs.\n{rightLabel}"; - - // Diff window will open in provisional (right hand) tab until document is touched. - frame = GitHub.VisualStudio.Services.DifferenceService.OpenComparisonWindow2( - leftFile, - rightFile, - caption, - tooltip, - leftLabel, - rightLabel, - string.Empty, - string.Empty, - (uint)options); - } - - object docView; - frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); - var diffViewer = ((IVsDifferenceCodeWindow)docView).DifferenceViewer; - - var session = ViewModel.Session; - AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, DiffSide.Left); - - if (!workingDirectory) - { - AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, DiffSide.Right); - EnableNavigateToEditor(diffViewer.LeftView, file); - EnableNavigateToEditor(diffViewer.RightView, file); - EnableNavigateToEditor(diffViewer.InlineView, file); - } - - if (workingDirectory) - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsCompareWithSolution); - else - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewChanges); - } - catch (Exception e) - { - ShowErrorInStatusBar("Error opening file", e); - } - } - - void AddBufferTag(ITextBuffer buffer, IPullRequestSession session, string path, DiffSide? side) - { - buffer.Properties.GetOrCreateSingletonProperty( - typeof(PullRequestTextBufferInfo), - () => new PullRequestTextBufferInfo(session, path, side)); - - var projection = buffer as IProjectionBuffer; - - if (projection != null) - { - foreach (var source in projection.SourceBuffers) - { - AddBufferTag(source, session, path, side); - } - } - } - - void EnableNavigateToEditor(IWpfTextView textView, IPullRequestFileNode file) - { - var view = EditorAdaptersFactoryService.GetViewAdapter(textView); - EnableNavigateToEditor(view, file); - } - - void EnableNavigateToEditor(IVsTextView textView, IPullRequestFileNode file) - { - var commandGroup = VSConstants.CMDSETID.StandardCommandSet2K_guid; - var commandId = (int)VSConstants.VSStd2KCmdID.RETURN; - new TextViewCommandDispatcher(textView, commandGroup, commandId).Exec += async (s, e) => await DoNavigateToEditor(file); - - var contextMenuCommandGroup = new Guid(Guids.guidContextMenuSetString); - var goToCommandId = PkgCmdIDList.openFileInSolutionCommand; - new TextViewCommandDispatcher(textView, contextMenuCommandGroup, goToCommandId).Exec += async (s, e) => await DoNavigateToEditor(file); - } - - void ShowErrorInStatusBar(string message, Exception e = null) - { - var ns = GitHub.VisualStudio.Services.DefaultExportProvider.GetExportedValue(); - if (e != null) - { - message += ": " + e.Message; - } - ns?.ShowMessage(message); - } - - private void FileListKeyUp(object sender, KeyEventArgs e) - { - if (e.Key == Key.Return) - { - var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; - if (file != null) - { - DoDiffFile(file, false).Forget(); - } - } - } - - void FileListMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; - - if (file != null) - { - DoDiffFile(file, false).Forget(); - } - } - - void FileListMouseRightButtonDown(object sender, MouseButtonEventArgs e) - { - var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); - - if (item != null) - { - // Select tree view item on right click. - item.IsSelected = true; - } - } - - ITextBuffer GetBufferAt(string filePath) - { - var editorAdapterFactoryService = GitHub.VisualStudio.Services.ComponentModel.GetService(); - IVsUIHierarchy uiHierarchy; - uint itemID; - IVsWindowFrame windowFrame; - - if (VsShellUtilities.IsDocumentOpen( - GitHub.VisualStudio.Services.GitHubServiceProvider, - filePath, - Guid.Empty, - out uiHierarchy, - out itemID, - out windowFrame)) - { - IVsTextView view = VsShellUtilities.GetTextView(windowFrame); - IVsTextLines lines; - if (view.GetBuffer(out lines) == 0) - { - var buffer = lines as IVsTextBuffer; - if (buffer != null) - return editorAdapterFactoryService.GetDataBuffer(buffer); - } - } - - return null; - } - - void TreeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) - { - ApplyContextMenuBinding(sender, e); - } - - void ApplyContextMenuBinding(object sender, ContextMenuEventArgs e) where TItem : Control - { - var container = (Control)sender; - var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); - - e.Handled = true; - - if (item != null) - { - var fileNode = item.DataContext as IPullRequestFileNode; - - if (fileNode != null) - { - container.ContextMenu.DataContext = this.DataContext; - - foreach (var menuItem in container.ContextMenu.Items.OfType()) - { - menuItem.CommandParameter = fileNode; - } - - e.Handled = false; - } - } - } - - void BodyFocusHack(object sender, RequestBringIntoViewEventArgs e) - { - if (e.TargetObject == bodyMarkdown) - { - // Hack to prevent pane scrolling to top. Instead focus selected tree view item. - // See https://github.com/github/VisualStudio/issues/1042 - var node = changesTree.GetTreeViewItem(changesTree.SelectedItem); - node?.Focus(); - e.Handled = true; - } - } - - async void ViewFileCommentsClick(object sender, RoutedEventArgs e) - { - try - { - var file = (e.OriginalSource as Hyperlink)?.DataContext as IPullRequestFileNode; - - if (file != null) - { - var param = (object)new InlineCommentNavigationParams - { - FromLine = -1, - }; - - await DoDiffFile(file, false); - - // HACK: We need to wait here for the diff view to set itself up and move its cursor - // to the first changed line. There must be a better way of doing this. - await Task.Delay(1500); - - GitHub.VisualStudio.Services.Dte.Commands.Raise( - Guids.CommandSetString, - PkgCmdIDList.NextInlineCommentId, - ref param, - null); - } - } - catch { } - } - void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) { Uri uri; @@ -411,12 +61,5 @@ void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) VisualStudioBrowser.OpenUrl(uri); } } - - static IDisposable OpenInProvisionalTab() - { - return new NewDocumentStateScope - (__VSNEWDOCUMENTSTATE.NDS_Provisional, - VSConstants.NewDocumentStateReason.SolutionExplorer); - } } } diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml new file mode 100644 index 0000000000..9195515a16 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs new file mode 100644 index 0000000000..2f2956ef45 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs @@ -0,0 +1,87 @@ +using System.ComponentModel.Composition; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using GitHub.Exports; +using GitHub.UI.Helpers; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestFilesViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestFilesView : UserControl + { + public PullRequestFilesView() + { + InitializeComponent(); + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + } + + void changesTree_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + ApplyContextMenuBinding(sender, e); + } + + void changesTree_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; + (DataContext as IPullRequestFilesViewModel)?.DiffFile.Execute(file); + } + + void changesTree_MouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + + if (item != null) + { + // Select tree view item on right click. + item.IsSelected = true; + } + } + + void ApplyContextMenuBinding(object sender, ContextMenuEventArgs e) where TItem : Control + { + var container = (Control)sender; + var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + + e.Handled = true; + + if (item != null) + { + var fileNode = item.DataContext as IPullRequestFileNode; + + if (fileNode != null) + { + container.ContextMenu.DataContext = this.DataContext; + + foreach (var menuItem in container.ContextMenu.Items.OfType()) + { + menuItem.CommandParameter = fileNode; + } + + e.Handled = false; + } + } + } + + private void changesTree_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Return) + { + var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; + + if (file != null) + { + (DataContext as IPullRequestFilesViewModel)?.DiffFile.Execute(file); + } + } + } + } +} diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs index c9e409c7c1..a344f1499a 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -49,102 +49,6 @@ public async Task ShouldAcceptNullHead() } } - public class TheChangedFilesTreeProperty - { - [Test] - public async Task ShouldCreateChangesTree() - { - var target = CreateTarget(); - var pr = CreatePullRequest(); - - pr.ChangedFiles = new[] - { - new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir1/f1.cs", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir1/f2.cs", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir1/dir1a/f3.cs", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir2/f4.cs", "abc", PullRequestFileStatus.Modified), - }; - - await target.Load(pr); - - Assert.That(3, Is.EqualTo(target.ChangedFilesTree.Count)); - - var dir1 = (PullRequestDirectoryNode)target.ChangedFilesTree[0]; - Assert.That("dir1", Is.EqualTo(dir1.DirectoryName)); - Assert.That(2, Is.EqualTo(dir1.Files.Count)); - Assert.That(1, Is.EqualTo(dir1.Directories.Count)); - Assert.That("f1.cs", Is.EqualTo(dir1.Files[0].FileName)); - Assert.That("f2.cs", Is.EqualTo(dir1.Files[1].FileName)); - Assert.That("dir1", Is.EqualTo(dir1.Files[0].DirectoryPath)); - Assert.That("dir1", Is.EqualTo(dir1.Files[1].DirectoryPath)); - - var dir1a = (PullRequestDirectoryNode)dir1.Directories[0]; - Assert.That("dir1a", Is.EqualTo(dir1a.DirectoryName)); - Assert.That(1, Is.EqualTo(dir1a.Files.Count)); - Assert.That(0, Is.EqualTo(dir1a.Directories.Count)); - - var dir2 = (PullRequestDirectoryNode)target.ChangedFilesTree[1]; - Assert.That("dir2", Is.EqualTo(dir2.DirectoryName)); - Assert.That(1, Is.EqualTo(dir2.Files.Count)); - Assert.That(0, Is.EqualTo(dir2.Directories.Count)); - - var readme = (PullRequestFileNode)target.ChangedFilesTree[2]; - Assert.That("readme.md", Is.EqualTo(readme.FileName)); - } - - [Test] - public async Task FileCommentCountShouldTrackSessionInlineComments() - { - var pr = CreatePullRequest(); - var file = Substitute.For(); - var thread1 = CreateThread(5); - var thread2 = CreateThread(6); - var outdatedThread = CreateThread(-1); - var session = Substitute.For(); - var sessionManager = Substitute.For(); - - file.InlineCommentThreads.Returns(new[] { thread1 }); - session.GetFile("readme.md").Returns(Task.FromResult(file)); - sessionManager.GetSession(pr).Returns(Task.FromResult(session)); - - var target = CreateTarget(sessionManager: sessionManager); - - pr.ChangedFiles = new[] - { - new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), - }; - - await target.Load(pr); - Assert.That(1, Is.EqualTo(((IPullRequestFileNode)target.ChangedFilesTree[0]).CommentCount)); - - file.InlineCommentThreads.Returns(new[] { thread1, thread2 }); - RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); - Assert.That(2, Is.EqualTo(((IPullRequestFileNode)target.ChangedFilesTree[0]).CommentCount)); - - // Outdated comment is not included in the count. - file.InlineCommentThreads.Returns(new[] { thread1, thread2, outdatedThread }); - RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); - Assert.That(2, Is.EqualTo(((IPullRequestFileNode)target.ChangedFilesTree[0]).CommentCount)); - - file.Received(1).PropertyChanged += Arg.Any(); - } - - IInlineCommentThreadModel CreateThread(int lineNumber) - { - var result = Substitute.For(); - result.LineNumber.Returns(lineNumber); - return result; - } - - void RaisePropertyChanged(T o, string propertyName) - where T : INotifyPropertyChanged - { - o.PropertyChanged += Raise.Event(new PropertyChangedEventArgs(propertyName)); - } - - } - public class TheCheckoutCommand { [Test] @@ -556,7 +460,8 @@ static Tuple CreateTargetAndSer Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For()); vm.InitializeAsync(repository, Substitute.For(), "owner", "repo", 1).Wait(); return Tuple.Create(vm, pullRequestService); diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs new file mode 100644 index 0000000000..67a6cf87e6 --- /dev/null +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs @@ -0,0 +1,128 @@ +using System; +using System.ComponentModel; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestFilesViewModelTests + { + static readonly Uri Uri = new Uri("http://foo"); + + [Test] + public async Task ShouldCreateChangesTree() + { + var target = CreateTarget(); + var session = CreateSession(); + + session.PullRequest.ChangedFiles.Returns(new[] + { + new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir1/f1.cs", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir1/f2.cs", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir1/dir1a/f3.cs", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir2/f4.cs", "abc", PullRequestFileStatus.Modified), + }); + + await target.InitializeAsync(session); + + Assert.That(target.Items.Count, Is.EqualTo(3)); + + var dir1 = (PullRequestDirectoryNode)target.Items[0]; + Assert.That(dir1.DirectoryName, Is.EqualTo("dir1")); + Assert.That(dir1.Files, Has.Exactly(2).Items); + + Assert.That(dir1.Directories, Has.One.Items); + Assert.That(dir1.Files[0].FileName, Is.EqualTo("f1.cs")); + Assert.That(dir1.Files[1].FileName, Is.EqualTo("f2.cs")); + Assert.That(dir1.Files[0].RelativePath, Is.EqualTo("dir1\\f1.cs")); + Assert.That(dir1.Files[1].RelativePath, Is.EqualTo("dir1\\f2.cs")); + + var dir1a = (PullRequestDirectoryNode)dir1.Directories[0]; + Assert.That(dir1a.DirectoryName, Is.EqualTo("dir1a")); + Assert.That(dir1a.Files, Has.One.Items); + Assert.That(dir1a.Directories, Is.Empty); + + var dir2 = (PullRequestDirectoryNode)target.Items[1]; + Assert.That(dir2.DirectoryName, Is.EqualTo("dir2")); + Assert.That(dir2.Files, Has.One.Items); + Assert.That(dir2.Directories, Is.Empty); + + var readme = (PullRequestFileNode)target.Items[2]; + Assert.That(readme.FileName, Is.EqualTo("readme.md")); + } + + [Test] + public async Task FileCommentCountShouldTrackSessionInlineComments() + { + var outdatedThread = CreateThread(-1); + var session = CreateSession(); + + session.PullRequest.ChangedFiles.Returns(new[] + { + new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), + }); + + var file = Substitute.For(); + var thread1 = CreateThread(5); + var thread2 = CreateThread(6); + file.InlineCommentThreads.Returns(new[] { thread1 }); + session.GetFile("readme.md").Returns(Task.FromResult(file)); + + var target = CreateTarget(); + + await target.InitializeAsync(session); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(1)); + + file.InlineCommentThreads.Returns(new[] { thread1, thread2 }); + RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(2)); + + // Outdated comment is not included in the count. + file.InlineCommentThreads.Returns(new[] { thread1, thread2, outdatedThread }); + RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(2)); + + file.Received(1).PropertyChanged += Arg.Any(); + } + + static PullRequestFilesViewModel CreateTarget() + { + var pullRequestService = Substitute.For(); + var editorService = Substitute.For(); + return new PullRequestFilesViewModel(pullRequestService, editorService); + } + + static IPullRequestSession CreateSession() + { + var author = Substitute.For(); + var pr = Substitute.For(); + + var repository = Substitute.For(); + repository.LocalPath.Returns(@"C:\Foo"); + + var result = Substitute.For(); + result.LocalRepository.Returns(repository); + result.PullRequest.Returns(pr); + return result; + } + + IInlineCommentThreadModel CreateThread(int lineNumber) + { + var result = Substitute.For(); + result.LineNumber.Returns(lineNumber); + return result; + } + + void RaisePropertyChanged(T o, string propertyName) + where T : INotifyPropertyChanged + { + o.PropertyChanged += Raise.Event(new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs b/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs index acbc202559..51838e4f11 100644 --- a/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs +++ b/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs @@ -2,6 +2,7 @@ using GitHub.Services; using NUnit.Framework; using NSubstitute; +using Microsoft.VisualStudio.Editor; public class PullRequestEditorServiceTests { @@ -48,6 +49,15 @@ public void FindNearestMatchingLine(IList fromLines, IList toLin static PullRequestEditorService CreateNavigationService() { var sp = Substitute.For(); - return new PullRequestEditorService(sp); + var pullRequestService = Substitute.For(); + var vsEditorAdaptersFactory = Substitute.For(); + var statusBar = Substitute.For(); + var usageTracker = Substitute.For(); + return new PullRequestEditorService( + sp, + pullRequestService, + vsEditorAdaptersFactory, + statusBar, + usageTracker); } } diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 6426dc4779..164350fce0 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -82,6 +82,10 @@ ..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll True + + ..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll + True + ..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll True @@ -240,6 +244,7 @@ + diff --git a/test/UnitTests/packages.config b/test/UnitTests/packages.config index a80e0e8c78..f6e22cda65 100644 --- a/test/UnitTests/packages.config +++ b/test/UnitTests/packages.config @@ -6,6 +6,7 @@ + From 6123b02cf7cbbc5af63c6de9578aa1f540f2c01a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 16 Mar 2018 17:37:48 +0100 Subject: [PATCH 05/78] Use the `ghfvs:` xmlns where possible. --- .../GitHubPane/PullRequestDetailView.xaml | 85 +++++++++---------- .../GitHubPane/PullRequestFilesView.xaml | 27 +++--- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml index 8ae87e52a2..00976c6ee0 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -2,41 +2,36 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" - xmlns:controls="clr-namespace:GitHub.VisualStudio.UI.Controls;assembly=GitHub.VisualStudio.UI" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:uir="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:vm="clr-namespace:GitHub.ViewModels.GitHubPane;assembly=GitHub.App" xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + xmlns:vsui="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.14.0" Background="{DynamicResource GitHubVsToolWindowBackground}" Foreground="{DynamicResource GitHubVsWindowText}" DataContext="{Binding ViewModel}" d:DesignWidth="356" d:DesignHeight="800" - mc:Ignorable="d" - xmlns:vsui="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.14.0"> + mc:Ignorable="d"> - - - - - + + + + Unable to connect to the internets over here! - - + + @@ -44,10 +39,10 @@ - - - - + + + + - + @@ -130,7 +125,7 @@ - + @@ -150,54 +145,54 @@ + Text="{Binding Model.UpdatedAt, StringFormat={x:Static prop:Resources.UpdatedFormat}, Converter={ghfvs:DurationToStringConverter}, Mode=OneWay}"/> - - + Visibility="{Binding UpdateState.UpToDate, FallbackValue=Collapsed, Converter={ghfvs:BooleanToInverseVisibilityConverter}}"> + - - + - - + - + + Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ghfvs:BooleanToVisibilityConverter}}" /> + Visibility="{Binding UpdateState.UpToDate, FallbackValue=Collapsed, Converter={ghfvs:BooleanToVisibilityConverter}}"> - + @@ -230,7 +225,7 @@ VerticalAlignment="Center" TextWrapping="Wrap" Style="{StaticResource FlatReadOnlyTextBox}" - Visibility="{Binding OperationError, Converter={ui:NullToVisibilityConverter}}"/> + Visibility="{Binding OperationError, Converter={ghfvs:NullToVisibilityConverter}}"/> @@ -254,7 +249,7 @@ - @@ -272,18 +267,18 @@ Markdown="{Binding Body}"/> - + View conversation on GitHub - + - - + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml index 9195515a16..219458683a 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml @@ -2,26 +2,23 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:vm="clr-namespace:GitHub.ViewModels.GitHubPane;assembly=GitHub.App" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" - xmlns:sample="clr-namespace:GitHub.SampleData;assembly=GitHub.App" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Name="root"> - + - - - - + + + + @@ -49,15 +46,15 @@ - - + - + @@ -81,7 +78,7 @@ - + @@ -125,7 +122,7 @@ - + From b203545ca5a81f1745129b87a969e05474a134a0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Feb 2018 16:04:58 +0100 Subject: [PATCH 06/78] Port changes from #1407. Enable navigation from diff view to editor --- src/GitHub.App/GitHub.App.csproj | 1 + .../Services/PullRequestEditorService.cs | 65 +++++++++++++++++++ .../Services}/TextViewCommandDispatcher.cs | 2 +- .../GitHub.VisualStudio.csproj | 1 - 4 files changed, 67 insertions(+), 2 deletions(-) rename src/{GitHub.VisualStudio/Views/GitHubPane => GitHub.App/Services}/TextViewCommandDispatcher.cs (98%) diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index f6c490a239..17331a586e 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -205,6 +205,7 @@ + diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 4e318239a6..563b176f58 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -9,12 +9,14 @@ using GitHub.Commands; using GitHub.Extensions; using GitHub.Models; +using GitHub.ViewModels.GitHubPane; using GitHub.VisualStudio; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; using Task = System.Threading.Tasks.Task; @@ -151,6 +153,9 @@ public async Task OpenDiff( if (!workingDirectory) { AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, DiffSide.Right); + EnableNavigateToEditor(diffViewer.LeftView, session, file); + EnableNavigateToEditor(diffViewer.RightView, session, file); + EnableNavigateToEditor(diffViewer.InlineView, session, file); } if (workingDirectory) @@ -350,6 +355,11 @@ IVsTextView OpenDocument(string fullPath) return view; } + void ShowErrorInStatusBar(string message) + { + statusBar.ShowMessage(message); + } + void ShowErrorInStatusBar(string message, Exception e) { statusBar.ShowMessage(message + ": " + e.Message); @@ -372,6 +382,54 @@ void AddBufferTag(ITextBuffer buffer, IPullRequestSession session, string path, } } + void EnableNavigateToEditor(IWpfTextView textView, IPullRequestSession session, IPullRequestSessionFile file) + { + var view = vsEditorAdaptersFactory.GetViewAdapter(textView); + EnableNavigateToEditor(view, session, file); + } + + void EnableNavigateToEditor(IVsTextView textView, IPullRequestSession session, IPullRequestSessionFile file) + { + var commandGroup = VSConstants.CMDSETID.StandardCommandSet2K_guid; + var commandId = (int)VSConstants.VSStd2KCmdID.RETURN; + new TextViewCommandDispatcher(textView, commandGroup, commandId).Exec += + async (s, e) => await DoNavigateToEditor(session, file); + + var contextMenuCommandGroup = new Guid(Guids.guidContextMenuSetString); + var goToCommandId = PkgCmdIDList.openFileInSolutionCommand; + new TextViewCommandDispatcher(textView, contextMenuCommandGroup, goToCommandId).Exec += + async (s, e) => await DoNavigateToEditor(session, file); + } + + async Task DoNavigateToEditor(IPullRequestSession session, IPullRequestSessionFile file) + { + try + { + if (!session.IsCheckedOut) + { + ShowInfoMessage("Checkout PR branch before opening file in solution."); + return; + } + + var fullPath = GetAbsolutePath(session, file); + + var activeView = FindActiveView(); + if (activeView == null) + { + ShowErrorInStatusBar("Couldn't find active view"); + return; + } + + NavigateToEquivalentPosition(activeView, fullPath); + + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsNavigateToEditor); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error navigating to editor", e); + } + } + async Task ExtractFile(IPullRequestSession session, IPullRequestSessionFile file, bool head) { var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); @@ -424,6 +482,13 @@ async Task GetBaseFileName(IPullRequestSession session, IPullRequestSess } } + void ShowInfoMessage(string message) + { + ErrorHandler.ThrowOnFailure(VsShellUtilities.ShowMessageBox( + serviceProvider, message, null, + OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST)); + } + static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFile file) { return Path.Combine(session.LocalRepository.LocalPath, file.RelativePath); diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/TextViewCommandDispatcher.cs b/src/GitHub.App/Services/TextViewCommandDispatcher.cs similarity index 98% rename from src/GitHub.VisualStudio/Views/GitHubPane/TextViewCommandDispatcher.cs rename to src/GitHub.App/Services/TextViewCommandDispatcher.cs index 10c7da216e..dd0f91f582 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/TextViewCommandDispatcher.cs +++ b/src/GitHub.App/Services/TextViewCommandDispatcher.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.TextManager.Interop; -namespace GitHub.VisualStudio.Views.GitHubPane +namespace GitHub.Services { /// /// Intercepts all commands sent to a and fires when a specified command is encountered. diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 522f30c4c2..14a5784b4f 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -392,7 +392,6 @@ NotAGitRepositoryView.xaml - RepositoryPublishView.xaml From d51f5f5052c88159030b0d1a7a5c6446b15f42cc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 12:47:07 +0100 Subject: [PATCH 07/78] Added GraphQL to GitHub.Api. And associated factory/keychain classes. --- src/GitHub.Api/GitHub.Api.csproj | 15 +++++++ src/GitHub.Api/GraphQLClientFactory.cs | 41 +++++++++++++++++++ .../GraphQLKeychainCredentialStore.cs | 32 +++++++++++++++ src/GitHub.Api/IGraphQLClientFactory.cs | 19 +++++++++ src/GitHub.Api/packages.config | 2 + 5 files changed, 109 insertions(+) create mode 100644 src/GitHub.Api/GraphQLClientFactory.cs create mode 100644 src/GitHub.Api/GraphQLKeychainCredentialStore.cs create mode 100644 src/GitHub.Api/IGraphQLClientFactory.cs diff --git a/src/GitHub.Api/GitHub.Api.csproj b/src/GitHub.Api/GitHub.Api.csproj index a825cfc079..f66d2730ad 100644 --- a/src/GitHub.Api/GitHub.Api.csproj +++ b/src/GitHub.Api/GitHub.Api.csproj @@ -46,6 +46,18 @@ + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + True + ..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll True @@ -66,6 +78,9 @@ ApiClientConfiguration_User.cs + + + diff --git a/src/GitHub.Api/GraphQLClientFactory.cs b/src/GitHub.Api/GraphQLClientFactory.cs new file mode 100644 index 0000000000..8151832a16 --- /dev/null +++ b/src/GitHub.Api/GraphQLClientFactory.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using Octokit.GraphQL; + +namespace GitHub.Api +{ + /// + /// Creates GraphQL s for querying the + /// GitHub GraphQL API. + /// + [Export(typeof(IGraphQLClientFactory))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class GraphQLClientFactory : IGraphQLClientFactory + { + readonly IKeychain keychain; + readonly IProgram program; + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + /// The program details. + [ImportingConstructor] + public GraphQLClientFactory(IKeychain keychain, IProgram program) + { + this.keychain = keychain; + this.program = program; + } + + /// + public Task CreateConnection(HostAddress address) + { + var credentials = new GraphQLKeychainCredentialStore(keychain, address); + var header = new ProductHeaderValue(program.ProductHeader.Name, program.ProductHeader.Version); + return Task.FromResult(new Connection(header, credentials)); + } + } +} diff --git a/src/GitHub.Api/GraphQLKeychainCredentialStore.cs b/src/GitHub.Api/GraphQLKeychainCredentialStore.cs new file mode 100644 index 0000000000..0098d15983 --- /dev/null +++ b/src/GitHub.Api/GraphQLKeychainCredentialStore.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Primitives; +using Octokit.GraphQL; + +namespace GitHub.Api +{ + /// + /// An Octokit.GraphQL credential store that reads from an . + /// + public class GraphQLKeychainCredentialStore : ICredentialStore + { + readonly IKeychain keychain; + readonly HostAddress address; + + public GraphQLKeychainCredentialStore(IKeychain keychain, HostAddress address) + { + Guard.ArgumentNotNull(keychain, nameof(keychain)); + Guard.ArgumentNotNull(address, nameof(keychain)); + + this.keychain = keychain; + this.address = address; + } + + public async Task GetCredentials() + { + var userPass = await keychain.Load(address).ConfigureAwait(false); + return userPass?.Item2; + } + } +} diff --git a/src/GitHub.Api/IGraphQLClientFactory.cs b/src/GitHub.Api/IGraphQLClientFactory.cs new file mode 100644 index 0000000000..464fab0de8 --- /dev/null +++ b/src/GitHub.Api/IGraphQLClientFactory.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using GitHub.Primitives; + +namespace GitHub.Api +{ + /// + /// Creates GraphQL s for querying the + /// GitHub GraphQL API. + /// + public interface IGraphQLClientFactory + { + /// + /// Creates a new . + /// + /// The address of the server. + /// A task returning the created connection. + Task CreateConnection(HostAddress address); + } +} \ No newline at end of file diff --git a/src/GitHub.Api/packages.config b/src/GitHub.Api/packages.config index bfb877d0d6..d5d4862325 100644 --- a/src/GitHub.Api/packages.config +++ b/src/GitHub.Api/packages.config @@ -1,4 +1,6 @@  + + \ No newline at end of file From 7799ed57a55565680eae5aa5050312ab0ae90e83 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 13:21:44 +0100 Subject: [PATCH 08/78] Use GraphQL to read PR reviews/comments. This is integrated a bit hackily into `ModelService`; `ModelService` doesn't really mesh well with GraphQL but without a lot of refactoring this was the best way to get things up and running. --- .../Factories/ModelServiceFactory.cs | 5 + src/GitHub.App/GitHub.App.csproj | 16 +- src/GitHub.App/Models/Account.cs | 9 +- src/GitHub.App/Models/IssueCommentModel.cs | 3 +- src/GitHub.App/Models/PullRequestModel.cs | 20 +- .../Models/PullRequestReviewCommentModel.cs | 3 + .../Models/PullRequestReviewModel.cs | 14 + src/GitHub.App/SampleData/AccountDesigner.cs | 1 + src/GitHub.App/Services/AvatarProvider.cs | 15 + src/GitHub.App/Services/ModelService.cs | 256 +++++++++++++++++- .../GitHubPane/PullRequestListViewModel.cs | 2 +- src/GitHub.App/packages.config | 3 +- .../Services/IAvatarProvider.cs | 1 + src/GitHub.Exports/GitHub.Exports.csproj | 1 + src/GitHub.Exports/Models/IAccount.cs | 3 +- src/GitHub.Exports/Models/ICommentModel.cs | 5 + .../Models/IPullRequestModel.cs | 7 +- .../Models/IPullRequestReviewCommentModel.cs | 10 + .../Models/IPullRequestReviewModel.cs | 71 +++++ .../GitHub.VisualStudio.csproj | 12 +- .../Properties/AssemblyInfo.cs | 3 + src/GitHub.VisualStudio/packages.config | 3 +- .../Factories/ModelServiceFactoryTests.cs | 3 + .../GitHub.App/Models/AccountModelTests.cs | 27 +- .../GitHub.App/Models/ModelServiceTests.cs | 51 ++-- .../PullRequestListViewModelTests.cs | 4 +- test/UnitTests/UnitTests.csproj | 12 + test/UnitTests/packages.config | 2 + 28 files changed, 501 insertions(+), 61 deletions(-) create mode 100644 src/GitHub.App/Models/PullRequestReviewModel.cs create mode 100644 src/GitHub.Exports/Models/IPullRequestReviewModel.cs diff --git a/src/GitHub.App/Factories/ModelServiceFactory.cs b/src/GitHub.App/Factories/ModelServiceFactory.cs index 8a6188b6bd..5ac1d26cdc 100644 --- a/src/GitHub.App/Factories/ModelServiceFactory.cs +++ b/src/GitHub.App/Factories/ModelServiceFactory.cs @@ -3,6 +3,7 @@ using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using GitHub.Api; using GitHub.Caches; using GitHub.Models; using GitHub.Services; @@ -15,6 +16,7 @@ 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(); @@ -23,10 +25,12 @@ 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; } @@ -43,6 +47,7 @@ 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 815b12f40b..d9513e8df0 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -89,10 +89,17 @@ False ..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - False + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + True @@ -152,6 +159,7 @@ + diff --git a/src/GitHub.App/Models/Account.cs b/src/GitHub.App/Models/Account.cs index e7a61c3146..050871f6fa 100644 --- a/src/GitHub.App/Models/Account.cs +++ b/src/GitHub.App/Models/Account.cs @@ -23,6 +23,7 @@ public Account( bool isEnterprise, int ownedPrivateRepositoryCount, long privateRepositoryInPlanCount, + string avatarUrl, IObservable bitmapSource) { Guard.ArgumentNotEmptyString(login, nameof(login)); @@ -34,6 +35,7 @@ public Account( PrivateReposInPlan = privateRepositoryInPlanCount; IsOnFreePlan = privateRepositoryInPlanCount == 0; HasMaximumPrivateRepositories = OwnedPrivateRepos >= PrivateReposInPlan; + AvatarUrl = avatarUrl; this.bitmapSource = bitmapSource; bitmapSourceSubscription = bitmapSource @@ -54,6 +56,7 @@ public Account(Octokit.Account account) OwnedPrivateRepos = account.OwnedPrivateRepos; IsOnFreePlan = PrivateReposInPlan == 0; HasMaximumPrivateRepositories = OwnedPrivateRepos >= PrivateReposInPlan; + AvatarUrl = account.AvatarUrl; } public Account(Octokit.Account account, IObservable bitmapSource) @@ -77,13 +80,15 @@ public Account(Octokit.Account account, IObservable bitmapSource) public long PrivateReposInPlan { get; private set; } + public string AvatarUrl { get; private set; } + public BitmapSource Avatar { get { return avatar; } set { avatar = value; this.RaisePropertyChanged(); } } -#region Equality things + #region Equality things public void CopyFrom(IAccount other) { if (!Equals(other)) @@ -115,7 +120,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return (Login?.GetHashCode() ?? 0) ^ IsUser .GetHashCode() ^ IsEnterprise.GetHashCode(); + return (Login?.GetHashCode() ?? 0) ^ IsUser.GetHashCode() ^ IsEnterprise.GetHashCode(); } bool IEquatable.Equals(IAccount other) diff --git a/src/GitHub.App/Models/IssueCommentModel.cs b/src/GitHub.App/Models/IssueCommentModel.cs index f4e4ddfe5e..5031aa2bd4 100644 --- a/src/GitHub.App/Models/IssueCommentModel.cs +++ b/src/GitHub.App/Models/IssueCommentModel.cs @@ -4,8 +4,9 @@ namespace GitHub.Models { public class IssueCommentModel : ICommentModel { - public string Body { get; set; } 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/PullRequestModel.cs b/src/GitHub.App/Models/PullRequestModel.cs index 6ae87f3012..8e9d6439d7 100644 --- a/src/GitHub.App/Models/PullRequestModel.cs +++ b/src/GitHub.App/Models/PullRequestModel.cs @@ -162,11 +162,23 @@ public string Body public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; } public IAccount Author { get; set; } - public IReadOnlyCollection ChangedFiles { get; set; } = new IPullRequestFileModel[0]; - public IReadOnlyCollection Comments { get; set; } = new ICommentModel[0]; + public IReadOnlyList ChangedFiles { get; set; } = new IPullRequestFileModel[0]; + public IReadOnlyList Comments { get; set; } = new ICommentModel[0]; - IReadOnlyCollection reviewComments = new IPullRequestReviewCommentModel[0]; - public IReadOnlyCollection ReviewComments + 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 diff --git a/src/GitHub.App/Models/PullRequestReviewCommentModel.cs b/src/GitHub.App/Models/PullRequestReviewCommentModel.cs index 1a073f84c0..7ae45548e6 100644 --- a/src/GitHub.App/Models/PullRequestReviewCommentModel.cs +++ b/src/GitHub.App/Models/PullRequestReviewCommentModel.cs @@ -5,6 +5,8 @@ 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; } @@ -14,5 +16,6 @@ public class PullRequestReviewCommentModel : IPullRequestReviewCommentModel 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 new file mode 100644 index 0000000000..ff7ad40d13 --- /dev/null +++ b/src/GitHub.App/Models/PullRequestReviewModel.cs @@ -0,0 +1,14 @@ +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; } + } +} diff --git a/src/GitHub.App/SampleData/AccountDesigner.cs b/src/GitHub.App/SampleData/AccountDesigner.cs index 8d555e2cdb..68770f8336 100644 --- a/src/GitHub.App/SampleData/AccountDesigner.cs +++ b/src/GitHub.App/SampleData/AccountDesigner.cs @@ -32,6 +32,7 @@ public BitmapSource Avatar public string Login { get; set; } public int OwnedPrivateRepos { get; set; } public long PrivateReposInPlan { get; set; } + public string AvatarUrl { get; set; } public override string ToString() { diff --git a/src/GitHub.App/Services/AvatarProvider.cs b/src/GitHub.App/Services/AvatarProvider.cs index 5ed2428434..1a99e815f6 100644 --- a/src/GitHub.App/Services/AvatarProvider.cs +++ b/src/GitHub.App/Services/AvatarProvider.cs @@ -70,6 +70,21 @@ public IObservable GetAvatar(IAvatarContainer apiAccount) .Catch(_ => Observable.Return(DefaultAvatar(apiAccount))); } + public IObservable GetAvatar(string url) + { + if (url == null) + { + return Observable.Return(DefaultUserBitmapImage); + } + + Uri avatarUrl; + Uri.TryCreate(url, UriKind.Absolute, out avatarUrl); + Log.Assert(avatarUrl != null, "Cannot have a null avatar url"); + + return imageCache.GetImage(avatarUrl) + .Catch(_ => Observable.Return(DefaultUserBitmapImage)); + } + public IObservable InvalidateAvatar(IAvatarContainer apiAccount) { return String.IsNullOrWhiteSpace(apiAccount?.Login) diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index e50eae69ec..030b22dc4b 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; using System.Threading.Tasks; using Akavache; using GitHub.Api; @@ -17,7 +18,9 @@ using GitHub.Models; using GitHub.Primitives; using Octokit; +using Octokit.GraphQL; using Serilog; +using static Octokit.GraphQL.Variable; namespace GitHub.Services { @@ -33,13 +36,16 @@ public class ModelService : IModelService readonly IBlobCache hostCache; readonly IAvatarProvider avatarProvider; + readonly Octokit.GraphQL.IConnection graphql; public ModelService( IApiClient apiClient, + Octokit.GraphQL.IConnection graphql, IBlobCache hostCache, IAvatarProvider avatarProvider) { this.ApiClient = apiClient; + this.graphql = graphql; this.hostCache = hostCache; this.avatarProvider = avatarProvider; } @@ -203,19 +209,22 @@ public IObservable GetPullRequest(string owner, string name, ApiClient.GetPullRequest(owner, name, number), ApiClient.GetPullRequestFiles(owner, name, number).ToList(), ApiClient.GetIssueComments(owner, name, number).ToList(), - ApiClient.GetPullRequestReviewComments(owner, name, number).ToList(), - (pr, files, comments, reviewComments) => new + GetPullRequestReviews(owner, name, number).ToObservable(), + GetPullRequestReviewComments(owner, name, number).ToObservable(), + (pr, files, comments, reviews, reviewComments) => new { PullRequest = pr, Files = files, Comments = comments, + Reviews = reviews, ReviewComments = reviewComments }) .Select(x => PullRequestCacheItem.Create( x.PullRequest, (IReadOnlyList)x.Files, (IReadOnlyList)x.Comments, - (IReadOnlyList)x.ReviewComments)), + (IReadOnlyList)x.Reviews, + (IReadOnlyList)x.ReviewComments)), TimeSpan.Zero, TimeSpan.FromDays(7)) .Select(Create); @@ -366,6 +375,154 @@ IObservable> GetOrganizationRepositories(s }); } +#pragma warning disable CS0618 // DatabaseId is marked obsolete by GraphQL but we need it + async Task> GetPullRequestReviews(string owner, string name, int number) + { + string cursor = null; + var result = new List(); + + while (true) + { + var query = new Query() + .Repository(owner, name) + .PullRequest(number) + .Reviews(first: 30, after: cursor) + .Select(x => new + { + x.PageInfo.HasNextPage, + x.PageInfo.EndCursor, + Items = x.Nodes.Select(y => new PullRequestReviewModel + { + Id = y.DatabaseId.Value, + NodeId = y.Id, + Body = y.Body, + CommitId = y.Commit.Oid, + State = FromGraphQL(y.State), + User = Create(y.Author.Login, y.Author.AvatarUrl(null)) + }).ToList() + }); + + var page = await graphql.Run(query); + result.AddRange(page.Items); + + if (page.HasNextPage) + cursor = page.EndCursor; + else + return result; + } + } + + async Task> GetPullRequestReviewComments(string owner, string name, int number) + { + var result = new List(); + var query = new Query() + .Repository(owner, name) + .PullRequest(number) + .Reviews(first: 100, after: Var("cursor")) + .Select(x => new + { + x.PageInfo.HasNextPage, + x.PageInfo.EndCursor, + Reviews = x.Nodes.Select(y => new + { + y.Id, + CommentPage = y.Comments(100, null, null, null).Select(z => new + { + z.PageInfo.HasNextPage, + z.PageInfo.EndCursor, + Items = z.Nodes.Select(a => new PullRequestReviewCommentModel + { + Id = a.DatabaseId.Value, + NodeId = a.Id, + Body = a.Body, + CommitId = a.Commit.Oid, + CreatedAt = a.CreatedAt.Value, + DiffHunk = a.DiffHunk, + OriginalCommitId = a.OriginalCommit.Oid, + OriginalPosition = a.OriginalPosition, + Path = a.Path, + Position = a.Position, + PullRequestReviewId = y.DatabaseId.Value, + User = Create(a.Author.Login, a.Author.AvatarUrl(null)), + IsPending = y.State == Octokit.GraphQL.Model.PullRequestReviewState.Pending, + }).ToList(), + }).Single() + }).ToList() + }).Compile(); + + var vars = new Dictionary + { + { "cursor", null } + }; + + while (true) + { + var reviewPage = await graphql.Run(query, vars); + + foreach (var review in reviewPage.Reviews) + { + result.AddRange(review.CommentPage.Items); + + if (review.CommentPage.HasNextPage) + { + result.AddRange(await GetPullRequestReviewComments(review.Id, review.CommentPage.EndCursor)); + } + } + + if (reviewPage.HasNextPage) + vars["cursor"] = reviewPage.EndCursor; + else + return result; + } + } + + private async Task> GetPullRequestReviewComments(string reviewId, string commentCursor) + { + var result = new List(); + var query = new Query() + .Node(reviewId) + .Cast() + .Select(x => new + { + CommentPage = x.Comments(100, Var("Cursor"), null, null).Select(z => new + { + z.PageInfo.HasNextPage, + z.PageInfo.EndCursor, + Items = z.Nodes.Select(a => new PullRequestReviewCommentModel + { + Id = a.DatabaseId.Value, + NodeId = a.Id, + Body = a.Body, + CommitId = a.Commit.Oid, + CreatedAt = a.CreatedAt.Value, + DiffHunk = a.DiffHunk, + OriginalCommitId = a.OriginalCommit.Oid, + OriginalPosition = a.OriginalPosition, + Path = a.Path, + Position = a.Position, + PullRequestReviewId = x.DatabaseId.Value, + User = Create(a.Author.Login, a.Author.AvatarUrl(null)), + }).ToList(), + }).Single() + }).Compile(); + var vars = new Dictionary + { + { "cursor", null } + }; + + while (true) + { + var page = await graphql.Run(query, vars); + result.AddRange(page.CommentPage.Items); + + if (page.CommentPage.HasNextPage) + vars["cursor"] = page.CommentPage.EndCursor; + else + return result; + } + } +#pragma warning restore CS0618 // Type or member is obsolete + public IObservable GetBranches(IRepositoryModel repo) { var keyobs = GetUserFromCache() @@ -394,9 +551,22 @@ IAccount Create(AccountCacheItem accountCacheItem) accountCacheItem.IsEnterprise, accountCacheItem.OwnedPrivateRepositoriesCount, accountCacheItem.PrivateRepositoriesInPlanCount, + accountCacheItem.AvatarUrl, avatarProvider.GetAvatar(accountCacheItem)); } + IAccount Create(string login, string avatarUrl) + { + return new Models.Account( + login, + true, + false, + 0, + 0, + avatarUrl, + avatarProvider.GetAvatar(avatarUrl)); + } + IRemoteRepositoryModel Create(RepositoryCacheItem item) { return new RemoteRepositoryModel( @@ -440,10 +610,22 @@ IPullRequestModel Create(PullRequestCacheItem prCacheItem) User = Create(x.User), CreatedAt = x.CreatedAt ?? DateTimeOffset.MinValue, }).ToList(), + Reviews = prCacheItem.Reviews.Select(x => + (IPullRequestReviewModel)new PullRequestReviewModel + { + Id = x.Id, + NodeId = x.NodeId, + User = Create(x.User), + Body = x.Body, + State = x.State, + CommitId = x.CommitId, + }).ToList(), ReviewComments = prCacheItem.ReviewComments.Select(x => (IPullRequestReviewCommentModel)new PullRequestReviewCommentModel { Id = x.Id, + NodeId = x.NodeId, + PullRequestReviewId = x.PullRequestReviewId, Path = x.Path, Position = x.Position, OriginalPosition = x.OriginalPosition, @@ -453,6 +635,7 @@ IPullRequestModel Create(PullRequestCacheItem prCacheItem) User = Create(x.User), Body = x.Body, CreatedAt = x.CreatedAt, + IsPending = x.IsPending, }).ToList(), CommentCount = prCacheItem.CommentCount, CommitCount = prCacheItem.CommitCount, @@ -478,6 +661,11 @@ public void Dispose() GC.SuppressFinalize(this); } + static GitHub.Models.PullRequestReviewState FromGraphQL(Octokit.GraphQL.Model.PullRequestReviewState s) + { + return (GitHub.Models.PullRequestReviewState)s; + } + public class GitIgnoreCacheItem : CacheItem { public static GitIgnoreCacheItem Create(string ignore) @@ -539,22 +727,28 @@ public class PullRequestCacheItem : CacheItem { public static PullRequestCacheItem Create(PullRequest pr) { - return new PullRequestCacheItem(pr, new PullRequestFile[0], new IssueComment[0], new PullRequestReviewComment[0]); + return new PullRequestCacheItem( + pr, + new PullRequestFile[0], + new IssueComment[0], + new IPullRequestReviewModel[0], + new IPullRequestReviewCommentModel[0]); } public static PullRequestCacheItem Create( PullRequest pr, IReadOnlyList files, IReadOnlyList comments, - IReadOnlyList reviewComments) + IReadOnlyList reviews, + IReadOnlyList reviewComments) { - return new PullRequestCacheItem(pr, files, comments, reviewComments); + return new PullRequestCacheItem(pr, files, comments, reviews, reviewComments); } public PullRequestCacheItem() {} public PullRequestCacheItem(PullRequest pr) - : this(pr, new PullRequestFile[0], new IssueComment[0], new PullRequestReviewComment[0]) + : this(pr, new PullRequestFile[0], new IssueComment[0], new IPullRequestReviewModel[0], new IPullRequestReviewCommentModel[0]) { } @@ -562,7 +756,8 @@ public PullRequestCacheItem( PullRequest pr, IReadOnlyList files, IReadOnlyList comments, - IReadOnlyList reviewComments) + IReadOnlyList reviews, + IReadOnlyList reviewComments) { Title = pr.Title; Number = pr.Number; @@ -580,7 +775,7 @@ public PullRequestCacheItem( Sha = pr.Head.Sha, RepositoryCloneUrl = pr.Head.Repository?.CloneUrl }; - CommentCount = pr.Comments + pr.ReviewComments; + CommentCount = pr.Comments; CommitCount = pr.Commits; Author = new AccountCacheItem(pr.User); Assignee = pr.Assignee != null ? new AccountCacheItem(pr.Assignee) : null; @@ -589,6 +784,7 @@ public PullRequestCacheItem( Body = pr.Body; ChangedFiles = files.Select(x => new PullRequestFileCacheItem(x)).ToList(); Comments = comments.Select(x => new IssueCommentCacheItem(x)).ToList(); + Reviews = reviews.Select(x => new PullRequestReviewCacheItem(x)).ToList(); ReviewComments = reviewComments.Select(x => new PullRequestReviewCommentCacheItem(x)).ToList(); State = GetState(pr); IsOpen = pr.State == ItemState.Open; @@ -610,6 +806,7 @@ public PullRequestCacheItem( public string Body { get; set; } public IList ChangedFiles { get; set; } = new PullRequestFileCacheItem[0]; public IList Comments { get; set; } = new IssueCommentCacheItem[0]; + public IList Reviews { get; set; } = new PullRequestReviewCacheItem[0]; public IList ReviewComments { get; set; } = new PullRequestReviewCommentCacheItem[0]; // Nullable for compatibility with old caches. @@ -674,27 +871,63 @@ public IssueCommentCacheItem(IssueComment comment) public DateTimeOffset? CreatedAt { get; set; } } + public class PullRequestReviewCacheItem + { + public PullRequestReviewCacheItem() + { + } + + public PullRequestReviewCacheItem(IPullRequestReviewModel review) + { + Id = review.Id; + NodeId = review.NodeId; + User = new AccountCacheItem + { + Login = review.User.Login, + AvatarUrl = review.User.AvatarUrl, + }; + Body = review.Body; + State = review.State; + } + + public long Id { get; set; } + public string NodeId { get; set; } + public AccountCacheItem User { get; set; } + public string Body { get; set; } + public GitHub.Models.PullRequestReviewState State { get; set; } + public string CommitId { get; set; } + } + public class PullRequestReviewCommentCacheItem { public PullRequestReviewCommentCacheItem() { } - public PullRequestReviewCommentCacheItem(PullRequestReviewComment comment) + public PullRequestReviewCommentCacheItem(IPullRequestReviewCommentModel comment) { Id = comment.Id; + NodeId = comment.NodeId; + PullRequestReviewId = comment.PullRequestReviewId; Path = comment.Path; Position = comment.Position; OriginalPosition = comment.OriginalPosition; CommitId = comment.CommitId; OriginalCommitId = comment.OriginalCommitId; DiffHunk = comment.DiffHunk; - User = new AccountCacheItem(comment.User); + User = new AccountCacheItem + { + Login = comment.User.Login, + AvatarUrl = comment.User.AvatarUrl, + }; Body = comment.Body; CreatedAt = comment.CreatedAt; + IsPending = comment.IsPending; } public int Id { get; } + public string NodeId { get; } + public int PullRequestReviewId { get; set; } public string Path { get; set; } public int? Position { get; set; } public int? OriginalPosition { get; set; } @@ -704,6 +937,7 @@ public PullRequestReviewCommentCacheItem(PullRequestReviewComment comment) public AccountCacheItem User { get; set; } public string Body { get; set; } public DateTimeOffset CreatedAt { get; set; } + public bool IsPending { get; set; } } public class GitReferenceCacheItem diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs index 88f79ffb9d..bed4cb897f 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs @@ -309,7 +309,7 @@ public IAccount SelectedAssignee set { this.RaiseAndSetIfChanged(ref selectedAssignee, value); } } - IAccount emptyUser = new Account("[None]", false, false, 0, 0, Observable.Empty()); + IAccount emptyUser = new Account("[None]", false, false, 0, 0, string.Empty, Observable.Empty()); public IAccount EmptyUser { get { return emptyUser; } diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index dfe0b889c6..50b25aa31e 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -9,7 +9,8 @@ - + + diff --git a/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs b/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs index ccf656280a..1006256d96 100644 --- a/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs +++ b/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs @@ -10,6 +10,7 @@ public interface IAvatarProvider : IDisposable BitmapImage DefaultUserBitmapImage { get; } BitmapImage DefaultOrgBitmapImage { get; } IObservable GetAvatar(IAvatarContainer account); + IObservable GetAvatar(string avatarUri); IObservable InvalidateAvatar(IAvatarContainer account); } } diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 43bc622b3b..2ac5424a96 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -167,6 +167,7 @@ + diff --git a/src/GitHub.Exports/Models/IAccount.cs b/src/GitHub.Exports/Models/IAccount.cs index 313d677944..617d770b91 100644 --- a/src/GitHub.Exports/Models/IAccount.cs +++ b/src/GitHub.Exports/Models/IAccount.cs @@ -14,6 +14,7 @@ public interface IAccount : ICopyable, string Login { get; } int OwnedPrivateRepos { get; } long PrivateReposInPlan { get; } - BitmapSource Avatar { get; } + string AvatarUrl { get; } + BitmapSource Avatar { get; } } } diff --git a/src/GitHub.Exports/Models/ICommentModel.cs b/src/GitHub.Exports/Models/ICommentModel.cs index 82fc036c30..121c7862fd 100644 --- a/src/GitHub.Exports/Models/ICommentModel.cs +++ b/src/GitHub.Exports/Models/ICommentModel.cs @@ -12,6 +12,11 @@ public interface ICommentModel /// int Id { get; } + /// + /// Gets the GraphQL ID of the comment. + /// + string NodeId { get; } + /// /// Gets the author of the comment. /// diff --git a/src/GitHub.Exports/Models/IPullRequestModel.cs b/src/GitHub.Exports/Models/IPullRequestModel.cs index f185c2ef44..40996c78dc 100644 --- a/src/GitHub.Exports/Models/IPullRequestModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestModel.cs @@ -31,8 +31,9 @@ public interface IPullRequestModel : ICopyable, DateTimeOffset UpdatedAt { get; } IAccount Author { get; } IAccount Assignee { get; } - IReadOnlyCollection ChangedFiles { get; } - IReadOnlyCollection Comments { get; } - IReadOnlyCollection ReviewComments { get; set; } + IReadOnlyList ChangedFiles { get; } + IReadOnlyList Comments { get; } + IReadOnlyList Reviews { get; set; } + IReadOnlyList ReviewComments { get; set; } } } diff --git a/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs b/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs index b695b116d9..f6d376bb13 100644 --- a/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs @@ -7,6 +7,11 @@ namespace GitHub.Models /// public interface IPullRequestReviewCommentModel : ICommentModel { + /// + /// Gets the ID of the related pull request review. + /// + int PullRequestReviewId { get; set; } + /// /// The relative path to the file that the comment was made on. /// @@ -38,5 +43,10 @@ public interface IPullRequestReviewCommentModel : ICommentModel /// The diff hunk used to match the pull request. /// string DiffHunk { get; } + + /// + /// Gets a value indicating whether the comment is part of a pending review. + /// + bool IsPending { get; } } } diff --git a/src/GitHub.Exports/Models/IPullRequestReviewModel.cs b/src/GitHub.Exports/Models/IPullRequestReviewModel.cs new file mode 100644 index 0000000000..d59d401e1e --- /dev/null +++ b/src/GitHub.Exports/Models/IPullRequestReviewModel.cs @@ -0,0 +1,71 @@ +using System; + +namespace GitHub.Models +{ + /// + /// The possible states of a pull request review. + /// + public enum PullRequestReviewState + { + /// + /// A review that has not yet been submitted. + /// + Pending, + + /// + /// An informational review. + /// + Commented, + + /// + /// A review allowing the pull request to merge. + /// + Approved, + + /// + /// A review blocking the pull request from merging. + /// + ChangesRequested, + + /// + /// A review that has been dismissed. + /// + Dismissed, + } + + /// + /// Represents a review of a pull request. + /// + public interface IPullRequestReviewModel + { + /// + /// Gets the ID of the review. + /// + long Id { get; } + + /// + /// Gets the GraphQL ID for the review. + /// + string NodeId { get; set; } + + /// + /// Gets the author of the review. + /// + IAccount User { get; } + + /// + /// Gets the body of the review. + /// + string Body { get; } + + /// + /// Gets the state of the review. + /// + PullRequestReviewState State { get; } + + /// + /// Gets the SHA of the commit that the review was submitted on. + /// + string CommitId { get; } + } +} diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 8f0a917e97..2145782c05 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -235,8 +235,16 @@ ..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll True - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll True diff --git a/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs b/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs index 0b3fff38d4..c0a4cbbb10 100644 --- a/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs +++ b/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs @@ -20,6 +20,8 @@ OldVersionLowerBound = "0.0.0.0", OldVersionUpperBound = AssemblyVersionInformation.Version)] [assembly: ProvideCodeBase(AssemblyName = "Octokit", CodeBase = @"$PackageFolder$\Octokit.dll")] +[assembly: ProvideCodeBase(AssemblyName = "Octokit.GraphQL", CodeBase = @"$PackageFolder$\Octokit.GraphQL.dll")] +[assembly: ProvideCodeBase(AssemblyName = "Octokit.GraphQL.Core", CodeBase = @"$PackageFolder$\Octokit.GraphQL.Core.dll")] [assembly: ProvideCodeBase(AssemblyName = "LibGit2Sharp", CodeBase = @"$PackageFolder$\LibGit2Sharp.dll")] [assembly: ProvideCodeBase(AssemblyName = "Splat", CodeBase = @"$PackageFolder$\Splat.dll")] [assembly: ProvideCodeBase(AssemblyName = "Rothko", CodeBase = @"$PackageFolder$\Rothko.dll")] @@ -28,6 +30,7 @@ [assembly: ProvideCodeBase(AssemblyName = "Serilog.Sinks.File", CodeBase = @"$PackageFolder$\Serilog.Sinks.File.dll")] [assembly: ProvideCodeBase(AssemblyName = "Markdig", CodeBase = @"$PackageFolder$\Markdig.dll")] [assembly: ProvideCodeBase(AssemblyName = "Markdig.Wpf", CodeBase = @"$PackageFolder$\Markdig.Wpf.dll")] +[assembly: ProvideCodeBase(AssemblyName = "Newtonsoft.Json", CodeBase = @"$PackageFolder$\Newtonsoft.Json.dll")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.Views")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.Views.Dialog")] diff --git a/src/GitHub.VisualStudio/packages.config b/src/GitHub.VisualStudio/packages.config index df355004a5..c968000021 100644 --- a/src/GitHub.VisualStudio/packages.config +++ b/src/GitHub.VisualStudio/packages.config @@ -34,7 +34,8 @@ - + + diff --git a/test/UnitTests/GitHub.App/Factories/ModelServiceFactoryTests.cs b/test/UnitTests/GitHub.App/Factories/ModelServiceFactoryTests.cs index a42a65c864..a47f5aa797 100644 --- a/test/UnitTests/GitHub.App/Factories/ModelServiceFactoryTests.cs +++ b/test/UnitTests/GitHub.App/Factories/ModelServiceFactoryTests.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using GitHub.Api; using GitHub.Caches; using GitHub.Extensions; using GitHub.Factories; @@ -63,12 +64,14 @@ static ModelServiceFactory CreateTarget( IHostCacheFactory hostCacheFactory = null) { var apiClientFactory = Substitute.For(); + var graphQLClientFactory = Substitute.For(); var avatarProvider = Substitute.For(); hostCacheFactory = hostCacheFactory ?? Substitute.For(); return new ModelServiceFactory( apiClientFactory, + graphQLClientFactory, hostCacheFactory, avatarProvider); } diff --git a/test/UnitTests/GitHub.App/Models/AccountModelTests.cs b/test/UnitTests/GitHub.App/Models/AccountModelTests.cs index 7f1f931843..6b31baa3bb 100644 --- a/test/UnitTests/GitHub.App/Models/AccountModelTests.cs +++ b/test/UnitTests/GitHub.App/Models/AccountModelTests.cs @@ -30,7 +30,7 @@ public void CopyFromDoesNotLoseAvatar() const string login = "foo"; const int initialOwnedPrivateRepositoryCount = 1; - var initialAccount = new Account(login, true, false, initialOwnedPrivateRepositoryCount, 0, initialBitmapImageSubject); + var initialAccount = new Account(login, true, false, initialOwnedPrivateRepositoryCount, 0, null, initialBitmapImageSubject); //Creating the test collection var col = new TrackingCollection(Observable.Empty(), OrderedComparer.OrderByDescending(x => x.Login).Compare); @@ -78,7 +78,7 @@ public void CopyFromDoesNotLoseAvatar() //Creating an account update const int updatedOwnedPrivateRepositoryCount = 2; var updatedBitmapImageSubject = new Subject(); - var updatedAccount = new Account(login, true, false, updatedOwnedPrivateRepositoryCount, 0, updatedBitmapImageSubject); + var updatedAccount = new Account(login, true, false, updatedOwnedPrivateRepositoryCount, 0, null, updatedBitmapImageSubject); //Updating the account in the collection col.AddItem(updatedAccount); @@ -119,13 +119,26 @@ public static bool BitmapSourcesAreEqual(BitmapSource image1, BitmapSource image public static byte[] BitmapSourceToBytes(BitmapSource image) { - var encoder = new BmpBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(image)); - using (MemoryStream ms = new MemoryStream()) + byte[] data = new byte[] { }; + if (image != null) { - encoder.Save(ms); - return ms.ToArray(); + try + { + var encoder = new BmpBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(image)); + using (MemoryStream ms = new MemoryStream()) + { + encoder.Save(ms); + data = ms.ToArray(); + } + return data; + } + catch (Exception ex) + { + } } + + return data; } } } diff --git a/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs b/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs index ed938565c3..b0192b04b8 100644 --- a/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs +++ b/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs @@ -29,10 +29,9 @@ public class TheGetCurrentUserMethod : TestBaseClass [Test] public async Task RetrievesCurrentUser() { - var apiClient = Substitute.For(); var cache = new InMemoryBlobCache(); await cache.InsertObject("user", new AccountCacheItem(CreateOctokitUser("octocat"))); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(hostCache: cache); var user = await modelService.GetCurrentUser(); @@ -45,9 +44,8 @@ public class TheInsertUserMethod : TestBaseClass [Test] public async Task AddsUserToCache() { - var apiClient = Substitute.For(); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(hostCache: cache); var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); @@ -65,7 +63,7 @@ public async Task CanRetrieveAndCacheGitIgnores() var apiClient = Substitute.For(); apiClient.GetGitIgnoreTemplates().Returns(data.ToObservable()); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var fetched = await modelService.GetGitIgnoreTemplates().ToList(); @@ -97,7 +95,7 @@ public async Task CanRetrieveAndCacheLicenses() var apiClient = Substitute.For(); apiClient.GetLicenses().Returns(data.ToObservable()); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var fetched = await modelService.GetLicenses().ToList(); @@ -120,8 +118,7 @@ public async Task ReturnsEmptyIfLicenseApiNotFound() var apiClient = Substitute.For(); apiClient.GetLicenses() .Returns(Observable.Throw(new NotFoundException("Not Found", HttpStatusCode.NotFound))); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient); var fetched = await modelService.GetLicenses().ToList(); @@ -135,7 +132,7 @@ public async Task ReturnsEmptyIfCacheReadFails() var cache = Substitute.For(); cache.Get(Args.String) .Returns(Observable.Throw(new InvalidOperationException("Unknown"))); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var fetched = await modelService.GetLicenses().ToList(); @@ -157,7 +154,7 @@ public async Task CanRetrieveAndCacheUserAndAccounts() apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("snoopy"))); apiClient.GetOrganizations().Returns(orgs.ToObservable()); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); await modelService.InsertUser(new AccountCacheItem { Login = "snoopy" }); var fetched = await modelService.GetAccounts(); @@ -185,7 +182,7 @@ public async Task CanRetrieveUserFromCacheAndAccountsFromApi() var apiClient = Substitute.For(); apiClient.GetOrganizations().Returns(orgs.ToObservable()); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); var fetched = await modelService.GetAccounts(); @@ -214,8 +211,7 @@ public async Task OnlyRetrievesOneUserEvenIfCacheOrApiReturnsMoreThanOne() var apiClient = Substitute.For(); apiClient.GetUser().Returns(users.ToObservable()); apiClient.GetOrganizations().Returns(Observable.Empty()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient); var fetched = await modelService.GetAccounts(); @@ -263,7 +259,7 @@ public async Task CanRetrieveAndCacheRepositoriesForUserAndOrganizations() apiClient.GetRepositoriesForOrganization("github").Returns(githubRepos.ToObservable()); apiClient.GetRepositoriesForOrganization("octokit").Returns(octokitRepos.ToObservable()); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); await modelService.InsertUser(new AccountCacheItem { Login = "opus" }); var fetched = await modelService.GetRepositories().ToList(); @@ -318,7 +314,7 @@ public async Task CanRetrieveAndCacheRepositoriesForUserAndOrganizations() public async Task WhenNotLoggedInReturnsEmptyCollection() { var apiClient = Substitute.For(); - var modelService = new ModelService(apiClient, new InMemoryBlobCache(), Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient); var repos = await modelService.GetRepositories(); @@ -329,7 +325,7 @@ public async Task WhenNotLoggedInReturnsEmptyCollection() public async Task WhenLoggedInDoesNotBlowUpOnUnexpectedNetworkProblems() { var apiClient = Substitute.For(); - var modelService = new ModelService(apiClient, new InMemoryBlobCache(), Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient); apiClient.GetOrganizations() .Returns(Observable.Throw(new NotFoundException("Not Found", HttpStatusCode.NotFound))); @@ -346,7 +342,7 @@ public async Task InvalidatesTheCache() { var apiClient = Substitute.For(); var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); //Assert.Single((await cache.GetAllObjects())); @@ -367,7 +363,7 @@ public async Task VaccumsTheCache() received = true; return Observable.Return(Unit.Default); }); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); await modelService.InvalidateAll(); Assert.True(received); @@ -387,7 +383,7 @@ public async Task NonExpiredIndexReturnsCache() var cache = new InMemoryBlobCache(); var apiClient = Substitute.For(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var user = CreateOctokitUser(username); apiClient.GetUser().Returns(Observable.Return(user)); apiClient.GetOrganizations().Returns(Observable.Empty()); @@ -438,7 +434,7 @@ public async Task ExpiredIndexReturnsLive() var cache = new InMemoryBlobCache(); var apiClient = Substitute.For(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var user = CreateOctokitUser(username); apiClient.GetUser().Returns(Observable.Return(user)); apiClient.GetOrganizations().Returns(Observable.Empty()); @@ -506,7 +502,7 @@ public async Task ExpiredIndexClearsItems() var cache = new InMemoryBlobCache(); var apiClient = Substitute.For(); - var modelService = new ModelService(apiClient, cache, Substitute.For()); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); var user = CreateOctokitUser(username); apiClient.GetUser().Returns(Observable.Return(user)); apiClient.GetOrganizations().Returns(Observable.Empty()); @@ -574,4 +570,17 @@ public async Task ExpiredIndexClearsItems() );*/ } } + + static ModelService CreateTarget( + IApiClient apiClient = null, + Octokit.GraphQL.IConnection graphql = null, + IBlobCache hostCache = null, + IAvatarProvider avatarProvider = null) + { + return new ModelService( + apiClient ?? Substitute.For(), + graphql ?? Substitute.For(), + hostCache ?? new InMemoryBlobCache(), + Substitute.For()); + } } diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModelTests.cs index 4d169be19a..3bdf322d62 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModelTests.cs @@ -131,9 +131,9 @@ IModelServiceFactory CreateModelServiceFactory() var pullRequest = new PullRequestModel( 1, "PR1", - new Account("foo", true, false, 1, 0, bitmapSource), + new Account("foo", true, false, 1, 0, null, bitmapSource), DateTimeOffset.MinValue); - pullRequest.Assignee = new Account("foo", true, false, 1, 0, bitmapSource); + pullRequest.Assignee = new Account("foo", true, false, 1, 0, null, bitmapSource); var pullRequestCollection = Substitute.For>(); pullRequestCollection[0].Returns(pullRequest); diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 6426dc4779..8a56045328 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -162,6 +162,10 @@ ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll True + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + ..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll True @@ -169,6 +173,14 @@ ..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + True + + + ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + True + diff --git a/test/UnitTests/packages.config b/test/UnitTests/packages.config index a80e0e8c78..145225d9f1 100644 --- a/test/UnitTests/packages.config +++ b/test/UnitTests/packages.config @@ -25,10 +25,12 @@ + + From cd366cf3af1b26924c4e046f9152c5b6907682b0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 13:35:25 +0100 Subject: [PATCH 09/78] Need to force-add the .nupkg. As it's ignored by default. --- lib/Octokit.GraphQL.0.0.1.nupkg | Bin 0 -> 164744 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/Octokit.GraphQL.0.0.1.nupkg diff --git a/lib/Octokit.GraphQL.0.0.1.nupkg b/lib/Octokit.GraphQL.0.0.1.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..589a5e0a76ce818b02d533a75ce841281111a03e GIT binary patch literal 164744 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 ACjbBd literal 0 HcmV?d00001 From d246220e327bf53d41548ef34d8671e28f48397d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 14:07:17 +0100 Subject: [PATCH 10/78] Fix PR review comments cursor bug. The cursor was not being used for the first page, or ever in fact because the capitalization was wrong. --- src/GitHub.App/Services/ModelService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index 030b22dc4b..18eb5f8193 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -484,7 +484,7 @@ private async Task> GetPullRequestRe .Cast() .Select(x => new { - CommentPage = x.Comments(100, Var("Cursor"), null, null).Select(z => new + CommentPage = x.Comments(100, Var("cursor"), null, null).Select(z => new { z.PageInfo.HasNextPage, z.PageInfo.EndCursor, @@ -507,7 +507,7 @@ private async Task> GetPullRequestRe }).Compile(); var vars = new Dictionary { - { "cursor", null } + { "cursor", commentCursor } }; while (true) From 0a2026225acaa8df7a438fd9ca62688c11f390d1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 14:32:47 +0100 Subject: [PATCH 11/78] Add binding redirect for Newtonsoft.Json... ...in UnitTests. Akavache uses Newtonsoft.Json 6.0.8 while we use 10.0.3. The earlier version needs to be redirected to the later version for tests to work. --- test/UnitTests/UnitTests.csproj | 3 +++ test/UnitTests/UnitTests.dll.config | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 test/UnitTests/UnitTests.dll.config diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 8a56045328..1163d5db35 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -384,6 +384,9 @@ Designer + + PreserveNewest + diff --git a/test/UnitTests/UnitTests.dll.config b/test/UnitTests/UnitTests.dll.config new file mode 100644 index 0000000000..5fbc6df21b --- /dev/null +++ b/test/UnitTests/UnitTests.dll.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file From fc38e0b2d60e4b7e97d4553bc13513cbe9cfb684 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 10:13:31 +0100 Subject: [PATCH 12/78] Unindent #region. --- src/GitHub.App/Models/Account.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.App/Models/Account.cs b/src/GitHub.App/Models/Account.cs index 050871f6fa..b2b07462aa 100644 --- a/src/GitHub.App/Models/Account.cs +++ b/src/GitHub.App/Models/Account.cs @@ -88,7 +88,7 @@ public BitmapSource Avatar set { avatar = value; this.RaisePropertyChanged(); } } - #region Equality things +#region Equality things public void CopyFrom(IAccount other) { if (!Equals(other)) From 1fcd815ff90fa9241868512fc518848ca6285ae8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 10:18:12 +0100 Subject: [PATCH 13/78] Try to explain GraphQL paging. --- src/GitHub.App/Services/ModelService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index 18eb5f8193..15f1321021 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -415,6 +415,8 @@ async Task> GetPullRequestReviews(string owner, s async Task> GetPullRequestReviewComments(string owner, string name, int number) { var result = new List(); + + // Reads a single page of reviews and for each review the first page of review comments. var query = new Query() .Repository(owner, name) .PullRequest(number) @@ -455,6 +457,7 @@ async Task> GetPullRequestReviewComments(s { "cursor", null } }; + // Read all pages of reviews. while (true) { var reviewPage = await graphql.Run(query, vars); @@ -463,6 +466,7 @@ async Task> GetPullRequestReviewComments(s { result.AddRange(review.CommentPage.Items); + // The the review has >1 page of review comments, read the remaining pages. if (review.CommentPage.HasNextPage) { result.AddRange(await GetPullRequestReviewComments(review.Id, review.CommentPage.EndCursor)); From cb0d491f6b695bbab3aa98b9861654171a082fb9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 13:21:44 +0100 Subject: [PATCH 14/78] Use GraphQL to read PR reviews/comments. This is integrated a bit hackily into `ModelService`; `ModelService` doesn't really mesh well with GraphQL but without a lot of refactoring this was the best way to get things up and running. --- src/GitHub.App/Models/Account.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.App/Models/Account.cs b/src/GitHub.App/Models/Account.cs index b2b07462aa..050871f6fa 100644 --- a/src/GitHub.App/Models/Account.cs +++ b/src/GitHub.App/Models/Account.cs @@ -88,7 +88,7 @@ public BitmapSource Avatar set { avatar = value; this.RaisePropertyChanged(); } } -#region Equality things + #region Equality things public void CopyFrom(IAccount other) { if (!Equals(other)) From 9353f41978af090d1714778cf7fb2bdc940dc83d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Mar 2018 15:55:30 +0100 Subject: [PATCH 15/78] Update octokit.net to 0.29.0 --- src/GitHub.App/Api/ApiClient.cs | 6 +----- submodules/octokit.net | 2 +- test/UnitTests/GitHub.Api/SimpleApiClientTests.cs | 2 +- .../GitHub.App/Models/ModelServiceTests.cs | 4 ++-- test/UnitTests/Helpers/TestBaseClass.cs | 14 +++++++------- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs index 8aa92a19a6..632db4c807 100644 --- a/src/GitHub.App/Api/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -269,11 +269,7 @@ public IObservable GetBranches(string owner, string repo) Guard.ArgumentNotEmptyString(owner, nameof(owner)); Guard.ArgumentNotEmptyString(repo, nameof(repo)); -#pragma warning disable 618 - // GetAllBranches is obsolete, but don't want to introduce the change to fix the - // warning in the PR, so disabling for now. - return gitHubClient.Repository.GetAllBranches(owner, repo); -#pragma warning restore + return gitHubClient.Repository.Branch.GetAll(owner, repo); } public IObservable GetRepository(string owner, string repo) diff --git a/submodules/octokit.net b/submodules/octokit.net index 8420cb8fcd..d807d06526 160000 --- a/submodules/octokit.net +++ b/submodules/octokit.net @@ -1 +1 @@ -Subproject commit 8420cb8fcdfecf67412cbdabca61f793c666f51f +Subproject commit d807d065263f140575d4e5e755c40f459a72c262 diff --git a/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs b/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs index 980da768df..34679192a8 100644 --- a/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs +++ b/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs @@ -237,7 +237,7 @@ private static Repository CreateRepository(int id, bool hasWiki) { return new Repository("", "", "", "", "", "", "", id, new User(), "", "", "", "", "", false, false, 0, 0, "", - 0, null, DateTimeOffset.Now, DateTimeOffset.Now, new RepositoryPermissions(), null, null, false, + 0, null, DateTimeOffset.Now, DateTimeOffset.Now, new RepositoryPermissions(), null, null, null, false, hasWiki, false, false, 0, 0, null, null, null); } } diff --git a/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs b/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs index b0192b04b8..844c2f3c95 100644 --- a/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs +++ b/test/UnitTests/GitHub.App/Models/ModelServiceTests.cs @@ -88,8 +88,8 @@ public async Task CanRetrieveAndCacheLicenses() { var data = new[] { - new LicenseMetadata("mit", "MIT", new Uri("https://github.com/")), - new LicenseMetadata("apache", "Apache", new Uri("https://github.com/")) + new LicenseMetadata("mit", "MIT", "foo", "https://github.com/", false), + new LicenseMetadata("apache", "Apache", "foo", "https://github.com/", false) }; var apiClient = Substitute.For(); diff --git a/test/UnitTests/Helpers/TestBaseClass.cs b/test/UnitTests/Helpers/TestBaseClass.cs index 8de707592b..406bfed43c 100644 --- a/test/UnitTests/Helpers/TestBaseClass.cs +++ b/test/UnitTests/Helpers/TestBaseClass.cs @@ -27,7 +27,7 @@ public virtual void OnExit() protected static User CreateOctokitUser(string login = "login", string url = "https://url") { return new User("https://url", "bio", "blog", 1, "GitHub", - DateTimeOffset.UtcNow, 0, "email", 100, 100, true, url, + DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0, "email", 100, 100, true, url, 10, 42, "location", login, "name", 1, new Plan(), 1, 1, 1, "https://url", new RepositoryPermissions(true, true, true), false, null, null); @@ -46,7 +46,7 @@ protected static Repository CreateRepository(string owner, string name, string d id, CreateOctokitUser(owner), name, "fullname", "description", notCloneUrl, "c#", false, parent != null, 0, 0, "master", 0, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, - new RepositoryPermissions(), parent, null, true, false, false, false, 0, 0, null, null, null); + new RepositoryPermissions(), parent, null, null, true, false, false, false, 0, 0, null, null, null); } protected static PullRequest CreatePullRequest(User user, int id, ItemState state, string title, @@ -58,18 +58,18 @@ protected static PullRequest CreatePullRequest(User user, int id, ItemState stat 1, user, "Repo", "Repo", string.Empty, string.Empty, string.Empty, false, false, 0, 0, "master", 0, null, createdAt, updatedAt, - null, null, null, + null, null, null, null, false, false, false, false, 0, 0, null, null, null); - return new PullRequest(0, uri, uri, uri, uri, uri, uri, + return new PullRequest(0, uris, uris, uris, uris, uris, uris, id, state, title, "", createdAt, updatedAt, null, null, new GitReference(uri.ToString(), "foo:bar", "bar", "123", user, repo), new GitReference(uri.ToString(), "foo:baz", "baz", "123", user, repo), - user, null, null, false, null, - commentCount, reviewCommentCount, 0, 0, 0, 0, - null, false); + user, null, null, false, null, null, null, + commentCount, 0, 0, 0, 0, + null, false, null); } protected class TempDirectory : IDisposable From 5af3529f646e54f5884f17f37f64d8ba5a23b5aa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 10:27:13 +0100 Subject: [PATCH 16/78] Unindent #region. --- src/GitHub.App/Models/Account.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.App/Models/Account.cs b/src/GitHub.App/Models/Account.cs index 050871f6fa..b2b07462aa 100644 --- a/src/GitHub.App/Models/Account.cs +++ b/src/GitHub.App/Models/Account.cs @@ -88,7 +88,7 @@ public BitmapSource Avatar set { avatar = value; this.RaisePropertyChanged(); } } - #region Equality things +#region Equality things public void CopyFrom(IAccount other) { if (!Equals(other)) From 68e3e4c5fd754225494ddd1d703510236a6b0079 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Feb 2018 15:05:57 +0100 Subject: [PATCH 17/78] Moved changed files tree into its own view. Ported from #1415. Moves the PR details changed files tree into its own view so that it can be shared by the PR reviews view. --- src/GitHub.App/GitHub.App.csproj | 57 ++- .../PullRequestDetailViewModelDesigner.cs | 8 +- .../PullRequestFilesViewModelDesigner.cs | 47 +++ .../Services/PullRequestEditorService.cs | 266 ++++++++++++- .../GitHubPane/PullRequestDetailViewModel.cs | 86 +---- .../GitHubPane/PullRequestDirectoryNode.cs | 10 +- .../GitHubPane/PullRequestFileNode.cs | 16 +- .../GitHubPane/PullRequestFilesViewModel.cs | 202 ++++++++++ src/GitHub.App/packages.config | 22 +- .../GitHub.Exports.Reactive.csproj | 2 + .../Services/IPullRequestEditorService.cs | 41 +- .../Services/PullRequestSessionExtensions.cs | 60 +++ .../GitHubPane/IPullRequestChangeNode.cs | 5 +- .../GitHubPane/IPullRequestDetailViewModel.cs | 4 +- .../GitHubPane/IPullRequestFilesViewModel.cs | 63 ++++ .../GitHub.VisualStudio.csproj | 7 + .../GitHubPane/PullRequestDetailView.xaml | 113 +----- .../GitHubPane/PullRequestDetailView.xaml.cs | 357 ------------------ .../GitHubPane/PullRequestFilesView.xaml | 138 +++++++ .../GitHubPane/PullRequestFilesView.xaml.cs | 87 +++++ .../PullRequestDetailViewModelTests.cs | 99 +---- .../PullRequestFilesViewModelTests.cs | 128 +++++++ .../Services/PullRequestEditorServiceTests.cs | 12 +- test/UnitTests/UnitTests.csproj | 5 + test/UnitTests/packages.config | 1 + 25 files changed, 1156 insertions(+), 680 deletions(-) create mode 100644 src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs create mode 100644 src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs create mode 100644 test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index d9513e8df0..74442bc191 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -61,8 +61,16 @@ ..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll True + + ..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll + True + - ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll + ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6071\lib\Microsoft.VisualStudio.OLE.Interop.dll True @@ -74,15 +82,50 @@ True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6072\lib\net11\Microsoft.VisualStudio.Shell.Interop.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30320\lib\net20\Microsoft.VisualStudio.Shell.Interop.10.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61031\lib\net20\Microsoft.VisualStudio.Shell.Interop.11.0.dll + True + + + True + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30111\lib\net20\Microsoft.VisualStudio.Shell.Interop.12.0.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.Shell.Interop.8.0.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll True - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll + ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6071\lib\net11\Microsoft.VisualStudio.TextManager.Interop.dll True - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.TextManager.Interop.8.0.dll True @@ -101,6 +144,10 @@ ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll True + + ..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll + True + @@ -160,6 +207,7 @@ + @@ -180,6 +228,7 @@ + diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs index 64126dc3bd..1ac923b1c3 100644 --- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -31,8 +31,6 @@ public class PullRequestUpdateStateDesigner : IPullRequestUpdateState [ExcludeFromCodeCoverage] public class PullRequestDetailViewModelDesigner : PanePageViewModelBase, IPullRequestDetailViewModel { - private List changedFilesTree; - public PullRequestDetailViewModelDesigner() { var repoPath = @"C:\Repo"; @@ -69,8 +67,7 @@ public PullRequestDetailViewModelDesigner() modelsDir.Files.Add(oldBranchModel); gitHubDir.Directories.Add(modelsDir); - changedFilesTree = new List(); - changedFilesTree.Add(gitHubDir); + Files = new PullRequestFilesViewModelDesigner(); } public IPullRequestModel Model { get; } @@ -84,7 +81,7 @@ public PullRequestDetailViewModelDesigner() public bool IsCheckedOut { get; } public bool IsFromFork { get; } public string Body { get; } - public IReadOnlyList ChangedFilesTree => changedFilesTree; + public IPullRequestFilesViewModel Files { get; set; } public IPullRequestCheckoutState CheckoutState { get; set; } public IPullRequestUpdateState UpdateState { get; set; } public string OperationError { get; set; } @@ -94,7 +91,6 @@ public PullRequestDetailViewModelDesigner() public ReactiveCommand Checkout { get; } public ReactiveCommand Pull { get; } public ReactiveCommand Push { get; } - public ReactiveCommand SyncSubmodules { get; } public ReactiveCommand OpenOnGitHub { get; } public ReactiveCommand DiffFile { get; } public ReactiveCommand DiffFileWithWorkingDirectory { get; } diff --git a/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs new file mode 100644 index 0000000000..c37398067a --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestFilesViewModelDesigner : PanePageViewModelBase, IPullRequestFilesViewModel + { + public PullRequestFilesViewModelDesigner() + { + Items = new[] + { + new PullRequestDirectoryNode("src") + { + Files = + { + new PullRequestFileNode("x", "src/File1.cs", "x", PullRequestFileStatus.Added, null), + new PullRequestFileNode("x", "src/File2.cs", "x", PullRequestFileStatus.Modified, null), + new PullRequestFileNode("x", "src/File3.cs", "x", PullRequestFileStatus.Removed, null), + new PullRequestFileNode("x", "src/File4.cs", "x", PullRequestFileStatus.Renamed, "src/Old.cs"), + } + } + }; + ChangedFilesCount = 4; + } + + public int ChangedFilesCount { get; set; } + public IReadOnlyList Items { get; } + public ReactiveCommand DiffFile { get; } + public ReactiveCommand ViewFile { get; } + public ReactiveCommand DiffFileWithWorkingDirectory { get; } + public ReactiveCommand OpenFileInWorkingDirectory { get; } + public ReactiveCommand OpenFirstComment { get; } + + public Task InitializeAsync( + IPullRequestSession session, + Func commentFilter = null) + { + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index b8c758fa39..4e318239a6 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -1,25 +1,195 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.VisualStudio; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; -using GitHub.Models; +using Task = System.Threading.Tasks.Task; namespace GitHub.Services { + /// + /// Services for opening views of pull request files in Visual Studio. + /// [Export(typeof(IPullRequestEditorService))] + [PartCreationPolicy(CreationPolicy.Shared)] public class PullRequestEditorService : IPullRequestEditorService { - readonly IGitHubServiceProvider serviceProvider; - // If the target line doesn't have a unique match, search this number of lines above looking for a match. public const int MatchLinesAboveTarget = 4; + readonly IGitHubServiceProvider serviceProvider; + readonly IPullRequestService pullRequestService; + readonly IVsEditorAdaptersFactoryService vsEditorAdaptersFactory; + readonly IStatusBarNotificationService statusBar; + readonly IUsageTracker usageTracker; + [ImportingConstructor] - public PullRequestEditorService(IGitHubServiceProvider serviceProvider) + public PullRequestEditorService( + IGitHubServiceProvider serviceProvider, + IPullRequestService pullRequestService, + IVsEditorAdaptersFactoryService vsEditorAdaptersFactory, + IStatusBarNotificationService statusBar, + IUsageTracker usageTracker) { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(pullRequestService, nameof(pullRequestService)); + Guard.ArgumentNotNull(vsEditorAdaptersFactory, nameof(vsEditorAdaptersFactory)); + Guard.ArgumentNotNull(statusBar, nameof(statusBar)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + this.serviceProvider = serviceProvider; + this.pullRequestService = pullRequestService; + this.vsEditorAdaptersFactory = vsEditorAdaptersFactory; + this.statusBar = statusBar; + this.usageTracker = usageTracker; + } + + /// + public async Task OpenFile( + IPullRequestSession session, + string relativePath, + bool workingDirectory) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + + try + { + var file = await session.GetFile(relativePath); + var fullPath = GetAbsolutePath(session, file); + var fileName = workingDirectory ? fullPath : await ExtractFile(session, file, true); + + using (workingDirectory ? null : OpenInProvisionalTab()) + { + var window = VisualStudio.Services.Dte.ItemOperations.OpenFile(fileName); + window.Document.ReadOnly = !workingDirectory; + + var buffer = GetBufferAt(fileName); + + if (!workingDirectory) + { + AddBufferTag(buffer, session, fullPath, null); + } + } + + if (workingDirectory) + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); + else + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error opening file", e); + } + } + + /// + public async Task OpenDiff( + IPullRequestSession session, + string relativePath, + bool workingDirectory) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + + try + { + var file = await session.GetFile(relativePath); + var rightPath = file.RelativePath; + var leftPath = await GetBaseFileName(session, file); + var rightFile = workingDirectory ? GetAbsolutePath(session, file) : await ExtractFile(session, file, true); + var leftFile = await ExtractFile(session, file, false); + var leftLabel = $"{leftPath};{session.GetBaseBranchDisplay()}"; + var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {session.PullRequest.Number}"; + var caption = $"Diff - {Path.GetFileName(file.RelativePath)}"; + var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | + __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; + + if (!workingDirectory) + { + options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; + } + + IVsWindowFrame frame; + using (OpenInProvisionalTab()) + { + var tooltip = $"{leftLabel}\nvs.\n{rightLabel}"; + + // Diff window will open in provisional (right hand) tab until document is touched. + frame = VisualStudio.Services.DifferenceService.OpenComparisonWindow2( + leftFile, + rightFile, + caption, + tooltip, + leftLabel, + rightLabel, + string.Empty, + string.Empty, + (uint)options); + } + + object docView; + frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); + var diffViewer = ((IVsDifferenceCodeWindow)docView).DifferenceViewer; + + AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, DiffSide.Left); + + if (!workingDirectory) + { + AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, DiffSide.Right); + } + + if (workingDirectory) + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsCompareWithSolution); + else + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewChanges); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error opening file", e); + } + } + + /// + public async Task OpenDiff( + IPullRequestSession session, + string relativePath, + IInlineCommentThreadModel thread) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + Guard.ArgumentNotNull(thread, nameof(thread)); + + await OpenDiff(session, relativePath, false); + + // HACK: We need to wait here for the diff view to set itself up and move its cursor + // to the first changed line. There must be a better way of doing this. + await Task.Delay(1500); + + var param = (object)new InlineCommentNavigationParams + { + FromLine = thread.LineNumber - 1, + }; + + VisualStudio.Services.Dte.Commands.Raise( + Guids.CommandSetString, + PkgCmdIDList.NextInlineCommentId, + ref param, + null); } public IVsTextView NavigateToEquivalentPosition(IVsTextView sourceView, string targetFile) @@ -180,6 +350,92 @@ IVsTextView OpenDocument(string fullPath) return view; } + void ShowErrorInStatusBar(string message, Exception e) + { + statusBar.ShowMessage(message + ": " + e.Message); + } + + void AddBufferTag(ITextBuffer buffer, IPullRequestSession session, string path, DiffSide? side) + { + buffer.Properties.GetOrCreateSingletonProperty( + typeof(PullRequestTextBufferInfo), + () => new PullRequestTextBufferInfo(session, path, side)); + + var projection = buffer as IProjectionBuffer; + + if (projection != null) + { + foreach (var source in projection.SourceBuffers) + { + AddBufferTag(source, session, path, side); + } + } + } + + async Task ExtractFile(IPullRequestSession session, IPullRequestSessionFile file, bool head) + { + var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); + var relativePath = head ? file.RelativePath : await GetBaseFileName(session, file); + + return await pullRequestService.ExtractFile( + session.LocalRepository, + session.PullRequest, + relativePath, + head, + encoding).ToTask(); + } + + ITextBuffer GetBufferAt(string filePath) + { + IVsUIHierarchy uiHierarchy; + uint itemID; + IVsWindowFrame windowFrame; + + if (VsShellUtilities.IsDocumentOpen( + serviceProvider, + filePath, + Guid.Empty, + out uiHierarchy, + out itemID, + out windowFrame)) + { + IVsTextView view = VsShellUtilities.GetTextView(windowFrame); + IVsTextLines lines; + if (view.GetBuffer(out lines) == 0) + { + var buffer = lines as IVsTextBuffer; + if (buffer != null) + return vsEditorAdaptersFactory.GetDataBuffer(buffer); + } + } + + return null; + } + + async Task GetBaseFileName(IPullRequestSession session, IPullRequestSessionFile file) + { + using (var changes = await pullRequestService.GetTreeChanges( + session.LocalRepository, + session.PullRequest)) + { + var fileChange = changes.FirstOrDefault(x => x.Path == file.RelativePath); + return fileChange?.Status == LibGit2Sharp.ChangeKind.Renamed ? + fileChange.OldPath : file.RelativePath; + } + } + + static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFile file) + { + return Path.Combine(session.LocalRepository.LocalPath, file.RelativePath); + } + + static IDisposable OpenInProvisionalTab() + { + return new NewDocumentStateScope( + __VSNEWDOCUMENTSTATE.NDS_Provisional, + VSConstants.NewDocumentStateReason.SolutionExplorer); + } + static IList ReadLines(string text) { var lines = new List(); diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index ad4c66f0c0..e2f092d2a8 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -42,7 +42,6 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq string targetBranchDisplayName; int commentCount; string body; - IReadOnlyList changedFilesTree; IPullRequestCheckoutState checkoutState; IPullRequestUpdateState updateState; string operationError; @@ -69,7 +68,8 @@ public PullRequestDetailViewModel( IModelServiceFactory modelServiceFactory, IUsageTracker usageTracker, ITeamExplorerContext teamExplorerContext, - IStatusBarNotificationService statusBarNotificationService) + IStatusBarNotificationService statusBarNotificationService, + IPullRequestFilesViewModel files) { Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService)); Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); @@ -84,6 +84,7 @@ public PullRequestDetailViewModel( this.usageTracker = usageTracker; this.teamExplorerContext = teamExplorerContext; this.statusBarNotificationService = statusBarNotificationService; + Files = files; Checkout = ReactiveCommand.CreateAsyncObservable( this.WhenAnyValue(x => x.CheckoutState) @@ -248,13 +249,9 @@ public string OperationError } /// - /// Gets the changed files as a tree. + /// Gets the pull request's changed files. /// - public IReadOnlyList ChangedFilesTree - { - get { return changedFilesTree; } - private set { this.RaiseAndSetIfChanged(ref changedFilesTree, value); } - } + public IPullRequestFilesViewModel Files { get; } /// /// Gets the web URL for the pull request. @@ -381,9 +378,7 @@ public async Task Load(IPullRequestModel pullRequest) TargetBranchDisplayName = GetBranchDisplayName(IsFromFork, pullRequest.Base?.Label); CommentCount = pullRequest.Comments.Count + pullRequest.ReviewComments.Count; Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; - - var changes = await pullRequestsService.GetTreeChanges(LocalRepository, pullRequest); - ChangedFilesTree = (await CreateChangedFilesTree(pullRequest, changes)).Children.ToList(); + await Files.InitializeAsync(Session); var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList(); @@ -507,15 +502,15 @@ public override async Task Refresh() /// The path to a temporary file. public Task ExtractFile(IPullRequestFileNode file, bool head) { - var relativePath = Path.Combine(file.DirectoryPath, file.FileName); - var encoding = pullRequestsService.GetEncoding(LocalRepository, relativePath); + var path = file.RelativePath; + var encoding = pullRequestsService.GetEncoding(LocalRepository, path); if (!head && file.OldPath != null) { - relativePath = file.OldPath; + path = file.OldPath; } - return pullRequestsService.ExtractFile(LocalRepository, model, relativePath, head, encoding).ToTask(); + return pullRequestsService.ExtractFile(LocalRepository, model, path, head, encoding).ToTask(); } /// @@ -525,7 +520,7 @@ public Task ExtractFile(IPullRequestFileNode file, bool head) /// The full path to the file in the working directory. public string GetLocalFilePath(IPullRequestFileNode file) { - return Path.Combine(LocalRepository.LocalPath, file.DirectoryPath, file.FileName); + return Path.Combine(LocalRepository.LocalPath, file.RelativePath); } /// @@ -560,54 +555,6 @@ void SubscribeOperationError(ReactiveCommand command) command.IsExecuting.Select(x => x).Subscribe(x => OperationError = null); } - async Task CreateChangedFilesTree(IPullRequestModel pullRequest, TreeChanges changes) - { - var dirs = new Dictionary - { - { string.Empty, new PullRequestDirectoryNode(string.Empty) } - }; - - foreach (var changedFile in pullRequest.ChangedFiles) - { - var node = new PullRequestFileNode( - LocalRepository.LocalPath, - changedFile.FileName, - changedFile.Sha, - changedFile.Status, - GetOldFileName(changedFile, changes)); - - var file = await Session.GetFile(changedFile.FileName); - var fileCommentCount = file?.WhenAnyValue(x => x.InlineCommentThreads) - .Subscribe(x => node.CommentCount = x.Count(y => y.LineNumber != -1)); - - var dir = GetDirectory(node.DirectoryPath, dirs); - dir.Files.Add(node); - } - - return dirs[string.Empty]; - } - - static PullRequestDirectoryNode GetDirectory(string path, Dictionary dirs) - { - PullRequestDirectoryNode dir; - - if (!dirs.TryGetValue(path, out dir)) - { - var parentPath = Path.GetDirectoryName(path); - var parentDir = GetDirectory(parentPath, dirs); - - dir = new PullRequestDirectoryNode(path); - - if (!parentDir.Directories.Any(x => x.DirectoryName == dir.DirectoryName)) - { - parentDir.Directories.Add(dir); - dirs.Add(path, dir); - } - } - - return dir; - } - static string GetBranchDisplayName(bool isFromFork, string targetBranchLabel) { if (targetBranchLabel != null) @@ -620,17 +567,6 @@ static string GetBranchDisplayName(bool isFromFork, string targetBranchLabel) } } - string GetOldFileName(IPullRequestFileModel file, TreeChanges changes) - { - if (file.Status == PullRequestFileStatus.Renamed) - { - var fileName = file.FileName.Replace("/", "\\"); - return changes?.Renamed.FirstOrDefault(x => x.Path == fileName)?.OldPath; - } - - return null; - } - IObservable DoCheckout(object unused) { return Observable.Defer(async () => diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs index 26b12b1dad..6a9d46ebde 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs @@ -13,10 +13,10 @@ public class PullRequestDirectoryNode : IPullRequestDirectoryNode /// Initializes a new instance of the class. /// /// The path to the directory, relative to the repository. - public PullRequestDirectoryNode(string fullPath) + public PullRequestDirectoryNode(string relativePath) { - DirectoryName = System.IO.Path.GetFileName(fullPath); - DirectoryPath = fullPath; + DirectoryName = System.IO.Path.GetFileName(relativePath); + RelativePath = relativePath.Replace("/", "\\"); Directories = new List(); Files = new List(); } @@ -27,9 +27,9 @@ public PullRequestDirectoryNode(string fullPath) public string DirectoryName { get; } /// - /// Gets the full directory path, relative to the root of the repository. + /// Gets the path to the directory, relative to the root of the repository. /// - public string DirectoryPath { get; } + public string RelativePath { get; } /// /// Gets the directory children of the node. diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs index ed9246612c..f9e1e4e164 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs @@ -18,7 +18,7 @@ public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode /// Initializes a new instance of the class. /// /// The absolute path to the repository. - /// The path to the file, relative to the repository. + /// The path to the file, relative to the repository. /// The SHA of the file. /// The way the file was changed. /// The string to display in the [message] box next to the filename. @@ -28,17 +28,17 @@ public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode /// public PullRequestFileNode( string repositoryPath, - string path, + string relativePath, string sha, PullRequestFileStatus status, string oldPath) { Guard.ArgumentNotEmptyString(repositoryPath, nameof(repositoryPath)); - Guard.ArgumentNotEmptyString(path, nameof(path)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); Guard.ArgumentNotEmptyString(sha, nameof(sha)); - FileName = Path.GetFileName(path); - DirectoryPath = Path.GetDirectoryName(path); + FileName = Path.GetFileName(relativePath); + RelativePath = relativePath.Replace("/", "\\"); Sha = sha; Status = status; OldPath = oldPath; @@ -51,7 +51,7 @@ public PullRequestFileNode( { if (oldPath != null) { - StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(path) ? + StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(relativePath) ? Path.GetFileName(oldPath) : oldPath; } else @@ -67,9 +67,9 @@ public PullRequestFileNode( public string FileName { get; } /// - /// Gets the path to the file's directory, relative to the root of the repository. + /// Gets the path to the file, relative to the root of the repository. /// - public string DirectoryPath { get; } + public string RelativePath { get; } /// /// Gets the old path of a moved/renamed file, relative to the root of the repository. diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs new file mode 100644 index 0000000000..bad6947451 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using ReactiveUI; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// View model displaying a tree of changed files in a pull request. + /// + [Export(typeof(IPullRequestFilesViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class PullRequestFilesViewModel : ViewModelBase, IPullRequestFilesViewModel + { + readonly IPullRequestService service; + readonly BehaviorSubject isBranchCheckedOut = new BehaviorSubject(false); + + IPullRequestSession pullRequestSession; + Func commentFilter; + int changedFilesCount; + IReadOnlyList items; + CompositeDisposable subscriptions; + + [ImportingConstructor] + public PullRequestFilesViewModel( + IPullRequestService service, + IPullRequestEditorService editorService) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(editorService, nameof(editorService)); + + this.service = service; + + DiffFile = ReactiveCommand.CreateAsyncTask(x => + editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, false)); + ViewFile = ReactiveCommand.CreateAsyncTask(x => + editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, false)); + DiffFileWithWorkingDirectory = ReactiveCommand.CreateAsyncTask( + isBranchCheckedOut, + x => editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, true)); + OpenFileInWorkingDirectory = ReactiveCommand.CreateAsyncTask( + isBranchCheckedOut, + x => editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, true)); + + OpenFirstComment = ReactiveCommand.CreateAsyncTask(async x => + { + var file = (IPullRequestFileNode)x; + var thread = await GetFirstCommentThread(file); + + if (thread != null) + { + await editorService.OpenDiff(pullRequestSession, file.RelativePath, thread); + } + }); + } + + /// + public int ChangedFilesCount + { + get { return changedFilesCount; } + private set { this.RaiseAndSetIfChanged(ref changedFilesCount, value); } + } + + /// + public IReadOnlyList Items + { + get { return items; } + private set { this.RaiseAndSetIfChanged(ref items, value); } + } + + /// + public void Dispose() + { + subscriptions?.Dispose(); + subscriptions = null; + } + + /// + public async Task InitializeAsync( + IPullRequestSession session, + Func filter = null) + { + Guard.ArgumentNotNull(session, nameof(session)); + + subscriptions?.Dispose(); + this.pullRequestSession = session; + this.commentFilter = filter; + subscriptions = new CompositeDisposable(); + subscriptions.Add(session.WhenAnyValue(x => x.IsCheckedOut).Subscribe(isBranchCheckedOut)); + + var dirs = new Dictionary + { + { string.Empty, new PullRequestDirectoryNode(string.Empty) } + }; + + using (var changes = await service.GetTreeChanges(session.LocalRepository, session.PullRequest)) + { + foreach (var changedFile in session.PullRequest.ChangedFiles) + { + var node = new PullRequestFileNode( + session.LocalRepository.LocalPath, + changedFile.FileName, + changedFile.Sha, + changedFile.Status, + GetOldFileName(changedFile, changes)); + var file = await session.GetFile(changedFile.FileName); + + if (file != null) + { + subscriptions.Add(file.WhenAnyValue(x => x.InlineCommentThreads) + .Subscribe(x => node.CommentCount = CountComments(x, filter))); + } + + var dir = GetDirectory(Path.GetDirectoryName(node.RelativePath), dirs); + dir.Files.Add(node); + } + } + + ChangedFilesCount = session.PullRequest.ChangedFiles.Count; + Items = dirs[string.Empty].Children.ToList(); + } + + /// + public ReactiveCommand DiffFile { get; } + + /// + public ReactiveCommand ViewFile { get; } + + /// + public ReactiveCommand DiffFileWithWorkingDirectory { get; } + + /// + public ReactiveCommand OpenFileInWorkingDirectory { get; } + + /// + public ReactiveCommand OpenFirstComment { get; } + + static int CountComments( + IEnumerable thread, + Func commentFilter) + { + return thread.Count(x => x.LineNumber != -1 && (commentFilter?.Invoke(x) ?? true)); + } + + static PullRequestDirectoryNode GetDirectory(string path, Dictionary dirs) + { + PullRequestDirectoryNode dir; + + if (!dirs.TryGetValue(path, out dir)) + { + var parentPath = Path.GetDirectoryName(path); + var parentDir = GetDirectory(parentPath, dirs); + + dir = new PullRequestDirectoryNode(path); + + if (!parentDir.Directories.Any(x => x.DirectoryName == dir.DirectoryName)) + { + parentDir.Directories.Add(dir); + dirs.Add(path, dir); + } + } + + return dir; + } + + static string GetOldFileName(IPullRequestFileModel file, TreeChanges changes) + { + if (file.Status == PullRequestFileStatus.Renamed) + { + var fileName = file.FileName.Replace("/", "\\"); + return changes?.Renamed.FirstOrDefault(x => x.Path == fileName)?.OldPath; + } + + return null; + } + + async Task GetFirstCommentThread(IPullRequestFileNode file) + { + var sessionFile = await pullRequestSession.GetFile(file.RelativePath); + var threads = sessionFile.InlineCommentThreads.AsEnumerable(); + + if (commentFilter != null) + { + threads = threads.Where(commentFilter); + } + + return threads.FirstOrDefault(); + } + } +} diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index 50b25aa31e..03a098bbfb 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -3,14 +3,24 @@ - + + + - - - - - + + + + + + + + + + + + + diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 8b399b9220..f653adf5dc 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -182,6 +182,7 @@ + @@ -194,6 +195,7 @@ + diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs index 0f5f779c8c..b0371575af 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs @@ -1,9 +1,48 @@ -using Microsoft.VisualStudio.TextManager.Interop; +using System.Threading.Tasks; +using GitHub.Models; +using Microsoft.VisualStudio.TextManager.Interop; namespace GitHub.Services { + /// + /// Services for opening views of pull request files in Visual Studio. + /// public interface IPullRequestEditorService { + /// + /// Opens an editor for a file in a pull request. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// + /// If true opens the file in the working directory, if false opens the file in the HEAD + /// commit of the pull request. + /// + /// A task tracking the operation. + Task OpenFile(IPullRequestSession session, string relativePath, bool workingDirectory); + + /// + /// Opens an diff viewer for a file in a pull request. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// + /// If true the right hand side of the diff will be the current state of the file in the + /// working directory, if false it will be the HEAD commit of the pull request. + /// + /// A task tracking the operation. + Task OpenDiff(IPullRequestSession session, string relativePath, bool workingDirectory); + + /// + /// Opens an diff viewer for a file in a pull request with the specified inline comment + /// thread open. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// The thread to open + /// A task tracking the operation. + Task OpenDiff(IPullRequestSession session, string relativePath, IInlineCommentThreadModel thread); + /// /// Find the active text view. /// diff --git a/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs b/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs new file mode 100644 index 0000000000..4d8f1dca8f --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using GitHub.Extensions; + +namespace GitHub.Services +{ + /// + /// Extension methods for . + /// + public static class PullRequestSessionExtensions + { + /// + /// Gets the head (source) branch label for a pull request, stripping the owner if the pull + /// request is not from a fork. + /// + /// The pull request session. + /// The head branch label + public static string GetHeadBranchDisplay(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + return GetBranchDisplay(session.IsPullRequestFromFork(), session.PullRequest?.Head?.Label); + } + + /// + /// Gets the head (target) branch label for a pull request, stripping the owner if the pull + /// request is not from a fork. + /// + /// The pull request session. + /// The head branch label + public static string GetBaseBranchDisplay(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + return GetBranchDisplay(session.IsPullRequestFromFork(), session.PullRequest?.Base?.Label); + } + + /// + /// Returns a value that determines whether the pull request comes from a fork. + /// + /// The pull request session. + /// True if the pull request is from a fork, otherwise false. + public static bool IsPullRequestFromFork(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + + var headUrl = session.PullRequest.Head.RepositoryCloneUrl?.ToRepositoryUrl(); + var localUrl = session.LocalRepository.CloneUrl?.ToRepositoryUrl(); + return headUrl != null && localUrl != null ? headUrl != localUrl : false; + } + + static string GetBranchDisplay(bool fork, string label) + { + if (label != null) + { + return fork ? label : label.Split(':').Last(); + } + + return "[invalid]"; + } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs index 2661367609..a47f4889d1 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs @@ -8,9 +8,8 @@ namespace GitHub.ViewModels.GitHubPane public interface IPullRequestChangeNode { /// - /// Gets the path to the file (not including the filename) or directory, relative to the - /// root of the repository. + /// Gets the path to the file or directory, relative to the root of the repository. /// - string DirectoryPath { get; } + string RelativePath { get; } } } \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs index 078692d948..0185e03242 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -126,9 +126,9 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse string Body { get; } /// - /// Gets the changed files as a tree. + /// Gets the pull request's changed files. /// - IReadOnlyList ChangedFilesTree { get; } + IPullRequestFilesViewModel Files { get; } /// /// Gets the state associated with the command. diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs new file mode 100644 index 0000000000..6953271341 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Represents a tree of changed files in a pull request. + /// + public interface IPullRequestFilesViewModel : IViewModel, IDisposable + { + /// + /// Gets the number of changed files in the pull request. + /// + int ChangedFilesCount { get; } + + /// + /// Gets the root nodes of the tree. + /// + IReadOnlyList Items { get; } + + /// + /// Gets a command that diffs an between BASE and HEAD. + /// + ReactiveCommand DiffFile { get; } + + /// + /// Gets a command that opens an as it appears in the PR. + /// + ReactiveCommand ViewFile { get; } + + /// + /// Gets a command that diffs an between the version in + /// the working directory and HEAD. + /// + ReactiveCommand DiffFileWithWorkingDirectory { get; } + + /// + /// Gets a command that opens an from disk. + /// + ReactiveCommand OpenFileInWorkingDirectory { get; } + + /// + /// Gets a command that opens the first comment for a in + /// the diff viewer. + /// + ReactiveCommand OpenFirstComment { get; } + + /// + /// Initializes the view model. + /// + /// The pull request session. + /// An optional review comment filter. + Task InitializeAsync( + IPullRequestSession session, + Func commentFilter = null); + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 2145782c05..0443a38f11 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -382,6 +382,9 @@ GitHubPaneView.xaml + + PullRequestFilesView.xaml + PullRequestListView.xaml @@ -535,6 +538,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml index b5f1be0547..00976c6ee0 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -273,118 +273,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs index 038160144a..542a62b614 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs @@ -1,32 +1,17 @@ using System; using System.ComponentModel.Composition; using System.Globalization; -using System.Linq; using System.Reactive.Linq; using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; using System.Windows.Input; -using System.Windows.Media; -using GitHub.Commands; using GitHub.Exports; using GitHub.Extensions; -using GitHub.Models; using GitHub.Services; using GitHub.UI; using GitHub.UI.Helpers; using GitHub.ViewModels.GitHubPane; using GitHub.VisualStudio.UI.Helpers; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Projection; -using Microsoft.VisualStudio.TextManager.Interop; using ReactiveUI; -using Task = System.Threading.Tasks.Task; namespace GitHub.VisualStudio.Views.GitHubPane { @@ -47,38 +32,12 @@ public PullRequestDetailView() this.WhenActivated(d => { d(ViewModel.OpenOnGitHub.Subscribe(_ => DoOpenOnGitHub())); - d(ViewModel.DiffFile.Subscribe(x => DoDiffFile((IPullRequestFileNode)x, false).Forget())); - d(ViewModel.ViewFile.Subscribe(x => DoOpenFile((IPullRequestFileNode)x, false).Forget())); - d(ViewModel.DiffFileWithWorkingDirectory.Subscribe(x => DoDiffFile((IPullRequestFileNode)x, true).Forget())); - d(ViewModel.OpenFileInWorkingDirectory.Subscribe(x => DoOpenFile((IPullRequestFileNode)x, true).Forget())); }); - - bodyGrid.RequestBringIntoView += BodyFocusHack; } - [Import] - ITeamExplorerServiceHolder TeamExplorerServiceHolder { get; set; } - [Import] IVisualStudioBrowser VisualStudioBrowser { get; set; } - [Import] - IEditorOptionsFactoryService EditorOptionsFactoryService { get; set; } - - [Import] - IUsageTracker UsageTracker { get; set; } - - [Import] - IPullRequestEditorService NavigationService { get; set; } - - [Import] - IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; } - - protected override void OnVisualParentChanged(DependencyObject oldParent) - { - base.OnVisualParentChanged(oldParent); - } - void DoOpenOnGitHub() { var browser = VisualStudioBrowser; @@ -93,315 +52,6 @@ static Uri ToPullRequestUrl(string host, string owner, string repositoryName, in return new Uri(url); } - async Task DoOpenFile(IPullRequestFileNode file, bool workingDirectory) - { - try - { - var fullPath = ViewModel.GetLocalFilePath(file); - var fileName = workingDirectory ? fullPath : await ViewModel.ExtractFile(file, true); - - using (workingDirectory ? null : OpenInProvisionalTab()) - { - var window = GitHub.VisualStudio.Services.Dte.ItemOperations.OpenFile(fileName); - window.Document.ReadOnly = !workingDirectory; - - var buffer = GetBufferAt(fileName); - - if (!workingDirectory) - { - AddBufferTag(buffer, ViewModel.Session, fullPath, null); - - var textView = NavigationService.FindActiveView(); - EnableNavigateToEditor(textView, file); - } - } - - if (workingDirectory) - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); - else - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); - } - catch (Exception e) - { - ShowErrorInStatusBar("Error opening file", e); - } - } - - async Task DoNavigateToEditor(IPullRequestFileNode file) - { - try - { - if (!ViewModel.IsCheckedOut) - { - ShowInfoMessage("Checkout PR branch before opening file in solution."); - return; - } - - var fullPath = ViewModel.GetLocalFilePath(file); - - var activeView = NavigationService.FindActiveView(); - if (activeView == null) - { - ShowErrorInStatusBar("Couldn't find active view"); - return; - } - - NavigationService.NavigateToEquivalentPosition(activeView, fullPath); - - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsNavigateToEditor); - } - catch (Exception e) - { - ShowErrorInStatusBar("Error navigating to editor", e); - } - } - - static void ShowInfoMessage(string message) - { - ErrorHandler.ThrowOnFailure(VsShellUtilities.ShowMessageBox( - Services.GitHubServiceProvider, message, null, - OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST)); - } - - async Task DoDiffFile(IPullRequestFileNode file, bool workingDirectory) - { - try - { - var rightPath = System.IO.Path.Combine(file.DirectoryPath, file.FileName); - var leftPath = file.OldPath ?? rightPath; - var rightFile = workingDirectory ? ViewModel.GetLocalFilePath(file) : await ViewModel.ExtractFile(file, true); - var leftFile = await ViewModel.ExtractFile(file, false); - var leftLabel = $"{leftPath};{ViewModel.TargetBranchDisplayName}"; - var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {ViewModel.Model.Number}"; - var caption = $"Diff - {file.FileName}"; - var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | - __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; - - if (!workingDirectory) - { - options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; - } - - IVsWindowFrame frame; - using (OpenInProvisionalTab()) - { - var tooltip = $"{leftLabel}\nvs.\n{rightLabel}"; - - // Diff window will open in provisional (right hand) tab until document is touched. - frame = GitHub.VisualStudio.Services.DifferenceService.OpenComparisonWindow2( - leftFile, - rightFile, - caption, - tooltip, - leftLabel, - rightLabel, - string.Empty, - string.Empty, - (uint)options); - } - - object docView; - frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); - var diffViewer = ((IVsDifferenceCodeWindow)docView).DifferenceViewer; - - var session = ViewModel.Session; - AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, DiffSide.Left); - - if (!workingDirectory) - { - AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, DiffSide.Right); - EnableNavigateToEditor(diffViewer.LeftView, file); - EnableNavigateToEditor(diffViewer.RightView, file); - EnableNavigateToEditor(diffViewer.InlineView, file); - } - - if (workingDirectory) - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsCompareWithSolution); - else - await UsageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewChanges); - } - catch (Exception e) - { - ShowErrorInStatusBar("Error opening file", e); - } - } - - void AddBufferTag(ITextBuffer buffer, IPullRequestSession session, string path, DiffSide? side) - { - buffer.Properties.GetOrCreateSingletonProperty( - typeof(PullRequestTextBufferInfo), - () => new PullRequestTextBufferInfo(session, path, side)); - - var projection = buffer as IProjectionBuffer; - - if (projection != null) - { - foreach (var source in projection.SourceBuffers) - { - AddBufferTag(source, session, path, side); - } - } - } - - void EnableNavigateToEditor(IWpfTextView textView, IPullRequestFileNode file) - { - var view = EditorAdaptersFactoryService.GetViewAdapter(textView); - EnableNavigateToEditor(view, file); - } - - void EnableNavigateToEditor(IVsTextView textView, IPullRequestFileNode file) - { - var commandGroup = VSConstants.CMDSETID.StandardCommandSet2K_guid; - var commandId = (int)VSConstants.VSStd2KCmdID.RETURN; - new TextViewCommandDispatcher(textView, commandGroup, commandId).Exec += async (s, e) => await DoNavigateToEditor(file); - - var contextMenuCommandGroup = new Guid(Guids.guidContextMenuSetString); - var goToCommandId = PkgCmdIDList.openFileInSolutionCommand; - new TextViewCommandDispatcher(textView, contextMenuCommandGroup, goToCommandId).Exec += async (s, e) => await DoNavigateToEditor(file); - } - - void ShowErrorInStatusBar(string message, Exception e = null) - { - var ns = GitHub.VisualStudio.Services.DefaultExportProvider.GetExportedValue(); - if (e != null) - { - message += ": " + e.Message; - } - ns?.ShowMessage(message); - } - - private void FileListKeyUp(object sender, KeyEventArgs e) - { - if (e.Key == Key.Return) - { - var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; - if (file != null) - { - DoDiffFile(file, false).Forget(); - } - } - } - - void FileListMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; - - if (file != null) - { - DoDiffFile(file, false).Forget(); - } - } - - void FileListMouseRightButtonDown(object sender, MouseButtonEventArgs e) - { - var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); - - if (item != null) - { - // Select tree view item on right click. - item.IsSelected = true; - } - } - - ITextBuffer GetBufferAt(string filePath) - { - var editorAdapterFactoryService = GitHub.VisualStudio.Services.ComponentModel.GetService(); - IVsUIHierarchy uiHierarchy; - uint itemID; - IVsWindowFrame windowFrame; - - if (VsShellUtilities.IsDocumentOpen( - GitHub.VisualStudio.Services.GitHubServiceProvider, - filePath, - Guid.Empty, - out uiHierarchy, - out itemID, - out windowFrame)) - { - IVsTextView view = VsShellUtilities.GetTextView(windowFrame); - IVsTextLines lines; - if (view.GetBuffer(out lines) == 0) - { - var buffer = lines as IVsTextBuffer; - if (buffer != null) - return editorAdapterFactoryService.GetDataBuffer(buffer); - } - } - - return null; - } - - void TreeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) - { - ApplyContextMenuBinding(sender, e); - } - - void ApplyContextMenuBinding(object sender, ContextMenuEventArgs e) where TItem : Control - { - var container = (Control)sender; - var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); - - e.Handled = true; - - if (item != null) - { - var fileNode = item.DataContext as IPullRequestFileNode; - - if (fileNode != null) - { - container.ContextMenu.DataContext = this.DataContext; - - foreach (var menuItem in container.ContextMenu.Items.OfType()) - { - menuItem.CommandParameter = fileNode; - } - - e.Handled = false; - } - } - } - - void BodyFocusHack(object sender, RequestBringIntoViewEventArgs e) - { - if (e.TargetObject == bodyMarkdown) - { - // Hack to prevent pane scrolling to top. Instead focus selected tree view item. - // See https://github.com/github/VisualStudio/issues/1042 - var node = changesTree.GetTreeViewItem(changesTree.SelectedItem); - node?.Focus(); - e.Handled = true; - } - } - - async void ViewFileCommentsClick(object sender, RoutedEventArgs e) - { - try - { - var file = (e.OriginalSource as Hyperlink)?.DataContext as IPullRequestFileNode; - - if (file != null) - { - var param = (object)new InlineCommentNavigationParams - { - FromLine = -1, - }; - - await DoDiffFile(file, false); - - // HACK: We need to wait here for the diff view to set itself up and move its cursor - // to the first changed line. There must be a better way of doing this. - await Task.Delay(1500); - - GitHub.VisualStudio.Services.Dte.Commands.Raise( - Guids.CommandSetString, - PkgCmdIDList.NextInlineCommentId, - ref param, - null); - } - } - catch { } - } - void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) { Uri uri; @@ -411,12 +61,5 @@ void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) VisualStudioBrowser.OpenUrl(uri); } } - - static IDisposable OpenInProvisionalTab() - { - return new NewDocumentStateScope - (__VSNEWDOCUMENTSTATE.NDS_Provisional, - VSConstants.NewDocumentStateReason.SolutionExplorer); - } } } diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml new file mode 100644 index 0000000000..9195515a16 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs new file mode 100644 index 0000000000..2f2956ef45 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs @@ -0,0 +1,87 @@ +using System.ComponentModel.Composition; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using GitHub.Exports; +using GitHub.UI.Helpers; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestFilesViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestFilesView : UserControl + { + public PullRequestFilesView() + { + InitializeComponent(); + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + } + + void changesTree_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + ApplyContextMenuBinding(sender, e); + } + + void changesTree_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; + (DataContext as IPullRequestFilesViewModel)?.DiffFile.Execute(file); + } + + void changesTree_MouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + + if (item != null) + { + // Select tree view item on right click. + item.IsSelected = true; + } + } + + void ApplyContextMenuBinding(object sender, ContextMenuEventArgs e) where TItem : Control + { + var container = (Control)sender; + var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + + e.Handled = true; + + if (item != null) + { + var fileNode = item.DataContext as IPullRequestFileNode; + + if (fileNode != null) + { + container.ContextMenu.DataContext = this.DataContext; + + foreach (var menuItem in container.ContextMenu.Items.OfType()) + { + menuItem.CommandParameter = fileNode; + } + + e.Handled = false; + } + } + } + + private void changesTree_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Return) + { + var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; + + if (file != null) + { + (DataContext as IPullRequestFilesViewModel)?.DiffFile.Execute(file); + } + } + } + } +} diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs index c9e409c7c1..a344f1499a 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -49,102 +49,6 @@ public async Task ShouldAcceptNullHead() } } - public class TheChangedFilesTreeProperty - { - [Test] - public async Task ShouldCreateChangesTree() - { - var target = CreateTarget(); - var pr = CreatePullRequest(); - - pr.ChangedFiles = new[] - { - new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir1/f1.cs", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir1/f2.cs", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir1/dir1a/f3.cs", "abc", PullRequestFileStatus.Modified), - new PullRequestFileModel("dir2/f4.cs", "abc", PullRequestFileStatus.Modified), - }; - - await target.Load(pr); - - Assert.That(3, Is.EqualTo(target.ChangedFilesTree.Count)); - - var dir1 = (PullRequestDirectoryNode)target.ChangedFilesTree[0]; - Assert.That("dir1", Is.EqualTo(dir1.DirectoryName)); - Assert.That(2, Is.EqualTo(dir1.Files.Count)); - Assert.That(1, Is.EqualTo(dir1.Directories.Count)); - Assert.That("f1.cs", Is.EqualTo(dir1.Files[0].FileName)); - Assert.That("f2.cs", Is.EqualTo(dir1.Files[1].FileName)); - Assert.That("dir1", Is.EqualTo(dir1.Files[0].DirectoryPath)); - Assert.That("dir1", Is.EqualTo(dir1.Files[1].DirectoryPath)); - - var dir1a = (PullRequestDirectoryNode)dir1.Directories[0]; - Assert.That("dir1a", Is.EqualTo(dir1a.DirectoryName)); - Assert.That(1, Is.EqualTo(dir1a.Files.Count)); - Assert.That(0, Is.EqualTo(dir1a.Directories.Count)); - - var dir2 = (PullRequestDirectoryNode)target.ChangedFilesTree[1]; - Assert.That("dir2", Is.EqualTo(dir2.DirectoryName)); - Assert.That(1, Is.EqualTo(dir2.Files.Count)); - Assert.That(0, Is.EqualTo(dir2.Directories.Count)); - - var readme = (PullRequestFileNode)target.ChangedFilesTree[2]; - Assert.That("readme.md", Is.EqualTo(readme.FileName)); - } - - [Test] - public async Task FileCommentCountShouldTrackSessionInlineComments() - { - var pr = CreatePullRequest(); - var file = Substitute.For(); - var thread1 = CreateThread(5); - var thread2 = CreateThread(6); - var outdatedThread = CreateThread(-1); - var session = Substitute.For(); - var sessionManager = Substitute.For(); - - file.InlineCommentThreads.Returns(new[] { thread1 }); - session.GetFile("readme.md").Returns(Task.FromResult(file)); - sessionManager.GetSession(pr).Returns(Task.FromResult(session)); - - var target = CreateTarget(sessionManager: sessionManager); - - pr.ChangedFiles = new[] - { - new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), - }; - - await target.Load(pr); - Assert.That(1, Is.EqualTo(((IPullRequestFileNode)target.ChangedFilesTree[0]).CommentCount)); - - file.InlineCommentThreads.Returns(new[] { thread1, thread2 }); - RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); - Assert.That(2, Is.EqualTo(((IPullRequestFileNode)target.ChangedFilesTree[0]).CommentCount)); - - // Outdated comment is not included in the count. - file.InlineCommentThreads.Returns(new[] { thread1, thread2, outdatedThread }); - RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); - Assert.That(2, Is.EqualTo(((IPullRequestFileNode)target.ChangedFilesTree[0]).CommentCount)); - - file.Received(1).PropertyChanged += Arg.Any(); - } - - IInlineCommentThreadModel CreateThread(int lineNumber) - { - var result = Substitute.For(); - result.LineNumber.Returns(lineNumber); - return result; - } - - void RaisePropertyChanged(T o, string propertyName) - where T : INotifyPropertyChanged - { - o.PropertyChanged += Raise.Event(new PropertyChangedEventArgs(propertyName)); - } - - } - public class TheCheckoutCommand { [Test] @@ -556,7 +460,8 @@ static Tuple CreateTargetAndSer Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For()); vm.InitializeAsync(repository, Substitute.For(), "owner", "repo", 1).Wait(); return Tuple.Create(vm, pullRequestService); diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs new file mode 100644 index 0000000000..67a6cf87e6 --- /dev/null +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs @@ -0,0 +1,128 @@ +using System; +using System.ComponentModel; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestFilesViewModelTests + { + static readonly Uri Uri = new Uri("http://foo"); + + [Test] + public async Task ShouldCreateChangesTree() + { + var target = CreateTarget(); + var session = CreateSession(); + + session.PullRequest.ChangedFiles.Returns(new[] + { + new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir1/f1.cs", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir1/f2.cs", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir1/dir1a/f3.cs", "abc", PullRequestFileStatus.Modified), + new PullRequestFileModel("dir2/f4.cs", "abc", PullRequestFileStatus.Modified), + }); + + await target.InitializeAsync(session); + + Assert.That(target.Items.Count, Is.EqualTo(3)); + + var dir1 = (PullRequestDirectoryNode)target.Items[0]; + Assert.That(dir1.DirectoryName, Is.EqualTo("dir1")); + Assert.That(dir1.Files, Has.Exactly(2).Items); + + Assert.That(dir1.Directories, Has.One.Items); + Assert.That(dir1.Files[0].FileName, Is.EqualTo("f1.cs")); + Assert.That(dir1.Files[1].FileName, Is.EqualTo("f2.cs")); + Assert.That(dir1.Files[0].RelativePath, Is.EqualTo("dir1\\f1.cs")); + Assert.That(dir1.Files[1].RelativePath, Is.EqualTo("dir1\\f2.cs")); + + var dir1a = (PullRequestDirectoryNode)dir1.Directories[0]; + Assert.That(dir1a.DirectoryName, Is.EqualTo("dir1a")); + Assert.That(dir1a.Files, Has.One.Items); + Assert.That(dir1a.Directories, Is.Empty); + + var dir2 = (PullRequestDirectoryNode)target.Items[1]; + Assert.That(dir2.DirectoryName, Is.EqualTo("dir2")); + Assert.That(dir2.Files, Has.One.Items); + Assert.That(dir2.Directories, Is.Empty); + + var readme = (PullRequestFileNode)target.Items[2]; + Assert.That(readme.FileName, Is.EqualTo("readme.md")); + } + + [Test] + public async Task FileCommentCountShouldTrackSessionInlineComments() + { + var outdatedThread = CreateThread(-1); + var session = CreateSession(); + + session.PullRequest.ChangedFiles.Returns(new[] + { + new PullRequestFileModel("readme.md", "abc", PullRequestFileStatus.Modified), + }); + + var file = Substitute.For(); + var thread1 = CreateThread(5); + var thread2 = CreateThread(6); + file.InlineCommentThreads.Returns(new[] { thread1 }); + session.GetFile("readme.md").Returns(Task.FromResult(file)); + + var target = CreateTarget(); + + await target.InitializeAsync(session); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(1)); + + file.InlineCommentThreads.Returns(new[] { thread1, thread2 }); + RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(2)); + + // Outdated comment is not included in the count. + file.InlineCommentThreads.Returns(new[] { thread1, thread2, outdatedThread }); + RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(2)); + + file.Received(1).PropertyChanged += Arg.Any(); + } + + static PullRequestFilesViewModel CreateTarget() + { + var pullRequestService = Substitute.For(); + var editorService = Substitute.For(); + return new PullRequestFilesViewModel(pullRequestService, editorService); + } + + static IPullRequestSession CreateSession() + { + var author = Substitute.For(); + var pr = Substitute.For(); + + var repository = Substitute.For(); + repository.LocalPath.Returns(@"C:\Foo"); + + var result = Substitute.For(); + result.LocalRepository.Returns(repository); + result.PullRequest.Returns(pr); + return result; + } + + IInlineCommentThreadModel CreateThread(int lineNumber) + { + var result = Substitute.For(); + result.LineNumber.Returns(lineNumber); + return result; + } + + void RaisePropertyChanged(T o, string propertyName) + where T : INotifyPropertyChanged + { + o.PropertyChanged += Raise.Event(new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs b/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs index acbc202559..51838e4f11 100644 --- a/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs +++ b/test/UnitTests/GitHub.Exports.Reactive/Services/PullRequestEditorServiceTests.cs @@ -2,6 +2,7 @@ using GitHub.Services; using NUnit.Framework; using NSubstitute; +using Microsoft.VisualStudio.Editor; public class PullRequestEditorServiceTests { @@ -48,6 +49,15 @@ public void FindNearestMatchingLine(IList fromLines, IList toLin static PullRequestEditorService CreateNavigationService() { var sp = Substitute.For(); - return new PullRequestEditorService(sp); + var pullRequestService = Substitute.For(); + var vsEditorAdaptersFactory = Substitute.For(); + var statusBar = Substitute.For(); + var usageTracker = Substitute.For(); + return new PullRequestEditorService( + sp, + pullRequestService, + vsEditorAdaptersFactory, + statusBar, + usageTracker); } } diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 1163d5db35..b9bd0c647c 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -82,6 +82,10 @@ ..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll True + + ..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll + True + ..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll True @@ -252,6 +256,7 @@ + diff --git a/test/UnitTests/packages.config b/test/UnitTests/packages.config index 145225d9f1..723198e5a5 100644 --- a/test/UnitTests/packages.config +++ b/test/UnitTests/packages.config @@ -6,6 +6,7 @@ + From 38ac0721b93f3d7159aed448d8cc8790e44aad97 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Feb 2018 16:04:58 +0100 Subject: [PATCH 18/78] Port changes from #1407. Enable navigation from diff view to editor --- src/GitHub.App/GitHub.App.csproj | 1 + .../Services/PullRequestEditorService.cs | 65 +++++++++++++++++++ .../Services}/TextViewCommandDispatcher.cs | 2 +- .../GitHub.VisualStudio.csproj | 1 - 4 files changed, 67 insertions(+), 2 deletions(-) rename src/{GitHub.VisualStudio/Views/GitHubPane => GitHub.App/Services}/TextViewCommandDispatcher.cs (98%) diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 74442bc191..3e9bff0d12 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -213,6 +213,7 @@ + diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 4e318239a6..563b176f58 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -9,12 +9,14 @@ using GitHub.Commands; using GitHub.Extensions; using GitHub.Models; +using GitHub.ViewModels.GitHubPane; using GitHub.VisualStudio; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; using Task = System.Threading.Tasks.Task; @@ -151,6 +153,9 @@ public async Task OpenDiff( if (!workingDirectory) { AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, DiffSide.Right); + EnableNavigateToEditor(diffViewer.LeftView, session, file); + EnableNavigateToEditor(diffViewer.RightView, session, file); + EnableNavigateToEditor(diffViewer.InlineView, session, file); } if (workingDirectory) @@ -350,6 +355,11 @@ IVsTextView OpenDocument(string fullPath) return view; } + void ShowErrorInStatusBar(string message) + { + statusBar.ShowMessage(message); + } + void ShowErrorInStatusBar(string message, Exception e) { statusBar.ShowMessage(message + ": " + e.Message); @@ -372,6 +382,54 @@ void AddBufferTag(ITextBuffer buffer, IPullRequestSession session, string path, } } + void EnableNavigateToEditor(IWpfTextView textView, IPullRequestSession session, IPullRequestSessionFile file) + { + var view = vsEditorAdaptersFactory.GetViewAdapter(textView); + EnableNavigateToEditor(view, session, file); + } + + void EnableNavigateToEditor(IVsTextView textView, IPullRequestSession session, IPullRequestSessionFile file) + { + var commandGroup = VSConstants.CMDSETID.StandardCommandSet2K_guid; + var commandId = (int)VSConstants.VSStd2KCmdID.RETURN; + new TextViewCommandDispatcher(textView, commandGroup, commandId).Exec += + async (s, e) => await DoNavigateToEditor(session, file); + + var contextMenuCommandGroup = new Guid(Guids.guidContextMenuSetString); + var goToCommandId = PkgCmdIDList.openFileInSolutionCommand; + new TextViewCommandDispatcher(textView, contextMenuCommandGroup, goToCommandId).Exec += + async (s, e) => await DoNavigateToEditor(session, file); + } + + async Task DoNavigateToEditor(IPullRequestSession session, IPullRequestSessionFile file) + { + try + { + if (!session.IsCheckedOut) + { + ShowInfoMessage("Checkout PR branch before opening file in solution."); + return; + } + + var fullPath = GetAbsolutePath(session, file); + + var activeView = FindActiveView(); + if (activeView == null) + { + ShowErrorInStatusBar("Couldn't find active view"); + return; + } + + NavigateToEquivalentPosition(activeView, fullPath); + + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsNavigateToEditor); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error navigating to editor", e); + } + } + async Task ExtractFile(IPullRequestSession session, IPullRequestSessionFile file, bool head) { var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); @@ -424,6 +482,13 @@ async Task GetBaseFileName(IPullRequestSession session, IPullRequestSess } } + void ShowInfoMessage(string message) + { + ErrorHandler.ThrowOnFailure(VsShellUtilities.ShowMessageBox( + serviceProvider, message, null, + OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST)); + } + static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFile file) { return Path.Combine(session.LocalRepository.LocalPath, file.RelativePath); diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/TextViewCommandDispatcher.cs b/src/GitHub.App/Services/TextViewCommandDispatcher.cs similarity index 98% rename from src/GitHub.VisualStudio/Views/GitHubPane/TextViewCommandDispatcher.cs rename to src/GitHub.App/Services/TextViewCommandDispatcher.cs index 10c7da216e..dd0f91f582 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/TextViewCommandDispatcher.cs +++ b/src/GitHub.App/Services/TextViewCommandDispatcher.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.TextManager.Interop; -namespace GitHub.VisualStudio.Views.GitHubPane +namespace GitHub.Services { /// /// Intercepts all commands sent to a and fires when a specified command is encountered. diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 0443a38f11..acf8349a34 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -400,7 +400,6 @@ NotAGitRepositoryView.xaml - RepositoryPublishView.xaml From e8cb2a9ebe8e167c083b79ea4552e31a422f7f76 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Feb 2018 13:21:44 +0100 Subject: [PATCH 19/78] Use GraphQL to read PR reviews/comments. This is integrated a bit hackily into `ModelService`; `ModelService` doesn't really mesh well with GraphQL but without a lot of refactoring this was the best way to get things up and running. --- src/GitHub.App/Models/Account.cs | 2 +- src/GitHub.App/packages.config | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GitHub.App/Models/Account.cs b/src/GitHub.App/Models/Account.cs index b2b07462aa..050871f6fa 100644 --- a/src/GitHub.App/Models/Account.cs +++ b/src/GitHub.App/Models/Account.cs @@ -88,7 +88,7 @@ public BitmapSource Avatar set { avatar = value; this.RaisePropertyChanged(); } } -#region Equality things + #region Equality things public void CopyFrom(IAccount other) { if (!Equals(other)) diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index 03a098bbfb..604d5310a1 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -20,7 +20,8 @@ - + + From be15e94c2cdbb80434c19ba132a5f727ef5e6c2b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Mar 2018 17:16:29 +0100 Subject: [PATCH 20/78] Fix dark theme foreground color. --- .../Views/GitHubPane/PullRequestFilesView.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml index 9195515a16..661954a200 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml @@ -46,6 +46,7 @@ From 98e8241cce7a2c59feeb7f7e2400bf170a74d162 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Mar 2018 16:32:19 +0100 Subject: [PATCH 21/78] Removed unused commands. --- .../PullRequestDetailViewModelDesigner.cs | 4 --- .../GitHubPane/PullRequestDetailViewModel.cs | 25 ------------------- .../GitHubPane/IPullRequestDetailViewModel.cs | 21 ---------------- 3 files changed, 50 deletions(-) diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs index 1ac923b1c3..63f163f42a 100644 --- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -92,10 +92,6 @@ public PullRequestDetailViewModelDesigner() public ReactiveCommand Pull { get; } public ReactiveCommand Push { get; } public ReactiveCommand OpenOnGitHub { get; } - public ReactiveCommand DiffFile { get; } - public ReactiveCommand DiffFileWithWorkingDirectory { get; } - public ReactiveCommand OpenFileInWorkingDirectory { get; } - public ReactiveCommand ViewFile { get; } public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int number) => Task.CompletedTask; diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index e2f092d2a8..f75616cc16 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -117,10 +117,6 @@ public PullRequestDetailViewModel( SubscribeOperationError(SyncSubmodules); OpenOnGitHub = ReactiveCommand.Create(); - DiffFile = ReactiveCommand.Create(); - DiffFileWithWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut)); - OpenFileInWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut)); - ViewFile = ReactiveCommand.Create(); } /// @@ -287,27 +283,6 @@ public Uri WebUrl /// public ReactiveCommand OpenOnGitHub { get; } - /// - /// Gets a command that diffs an between BASE and HEAD. - /// - public ReactiveCommand DiffFile { get; } - - /// - /// Gets a command that diffs an between the version in - /// the working directory and HEAD. - /// - public ReactiveCommand DiffFileWithWorkingDirectory { get; } - - /// - /// Gets a command that opens an from disk. - /// - public ReactiveCommand OpenFileInWorkingDirectory { get; } - - /// - /// Gets a command that opens an as it appears in the PR. - /// - public ReactiveCommand ViewFile { get; } - /// /// Initializes the view model. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs index 0185e03242..6d6a4262ac 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -165,27 +165,6 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse /// ReactiveCommand OpenOnGitHub { get; } - /// - /// Gets a command that diffs an between BASE and HEAD. - /// - ReactiveCommand DiffFile { get; } - - /// - /// Gets a command that diffs an between the version in - /// the working directory and HEAD. - /// - ReactiveCommand DiffFileWithWorkingDirectory { get; } - - /// - /// Gets a command that opens an from disk. - /// - ReactiveCommand OpenFileInWorkingDirectory { get; } - - /// - /// Gets a command that opens an as it appears in the PR. - /// - ReactiveCommand ViewFile { get; } - /// /// Initializes the view model. /// From c8d2b5343e89d3c62d6d44a72788bf88b97e2415 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Mar 2018 18:15:19 +0100 Subject: [PATCH 22/78] Add "Reviewers" section to PR details. --- src/GitHub.App/GitHub.App.csproj | 1 + src/GitHub.App/Resources.Designer.cs | 36 ++++ src/GitHub.App/Resources.resx | 12 ++ .../PullRequestDetailViewModelDesigner.cs | 30 ++++ .../GitHubPane/PullRequestDetailViewModel.cs | 23 +++ .../PullRequestReviewSummaryViewModel.cs | 124 +++++++++++++ .../GitHub.Exports.Reactive.csproj | 1 + .../GitHubPane/IPullRequestDetailViewModel.cs | 10 ++ .../IPullRequestReviewSummaryViewModel.cs | 35 ++++ .../NotEqualsToVisibilityConverter.cs | 30 ++++ src/GitHub.UI/GitHub.UI.csproj | 1 + .../Resources.Designer.cs | 27 +++ src/GitHub.VisualStudio.UI/Resources.resx | 9 + .../GitHub.VisualStudio.csproj | 7 + .../GitHubPane/PullRequestDetailView.xaml | 21 ++- .../PullRequestReviewSummaryView.xaml | 96 ++++++++++ .../PullRequestReviewSummaryView.xaml.cs | 12 ++ .../PullRequestDetailViewModelTests.cs | 169 ++++++++++++++---- 18 files changed, 610 insertions(+), 34 deletions(-) create mode 100644 src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs create mode 100644 src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 3e9bff0d12..0dc3936064 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -236,6 +236,7 @@ + diff --git a/src/GitHub.App/Resources.Designer.cs b/src/GitHub.App/Resources.Designer.cs index d658cc788d..5851e6a658 100644 --- a/src/GitHub.App/Resources.Designer.cs +++ b/src/GitHub.App/Resources.Designer.cs @@ -69,6 +69,15 @@ internal static string AddedFileStatus { } } + /// + /// Looks up a localized string similar to Approved. + /// + internal static string Approved { + get { + return ResourceManager.GetString("Approved", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select a containing folder for your new repository.. /// @@ -78,6 +87,15 @@ internal static string BrowseForDirectory { } } + /// + /// Looks up a localized string similar to Changes Requested. + /// + internal static string ChangesRequested { + get { + return ResourceManager.GetString("ChangesRequested", resourceCulture); + } + } + /// /// Looks up a localized string similar to Clone a {0} Repository. /// @@ -87,6 +105,15 @@ internal static string CloneTitle { } } + /// + /// Looks up a localized string similar to Commented. + /// + internal static string Commented { + get { + return ResourceManager.GetString("Commented", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not connect to github.com. /// @@ -180,6 +207,15 @@ internal static string Fork { } } + /// + /// Looks up a localized string similar to InProgress. + /// + internal static string InProgress { + get { + return ResourceManager.GetString("InProgress", resourceCulture); + } + } + /// /// Looks up a localized string similar to [invalid]. /// diff --git a/src/GitHub.App/Resources.resx b/src/GitHub.App/Resources.resx index 7fa2be0265..153a52be18 100644 --- a/src/GitHub.App/Resources.resx +++ b/src/GitHub.App/Resources.resx @@ -294,4 +294,16 @@ Please install Git for Windows from: https://git-scm.com/download/win + + Approved + + + Changes Requested + + + Commented + + + InProgress + \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs index 63f163f42a..387bfeda66 100644 --- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -67,6 +67,34 @@ public PullRequestDetailViewModelDesigner() modelsDir.Files.Add(oldBranchModel); gitHubDir.Directories.Add(modelsDir); + Reviews = new[] + { + new PullRequestReviewSummaryViewModel + { + Id = 2, + User = new AccountDesigner { Login = "grokys", IsUser = true }, + State = PullRequestReviewState.Pending, + FileCommentCount = 0, + }, + new PullRequestReviewSummaryViewModel + { + Id = 1, + User = new AccountDesigner { Login = "jcansdale", IsUser = true }, + State = PullRequestReviewState.Approved, + FileCommentCount = 5, + }, + new PullRequestReviewSummaryViewModel + { + Id = 2, + User = new AccountDesigner { Login = "shana", IsUser = true }, + State = PullRequestReviewState.ChangesRequested, + FileCommentCount = 5, + }, + new PullRequestReviewSummaryViewModel + { + }, + }; + Files = new PullRequestFilesViewModelDesigner(); } @@ -81,6 +109,7 @@ public PullRequestDetailViewModelDesigner() public bool IsCheckedOut { get; } public bool IsFromFork { get; } public string Body { get; } + public IReadOnlyList Reviews { get; } public IPullRequestFilesViewModel Files { get; set; } public IPullRequestCheckoutState CheckoutState { get; set; } public IPullRequestUpdateState UpdateState { get; set; } @@ -92,6 +121,7 @@ public PullRequestDetailViewModelDesigner() public ReactiveCommand Pull { get; } public ReactiveCommand Push { get; } public ReactiveCommand OpenOnGitHub { get; } + public ReactiveCommand ShowReview { get; } public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int number) => Task.CompletedTask; diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index f75616cc16..aeea37e939 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -42,6 +42,7 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq string targetBranchDisplayName; int commentCount; string body; + IReadOnlyList reviews; IPullRequestCheckoutState checkoutState; IPullRequestUpdateState updateState; string operationError; @@ -117,6 +118,7 @@ public PullRequestDetailViewModel( SubscribeOperationError(SyncSubmodules); OpenOnGitHub = ReactiveCommand.Create(); + ShowReview = ReactiveCommand.Create().OnExecuteCompleted(DoShowReview); } /// @@ -244,6 +246,15 @@ public string OperationError private set { this.RaiseAndSetIfChanged(ref operationError, value); } } + /// + /// Gets the latest pull request review for each user. + /// + public IReadOnlyList Reviews + { + get { return reviews; } + private set { this.RaiseAndSetIfChanged(ref reviews, value); } + } + /// /// Gets the pull request's changed files. /// @@ -283,6 +294,11 @@ public Uri WebUrl /// public ReactiveCommand OpenOnGitHub { get; } + /// + /// Gets a command that navigates to a pull request review. + /// + public ReactiveCommand ShowReview { get; } + /// /// Initializes the view model. /// @@ -353,6 +369,8 @@ public async Task Load(IPullRequestModel pullRequest) TargetBranchDisplayName = GetBranchDisplayName(IsFromFork, pullRequest.Base?.Label); CommentCount = pullRequest.Comments.Count + pullRequest.ReviewComments.Count; Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; + Reviews = PullRequestReviewSummaryViewModel.BuildByUser(pullRequest).ToList(); + await Files.InitializeAsync(Session); var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList(); @@ -616,6 +634,11 @@ async Task DoSyncSubmodules(object unused) } } + void DoShowReview(object item) + { + // TODO + } + class CheckoutCommandState : IPullRequestCheckoutState { public CheckoutCommandState(string caption, string disabledMessage) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs new file mode 100644 index 0000000000..35c6cb9183 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GitHub.App; +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Displays a short overview of a pull request review in the . + /// + public class PullRequestReviewSummaryViewModel : IPullRequestReviewSummaryViewModel + { + private static Dictionary StatePriorities = new Dictionary + { + { PullRequestReviewState.Approved, 1 }, + { PullRequestReviewState.ChangesRequested, 1 }, + { PullRequestReviewState.Commented, 0 }, + { PullRequestReviewState.Dismissed, 0 }, + { PullRequestReviewState.Pending, 2 }, + }; + + /// + public long Id { get; set; } + + /// + public IAccount User { get; set; } + + /// + public PullRequestReviewState State { get; set; } + + /// + public string StateDisplay => ToString(State); + + /// + public int FileCommentCount { get; set; } + + /// + /// Builds a collection of s by user. + /// + /// The pull request model. + /// + /// This method builds a list similar to that found in the "Reviewers" section at the top- + /// right of the Pull Request page on GitHub. + /// + public static IEnumerable BuildByUser(IPullRequestModel pullRequest) + { + var existing = new Dictionary(); + + foreach (var review in pullRequest.Reviews.OrderBy(x => x.Id)) + { + PullRequestReviewSummaryViewModel previous; + existing.TryGetValue(review.User.Login, out previous); + + var previousPriority = ToPriority(previous); + var reviewPriority = ToPriority(review.State); + + if (reviewPriority >= previousPriority) + { + var count = pullRequest.ReviewComments + .Where(x => x.PullRequestReviewId == review.Id) + .Count(); + existing[review.User.Login] = new PullRequestReviewSummaryViewModel + { + Id = review.Id, + User = review.User, + State = review.State, + FileCommentCount = count + }; + } + } + + var result = existing.Values.OrderBy(x => x.User).AsEnumerable(); + + if (!result.Any(x => x.State == PullRequestReviewState.Pending)) + { + var newReview = new PullRequestReviewSummaryViewModel + { + State = PullRequestReviewState.Pending, + }; + result = result.Concat(new[] { newReview }); + } + + return result; + } + + static int ToPriority(PullRequestReviewSummaryViewModel review) + { + return review != null ? ToPriority(review.State) : 0; + } + + static int ToPriority(PullRequestReviewState state) + { + switch (state) + { + case PullRequestReviewState.Approved: + case PullRequestReviewState.ChangesRequested: + return 1; + case PullRequestReviewState.Pending: + return 2; + default: + return 0; + } + } + + static string ToString(PullRequestReviewState state) + { + switch (state) + { + case PullRequestReviewState.Approved: + return Resources.Approved; + case PullRequestReviewState.ChangesRequested: + return Resources.ChangesRequested; + case PullRequestReviewState.Commented: + case PullRequestReviewState.Dismissed: + return Resources.Commented; + case PullRequestReviewState.Pending: + return Resources.InProgress; + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index f653adf5dc..07a1464237 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -197,6 +197,7 @@ + diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs index 6d6a4262ac..fa091605d7 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -125,6 +125,11 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse /// string Body { get; } + /// + /// Gets the latest pull request review for each user. + /// + IReadOnlyList Reviews { get; } + /// /// Gets the pull request's changed files. /// @@ -165,6 +170,11 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse /// ReactiveCommand OpenOnGitHub { get; } + /// + /// Gets a command that navigates to a pull request review. + /// + ReactiveCommand ShowReview { get; } + /// /// Initializes the view model. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs new file mode 100644 index 0000000000..178a1d8d0f --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs @@ -0,0 +1,35 @@ +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Displays a short overview of a pull request review in the . + /// + public interface IPullRequestReviewSummaryViewModel + { + /// + /// Gets the ID of the pull request review. + /// + long Id { get; set; } + + /// + /// Gets the user who submitted the review. + /// + IAccount User { get; set; } + + /// + /// Gets the state of the review. + /// + PullRequestReviewState State { get; set; } + + /// + /// Gets a string representing the state of the review. + /// + string StateDisplay { get; } + + /// + /// Gets the number of file comments in the review. + /// + int FileCommentCount { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs b/src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs new file mode 100644 index 0000000000..42d186302b --- /dev/null +++ b/src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace GitHub.UI +{ + public class NotEqualsToVisibilityConverter : MarkupExtension, IValueConverter + { + readonly string collapsedValue; + + public NotEqualsToVisibilityConverter(string collapsedValue) + { + this.collapsedValue = collapsedValue; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString() != collapsedValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) => this; + } +} diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index ed3bbb281e..6fcc20e64a 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -92,6 +92,7 @@ + diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs index c69eb5a5c3..c1461c477f 100644 --- a/src/GitHub.VisualStudio.UI/Resources.Designer.cs +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to Add your review. + /// + public static string AddYourReview { + get { + return ResourceManager.GetString("AddYourReview", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid authentication code. /// @@ -150,6 +159,15 @@ public static string CompareFileAsDefaultAction { } } + /// + /// Looks up a localized string similar to Continue your review. + /// + public static string ContinueYourReview { + get { + return ResourceManager.GetString("ContinueYourReview", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not connect to github.com. /// @@ -753,6 +771,15 @@ public static string resendCodeButtonToolTip { } } + /// + /// Looks up a localized string similar to Reviewers. + /// + public static string Reviewers { + get { + return ResourceManager.GetString("Reviewers", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sign in.... /// diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index 9f7b318db5..6bc1e1e436 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -401,4 +401,13 @@ Token + + Continue your review + + + Add your review + + + Reviewers + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index acf8349a34..ac1602410c 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -382,6 +382,9 @@ GitHubPaneView.xaml + + PullRequestReviewSummaryView.xaml + PullRequestFilesView.xaml @@ -537,6 +540,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml index 00976c6ee0..76d6f5d1e4 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -80,7 +80,7 @@ - + @@ -237,14 +237,16 @@ - + + + @@ -252,7 +254,7 @@ + Height="16" Margin="0,0,0,1"/> @@ -271,6 +273,19 @@ View conversation on GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | + + + + + + + + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs new file mode 100644 index 0000000000..a2c397b589 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public partial class PullRequestReviewSummaryView : UserControl + { + public PullRequestReviewSummaryView() + { + InitializeComponent(); + } + } +} diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs index a344f1499a..c4dd110a8d 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; @@ -15,7 +17,7 @@ namespace UnitTests.GitHub.App.ViewModels.GitHubPane { - public class PullRequestDetailViewModelTests : TestBaseClass + public class PullRequestDetailViewModelTests { static readonly Uri Uri = new Uri("http://foo"); @@ -26,19 +28,19 @@ public async Task ShouldUsePlaceholderBodyIfNoneExists() { var target = CreateTarget(); - await target.Load(CreatePullRequest(body: string.Empty)); + await target.Load(CreatePullRequestModel(body: string.Empty)); Assert.That("*No description provided.*", Is.EqualTo(target.Body)); } } - public class TheHeadProperty + public class TheHeadProperty : TestBaseClass { [Test] public async Task ShouldAcceptNullHead() { var target = CreateTarget(); - var model = CreatePullRequest(); + var model = CreatePullRequestModel(); // PullRequest.Head can be null for example if a user deletes the repository after creating the PR. model.Head = null; @@ -49,7 +51,98 @@ public async Task ShouldAcceptNullHead() } } - public class TheCheckoutCommand + public class TheReviewsProperty : TestBaseClass + { + [Test] + public async Task ShouldShowLatestAcceptedOrChangesRequestedReview() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel(1, "grokys", PullRequestReviewState.ChangesRequested), + CreatePullRequestReviewModel(2, "shana", PullRequestReviewState.ChangesRequested), + CreatePullRequestReviewModel(3, "grokys", PullRequestReviewState.Approved), + CreatePullRequestReviewModel(4, "grokys", PullRequestReviewState.Commented)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(3)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[1].User.Login, Is.EqualTo("shana")); + Assert.That(target.Reviews[2].User, Is.Null); + Assert.That(target.Reviews[0].Id, Is.EqualTo(3)); + Assert.That(target.Reviews[1].Id, Is.EqualTo(2)); + Assert.That(target.Reviews[2].Id, Is.EqualTo(0)); + } + + [Test] + public async Task ShouldShowLatestCommentedReviewIfNothingElsePresent() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel(1, "grokys", PullRequestReviewState.Commented), + CreatePullRequestReviewModel(2, "grokys", PullRequestReviewState.Commented)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(2)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[1].User, Is.Null); + Assert.That(target.Reviews[0].Id, Is.EqualTo(2)); + } + + [Test] + public async Task ShouldNotShowStartNewReviewWhenHasPendingReview() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel(1, "grokys", PullRequestReviewState.Pending)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(1)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo(1)); + } + + [Test] + public async Task ShouldShowPendingReviewOverApproved() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel(1, "grokys", PullRequestReviewState.Approved), + CreatePullRequestReviewModel(2, "grokys", PullRequestReviewState.Pending)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(1)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo(2)); + } + + static PullRequestModel CreatePullRequestModel( + params IPullRequestReviewModel[] reviews) + { + return PullRequestDetailViewModelTests.CreatePullRequestModel(reviews: reviews); + } + + static PullRequestReviewModel CreatePullRequestReviewModel( + long id, + string login, + PullRequestReviewState state) + { + var account = Substitute.For(); + account.Login.Returns(login); + + return new PullRequestReviewModel + { + Id = id, + User = account, + State = state, + }; + } + } + + public class TheCheckoutCommand : TestBaseClass { [Test] public async Task CheckedOutAndUpToDate() @@ -58,7 +151,7 @@ public async Task CheckedOutAndUpToDate() currentBranch: "pr/123", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Checkout.CanExecute(null)); Assert.That(target.CheckoutState, Is.Null); @@ -71,7 +164,7 @@ public async Task NotCheckedOut() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Checkout.CanExecute(null)); Assert.True(target.CheckoutState.IsEnabled); @@ -86,7 +179,7 @@ public async Task NotCheckedOutWithWorkingDirectoryDirty() existingPrBranch: "pr/123", dirty: true); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Checkout.CanExecute(null)); Assert.That("Cannot checkout as your working directory has uncommitted changes.", Is.EqualTo(target.CheckoutState.ToolTip)); @@ -99,7 +192,7 @@ public async Task CheckoutExistingLocalBranch() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest(number: 123)); + await target.Load(CreatePullRequestModel(number: 123)); Assert.True(target.Checkout.CanExecute(null)); Assert.That("Checkout pr/123", Is.EqualTo(target.CheckoutState.Caption)); @@ -111,7 +204,7 @@ public async Task CheckoutNonExistingLocalBranch() var target = CreateTarget( currentBranch: "master"); - await target.Load(CreatePullRequest(number: 123)); + await target.Load(CreatePullRequestModel(number: 123)); Assert.True(target.Checkout.CanExecute(null)); Assert.That("Checkout to pr/123", Is.EqualTo(target.CheckoutState.Caption)); @@ -123,7 +216,7 @@ public async Task UpdatesOperationErrorWithExceptionMessage() var target = CreateTarget( currentBranch: "master", existingPrBranch: "pr/123"); - var pr = CreatePullRequest(); + var pr = CreatePullRequestModel(); pr.Head = new GitReferenceModel("source", null, "sha", (string)null); @@ -140,7 +233,7 @@ public async Task SetsOperationErrorOnCheckoutFailure() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Checkout.CanExecute(null)); @@ -156,7 +249,7 @@ public async Task ClearsOperationErrorOnCheckoutSuccess() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Checkout.CanExecute(null)); Assert.ThrowsAsync(async () => await target.Checkout.ExecuteAsyncTask()); @@ -173,7 +266,7 @@ public async Task ClearsOperationErrorOnCheckoutRefresh() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Checkout.CanExecute(null)); Assert.ThrowsAsync(async () => await target.Checkout.ExecuteAsyncTask()); @@ -184,7 +277,7 @@ public async Task ClearsOperationErrorOnCheckoutRefresh() } } - public class ThePullCommand + public class ThePullCommand : TestBaseClass { [Test] public async Task NotCheckedOut() @@ -193,7 +286,7 @@ public async Task NotCheckedOut() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Pull.CanExecute(null)); Assert.That(target.UpdateState, Is.Null); @@ -206,7 +299,7 @@ public async Task CheckedOutAndUpToDate() currentBranch: "pr/123", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Pull.CanExecute(null)); Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -222,7 +315,7 @@ public async Task CheckedOutAndBehind() existingPrBranch: "pr/123", behindBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Pull.CanExecute(null)); Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -239,7 +332,7 @@ public async Task CheckedOutAndAheadAndBehind() aheadBy: 3, behindBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Pull.CanExecute(null)); Assert.That(3, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -256,7 +349,7 @@ public async Task CheckedOutAndBehindFork() prFromFork: true, behindBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Pull.CanExecute(null)); Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -271,14 +364,14 @@ public async Task UpdatesOperationErrorWithExceptionMessage() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.ThrowsAsync(() => target.Pull.ExecuteAsyncTask(null)); Assert.That("Pull threw", Is.EqualTo(target.OperationError)); } } - public class ThePushCommand + public class ThePushCommand : TestBaseClass { [Test] public async Task NotCheckedOut() @@ -287,7 +380,7 @@ public async Task NotCheckedOut() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Push.CanExecute(null)); Assert.That(target.UpdateState, Is.Null); @@ -300,7 +393,7 @@ public async Task CheckedOutAndUpToDate() currentBranch: "pr/123", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Push.CanExecute(null)); Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -316,7 +409,7 @@ public async Task CheckedOutAndAhead() existingPrBranch: "pr/123", aheadBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Push.CanExecute(null)); Assert.That(2, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -332,7 +425,7 @@ public async Task CheckedOutAndBehind() existingPrBranch: "pr/123", behindBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Push.CanExecute(null)); Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -349,7 +442,7 @@ public async Task CheckedOutAndAheadAndBehind() aheadBy: 3, behindBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.False(target.Push.CanExecute(null)); Assert.That(3, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -366,7 +459,7 @@ public async Task CheckedOutAndAheadOfFork() prFromFork: true, aheadBy: 2); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.True(target.Push.CanExecute(null)); Assert.That(2, Is.EqualTo(target.UpdateState.CommitsAhead)); @@ -381,7 +474,7 @@ public async Task UpdatesOperationErrorWithExceptionMessage() currentBranch: "master", existingPrBranch: "pr/123"); - await target.Load(CreatePullRequest()); + await target.Load(CreatePullRequestModel()); Assert.ThrowsAsync(() => target.Push.ExecuteAsyncTask(null)); Assert.That("Push threw", Is.EqualTo(target.OperationError)); @@ -454,9 +547,19 @@ static Tuple CreateTargetAndSer pullRequestService.CalculateHistoryDivergence(repository, Arg.Any()) .Returns(Observable.Return(divergence)); + if (sessionManager == null) + { + var currentSession = Substitute.For(); + currentSession.User.Login.Returns("[CurrentUser]"); + + sessionManager = Substitute.For(); + sessionManager.CurrentSession.Returns(currentSession); + sessionManager.GetSession(null).ReturnsForAnyArgs(currentSession); + } + var vm = new PullRequestDetailViewModel( pullRequestService, - sessionManager ?? Substitute.For(), + sessionManager, Substitute.For(), Substitute.For(), Substitute.For(), @@ -467,7 +570,10 @@ static Tuple CreateTargetAndSer return Tuple.Create(vm, pullRequestService); } - static PullRequestModel CreatePullRequest(int number = 1, string body = "PR Body") + static PullRequestModel CreatePullRequestModel( + int number = 1, + string body = "PR Body", + IEnumerable reviews = null) { var author = Substitute.For(); @@ -477,6 +583,7 @@ static PullRequestModel CreatePullRequest(int number = 1, string body = "PR Body Body = string.Empty, Head = new GitReferenceModel("source", "foo:baz", "sha", "https://github.com/foo/bar.git"), Base = new GitReferenceModel("dest", "foo:bar", "sha", "https://github.com/foo/bar.git"), + Reviews = reviews.ToList(), }; } From 63691853aaa5c2c3757b01de2c101d1b34ee8ca5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Mar 2018 18:24:39 +0100 Subject: [PATCH 23/78] Use resource for pending review b/g. --- src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml | 1 + src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml | 1 + src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml | 1 + .../Views/GitHubPane/PullRequestReviewSummaryView.xaml | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml index f2a8b1f284..215464137f 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml @@ -63,5 +63,6 @@ + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml index 2a2dd5f175..231176b069 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml @@ -63,5 +63,6 @@ + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml index cbaa1fa55d..65d7a28d90 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml @@ -63,5 +63,6 @@ + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml index b4c7015389..c0b50d5006 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml @@ -39,7 +39,7 @@ - + From 2de559aec754c3523cb0f60403bcd270cf33d77d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Mar 2018 18:39:41 +0100 Subject: [PATCH 24/78] Removed unused code. --- .../GitHubPane/PullRequestReviewSummaryViewModel.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs index 35c6cb9183..7acbb83657 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs @@ -11,15 +11,6 @@ namespace GitHub.ViewModels.GitHubPane /// public class PullRequestReviewSummaryViewModel : IPullRequestReviewSummaryViewModel { - private static Dictionary StatePriorities = new Dictionary - { - { PullRequestReviewState.Approved, 1 }, - { PullRequestReviewState.ChangesRequested, 1 }, - { PullRequestReviewState.Commented, 0 }, - { PullRequestReviewState.Dismissed, 0 }, - { PullRequestReviewState.Pending, 2 }, - }; - /// public long Id { get; set; } From b04ef7608c381113de9963fcff4a2805fa8781d7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Mar 2018 19:01:42 +0100 Subject: [PATCH 25/78] Fix broken tests. --- .../ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs index c4dd110a8d..972b2f4ef9 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -577,6 +577,8 @@ static PullRequestModel CreatePullRequestModel( { var author = Substitute.For(); + reviews = reviews ?? new IPullRequestReviewModel[0]; + return new PullRequestModel(number, "PR 1", author, DateTimeOffset.Now) { State = PullRequestStateEnum.Open, From 8fa672355067f8261969ad992dabe7557faf5b6d Mon Sep 17 00:00:00 2001 From: Don Okuda Date: Tue, 13 Mar 2018 11:19:13 -0700 Subject: [PATCH 26/78] Update pending reivew bg color on dark theme --- src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml index 231176b069..47ec2ac1f9 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml @@ -63,6 +63,6 @@ - + \ No newline at end of file From 8b6a42449b4189092995aad79b0bcb88b3cd1087 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 14 Mar 2018 10:54:13 +0100 Subject: [PATCH 27/78] Don't show other users' pending reviews. --- .../GitHubPane/PullRequestDetailViewModel.cs | 2 +- .../PullRequestReviewSummaryViewModel.cs | 9 ++++++- .../PullRequestDetailViewModelTests.cs | 26 ++++++++++++++----- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index aeea37e939..145493c5ee 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -369,7 +369,7 @@ public async Task Load(IPullRequestModel pullRequest) TargetBranchDisplayName = GetBranchDisplayName(IsFromFork, pullRequest.Base?.Label); CommentCount = pullRequest.Comments.Count + pullRequest.ReviewComments.Count; Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; - Reviews = PullRequestReviewSummaryViewModel.BuildByUser(pullRequest).ToList(); + Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, pullRequest).ToList(); await Files.InitializeAsync(Session); diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs index 7acbb83657..b321b35bbe 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs @@ -29,17 +29,23 @@ public class PullRequestReviewSummaryViewModel : IPullRequestReviewSummaryViewMo /// /// Builds a collection of s by user. /// + /// The current user. /// The pull request model. /// /// This method builds a list similar to that found in the "Reviewers" section at the top- /// right of the Pull Request page on GitHub. /// - public static IEnumerable BuildByUser(IPullRequestModel pullRequest) + public static IEnumerable BuildByUser( + IAccount currentUser, + IPullRequestModel pullRequest) { var existing = new Dictionary(); foreach (var review in pullRequest.Reviews.OrderBy(x => x.Id)) { + if (review.State == PullRequestReviewState.Pending && review.User.Login != currentUser.Login) + continue; + PullRequestReviewSummaryViewModel previous; existing.TryGetValue(review.User.Login, out previous); @@ -68,6 +74,7 @@ public static IEnumerable BuildByUser(IPullRe var newReview = new PullRequestReviewSummaryViewModel { State = PullRequestReviewState.Pending, + User = currentUser, }; result = result.Concat(new[] { newReview }); } diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs index 972b2f4ef9..b3c216944e 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -68,7 +68,7 @@ public async Task ShouldShowLatestAcceptedOrChangesRequestedReview() Assert.That(target.Reviews, Has.Count.EqualTo(3)); Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); Assert.That(target.Reviews[1].User.Login, Is.EqualTo("shana")); - Assert.That(target.Reviews[2].User, Is.Null); + Assert.That(target.Reviews[2].User.Login, Is.EqualTo("grokys")); Assert.That(target.Reviews[0].Id, Is.EqualTo(3)); Assert.That(target.Reviews[1].Id, Is.EqualTo(2)); Assert.That(target.Reviews[2].Id, Is.EqualTo(0)); @@ -79,14 +79,14 @@ public async Task ShouldShowLatestCommentedReviewIfNothingElsePresent() { var target = CreateTarget(); var model = CreatePullRequestModel( - CreatePullRequestReviewModel(1, "grokys", PullRequestReviewState.Commented), - CreatePullRequestReviewModel(2, "grokys", PullRequestReviewState.Commented)); + CreatePullRequestReviewModel(1, "shana", PullRequestReviewState.Commented), + CreatePullRequestReviewModel(2, "shana", PullRequestReviewState.Commented)); await target.Load(model); Assert.That(target.Reviews, Has.Count.EqualTo(2)); - Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); - Assert.That(target.Reviews[1].User, Is.Null); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("shana")); + Assert.That(target.Reviews[1].User.Login, Is.EqualTo("grokys")); Assert.That(target.Reviews[0].Id, Is.EqualTo(2)); } @@ -119,6 +119,20 @@ public async Task ShouldShowPendingReviewOverApproved() Assert.That(target.Reviews[0].Id, Is.EqualTo(2)); } + [Test] + public async Task ShouldNotShowPendingReviewForOtherUser() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel(1, "shana", PullRequestReviewState.Pending)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(1)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo(0)); + } + static PullRequestModel CreatePullRequestModel( params IPullRequestReviewModel[] reviews) { @@ -550,7 +564,7 @@ static Tuple CreateTargetAndSer if (sessionManager == null) { var currentSession = Substitute.For(); - currentSession.User.Login.Returns("[CurrentUser]"); + currentSession.User.Login.Returns("grokys"); sessionManager = Substitute.For(); sessionManager.CurrentSession.Returns(currentSession); From f0fca863aa4a068bb77b56b00b17313c12fe0949 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 10:57:37 +0100 Subject: [PATCH 28/78] Fix broken merge. --- src/GitHub.App/GitHub.App.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 157f13342b..46cd1945a0 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -147,15 +147,11 @@ ..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll True + ..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll True - - False - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - False - From cbb529a428395c7d33de36bb785ac8086d3e8592 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 11:30:31 +0100 Subject: [PATCH 29/78] Ported changes from #1533. Update the header for pull request description. --- .../GitHubPane/PullRequestDetailView.xaml | 260 +++++++++--------- 1 file changed, 133 insertions(+), 127 deletions(-) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml index 76d6f5d1e4..85419b6c70 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -20,11 +20,6 @@ - @@ -88,34 +83,29 @@ + + + + + - - + - - - - - - - + - - - - - + - @@ -125,94 +115,36 @@ - - + - + - + + + - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - + + + + + - - - - - - - - - + + @@ -249,34 +181,108 @@ - - - - - - - + + + View on GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - View conversation on GitHub - + + + + + + + + + + + + + + + + + HeaderText="Reviewers" + IsExpanded="True" + Margin="0 8 0 0" + Grid.Row="2"> @@ -291,10 +297,10 @@ Grid.Row="4" IsExpanded="True" HeaderText="{Binding Files.ChangedFilesCount, StringFormat={x:Static prop:Resources.ChangesCountFormat}}" - Margin="0 5 10 0"> + Margin="0 8 10 0"> - + \ No newline at end of file From 87dd7ab0aef2cc3661b356ea426af1939a4daed3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 12:34:50 +0100 Subject: [PATCH 30/78] Fix GitHubPane route regex. Need to make sure we match the start and end of the string. --- src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs index d4784540a4..1f6cb01470 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs @@ -408,7 +408,7 @@ static async Task IsValidRepository(ISimpleApiClient client) static Regex CreateRoute(string route) { // Build RegEx from route (:foo to named group (?[\w_.-]+)). - var routeFormat = new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.-]+)"); + var routeFormat = "^" + new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.-]+)") + "$"; return new Regex(routeFormat, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); } } From 0c86084d70167076d04759cb3331989dc62fec6d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 13:22:19 +0100 Subject: [PATCH 31/78] Added IModelService.GetUser We need to be able to read a user model by their login from the API. --- src/GitHub.App/Api/ApiClient.cs | 5 +++++ src/GitHub.App/Services/ModelService.cs | 7 +++++++ src/GitHub.Exports.Reactive/Api/IApiClient.cs | 1 + src/GitHub.Exports.Reactive/Services/IModelService.cs | 1 + 4 files changed, 14 insertions(+) diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs index 632db4c807..09e46411b1 100644 --- a/src/GitHub.App/Api/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -88,6 +88,11 @@ public IObservable GetUser() return gitHubClient.User.Current(); } + public IObservable GetUser(string login) + { + return gitHubClient.User.Get(login); + } + public IObservable GetOrganizations() { // Organization.GetAllForCurrent doesn't return all of the information we need (we diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index 15f1321021..31a9e1ece7 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -57,6 +57,13 @@ public IObservable GetCurrentUser() return GetUserFromCache().Select(Create); } + public IObservable GetUser(string login) + { + return hostCache.GetAndRefreshObject("user|" + login, + () => ApiClient.GetUser(login).Select(AccountCacheItem.Create), TimeSpan.FromMinutes(5), TimeSpan.FromDays(7)) + .Select(Create); + } + public IObservable GetGitIgnoreTemplates() { return Observable.Defer(() => diff --git a/src/GitHub.Exports.Reactive/Api/IApiClient.cs b/src/GitHub.Exports.Reactive/Api/IApiClient.cs index 66e0a19321..32fc0cfd70 100644 --- a/src/GitHub.Exports.Reactive/Api/IApiClient.cs +++ b/src/GitHub.Exports.Reactive/Api/IApiClient.cs @@ -16,6 +16,7 @@ public interface IApiClient IObservable CreateRepository(NewRepository repository, string login, bool isUser); IObservable CreateGist(NewGist newGist); IObservable GetUser(); + IObservable GetUser(string login); IObservable GetOrganizations(); /// /// Retrieves all repositories that belong to this user. diff --git a/src/GitHub.Exports.Reactive/Services/IModelService.cs b/src/GitHub.Exports.Reactive/Services/IModelService.cs index f5e11a9168..8dc757a57e 100644 --- a/src/GitHub.Exports.Reactive/Services/IModelService.cs +++ b/src/GitHub.Exports.Reactive/Services/IModelService.cs @@ -17,6 +17,7 @@ public interface IModelService : IDisposable IApiClient ApiClient { get; } IObservable GetCurrentUser(); + IObservable GetUser(string login); IObservable InsertUser(AccountCacheItem user); IObservable> GetAccounts(); IObservable GetRepository(string owner, string repo); From 44adbb383a42d8829e087aadcecef70649bc56d1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 14:21:00 +0100 Subject: [PATCH 32/78] Fix dark theme foreground color. --- .../Views/GitHubPane/PullRequestFilesView.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml index 219458683a..b344d21c01 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml @@ -43,6 +43,7 @@ From f215aebfef30fca7caba513ea84a141a492fb7da Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 14:36:30 +0100 Subject: [PATCH 33/78] Added TrimNewlinesConverter. This is needed to ensure comment summaries appear without newlines. --- .../Converters/TrimNewlinesConverter.cs | 20 +++++++++++++++++++ src/GitHub.UI/GitHub.UI.csproj | 1 + 2 files changed, 21 insertions(+) create mode 100644 src/GitHub.UI/Converters/TrimNewlinesConverter.cs diff --git a/src/GitHub.UI/Converters/TrimNewlinesConverter.cs b/src/GitHub.UI/Converters/TrimNewlinesConverter.cs new file mode 100644 index 0000000000..9760a2a146 --- /dev/null +++ b/src/GitHub.UI/Converters/TrimNewlinesConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace GitHub.UI +{ + /// + /// An that trims newlines from a string and replaces them + /// with spaces. + /// + public class TrimNewlinesConverter : ValueConverterMarkupExtension + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var text = value as string; + if (String.IsNullOrEmpty(text)) return null; + return Regex.Replace(text, @"\t|\n|\r", " "); + } + } +} diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index 6fcc20e64a..82703f4f1c 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -98,6 +98,7 @@ + From b35ddfc59468fe4f7299337c9ce4b918e2eb0dcf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 14:37:06 +0100 Subject: [PATCH 34/78] Added TrimmedPathTextBlock. Displays a path and intelligently trims with ellipsis when the path doesn't fit in the allocated size. --- .../Controls/TrimmedPathTextBlock.cs | 161 ++++++++++++++++++ src/GitHub.UI/GitHub.UI.csproj | 1 + 2 files changed, 162 insertions(+) create mode 100644 src/GitHub.UI/Controls/TrimmedPathTextBlock.cs diff --git a/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs b/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs new file mode 100644 index 0000000000..d272f9fd77 --- /dev/null +++ b/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs @@ -0,0 +1,161 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace GitHub.UI +{ + /// + /// TextBlock that displays a path and intelligently trims with ellipsis when the path doesn't + /// fit in the allocated size. + /// + /// + /// When displaying a path that is too long for its allocated space, we need to trim the path + /// with ellipses intelligently instead of simply trimming the end (as this is the filename + /// which is the most important part!). This control trims a path in the following manner with + /// decreasing allocated space: + /// + /// - VisualStudio\src\GitHub.UI\Controls\TrimmedPathTextBlock.cs + /// - VisualStudio\...\GitHub.UI\Controls\TrimmedPathTextBlock.cs + /// - VisualStudio\...\...\Controls\TrimmedPathTextBlock.cs + /// - VisualStudio\...\...\...\TrimmedPathTextBlock.cs + /// - ...\...\...\...\TrimmedPathTextBlock.cs + /// + public class TrimmedPathTextBlock : FrameworkElement + { + public static readonly DependencyProperty FontFamilyProperty = + TextBlock.FontFamilyProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontSizeProperty = + TextBlock.FontSizeProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontStretchProperty = + TextBlock.FontStretchProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontStyleProperty = + TextBlock.FontStyleProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontWeightProperty = + TextBlock.FontWeightProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty ForegroundProperty = + TextBlock.ForegroundProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty TextProperty = + TextBlock.TextProperty.AddOwner( + typeof(TrimmedPathTextBlock), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, + TextChanged)); + + FormattedText formattedText; + FormattedText renderText; + + public FontFamily FontFamily + { + get { return (FontFamily)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public FontStretch FontStretch + { + get { return (FontStretch)GetValue(FontStretchProperty); } + set { SetValue(FontStretchProperty, value); } + } + + public FontStyle FontStyle + { + get { return (FontStyle)GetValue(FontStyleProperty); } + set { SetValue(FontStyleProperty, value); } + } + + public FontWeight FontWeight + { + get { return (FontWeight)GetValue(FontWeightProperty); } + set { SetValue(FontWeightProperty, value); } + } + + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + protected FormattedText FormattedText + { + get + { + if (formattedText == null && Text != null) + { + formattedText = CreateFormattedText(Text); + } + + return formattedText; + } + } + + protected override Size MeasureOverride(Size availableSize) + { + var parts = Text + .Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) + .ToList(); + var nextPart = Math.Min(1, parts.Count - 1); + + while (true) + { + renderText = CreateFormattedText(string.Join(Path.DirectorySeparatorChar.ToString(), parts)); + + if (renderText.Width <= availableSize.Width || nextPart == -1) + break; + + parts[nextPart] = "\u2026"; + + if (nextPart == 0) + nextPart = -1; + else if (nextPart == parts.Count - 2) + nextPart = 0; + else + nextPart++; + }; + + return new Size(renderText.Width, renderText.Height); + } + + protected override void OnRender(DrawingContext drawingContext) + { + drawingContext.DrawText(renderText, new Point()); + } + + FormattedText CreateFormattedText(string text) + { + return new FormattedText( + text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), + FontSize, + Foreground); + } + + static void TextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + var textBlock = sender as TrimmedPathTextBlock; + + if (textBlock != null) + { + textBlock.formattedText = null; + textBlock.renderText = null; + } + } + } +} diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index 82703f4f1c..20a45d3522 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -88,6 +88,7 @@ Spinner.xaml + From 8d8b7c700b6fd1e2eb266a6bad72dc90ac1e5afb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 14:37:36 +0100 Subject: [PATCH 35/78] Restyle expander The default expander style doesn't fit with the VS UI theme - set a new default style which uses a triangle octicon. --- src/GitHub.UI/Assets/Controls.xaml | 114 +++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/GitHub.UI/Assets/Controls.xaml b/src/GitHub.UI/Assets/Controls.xaml index 47977c775a..ab1683e74a 100644 --- a/src/GitHub.UI/Assets/Controls.xaml +++ b/src/GitHub.UI/Assets/Controls.xaml @@ -419,7 +419,121 @@ + + + + + + + + + + From d5ac99a2f90df1cf38b33efcf84cc6e00102040e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 14:38:37 +0100 Subject: [PATCH 36/78] Display user reviews. When the user clicks on a "Reviewer" item in the PR details view, navigate to a new view to show the PR reviews by that user. --- src/GitHub.App/GitHub.App.csproj | 6 + .../Models/PullRequestReviewModel.cs | 1 + ...questReviewFileCommentViewModelDesigner.cs | 13 ++ .../PullRequestReviewViewModelDesigner.cs | 85 +++++++++ ...PullRequestUserReviewsViewModelDesigner.cs | 77 ++++++++ .../GitHubPane/GitHubPaneViewModel.cs | 23 +++ .../GitHubPane/PullRequestDetailViewModel.cs | 12 +- .../PullRequestReviewFileCommentViewModel.cs | 42 +++++ .../GitHubPane/PullRequestReviewViewModel.cs | 148 +++++++++++++++ .../PullRequestUserReviewsViewModel.cs | 175 ++++++++++++++++++ .../GitHub.Exports.Reactive.csproj | 3 + .../IPullRequestReviewFileCommentViewModel.cs | 27 +++ .../GitHubPane/IPullRequestReviewViewModel.cs | 66 +++++++ .../IPullRequestUserReviewsViewModel.cs | 77 ++++++++ .../Models/IPullRequestReviewModel.cs | 5 + .../GitHubPane/IGitHubPaneViewModel.cs | 9 + .../GitHub.VisualStudio.csproj | 14 ++ .../PullRequestFileCommentsView.xaml | 84 +++++++++ .../PullRequestFileCommentsView.xaml.cs | 28 +++ .../PullRequestUserReviewsView.xaml | 105 +++++++++++ .../PullRequestUserReviewsView.xaml.cs | 17 ++ 21 files changed, 1016 insertions(+), 1 deletion(-) create mode 100644 src/GitHub.App/SampleData/PullRequestReviewFileCommentViewModelDesigner.cs create mode 100644 src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs create mode 100644 src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs create mode 100644 src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewFileCommentViewModel.cs create mode 100644 src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs create mode 100644 src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml create mode 100644 src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 46cd1945a0..39bce4ff38 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -212,6 +212,9 @@ + + + @@ -240,7 +243,10 @@ + + + diff --git a/src/GitHub.App/Models/PullRequestReviewModel.cs b/src/GitHub.App/Models/PullRequestReviewModel.cs index ff7ad40d13..aab24ab076 100644 --- a/src/GitHub.App/Models/PullRequestReviewModel.cs +++ b/src/GitHub.App/Models/PullRequestReviewModel.cs @@ -10,5 +10,6 @@ public class PullRequestReviewModel : IPullRequestReviewModel 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/PullRequestReviewFileCommentViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewFileCommentViewModelDesigner.cs new file mode 100644 index 0000000000..634cea4abc --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestReviewFileCommentViewModelDesigner.cs @@ -0,0 +1,13 @@ +using System.Reactive; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestReviewFileCommentViewModelDesigner : IPullRequestReviewFileCommentViewModel + { + public string Body { get; set; } + public string RelativePath { get; set; } + public ReactiveCommand Open { get; } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs new file mode 100644 index 0000000000..c2724e35a3 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestReviewViewModelDesigner : PanePageViewModelBase, IPullRequestReviewViewModel + { + public PullRequestReviewViewModelDesigner() + { + 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)); + + Model = new PullRequestReviewModel + { + + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + User = new AccountDesigner { Login = "Haacked", IsUser = true }, + }; + + Body = @"Just a few comments. I don't feel too strongly about them though. + +Otherwise, very nice work here! ✨"; + + StateDisplay = "approved"; + + FileComments = new[] + { + new PullRequestReviewFileCommentViewModelDesigner + { + Body = @"These should probably be properties. Most likely they should be readonly properties. I know that makes creating instances of these not look as nice as using property initializers when constructing an instance, but if these properties should never be mutated after construction, then it guides future consumers to the right behavior. + +However, if you're two-way binding these properties to a UI, then ignore the readonly part and make them properties. But in that case they should probably be reactive properties (or implement INPC).", + RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs", + }, + new PullRequestReviewFileCommentViewModelDesigner + { + Body = "While I have no problems with naming a variable ass I think we should probably avoid swear words in case Microsoft runs their Policheck tool against this code.", + RelativePath = "src/GitHub.App/ViewModels/PullRequestListViewModel.cs", + }, + }; + + OutdatedFileComments = new[] + { + new PullRequestReviewFileCommentViewModelDesigner + { + Body = @"So this is just casting a mutable list to an IReadOnlyList which can be cast back to List. I know we probably won't do that, but I'm thinking of the next person to come along. The safe thing to do is to wrap List with a ReadOnlyList. We have an extension method ToReadOnlyList for observables. Wouldn't be hard to write one for IEnumerable.", + RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs", + }, + }; + } + + public string Body { get; } + public IReadOnlyList FileComments { get; set; } + public bool IsExpanded { get; set; } + public bool HasDetails { get; set; } + public ILocalRepositoryModel LocalRepository { get; set; } + public IPullRequestReviewModel Model { get; set; } + public ReactiveCommand NavigateToPullRequest { get; } + public IReadOnlyList OutdatedFileComments { get; set; } + public IPullRequestModel PullRequestModel { get; set; } + public string RemoteRepositoryOwner { get; set; } + public string StateDisplay { get; set; } + + public Task InitializeAsync( + ILocalRepositoryModel localRepository, + string owner, + IPullRequestModel pullRequest, + long pullRequestReviewId) + { + throw new NotImplementedException(); + } + + public Task Load(IPullRequestModel pullRequest) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs new file mode 100644 index 0000000000..9124c44c46 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestUserReviewsViewModelDesigner : PanePageViewModelBase, IPullRequestUserReviewsViewModel + { + public PullRequestUserReviewsViewModelDesigner() + { + User = new AccountDesigner { Login = "Haacked", IsUser = true }; + PullRequestNumber = 123; + PullRequestTitle = "Error handling/bubbling from viewmodels to views to viewhosts"; + Reviews = new[] + { + new PullRequestReviewViewModelDesigner() + { + IsExpanded = true, + HasDetails = true, + FileComments = new PullRequestReviewFileCommentViewModel[0], + StateDisplay = "approved", + Model = new PullRequestReviewModel + { + State = PullRequestReviewState.Approved, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + User = User, + }, + }, + new PullRequestReviewViewModelDesigner() + { + IsExpanded = true, + HasDetails = true, + StateDisplay = "requested changes", + Model = new PullRequestReviewModel + { + State = PullRequestReviewState.ChangesRequested, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + User = User, + }, + }, + new PullRequestReviewViewModelDesigner() + { + IsExpanded = false, + HasDetails = false, + StateDisplay = "commented", + Model = new PullRequestReviewModel + { + State = PullRequestReviewState.Commented, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + User = User, + }, + } + }; + } + + public ILocalRepositoryModel LocalRepository { get; set; } + public string RemoteRepositoryOwner { get; set; } + public int PullRequestNumber { get; set; } + public IAccount User { get; set; } + public IReadOnlyList Reviews { get; set; } + public string PullRequestTitle { get; set; } + public ReactiveCommand NavigateToPullRequest { get; } + + public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int pullRequestNumber, string login) + { + return Task.CompletedTask; + } + + public Task Load(IAccount user, IPullRequestModel pullRequest) + { + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs index 1f6cb01470..1d949821fe 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs @@ -33,6 +33,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I { static readonly ILogger log = LogManager.ForContext(); static readonly Regex pullUri = CreateRoute("/:owner/:repo/pull/:number"); + static readonly Regex pullUserReviewsUri = CreateRoute("/:owner/:repo/pull/:number/reviews/:login"); readonly IViewViewModelFactory viewModelFactory; readonly ISimpleApiClientFactory apiClientFactory; @@ -243,6 +244,14 @@ public async Task NavigateTo(Uri uri) var number = int.Parse(match.Groups["number"].Value); await ShowPullRequest(owner, repo, number); } + else if ((match = pullUserReviewsUri.Match(uri.AbsolutePath))?.Success == true) + { + var owner = match.Groups["owner"].Value; + var repo = match.Groups["repo"].Value; + var number = int.Parse(match.Groups["number"].Value); + var login = match.Groups["login"].Value; + await ShowPullRequestReviews(owner, repo, number, login); + } else { throw new NotSupportedException("Unrecognised GitHub pane URL: " + uri.AbsolutePath); @@ -282,6 +291,20 @@ public Task ShowPullRequest(string owner, string repo, int number) x => x.RemoteRepositoryOwner == owner && x.LocalRepository.Name == repo && x.Number == number); } + /// + public Task ShowPullRequestReviews(string owner, string repo, int number, string login) + { + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(repo, nameof(repo)); + + return NavigateTo( + x => x.InitializeAsync(LocalRepository, Connection, owner, repo, number, login), + x => x.RemoteRepositoryOwner == owner && + x.LocalRepository.Name == repo && + x.PullRequestNumber == number && + x.User.Login == login); + } + async Task CreateInitializeTask(IServiceProvider paneServiceProvider) { await UpdateContent(teamExplorerContext.ActiveRepository); diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index 145493c5ee..6144bd920f 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -18,6 +18,7 @@ using LibGit2Sharp; using ReactiveUI; using Serilog; +using static System.FormattableString; namespace GitHub.ViewModels.GitHubPane { @@ -636,7 +637,16 @@ async Task DoSyncSubmodules(object unused) void DoShowReview(object item) { - // TODO + var review = (PullRequestReviewSummaryViewModel)item; + + if (review.State == PullRequestReviewState.Pending) + { + throw new NotImplementedException(); + } + else + { + NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{Number}/reviews/{review.User.Login}")); + } } class CheckoutCommandState : IPullRequestCheckoutState diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewFileCommentViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewFileCommentViewModel.cs new file mode 100644 index 0000000000..4af04d73b4 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewFileCommentViewModel.cs @@ -0,0 +1,42 @@ +using System; +using System.Reactive; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// A view model for a file comment in a . + /// + public class PullRequestReviewFileCommentViewModel : IPullRequestReviewFileCommentViewModel + { + readonly IPullRequestEditorService editorService; + readonly IPullRequestSession session; + readonly IPullRequestReviewCommentModel model; + + public PullRequestReviewFileCommentViewModel( + IPullRequestEditorService editorService, + IPullRequestSession session, + IPullRequestReviewCommentModel model) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotNull(model, nameof(model)); + + this.editorService = editorService; + this.session = session; + this.model = model; + } + + /// + public string Body => model.Body; + + /// + public string RelativePath => model.Path; + + /// + public ReactiveCommand Open { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs new file mode 100644 index 0000000000..b11a45d3e3 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using Serilog; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// View model for displaying details of a pull request review. + /// + public class PullRequestReviewViewModel : ViewModelBase, IPullRequestReviewViewModel + { + static readonly ILogger log = LogManager.ForContext(); + + readonly IPullRequestEditorService editorService; + readonly IPullRequestSession session; + + /// + /// Initializes a new instance of the class. + /// + /// The local repository. + /// The pull request's repository owner. + /// The pull request model. + /// The pull request review ID. + public PullRequestReviewViewModel( + IPullRequestEditorService editorService, + IPullRequestSession session, + ILocalRepositoryModel localRepository, + string owner, + IPullRequestModel pullRequest, + long pullRequestReviewId) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotNull(localRepository, nameof(localRepository)); + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(pullRequest, nameof(pullRequest)); + + this.editorService = editorService; + this.session = session; + + LocalRepository = localRepository; + RemoteRepositoryOwner = owner; + Model = GetModel(pullRequest, pullRequestReviewId); + PullRequestModel = pullRequest; + Body = string.IsNullOrWhiteSpace(Model.Body) ? null : Model.Body; + StateDisplay = ToString(Model.State); + + var comments = new List(); + var outdated = new List(); + + foreach (var comment in PullRequestModel.ReviewComments) + { + if (comment.PullRequestReviewId == pullRequestReviewId) + { + var vm = new PullRequestReviewFileCommentViewModel( + editorService, + session, + comment); + + if (comment.Position.HasValue) + comments.Add(vm); + else + outdated.Add(vm); + } + } + + FileComments = comments; + OutdatedFileComments = outdated; + + HasDetails = Body != null || + FileComments.Count > 0 || + OutdatedFileComments.Count > 0; + IsExpanded = HasDetails && CalculateIsLatest(pullRequest, Model); + } + + /// + public ILocalRepositoryModel LocalRepository { get; private set; } + + /// + public string RemoteRepositoryOwner { get; private set; } + + /// + public IPullRequestReviewModel Model { get; } + + /// + public IPullRequestModel PullRequestModel { get; } + + /// + public string Body { get; } + + /// + public string StateDisplay { get; } + + /// + public bool IsExpanded { get; } + + /// + public bool HasDetails { get; } + + /// + public IReadOnlyList FileComments { get; } + + /// + public IReadOnlyList OutdatedFileComments { get; } + + static bool CalculateIsLatest(IPullRequestModel pullRequest, IPullRequestReviewModel model) + { + return !pullRequest.Reviews.Any(x => + x.User.Login == model.User.Login && + x.SubmittedAt > model.SubmittedAt); + } + + static IPullRequestReviewModel GetModel(IPullRequestModel pullRequest, long pullRequestReviewId) + { + var result = pullRequest.Reviews.FirstOrDefault(x => x.Id == pullRequestReviewId); + + if (result == null) + { + throw new KeyNotFoundException( + $"Unable to find review {pullRequestReviewId} in pull request #{pullRequest.Number}"); + } + + return result; + } + + static string ToString(PullRequestReviewState state) + { + switch (state) + { + case PullRequestReviewState.Approved: + return "approved"; + case PullRequestReviewState.ChangesRequested: + return "requested changes"; + case PullRequestReviewState.Commented: + case PullRequestReviewState.Dismissed: + return "commented"; + default: + throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs new file mode 100644 index 0000000000..895ed9adf2 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; +using Serilog; +using static System.FormattableString; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Displays all reviews made by a user on a pull request. + /// + [Export(typeof(IPullRequestUserReviewsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestUserReviewsViewModel : PanePageViewModelBase, IPullRequestUserReviewsViewModel + { + static readonly ILogger log = LogManager.ForContext(); + + readonly IPullRequestEditorService editorService; + readonly IPullRequestSessionManager sessionManager; + readonly IModelServiceFactory modelServiceFactory; + IModelService modelService; + IPullRequestSession session; + IAccount user; + string title; + IReadOnlyList reviews; + + [ImportingConstructor] + public PullRequestUserReviewsViewModel( + IPullRequestEditorService editorService, + IPullRequestSessionManager sessionManager, + IModelServiceFactory modelServiceFactory) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + + this.editorService = editorService; + this.sessionManager = sessionManager; + this.modelServiceFactory = modelServiceFactory; + + NavigateToPullRequest = ReactiveCommand.Create().OnExecuteCompleted(_ => + NavigateTo(Invariant($"{LocalRepository.Owner}/{LocalRepository.Name}/pull/{PullRequestNumber}"))); + } + + /// + public ILocalRepositoryModel LocalRepository { get; private set; } + + /// + public string RemoteRepositoryOwner { get; private set; } + + /// + public int PullRequestNumber { get; private set; } + + public IAccount User + { + get { return user; } + private set { this.RaiseAndSetIfChanged(ref user, value); } + } + + /// + public IReadOnlyList Reviews + { + get { return reviews; } + private set { this.RaiseAndSetIfChanged(ref reviews, value); } + } + + /// + public string PullRequestTitle + { + get { return title; } + private set { this.RaiseAndSetIfChanged(ref title, value); } + } + + /// + public ReactiveCommand NavigateToPullRequest { get; } + + /// + public async Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber, + string login) + { + if (repo != localRepository.Name) + { + throw new NotSupportedException(); + } + + IsLoading = true; + + try + { + LocalRepository = localRepository; + RemoteRepositoryOwner = owner; + PullRequestNumber = pullRequestNumber; + modelService = await modelServiceFactory.CreateAsync(connection); + User = await modelService.GetUser(login); + await Refresh(); + } + finally + { + IsLoading = false; + } + } + + /// + public override async Task Refresh() + { + try + { + Error = null; + IsBusy = true; + var pullRequest = await modelService.GetPullRequest(RemoteRepositoryOwner, LocalRepository.Name, PullRequestNumber); + await Load(User, pullRequest); + } + catch (Exception ex) + { + log.Error( + ex, + "Error loading pull request reviews {Owner}/{Repo}/{Number} from {Address}", + RemoteRepositoryOwner, + LocalRepository.Name, + PullRequestNumber, + modelService.ApiClient.HostAddress.Title); + Error = ex; + IsBusy = false; + } + } + + /// + public async Task Load(IAccount user, IPullRequestModel pullRequest) + { + try + { + session = await sessionManager.GetSession(pullRequest); + User = user; + PullRequestTitle = pullRequest.Title; + + var reviews = new List(); + + foreach (var review in pullRequest.Reviews.OrderByDescending(x => x.SubmittedAt)) + { + if (review.User.Login == user.Login) + { + var vm = new PullRequestReviewViewModel( + editorService, + session, + LocalRepository, + RemoteRepositoryOwner, + pullRequest, + review.Id); + reviews.Add(vm); + } + } + + Reviews = reviews; + } + finally + { + IsBusy = false; + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 07a1464237..d50ae7ae5e 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -197,7 +197,10 @@ + + + diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs new file mode 100644 index 0000000000..4b03eaae05 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Reactive; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Represents a view model for a file comment in an . + /// + public interface IPullRequestReviewFileCommentViewModel + { + /// + /// Gets the body of the comment. + /// + string Body { get; } + + /// + /// Gets the path to the file, relative to the root of the repository. + /// + string RelativePath { get; } + + /// + /// Gets a comment which opens the comment in a diff view. + /// + ReactiveCommand Open { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs new file mode 100644 index 0000000000..6875fe7dc5 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Represents a view model that displays a pull request review. + /// + public interface IPullRequestReviewViewModel : IViewModel + { + /// + /// Gets the local repository. + /// + ILocalRepositoryModel LocalRepository { get; } + + /// + /// Gets the owner of the remote repository that contains the pull request. + /// + /// + /// The remote repository may be different from the local repository if the local + /// repository is a fork and the user is viewing pull requests from the parent repository. + /// + string RemoteRepositoryOwner { get; } + + /// + /// Gets the underlying pull request review model. + /// + IPullRequestReviewModel Model { get; } + + /// + /// Gets the underlying pull request model. + /// + IPullRequestModel PullRequestModel { get; } + + /// + /// Gets the body of the review. + /// + string Body { get; } + + /// + /// Gets the state of the pull request review as a string. + /// + string StateDisplay { get; } + + /// + /// Gets a value indicating whether the pull request review should initially be expanded. + /// + bool IsExpanded { get; } + + /// + /// Gets a value indicating whether the pull request review has a body or file comments. + /// + bool HasDetails { get; } + + /// + /// Gets a list of the file comments in the review. + /// + IReadOnlyList FileComments { get; } + + /// + /// Gets a list of outdated file comments in the review. + /// + IReadOnlyList OutdatedFileComments { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs new file mode 100644 index 0000000000..bc33490083 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Displays all reviews made by a user on a pull request. + /// + public interface IPullRequestUserReviewsViewModel : IPanePageViewModel + { + /// + /// Gets the local repository. + /// + ILocalRepositoryModel LocalRepository { get; } + + /// + /// Gets the owner of the remote repository that contains the pull request. + /// + /// + /// The remote repository may be different from the local repository if the local + /// repository is a fork and the user is viewing pull requests from the parent repository. + /// + string RemoteRepositoryOwner { get; } + + /// + /// Gets the number of the pull request. + /// + int PullRequestNumber { get; } + + /// + /// Gets the reviews made by the . + /// + IReadOnlyList Reviews { get; } + + /// + /// Gets the title of the pull request. + /// + string PullRequestTitle { get; } + + /// + /// Gets the user whose reviews are being shown. + /// + IAccount User { get; } + + /// + /// Gets a command that navigates to the parent pull request in the GitHub pane. + /// + ReactiveCommand NavigateToPullRequest { get; } + + /// + /// Initializes the view model, loading data from the API. + /// + /// The local repository. + /// The connection to the repository host. + /// The pull request's repository owner. + /// The pull request's repository name. + /// The pull request number. + /// The user's login. + Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber, + string login); + + /// + /// Loads the view model from pre-loaded models. + /// + /// The user model. + /// The pull request model. + /// + Task Load(IAccount user, IPullRequestModel pullRequest); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/IPullRequestReviewModel.cs b/src/GitHub.Exports/Models/IPullRequestReviewModel.cs index d59d401e1e..4e633ccb68 100644 --- a/src/GitHub.Exports/Models/IPullRequestReviewModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestReviewModel.cs @@ -67,5 +67,10 @@ public interface IPullRequestReviewModel /// Gets the SHA of the commit that the review was submitted on. /// string CommitId { get; } + + /// + /// Gets the date/time that the review was submitted. + /// + DateTimeOffset? SubmittedAt { get; } } } diff --git a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs index 5b6771c4d3..dc6c4a669f 100644 --- a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs +++ b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs @@ -90,5 +90,14 @@ public interface IGitHubPaneViewModel : IViewModel /// The repository name. /// The pull rqeuest number. Task ShowPullRequest(string owner, string repo, int number); + + /// + /// Shows the pull requests reviews authored by a user. + /// + /// The repository owner. + /// The repository name. + /// The pull rqeuest number. + /// The user login. + Task ShowPullRequestReviews(string owner, string repo, int number, string login); } } \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index ac1602410c..0c9e40efb0 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -382,6 +382,9 @@ GitHubPaneView.xaml + + PullRequestFileCommentsView.xaml + PullRequestReviewSummaryView.xaml @@ -403,6 +406,9 @@ NotAGitRepositoryView.xaml + + PullRequestUserReviewsView.xaml + RepositoryPublishView.xaml @@ -540,6 +546,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -572,6 +582,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml new file mode 100644 index 0000000000..8fb259600e --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs new file mode 100644 index 0000000000..ac60ef8035 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + /// + /// Interaction logic for PullRequestFileCommentsView.xaml + /// + public partial class PullRequestFileCommentsView : UserControl + { + public PullRequestFileCommentsView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml new file mode 100644 index 0000000000..a364f3cf85 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + Reviews by + + for + + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Description + + + + + + Comments + + + + + + Outdated comments + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs new file mode 100644 index 0000000000..f3e1a31cc5 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.Composition; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestUserReviewsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestUserReviewsView : UserControl + { + public PullRequestUserReviewsView() + { + InitializeComponent(); + } + } +} From 6297f67e6cf3208f2e00076a661e0b7c3d3e7f18 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Mar 2018 18:40:45 +0100 Subject: [PATCH 37/78] Added PullRequestUserReviewsViewModel tests. And refactored the `PullRequestUserReviewsViewModel` a little bit to remove stuff that is no longer necessary. --- .../GitHubPane/PullRequestReviewViewModel.cs | 63 ++--- .../PullRequestUserReviewsViewModel.cs | 18 +- .../GitHubPane/IPullRequestReviewViewModel.cs | 19 -- .../IPullRequestUserReviewsViewModel.cs | 8 - .../PullRequestUserReviewsViewModelTests.cs | 232 ++++++++++++++++++ 5 files changed, 256 insertions(+), 84 deletions(-) create mode 100644 test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs index b11a45d3e3..dc1970d3e3 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; using GitHub.Extensions; using GitHub.Logging; using GitHub.Models; using GitHub.Services; +using ReactiveUI; using Serilog; namespace GitHub.ViewModels.GitHubPane @@ -19,44 +18,38 @@ public class PullRequestReviewViewModel : ViewModelBase, IPullRequestReviewViewM readonly IPullRequestEditorService editorService; readonly IPullRequestSession session; + bool isExpanded; /// /// Initializes a new instance of the class. /// - /// The local repository. - /// The pull request's repository owner. + /// The pull request editor service. + /// The pull request session. /// The pull request model. - /// The pull request review ID. + /// The pull request review model. public PullRequestReviewViewModel( IPullRequestEditorService editorService, IPullRequestSession session, - ILocalRepositoryModel localRepository, - string owner, IPullRequestModel pullRequest, - long pullRequestReviewId) + IPullRequestReviewModel model) { Guard.ArgumentNotNull(editorService, nameof(editorService)); Guard.ArgumentNotNull(session, nameof(session)); - Guard.ArgumentNotNull(localRepository, nameof(localRepository)); - Guard.ArgumentNotNull(owner, nameof(owner)); - Guard.ArgumentNotNull(pullRequest, nameof(pullRequest)); + Guard.ArgumentNotNull(model, nameof(model)); this.editorService = editorService; this.session = session; - LocalRepository = localRepository; - RemoteRepositoryOwner = owner; - Model = GetModel(pullRequest, pullRequestReviewId); - PullRequestModel = pullRequest; + Model = model; Body = string.IsNullOrWhiteSpace(Model.Body) ? null : Model.Body; StateDisplay = ToString(Model.State); var comments = new List(); var outdated = new List(); - foreach (var comment in PullRequestModel.ReviewComments) + foreach (var comment in pullRequest.ReviewComments) { - if (comment.PullRequestReviewId == pullRequestReviewId) + if (comment.PullRequestReviewId == model.Id) { var vm = new PullRequestReviewFileCommentViewModel( editorService, @@ -76,21 +69,11 @@ public PullRequestReviewViewModel( HasDetails = Body != null || FileComments.Count > 0 || OutdatedFileComments.Count > 0; - IsExpanded = HasDetails && CalculateIsLatest(pullRequest, Model); } - /// - public ILocalRepositoryModel LocalRepository { get; private set; } - - /// - public string RemoteRepositoryOwner { get; private set; } - /// public IPullRequestReviewModel Model { get; } - /// - public IPullRequestModel PullRequestModel { get; } - /// public string Body { get; } @@ -98,7 +81,11 @@ public PullRequestReviewViewModel( public string StateDisplay { get; } /// - public bool IsExpanded { get; } + public bool IsExpanded + { + get { return isExpanded; } + set { this.RaiseAndSetIfChanged(ref isExpanded, value); } + } /// public bool HasDetails { get; } @@ -109,26 +96,6 @@ public PullRequestReviewViewModel( /// public IReadOnlyList OutdatedFileComments { get; } - static bool CalculateIsLatest(IPullRequestModel pullRequest, IPullRequestReviewModel model) - { - return !pullRequest.Reviews.Any(x => - x.User.Login == model.User.Login && - x.SubmittedAt > model.SubmittedAt); - } - - static IPullRequestReviewModel GetModel(IPullRequestModel pullRequest, long pullRequestReviewId) - { - var result = pullRequest.Reviews.FirstOrDefault(x => x.Id == pullRequestReviewId); - - if (result == null) - { - throw new KeyNotFoundException( - $"Unable to find review {pullRequestReviewId} in pull request #{pullRequest.Number}"); - } - - return result; - } - static string ToString(PullRequestReviewState state) { switch (state) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs index 895ed9adf2..4237c78cfe 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs @@ -139,8 +139,10 @@ public override async Task Refresh() } /// - public async Task Load(IAccount user, IPullRequestModel pullRequest) + async Task Load(IAccount user, IPullRequestModel pullRequest) { + IsBusy = true; + try { session = await sessionManager.GetSession(pullRequest); @@ -148,19 +150,17 @@ public async Task Load(IAccount user, IPullRequestModel pullRequest) PullRequestTitle = pullRequest.Title; var reviews = new List(); + var isFirst = true; foreach (var review in pullRequest.Reviews.OrderByDescending(x => x.SubmittedAt)) { - if (review.User.Login == user.Login) + if (review.User.Login == user.Login && + review.State != PullRequestReviewState.Pending) { - var vm = new PullRequestReviewViewModel( - editorService, - session, - LocalRepository, - RemoteRepositoryOwner, - pullRequest, - review.Id); + var vm = new PullRequestReviewViewModel(editorService, session, pullRequest, review); + vm.IsExpanded = isFirst; reviews.Add(vm); + isFirst = false; } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs index 6875fe7dc5..5a680e9eee 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs @@ -9,30 +9,11 @@ namespace GitHub.ViewModels.GitHubPane /// public interface IPullRequestReviewViewModel : IViewModel { - /// - /// Gets the local repository. - /// - ILocalRepositoryModel LocalRepository { get; } - - /// - /// Gets the owner of the remote repository that contains the pull request. - /// - /// - /// The remote repository may be different from the local repository if the local - /// repository is a fork and the user is viewing pull requests from the parent repository. - /// - string RemoteRepositoryOwner { get; } - /// /// Gets the underlying pull request review model. /// IPullRequestReviewModel Model { get; } - /// - /// Gets the underlying pull request model. - /// - IPullRequestModel PullRequestModel { get; } - /// /// Gets the body of the review. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs index bc33490083..df18bf8547 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs @@ -65,13 +65,5 @@ Task InitializeAsync( string repo, int pullRequestNumber, string login); - - /// - /// Loads the view model from pre-loaded models. - /// - /// The user model. - /// The pull request model. - /// - Task Load(IAccount user, IPullRequestModel pullRequest); } } \ No newline at end of file diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs new file mode 100644 index 0000000000..a757fa3dd7 --- /dev/null +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs @@ -0,0 +1,232 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestUserReviewsViewModelTests + { + const string AuthorLogin = "grokys"; + + [Test] + public async Task InitializeAsync_Loads_User() + { + var modelSerivce = Substitute.For(); + var user = Substitute.For(); + modelSerivce.GetUser(AuthorLogin).Returns(Observable.Return(user)); + + var target = CreateTarget( + modelServiceFactory: CreateFactory(modelSerivce)); + + await Initialize(target); + + Assert.That(target.User, Is.SameAs(user)); + } + + [Test] + public async Task InitializeAsync_Creates_Reviews() + { + var author = Substitute.For(); + author.Login.Returns(AuthorLogin); + + var anotherAuthor = Substitute.For(); + anotherAuthor.Login.Returns("SomeoneElse"); + + var pullRequest = new PullRequestModel(5, "PR title", author, DateTimeOffset.Now) + { + Reviews = new[] + { + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.ChangesRequested, + }, + new PullRequestReviewModel + { + User = anotherAuthor, + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Dismissed, + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Pending, + }, + }, + ReviewComments = new IPullRequestReviewCommentModel[0], + }; + + var modelSerivce = Substitute.For(); + modelSerivce.GetUser(AuthorLogin).Returns(Observable.Return(author)); + modelSerivce.GetPullRequest("owner", "repo", 5).Returns(Observable.Return(pullRequest)); + + var user = Substitute.For(); + var target = CreateTarget( + modelServiceFactory: CreateFactory(modelSerivce)); + + await Initialize(target); + + // Should load reviews by the correct author which are not Pending. + Assert.That(target.Reviews, Has.Count.EqualTo(3)); + } + + [Test] + public async Task Orders_Reviews_Descending() + { + var author = Substitute.For(); + author.Login.Returns(AuthorLogin); + + var pullRequest = new PullRequestModel(5, "PR title", author, DateTimeOffset.Now) + { + Reviews = new[] + { + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Approved, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.ChangesRequested, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(3), + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Dismissed, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + }, + }, + ReviewComments = new IPullRequestReviewCommentModel[0], + }; + + var modelSerivce = Substitute.For(); + modelSerivce.GetUser(AuthorLogin).Returns(Observable.Return(author)); + modelSerivce.GetPullRequest("owner", "repo", 5).Returns(Observable.Return(pullRequest)); + + var user = Substitute.For(); + var target = CreateTarget( + modelServiceFactory: CreateFactory(modelSerivce)); + + await Initialize(target); + + Assert.That( + target.Reviews.Select(x => x.Model.SubmittedAt), + Is.EqualTo(target.Reviews.Select(x => x.Model.SubmittedAt).OrderByDescending(x => x))); + } + + [Test] + public async Task First_Review_Is_Expanded() + { + var author = Substitute.For(); + author.Login.Returns(AuthorLogin); + + var anotherAuthor = Substitute.For(); + author.Login.Returns("SomeoneElse"); + + var pullRequest = new PullRequestModel(5, "PR title", author, DateTimeOffset.Now) + { + Reviews = new[] + { + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.ChangesRequested, + }, + new PullRequestReviewModel + { + User = author, + State = PullRequestReviewState.Dismissed, + }, + }, + ReviewComments = new IPullRequestReviewCommentModel[0], + }; + + var modelSerivce = Substitute.For(); + modelSerivce.GetUser(AuthorLogin).Returns(Observable.Return(author)); + modelSerivce.GetPullRequest("owner", "repo", 5).Returns(Observable.Return(pullRequest)); + + var user = Substitute.For(); + var target = CreateTarget( + modelServiceFactory: CreateFactory(modelSerivce)); + + await Initialize(target); + + Assert.That(target.Reviews[0].IsExpanded, Is.True); + Assert.That(target.Reviews[1].IsExpanded, Is.False); + Assert.That(target.Reviews[2].IsExpanded, Is.False); + } + + async Task Initialize( + PullRequestUserReviewsViewModel target, + ILocalRepositoryModel localRepository = null, + IConnection connection = null, + int pullRequestNumber = 5, + string login = AuthorLogin) + { + localRepository = localRepository ?? CreateRepository(); + connection = connection ?? Substitute.For(); + + await target.InitializeAsync( + localRepository, + connection, + localRepository.Owner, + localRepository.Name, + pullRequestNumber, + login); + } + + PullRequestUserReviewsViewModel CreateTarget( + IPullRequestEditorService editorService = null, + IPullRequestSessionManager sessionManager = null, + IModelServiceFactory modelServiceFactory = null) + { + editorService = editorService ?? Substitute.For(); + sessionManager = sessionManager ?? Substitute.For(); + modelServiceFactory = modelServiceFactory ?? Substitute.For(); + + return new PullRequestUserReviewsViewModel( + editorService, + sessionManager, + modelServiceFactory); + } + + IModelServiceFactory CreateFactory(IModelService modelService) + { + var result = Substitute.For(); + result.CreateAsync(null).ReturnsForAnyArgs(modelService); + return result; + } + + ILocalRepositoryModel CreateRepository(string owner = "owner", string name = "repo") + { + var result = Substitute.For(); + result.Owner.Returns(owner); + result.Name.Returns(name); + return result; + } + } +} From 6f783d27b7c3e3e20c0ec7dc03623cf4aee78d87 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Mar 2018 15:43:28 +0100 Subject: [PATCH 38/78] Don't gray out disabled expander headers. --- .../Views/GitHubPane/PullRequestUserReviewsView.xaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml index a364f3cf85..bcd572af86 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml @@ -52,6 +52,7 @@ @@ -69,7 +70,8 @@ - From 6efaefeb39311a16a176439df2b82a78688a9753 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Mar 2018 16:04:45 +0100 Subject: [PATCH 39/78] Added PullRequestReviewViewModel tests. --- .../PullRequestReviewViewModelTests.cs | 158 ++++++++++++++++++ test/UnitTests/UnitTests.csproj | 2 + 2 files changed, 160 insertions(+) create mode 100644 test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs new file mode 100644 index 0000000000..f740ce9be2 --- /dev/null +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Linq; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestReviewViewModelTests + { + [Test] + public void Empty_Body_Is_Exposed_As_Null() + { + var pr = CreatePullRequest(); + ((PullRequestReviewModel)pr.Reviews[0]).Body = string.Empty; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.Body, Is.Null); + } + + [Test] + public void Creates_FileComments_And_OutdatedComments() + { + var pr = CreatePullRequest(); + ((PullRequestReviewModel)pr.Reviews[0]).Body = string.Empty; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.FileComments, Has.Count.EqualTo(2)); + Assert.That(target.OutdatedFileComments, Has.Count.EqualTo(1)); + } + + [Test] + public void HasDetails_True_When_Has_Body() + { + var pr = CreatePullRequest(); + pr.ReviewComments = new IPullRequestReviewCommentModel[0]; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.HasDetails, Is.True); + } + + [Test] + public void HasDetails_True_When_Has_Comments() + { + var pr = CreatePullRequest(); + ((PullRequestReviewModel)pr.Reviews[0]).Body = string.Empty; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.HasDetails, Is.True); + } + + [Test] + public void HasDetails_False_When_Has_No_Body_Or_Comments() + { + var pr = CreatePullRequest(); + ((PullRequestReviewModel)pr.Reviews[0]).Body = string.Empty; + pr.ReviewComments = new IPullRequestReviewCommentModel[0]; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.HasDetails, Is.False); + } + + PullRequestReviewViewModel CreateTarget( + IPullRequestEditorService editorService = null, + IPullRequestSession session = null, + IPullRequestModel pullRequest = null, + IPullRequestReviewModel model = null) + { + editorService = editorService ?? Substitute.For(); + session = session ?? Substitute.For(); + pullRequest = pullRequest ?? CreatePullRequest(); + model = model ?? pullRequest.Reviews[0]; + + return new PullRequestReviewViewModel( + editorService, + session, + pullRequest, + model); + } + + private PullRequestModel CreatePullRequest( + int number = 5, + string title = "Pull Request Title", + string body = "Pull Request Body", + IAccount author = null, + DateTimeOffset? createdAt = null) + { + author = author ?? Substitute.For(); + createdAt = createdAt ?? DateTimeOffset.Now; + + return new PullRequestModel(number, title, author, createdAt.Value) + { + Body = body, + Reviews = new[] + { + new PullRequestReviewModel + { + Id = 1, + Body = "Looks good to me!", + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + Id = 2, + Body = "Changes please.", + State = PullRequestReviewState.ChangesRequested, + }, + }, + ReviewComments = new[] + { + new PullRequestReviewCommentModel + { + Body = "I like this.", + PullRequestReviewId = 1, + Position = 10, + }, + new PullRequestReviewCommentModel + { + Body = "This is good.", + PullRequestReviewId = 1, + Position = 11, + }, + new PullRequestReviewCommentModel + { + Body = "Fine, but outdated.", + PullRequestReviewId = 1, + Position = null, + }, + new PullRequestReviewCommentModel + { + Body = "Not great.", + PullRequestReviewId = 2, + Position = 20, + }, + new PullRequestReviewCommentModel + { + Body = "This sucks.", + PullRequestReviewId = 2, + Position = 21, + }, + new PullRequestReviewCommentModel + { + Body = "Bad and old.", + PullRequestReviewId = 2, + Position = null, + }, + } + }; + } + } +} diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index b9bd0c647c..819bd3ce00 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -257,6 +257,8 @@ + + From 9434e9bcf038363842d738eabb2490ffb754d77a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Mar 2018 16:23:33 +0100 Subject: [PATCH 40/78] Read Review SubmittedAt. --- src/GitHub.App/Services/ModelService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index 31a9e1ece7..1c86ea394b 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -405,6 +405,7 @@ async Task> GetPullRequestReviews(string owner, s Body = y.Body, CommitId = y.Commit.Oid, State = FromGraphQL(y.State), + SubmittedAt = y.SubmittedAt, User = Create(y.Author.Login, y.Author.AvatarUrl(null)) }).ToList() }); @@ -630,6 +631,7 @@ IPullRequestModel Create(PullRequestCacheItem prCacheItem) Body = x.Body, State = x.State, CommitId = x.CommitId, + SubmittedAt = x.SubmittedAt, }).ToList(), ReviewComments = prCacheItem.ReviewComments.Select(x => (IPullRequestReviewCommentModel)new PullRequestReviewCommentModel @@ -899,6 +901,7 @@ public PullRequestReviewCacheItem(IPullRequestReviewModel review) }; Body = review.Body; State = review.State; + SubmittedAt = review.SubmittedAt; } public long Id { get; set; } @@ -907,6 +910,7 @@ public PullRequestReviewCacheItem(IPullRequestReviewModel review) public string Body { get; set; } public GitHub.Models.PullRequestReviewState State { get; set; } public string CommitId { get; set; } + public DateTimeOffset? SubmittedAt { get; set; } } public class PullRequestReviewCommentCacheItem From 57ca8914f5b684476b82603fb4ae582fc0b6926c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Mar 2018 16:24:29 +0100 Subject: [PATCH 41/78] Hide empty review details. --- .../Views/GitHubPane/PullRequestUserReviewsView.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml index bcd572af86..67e29375f9 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml @@ -69,7 +69,8 @@ - + Date: Tue, 20 Mar 2018 17:01:54 +0100 Subject: [PATCH 42/78] Restyle Expander button. Use the same triangle as used elsewhere (e.g. in `SectionControl`). --- src/GitHub.UI/Assets/Controls.xaml | 35 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/GitHub.UI/Assets/Controls.xaml b/src/GitHub.UI/Assets/Controls.xaml index ab1683e74a..78925a17ec 100644 --- a/src/GitHub.UI/Assets/Controls.xaml +++ b/src/GitHub.UI/Assets/Controls.xaml @@ -424,10 +424,10 @@ + \ No newline at end of file diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index 20a45d3522..b5e6c4b839 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -79,6 +79,7 @@ + True True @@ -220,6 +221,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile @@ -242,6 +247,10 @@ MSBuild:Compile + + MSBuild:Compile + Designer + diff --git a/src/GitHub.UI/Themes/Generic.xaml b/src/GitHub.UI/Themes/Generic.xaml new file mode 100644 index 0000000000..41baf6c6ff --- /dev/null +++ b/src/GitHub.UI/Themes/Generic.xaml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml b/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml index b041d7bc99..5b9b6c0dc3 100644 --- a/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml @@ -1,7 +1,7 @@  - + - + diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs index c1461c477f..9ef2087be0 100644 --- a/src/GitHub.VisualStudio.UI/Resources.Designer.cs +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -60,6 +60,24 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to Add review comment. + /// + public static string AddReviewComment { + get { + return ResourceManager.GetString("AddReviewComment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add a single comment. + /// + public static string AddSingleComment { + get { + return ResourceManager.GetString("AddSingleComment", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add your review. /// diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index 6bc1e1e436..f3406dd482 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -410,4 +410,10 @@ Reviewers + + Add review comment + + + Add a single comment + \ No newline at end of file diff --git a/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj b/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj index 14fd81cfc6..2dd110ce3f 100644 --- a/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj +++ b/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj @@ -129,6 +129,7 @@ + diff --git a/test/GitHub.InlineReviews.UnitTests/ViewModels/PullRequestReviewCommentViewModelTests.cs b/test/GitHub.InlineReviews.UnitTests/ViewModels/PullRequestReviewCommentViewModelTests.cs new file mode 100644 index 0000000000..f9becd8117 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/ViewModels/PullRequestReviewCommentViewModelTests.cs @@ -0,0 +1,130 @@ +using System; +using System.Reactive.Linq; +using GitHub.InlineReviews.ViewModels; +using GitHub.Models; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; +using ReactiveUI; + +namespace GitHub.InlineReviews.UnitTests.ViewModels +{ + public class PullRequestReviewCommentViewModelTests + { + public class TheCanStartReviewProperty + { + [Test] + public void IsFalseWhenSessionHasPendingReview() + { + var session = CreateSession(true); + var target = CreateTarget(session); + + Assert.That(target.CanStartReview, Is.False); + } + + [Test] + public void IsTrueWhenSessionHasNoPendingReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + Assert.That(target.CanStartReview, Is.True); + } + } + + public class TheCommitCaptionProperty + { + [Test] + public void IsAddReviewCommentWhenSessionHasPendingReview() + { + var session = CreateSession(true); + var target = CreateTarget(session); + + Assert.That(target.CommitCaption, Is.EqualTo("Add review comment")); + } + + [Test] + public void IsAddSingleCommentWhenSessionHasNoPendingReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + Assert.That(target.CommitCaption, Is.EqualTo("Add a single comment")); + } + } + + public class TheStartReviewCommand + { + [Test] + public void IsDisabledWhenSessionHasPendingReview() + { + var session = CreateSession(true); + var target = CreateTarget(session); + + Assert.That(target.StartReview.CanExecute(null), Is.False); + } + + [Test] + public void IsDisabledWhenSessionHasNoPendingReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + Assert.That(target.StartReview.CanExecute(null), Is.False); + } + + [Test] + public void IsEnabledWhenSessionHasNoPendingReviewAndBodyNotEmpty() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + target.Body = "body"; + + Assert.That(target.StartReview.CanExecute(null), Is.True); + } + + [Test] + public void CallsSessionStartReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + target.Body = "body"; + target.StartReview.Execute(null); + + session.Received(1).StartReview(); + } + } + + static PullRequestReviewCommentViewModel CreateTarget( + IPullRequestSession session = null, + ICommentThreadViewModel thread = null) + { + session = session ?? CreateSession(); + thread = thread ?? CreateThread(); + + return new PullRequestReviewCommentViewModel( + session, + thread, + Substitute.For(), + Substitute.For()); + } + + static IPullRequestSession CreateSession( + bool hasPendingReview = false) + { + var result = Substitute.For(); + result.HasPendingReview.Returns(hasPendingReview); + return result; + } + + static ICommentThreadViewModel CreateThread( + bool canPost = true) + { + var result = Substitute.For(); + result.PostComment.Returns(new ReactiveCommand(Observable.Return(canPost), _ => null)); + return result; + } + } +} From 236003883396e3166f663a9043a7bbd9e3320881 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 23 Mar 2018 11:27:38 +0100 Subject: [PATCH 50/78] Remove pending badge from comment when review submitted. --- .../PullRequestReviewAuthoringViewModel.cs | 8 +-- .../Models/IPullRequestReviewCommentModel.cs | 2 +- .../Services/PullRequestSession.cs | 12 ++++ .../Services/PullRequestSessionTests.cs | 29 +++++++-- ...ullRequestReviewAuthoringViewModelTests.cs | 61 +++++++++++++++++++ 5 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs index 17664e6109..47f56a6b3b 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs @@ -209,10 +209,10 @@ async Task UpdateFileComments() { var result = new List(); - //if (Model.Id == 0 && session.PendingReviewId != 0) - //{ - // ((PullRequestReviewModel)Model).Id = session.PendingReviewId; - //} + if (Model.Id == 0 && session.PendingReviewId != 0) + { + ((PullRequestReviewModel)Model).Id = session.PendingReviewId; + } foreach (var file in await session.GetAllFiles()) { diff --git a/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs b/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs index f6d376bb13..b2399aa15a 100644 --- a/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestReviewCommentModel.cs @@ -47,6 +47,6 @@ public interface IPullRequestReviewCommentModel : ICommentModel /// /// Gets a value indicating whether the comment is part of a pending review. /// - bool IsPending { get; } + bool IsPending { get; set; } } } diff --git a/src/GitHub.InlineReviews/Services/PullRequestSession.cs b/src/GitHub.InlineReviews/Services/PullRequestSession.cs index d8ddfa90d7..23cbfb3197 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSession.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSession.cs @@ -272,6 +272,18 @@ async Task AddReview(IPullRequestReviewModel review) .Where(x => x.NodeId != review.NodeId) .Concat(new[] { review }) .ToList(); + + if (review.State != PullRequestReviewState.Pending) + { + foreach (var comment in PullRequest.ReviewComments) + { + if (comment.PullRequestReviewId == review.Id) + { + comment.IsPending = false; + } + } + } + await Update(PullRequest); } diff --git a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs index 83464b98ac..fc5af247f9 100644 --- a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs +++ b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs @@ -343,15 +343,15 @@ public async Task ReplacesPendingReviewWithModel() var target = CreateTarget(service, "fork", "owner", true); + Assert.That( + target.PullRequest.Reviews.Where(x => x.State == PullRequestReviewState.Pending).Count(), + Is.EqualTo(1)); + var submittedReview = CreatePullRequestReview(target.User, PullRequestReviewState.Approved); submittedReview.NodeId.Returns("pendingReviewId"); service.SubmitPendingReview(null, null, null, null, Octokit.PullRequestReviewEvent.Approve) .ReturnsForAnyArgs(submittedReview); - Assert.That( - target.PullRequest.Reviews.Where(x => x.State == PullRequestReviewState.Pending).Count(), - Is.EqualTo(1)); - var model = await target.PostReview("New Review", Octokit.PullRequestReviewEvent.Approve); Assert.That( @@ -359,6 +359,23 @@ public async Task ReplacesPendingReviewWithModel() Is.Zero); } + [Test] + public async Task MarksAssociatedCommentsAsNonPending() + { + var service = Substitute.For(); + var target = CreateTarget(service, "fork", "owner", true); + + Assert.That(target.PullRequest.ReviewComments[0].IsPending, Is.True); + + var submittedReview = CreatePullRequestReview(target.User, PullRequestReviewState.Approved); + submittedReview.NodeId.Returns("pendingReviewId"); + service.SubmitPendingReview(null, null, null, null, Octokit.PullRequestReviewEvent.Approve) + .ReturnsForAnyArgs(submittedReview); + var model = await target.PostReview("New Review", Octokit.PullRequestReviewEvent.RequestChanges); + + target.PullRequest.ReviewComments[0].Received(1).IsPending = false; + } + PullRequestSession CreateTarget( IPullRequestSessionService service, string localRepositoryOwner, @@ -376,6 +393,10 @@ PullRequestSession CreateTarget( if (hasPendingReview) { + var reviewComment = Substitute.For(); + reviewComment.IsPending.Returns(true); + pr.ReviewComments.Returns(new[] { reviewComment }); + var review = CreatePullRequestReview(user, PullRequestReviewState.Pending); review.NodeId.Returns("pendingReviewId"); pr.Reviews.Returns(new[] { review }); diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs index 66b526b45d..ab25d2a40b 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs @@ -183,6 +183,67 @@ public async Task Updates_FileComments_When_Session_PullRequestChanged() Assert.That(target.FileComments, Has.Count.EqualTo(1)); } + [Test] + public async Task Updates_Model_Id_From_PendingReviewId_When_Session_PullRequestChanged() + { + var model = CreatePullRequest(); + var session = CreateSession( + "grokys", + CreateSessionFile( + CreateInlineCommentThread( + CreateReviewComment(11)), + CreateInlineCommentThread( + CreateReviewComment(12), + CreateReviewComment(12)))); + + var target = CreateTarget(model, session); + + await Initialize(target); + + Assert.That(target.Model.Id, Is.EqualTo(0)); + + session.PendingReviewId.Returns(123); + RaisePullRequestChanged(session, model); + + Assert.That(target.Model.Id, Is.EqualTo(123)); + } + + [Test] + public async Task Submit_Calls_Session_PostReview() + { + var review = CreateReview(12, "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + + await Initialize(target); + + target.Body = "Post review"; + target.Submit.Execute(Octokit.PullRequestReviewEvent.Approve); + + await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.Approve); + } + + [Test] + public async Task Submit_Closes_Page() + { + var review = CreateReview(12, "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + + await Initialize(target); + target.Body = "Post review"; + + target.CloseRequested.Subscribe(_ => closed = true); + target.Submit.Execute(Octokit.PullRequestReviewEvent.Approve); + + Assert.True(closed); + } + static PullRequestReviewAuthoringViewModel CreateTarget( IPullRequestModel model, IPullRequestSession session = null) From b95d370909afdbb4f90a82e2fba7f734aa94400c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 23 Mar 2018 11:40:35 +0100 Subject: [PATCH 51/78] Update PR details when review submitted. --- .../ViewModels/GitHubPane/PullRequestDetailViewModel.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index ccd1ae7a61..27d4509631 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -53,6 +53,7 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq bool active; bool refreshOnActivate; Uri webUrl; + IDisposable sessionSubscription; /// /// Initializes a new instance of the class. @@ -441,6 +442,11 @@ public async Task Load(IPullRequestModel pullRequest) UpdateState = null; } + sessionSubscription?.Dispose(); + sessionSubscription = Session.WhenAnyValue(x => x.HasPendingReview) + .Skip(1) + .Subscribe(x => Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, Session.PullRequest).ToList()); + if (firstLoad) { usageTracker.IncrementCounter(x => x.NumberOfPullRequestsOpened).Forget(); From c0e02a8bd7807b852fb39f1c69d069a165f2c06c Mon Sep 17 00:00:00 2001 From: Don Okuda Date: Mon, 26 Mar 2018 16:57:54 -0700 Subject: [PATCH 52/78] :fire: Reviewer meta data Save space and avoid any confusion around number of file comments --- .../Views/GitHubPane/PullRequestReviewSummaryView.xaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml index c0b50d5006..95a0b4a7de 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml @@ -55,7 +55,6 @@ - @@ -76,14 +75,6 @@ Content="{x:Static prop:Resources.ContinueYourReview}" Visibility="{Binding State, Converter={ui:EqualsToVisibilityConverter Pending}}"/> - - - - | - - From e3f94b3adbc1e1192c982c2ae243219fc8468a48 Mon Sep 17 00:00:00 2001 From: Don Okuda Date: Mon, 26 Mar 2018 16:59:15 -0700 Subject: [PATCH 53/78] Make the name bold, but not too bold --- .../Views/GitHubPane/PullRequestReviewSummaryView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml index 95a0b4a7de..f3805f77fb 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml @@ -58,7 +58,7 @@ - Date: Tue, 27 Mar 2018 12:46:36 +0200 Subject: [PATCH 54/78] Add PR reviews metrics. Adds two new metrics: - `NumberOfPRReviewDiffViewInlineCommentStartReview`: incremented when the "Start Review" button is clicked from an inline comment - `NumberOfPRReviewPosts`: incremented when a PR review is submitted --- src/GitHub.Exports/Models/UsageModel.cs | 2 ++ .../Services/PullRequestSessionService.cs | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs index 216619e55f..325cd74e30 100644 --- a/src/GitHub.Exports/Models/UsageModel.cs +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -42,6 +42,8 @@ public struct UsageModel public int NumberOfPRDetailsNavigateToEditor { get; set; } public int NumberOfPRReviewDiffViewInlineCommentOpen { get; set; } public int NumberOfPRReviewDiffViewInlineCommentPost { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentStartReview { get; set; } + public int NumberOfPRReviewPosts { get; set; } public int NumberOfShowCurrentPullRequest { get; set; } public UsageModel Clone(bool includeWeekly, bool includeMonthly) diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs index b8349994e6..64b914f1d7 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs @@ -344,7 +344,9 @@ public async Task CreatePendingReview( User = user, }); - return await graphql.Run(addReview); + var result = await graphql.Run(addReview); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentStartReview); + return result; } /// @@ -368,6 +370,8 @@ public async Task PostReview( body, e); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts); + return new PullRequestReviewModel { Id = result.Id, @@ -407,7 +411,9 @@ public async Task SubmitPendingReview( User = user, }); - return await graphql.Run(mutation); + var result = await graphql.Run(mutation); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts); + return result; } /// @@ -451,7 +457,9 @@ public async Task PostPendingReviewComment( IsPending = true, }); - return await graphql.Run(addComment); + var result = await graphql.Run(addComment); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); + return result; } /// @@ -491,7 +499,9 @@ public async Task PostPendingReviewCommentReply( IsPending = true, }); - return await graphql.Run(addComment); + var result = await graphql.Run(addComment); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); + return result; } /// From d57692b201a6ad3556acb64daff94f0ef0295778 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 28 Mar 2018 17:54:35 +0200 Subject: [PATCH 55/78] Don't retain placeholder when starting a review. Starting a review by replying to an existing comment was causing the submitted comment's text to be copied into the placeholder after submission. Add a unit test to check for this and fix it. --- .../SampleData/CommentViewModelDesigner.cs | 1 + .../ViewModels/CommentViewModel.cs | 13 +++++++ .../ViewModels/ICommentViewModel.cs | 6 +++ .../ViewModels/InlineCommentPeekViewModel.cs | 7 ++-- .../PullRequestReviewCommentViewModel.cs | 13 ++++++- .../InlineCommentPeekViewModelTests.cs | 39 ++++++++++++++++++- 6 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs index 3e8744aa4c..d6cf93a107 100644 --- a/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs +++ b/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs @@ -23,6 +23,7 @@ public CommentViewModelDesigner() public string ErrorMessage { get; set; } public CommentEditState EditState { get; set; } public bool IsReadOnly { get; set; } + public bool IsSubmitting { get; set; } public ICommentThreadViewModel Thread { get; } public DateTimeOffset UpdatedAt => DateTime.Now.Subtract(TimeSpan.FromDays(3)); public IAccount User { get; set; } diff --git a/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs index ee763b96cd..be3c623afb 100644 --- a/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs +++ b/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs @@ -20,6 +20,7 @@ public class CommentViewModel : ReactiveObject, ICommentViewModel string body; string errorMessage; bool isReadOnly; + bool isSubmitting; CommentEditState state; DateTimeOffset updatedAt; string undoBody; @@ -127,6 +128,7 @@ async Task DoCommitEdit(object unused) try { ErrorMessage = null; + IsSubmitting = true; var model = await Thread.PostComment.ExecuteAsyncTask(Body); Id = model.Id; @@ -140,6 +142,10 @@ async Task DoCommitEdit(object unused) ErrorMessage = message; log.Error(e, "Error posting comment"); } + finally + { + IsSubmitting = false; + } } /// @@ -176,6 +182,13 @@ public bool IsReadOnly set { this.RaiseAndSetIfChanged(ref isReadOnly, value); } } + /// + public bool IsSubmitting + { + get { return isSubmitting; } + protected set { this.RaiseAndSetIfChanged(ref isSubmitting, value); } + } + /// public DateTimeOffset UpdatedAt { diff --git a/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs index 979542ab23..05080e2052 100644 --- a/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs +++ b/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs @@ -48,6 +48,12 @@ public interface ICommentViewModel : IViewModel /// bool IsReadOnly { get; set; } + /// + /// Gets a value indicating whether the comment is currently in the process of being + /// submitted. + /// + bool IsSubmitting { get; } + /// /// Gets the modified date of the comment. /// diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs index fdfa2f2e95..3c018872f0 100644 --- a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs +++ b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs @@ -154,7 +154,7 @@ async void LinesChanged(IReadOnlyList> lines) async Task UpdateThread() { - var placeholderBody = await GetPlaceholderBodyToPreserve(); + var placeholderBody = GetPlaceholderBodyToPreserve(); Thread = null; threadSubscription?.Dispose(); @@ -208,14 +208,13 @@ async Task SessionChanged(IPullRequestSession pullRequestSession) } } - async Task GetPlaceholderBodyToPreserve() + string GetPlaceholderBodyToPreserve() { var lastComment = Thread?.Comments.LastOrDefault(); if (lastComment?.EditState == CommentEditState.Editing) { - var executing = await lastComment.CommitEdit.IsExecuting.FirstAsync(); - if (!executing) return lastComment.Body; + if (!lastComment.IsSubmitting) return lastComment.Body; } return null; diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs index ba6d8ff4ab..3f0182aed8 100644 --- a/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs +++ b/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs @@ -120,8 +120,17 @@ public static CommentViewModel CreatePlaceholder( async Task DoStartReview(object unused) { - await session.StartReview(); - await CommitEdit.ExecuteAsync(null); + IsSubmitting = true; + + try + { + await session.StartReview(); + await CommitEdit.ExecuteAsync(null); + } + finally + { + IsSubmitting = false; + } } } } diff --git a/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs b/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs index 15dfffddc7..bf983d5f5d 100644 --- a/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs +++ b/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs @@ -195,7 +195,7 @@ public async Task RetainsCommentBeingEditedWhenSessionRefreshed() } [Test] - public async Task DoesntRetainSubmittedCommentInPlaceholderAfterPost() + public async Task CommittingEditDoesntRetainSubmittedCommentInPlaceholderAfterPost() { var sessionManager = CreateSessionManager(); var peekSession = CreatePeekSession(); @@ -231,6 +231,43 @@ public async Task DoesntRetainSubmittedCommentInPlaceholderAfterPost() Assert.That(string.Empty, Is.EqualTo(placeholder.Body)); } + [Test] + public async Task StartingReviewDoesntRetainSubmittedCommentInPlaceholderAfterPost() + { + var sessionManager = CreateSessionManager(); + var peekSession = CreatePeekSession(); + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + peekSession, + sessionManager, + Substitute.For(), + Substitute.For()); + + await target.Initialize(); + + Assert.That(2, Is.EqualTo(target.Thread.Comments.Count)); + + sessionManager.CurrentSession.StartReview() + .ReturnsForAnyArgs(async x => + { + var file = await sessionManager.GetLiveFile( + RelativePath, + peekSession.TextView, + peekSession.TextView.TextBuffer); + RaiseLinesChanged(file, Tuple.Create(10, DiffSide.Right)); + return Substitute.For(); + }); + + var placeholder = (IPullRequestReviewCommentViewModel)target.Thread.Comments.Last(); + placeholder.BeginEdit.Execute(null); + placeholder.Body = "Comment being edited"; + placeholder.StartReview.Execute(null); + + placeholder = (IPullRequestReviewCommentViewModel)target.Thread.Comments.Last(); + Assert.That(CommentEditState.Placeholder, Is.EqualTo(placeholder.EditState)); + Assert.That(string.Empty, Is.EqualTo(placeholder.Body)); + } + void AddCommentToExistingThread(IPullRequestSessionFile file) { var newThreads = file.InlineCommentThreads.ToList(); From 3ba77731abc192d630580b587a7425b8f52f5d24 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 29 Mar 2018 10:56:15 +0200 Subject: [PATCH 56/78] Updated version to 2.4.99. Using .99 for patch part of the version number to signify beta release. --- appveyor.yml | 2 +- src/GitHub.VisualStudio/source.extension.vsixmanifest | 4 ++-- src/common/SolutionInfo.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 63fa013b88..cd32d5c109 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '2.4.4.{build}' +version: '2.4.99.{build}' skip_tags: true install: - ps: | diff --git a/src/GitHub.VisualStudio/source.extension.vsixmanifest b/src/GitHub.VisualStudio/source.extension.vsixmanifest index 6b1eef68fa..b5e3a64f6b 100644 --- a/src/GitHub.VisualStudio/source.extension.vsixmanifest +++ b/src/GitHub.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + GitHub Extension for Visual Studio A Visual Studio Extension that brings the GitHub Flow into Visual Studio. GitHub.VisualStudio @@ -39,4 +39,4 @@ - + \ No newline at end of file diff --git a/src/common/SolutionInfo.cs b/src/common/SolutionInfo.cs index d1797cd9ab..a993052159 100644 --- a/src/common/SolutionInfo.cs +++ b/src/common/SolutionInfo.cs @@ -18,6 +18,6 @@ namespace System { internal static class AssemblyVersionInformation { - internal const string Version = "2.4.4.0"; + internal const string Version = "2.4.99.0"; } } From f629007be460baac4f8a8be7a9418994d1b3d698 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 30 Mar 2018 14:15:08 +0200 Subject: [PATCH 57/78] Add ability to cancel a PR review. --- ...RequestReviewAuthoringViewModelDesigner.cs | 1 + .../PullRequestReviewAuthoringViewModel.cs | 26 ++++ .../Services/IPullRequestSession.cs | 8 ++ .../IPullRequestReviewAuthoringViewModel.cs | 5 + .../Services/IPullRequestSessionService.cs | 8 ++ .../Services/PullRequestSession.cs | 20 +++ .../Services/PullRequestSessionService.cs | 20 +++ .../PullRequestReviewAuthoringView.xaml | 2 +- .../Services/PullRequestSessionTests.cs | 136 +++++++++++++++--- ...ullRequestReviewAuthoringViewModelTests.cs | 35 +++++ 10 files changed, 242 insertions(+), 19 deletions(-) diff --git a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs index eb7fe5f11a..7b2b0c0ad7 100644 --- a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs @@ -48,6 +48,7 @@ public PullRequestReviewAuthoringViewModelDesigner() public IPullRequestModel PullRequestModel { get; set; } public string RemoteRepositoryOwner { get; set; } public ReactiveCommand Submit { get; } + public ReactiveCommand Cancel { get; } public Task InitializeAsync( ILocalRepositoryModel localRepository, diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs index 47f56a6b3b..cf7b4b6ca0 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs @@ -58,6 +58,7 @@ public PullRequestReviewAuthoringViewModel( Files = files; Submit = ReactiveCommand.CreateAsyncTask(DoSubmit); + Cancel = ReactiveCommand.CreateAsyncTask(DoCancel); } /// @@ -112,6 +113,7 @@ public IReadOnlyList FileComments public ReactiveCommand NavigateToPullRequest { get; } public ReactiveCommand Submit { get; } + public ReactiveCommand Cancel { get; } public async Task InitializeAsync( ILocalRepositoryModel localRepository, @@ -259,5 +261,29 @@ async Task DoSubmit(object arg) IsBusy = false; } } + + async Task DoCancel(object arg) + { + OperationError = null; + IsBusy = true; + + try + { + if (Model?.Id != 0) + { + await session.CancelReview(); + } + + Close(); + } + catch (Exception ex) + { + OperationError = ex.Message; + } + finally + { + IsBusy = false; + } + } } } diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs index c6f4defc56..cff17965b8 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs @@ -122,6 +122,14 @@ Task PostReviewComment( /// Task StartReview(); + /// + /// Cancels the currently pending review. + /// + /// + /// There is no pending review. + /// + Task CancelReview(); + /// /// Posts the currently pending review. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs index 71099f48d0..9baa2d49e6 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs @@ -72,6 +72,11 @@ public interface IPullRequestReviewAuthoringViewModel : IPanePageViewModel, IDis /// ReactiveCommand Submit { get; } + /// + /// Gets a command which cancels the review. + /// + ReactiveCommand Cancel { get; } + /// /// Initializes the view model for creating a new review. /// diff --git a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs index 7d1e7d6221..8a12f9f7ac 100644 --- a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs @@ -181,6 +181,14 @@ Task CreatePendingReview( IAccount user, string pullRequestId); + /// + /// Cancels a pending review on the server. + /// + /// The GraphQL ID of the review. + Task CancelPendingReview( + ILocalRepositoryModel localRepository, + string reviewId); + /// /// Posts PR review with no comments. /// diff --git a/src/GitHub.InlineReviews/Services/PullRequestSession.cs b/src/GitHub.InlineReviews/Services/PullRequestSession.cs index 23cbfb3197..335f4b21d9 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSession.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSession.cs @@ -213,6 +213,26 @@ public async Task StartReview() return model; } + /// + public async Task CancelReview() + { + if (!HasPendingReview) + { + throw new InvalidOperationException("There is no pending review to cancel."); + } + + await service.CancelPendingReview(LocalRepository, pendingReviewNodeId); + + PullRequest.Reviews = PullRequest.Reviews + .Where(x => x.NodeId != pendingReviewNodeId) + .ToList(); + PullRequest.ReviewComments = PullRequest.ReviewComments + .Where(x => x.PullRequestReviewId != PendingReviewId) + .ToList(); + + await Update(PullRequest); + } + /// public async Task PostReview(string body, Octokit.PullRequestReviewEvent e) { diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs index 64b914f1d7..ad54647b22 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs @@ -349,6 +349,26 @@ public async Task CreatePendingReview( return result; } + /// + public async Task CancelPendingReview( + ILocalRepositoryModel localRepository, + string reviewId) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var delete = new DeletePullRequestReviewInput + { + PullRequestReviewId = reviewId, + }; + + var deleteReview = new Mutation() + .DeletePullRequestReview(delete) + .Select(x => x.ClientMutationId); + + await graphql.Run(deleteReview); + } + /// public async Task PostReview( ILocalRepositoryModel localRepository, diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml index 188d414907..14a4f5a57f 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml @@ -74,7 +74,7 @@ - Cancel + Cancel (), CreatePullRequest(), Substitute.For(), @@ -49,7 +49,7 @@ public void IsFalseWithPendingReviewForOtherUser() pr.Reviews.Returns(new[] { review }); var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), currentUser, pr, Substitute.For(), @@ -68,7 +68,7 @@ public void IsFalseWithNonPendingReviewForCurrentUser() pr.Reviews.Returns(new[] { review }); var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), currentUser, pr, Substitute.For(), @@ -87,7 +87,7 @@ public void IsTrueWithPendingReviewForCurrentUser() pr.Reviews.Returns(new[] { review }); var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), currentUser, pr, Substitute.For(), @@ -102,7 +102,7 @@ public async Task IsTrueWithUpdatedWithPendingReview() { var currentUser = Substitute.For(); var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), currentUser, CreatePullRequest(), Substitute.For(), @@ -141,6 +141,29 @@ public async Task IsTrueWhenStartReviewCalled() Assert.That(target.HasPendingReview, Is.True); } + + [Test] + public async Task IsFalseWhenReviewCancelled() + { + var currentUser = Substitute.For(); + var pr = CreatePullRequest(); + var review = CreatePullRequestReview(currentUser, PullRequestReviewState.Pending); + pr.Reviews.Returns(new[] { review }); + + var target = new PullRequestSession( + Substitute.For(), + currentUser, + pr, + Substitute.For(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.True); + + await target.CancelReview(); + + Assert.That(target.HasPendingReview, Is.False); + } } public class TheGetFileMethod @@ -149,7 +172,7 @@ public class TheGetFileMethod public async Task BaseShaIsSet() { var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), Substitute.For(), CreatePullRequest(), Substitute.For(), @@ -164,7 +187,7 @@ public async Task BaseShaIsSet() public async Task HeadCommitShaIsSet() { var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), Substitute.For(), CreatePullRequest(), Substitute.For(), @@ -180,7 +203,7 @@ public async Task HeadCommitShaIsSet() public async Task PinnedCommitShaIsSet() { var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), Substitute.For(), CreatePullRequest(), Substitute.For(), @@ -196,7 +219,7 @@ public async Task PinnedCommitShaIsSet() public async Task DiffShaIsSet() { var diff = new List(); - var sessionService = CreateSessionService(); + var sessionService = CreateRealSessionService(); sessionService.Diff( Arg.Any(), @@ -237,7 +260,7 @@ Line 2 using (var diffService = new FakeDiffService()) { var pullRequest = CreatePullRequest(comment); - var service = CreateSessionService(diffService); + var service = CreateRealSessionService(diffService); diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); diffService.AddFile(FilePath, headContents, "HEAD_SHA"); @@ -260,7 +283,7 @@ Line 2 public async Task SameNonHeadCommitShasReturnSameFiles() { var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), Substitute.For(), CreatePullRequest(), Substitute.For(), @@ -276,7 +299,7 @@ public async Task SameNonHeadCommitShasReturnSameFiles() public async Task DifferentCommitShasReturnDifferentFiles() { var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), Substitute.For(), CreatePullRequest(), Substitute.For(), @@ -289,6 +312,79 @@ public async Task DifferentCommitShasReturnDifferentFiles() } } + public class TheCancelReviewMethod + { + [Test] + public void ThrowsWithNoPendingReview() + { + var target = new PullRequestSession( + CreateRealSessionService(), + Substitute.For(), + CreatePullRequest(), + Substitute.For(), + "owner", + true); + + Assert.ThrowsAsync(async () => await target.CancelReview()); + } + + [Test] + public async Task CallsServiceWithNodeId() + { + var service = Substitute.For(); + var target = CreateTargetWithPendingReview(service); + + await target.CancelReview(); + + await service.Received(1).CancelPendingReview( + Arg.Any(), + "nodeId1"); + } + + [Test] + public async Task RemovesReviewFromModel() + { + var service = Substitute.For(); + var target = CreateTargetWithPendingReview(service); + + await target.CancelReview(); + + Assert.IsEmpty(target.PullRequest.Reviews); + } + + [Test] + public async Task RemovesCommentsFromModel() + { + var service = Substitute.For(); + var target = CreateTargetWithPendingReview(service); + + await target.CancelReview(); + + Assert.IsEmpty(target.PullRequest.ReviewComments); + } + + public static PullRequestSession CreateTargetWithPendingReview( + IPullRequestSessionService service) + { + var currentUser = Substitute.For(); + var pr = CreatePullRequest(); + var review = CreatePullRequestReview(currentUser, PullRequestReviewState.Pending); + var comment = Substitute.For(); + + comment.PullRequestReviewId.Returns(1); + pr.Reviews.Returns(new[] { review }); + pr.ReviewComments.Returns(new[] { comment }); + + return new PullRequestSession( + service, + currentUser, + pr, + Substitute.For(), + "owner", + true); + } + } + public class ThePostReviewMethod { [Test] @@ -394,6 +490,7 @@ PullRequestSession CreateTarget( if (hasPendingReview) { var reviewComment = Substitute.For(); + reviewComment.PullRequestReviewId.Returns(1); reviewComment.IsPending.Returns(true); pr.ReviewComments.Returns(new[] { reviewComment }); @@ -522,7 +619,7 @@ public class TheUpdateMethod public async Task UpdatesThePullRequestModel() { var target = new PullRequestSession( - CreateSessionService(), + CreateRealSessionService(), Substitute.For(), CreatePullRequest(), Substitute.For(), @@ -563,7 +660,7 @@ Line 2 using (var diffService = new FakeDiffService()) { var pullRequest = CreatePullRequest(comment1); - var service = CreateSessionService(diffService); + var service = CreateRealSessionService(diffService); diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); diffService.AddFile(FilePath, headContents, "HEAD_SHA"); @@ -615,7 +712,7 @@ Line 2 using (var diffService = new FakeDiffService()) { var pullRequest = CreatePullRequest(comment1); - var service = CreateSessionService(diffService); + var service = CreateRealSessionService(diffService); diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); diffService.AddFile(FilePath, headContents, "123"); @@ -653,7 +750,7 @@ Line 2 using (var diffService = new FakeDiffService()) { var pullRequest = CreatePullRequest(comment); - var service = CreateSessionService(diffService); + var service = CreateRealSessionService(diffService); var target = new PullRequestSession( service, @@ -722,9 +819,12 @@ static IPullRequestModel CreatePullRequest(params IPullRequestReviewCommentModel static IPullRequestReviewModel CreatePullRequestReview( IAccount author, - PullRequestReviewState state) + PullRequestReviewState state, + long id = 1) { var result = Substitute.For(); + result.Id.Returns(id); + result.NodeId.Returns("nodeId" + id); result.User.Returns(author); result.State.Returns(state); return result; @@ -748,7 +848,7 @@ static ILocalRepositoryModel CreateLocalRepository() return result; } - static IPullRequestSessionService CreateSessionService(IDiffService diffService = null) + static IPullRequestSessionService CreateRealSessionService(IDiffService diffService = null) { var result = Substitute.ForPartsOf( Substitute.For(), diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs index ab25d2a40b..5ebbee8eb0 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs @@ -244,6 +244,41 @@ public async Task Submit_Closes_Page() Assert.True(closed); } + [Test] + public async Task Cancel_Calls_Session_CancelReview_And_Closes_When_Has_Pending_Review() + { + var review = CreateReview(12, "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + await Initialize(target); + + target.CloseRequested.Subscribe(_ => closed = true); + target.Cancel.Execute(null); + + await session.Received(1).CancelReview(); + Assert.True(closed); + } + + [Test] + public async Task Cancel_Just_Closes_When_Has_No_Pending_Review() + { + var model = CreatePullRequest("shana"); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + await Initialize(target); + + target.CloseRequested.Subscribe(_ => closed = true); + target.Cancel.Execute(null); + + await session.Received(0).CancelReview(); + Assert.True(closed); + } + static PullRequestReviewAuthoringViewModel CreateTarget( IPullRequestModel model, IPullRequestSession session = null) From 1f4f82b3ba382bce6780536c9ebdecdcef3e5f73 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Apr 2018 11:51:14 +0200 Subject: [PATCH 58/78] Use correct GraphQL endpoint for enterprise. Previously we were always using github.com's endpoint even when talking to an enterprise instance. --- src/GitHub.Api/GraphQLClientFactory.cs | 2 +- src/GitHub.Exports/Primitives/HostAddress.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/GitHub.Api/GraphQLClientFactory.cs b/src/GitHub.Api/GraphQLClientFactory.cs index 8151832a16..cd91295935 100644 --- a/src/GitHub.Api/GraphQLClientFactory.cs +++ b/src/GitHub.Api/GraphQLClientFactory.cs @@ -35,7 +35,7 @@ public GraphQLClientFactory(IKeychain keychain, IProgram program) { var credentials = new GraphQLKeychainCredentialStore(keychain, address); var header = new ProductHeaderValue(program.ProductHeader.Name, program.ProductHeader.Version); - return Task.FromResult(new Connection(header, credentials)); + return Task.FromResult(new Connection(header, address.GraphQLUri, credentials)); } } } diff --git a/src/GitHub.Exports/Primitives/HostAddress.cs b/src/GitHub.Exports/Primitives/HostAddress.cs index 61b3e215c4..bcea51d364 100644 --- a/src/GitHub.Exports/Primitives/HostAddress.cs +++ b/src/GitHub.Exports/Primitives/HostAddress.cs @@ -44,7 +44,7 @@ private HostAddress(Uri enterpriseUri) { WebUri = new Uri(enterpriseUri, new Uri("/", UriKind.Relative)); ApiUri = new Uri(enterpriseUri, new Uri("/api/v3/", UriKind.Relative)); - //CredentialCacheKeyHost = ApiUri.Host; + GraphQLUri = new Uri(enterpriseUri, new Uri("/api/graphql", UriKind.Relative)); CredentialCacheKeyHost = WebUri.ToString(); } @@ -52,7 +52,7 @@ public HostAddress() { WebUri = new Uri("https://github.com"); ApiUri = new Uri("https://api.github.com"); - //CredentialCacheKeyHost = "github.com"; + GraphQLUri = new Uri("https://api.github.com/graphql"); CredentialCacheKeyHost = WebUri.ToString(); } @@ -67,6 +67,12 @@ public HostAddress() /// public Uri ApiUri { get; set; } + /// + /// The Base Url to the host's GraphQL API endpoint. For example, "https://api.github.com/graphql" or + /// "https://github-enterprise.com/api/graphql" + /// + public Uri GraphQLUri { get; set; } + // If the host name is "api.github.com" or "gist.github.com", we really only want "github.com", // since that's the same cache key for all the other github.com operations. public string CredentialCacheKeyHost { get; private set; } From c917d44fcf949edc017f7082e75d2a8646172dc5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Apr 2018 16:49:04 +0200 Subject: [PATCH 59/78] Fix navigate to editor. This code was accidentally removed in fa8aab8c6796041522bb192a9e2581ae7f6ec870 --- src/GitHub.App/Services/PullRequestEditorService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index df1b49fb6f..0fe2435b19 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -182,6 +182,9 @@ await pullRequestService.ExtractToTempFile( if (!workingDirectory) { AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, file.CommitSha, DiffSide.Right); + EnableNavigateToEditor(diffViewer.LeftView, session, file); + EnableNavigateToEditor(diffViewer.RightView, session, file); + EnableNavigateToEditor(diffViewer.InlineView, session, file); } if (workingDirectory) From 106487ff12c10dc7e777b642ea1e447bb60cee43 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 4 Apr 2018 11:40:28 +0200 Subject: [PATCH 60/78] Don't ignore .nupkg files in lib. --- lib/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/.gitignore diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000000..296234c026 --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1 @@ +!*.nupkg From 9c36a142291cc67eddce91df0b4f31875c524885 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 4 Apr 2018 11:41:04 +0200 Subject: [PATCH 61/78] Update octokit.graphql to 0.0.2. This fixes the escaping issue at https://github.com/grokys/Octokit.GraphQL/issues/72. --- lib/Octokit.GraphQL.0.0.1.nupkg | Bin 164744 -> 0 bytes lib/Octokit.GraphQL.0.0.2-alpha.nupkg | Bin 0 -> 165289 bytes src/GitHub.Api/GitHub.Api.csproj | 8 ++++---- src/GitHub.Api/packages.config | 2 +- src/GitHub.App/GitHub.App.csproj | 16 ++++++++-------- src/GitHub.App/packages.config | 2 +- .../GitHub.InlineReviews.csproj | 8 ++++---- src/GitHub.InlineReviews/packages.config | 2 +- .../GitHub.VisualStudio.csproj | 8 ++++---- src/GitHub.VisualStudio/packages.config | 2 +- test/UnitTests/UnitTests.csproj | 8 ++++---- test/UnitTests/packages.config | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 lib/Octokit.GraphQL.0.0.1.nupkg create mode 100644 lib/Octokit.GraphQL.0.0.2-alpha.nupkg diff --git a/lib/Octokit.GraphQL.0.0.1.nupkg b/lib/Octokit.GraphQL.0.0.1.nupkg deleted file mode 100644 index 589a5e0a76ce818b02d533a75ce841281111a03e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164744 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 ACjbBd diff --git a/lib/Octokit.GraphQL.0.0.2-alpha.nupkg b/lib/Octokit.GraphQL.0.0.2-alpha.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..88686b772909e692b00614980322e86eda0425e4 GIT binary patch literal 165289 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�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

A$j^ovX=o-N>M1;~v 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 =TrFf6~*!@=Z`T_PA+jF_uuhKwbqm(C_-H+MWj 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

D z6!D+sf-riz0!s00sn6B9@~Gw4Mkr2a~_S>^zKQi+bhnjL6PF%1t9ra10cD=suDhqtwDu*jKA`YFCp z9cKSxasVdaPcV7=dbEz2F!9$$$h|*inH3z8dnfWskT-JY#z22D0R*!Eb*Rl>^y_Q| z^7ip!(taUsc;VT6);hF1cX_TvL2k}4#nlVuoXDcLN=8U&`ghrZ7s=~UK<@`cZ>IkT z^z|t9jelkYg^6zY##RhO6Ppk?k}TZyb`KVnL=3)E>yZjci~3L1I#|ngWc7THXvWkm zm?L5j-#ZbEbu{Hn*rMFAS8|c}vB>dm3>SVkf=w&@VANn6FceRp#km6_(px6cyy9_| zv-wdy;wO`nCPycFeH*lip>XnsO!z(3t^%Bo@5dCqULMIlXVMv z*5hCCy9z#LbpTf7_igrCsE!Q7Qa`gn;yndL?E zM^+e>WIlfarwFT9#^UiS?6*f7`*ZB^$l2_BKOrF&&*6Tf|{H~O@#78<{S*pc?esSfM0tsIM-hBMMG-Rf#+_$7Q`Lvp?yLQ@ zcV~zCQh80SrTS8Ytjjsw%0zWV5&NhQ$)_pS7w=XPrzUf84ylf}O^5wB0kx7m$5ztG}m?^U{iV60Cz_t#_yAn%oCgCbEKrHF3$8>Kzpe9 zo1{(bo9xs;`k5;E*6NtMzDZR&gj=Swf%(^-f?4OMWwkjrM#`iH>2&)#jo=^7iUG&p z6?}(iy9kpJ$rCx%W3D>?^JQ8pqo!L92<07FCcG+|)x4-Q)bj7fy$3E~{3Q(um zFsL1YyDX0a^@!2-fhXmgmvohFEV%O%O7Zb86+1p<1DhzLBDr9W@44Zz8`|SkeY72G zK0Fzbl^Fm7bsmdrhDdoA`Lgy4A*|ag)TrG*6BUGv!oc`on&48%^#Op-NYTD{eUVAG z6SOUg=&5TH&`uUw-9mPVTy9Ch!WFl7!`#R`Eb=Gt2`B9+gC}zAUQ?S0N=hp_M5%RY z6fV3On6odSQ;z}|BSoP%-G08K=5=0vw@|QOaKXX2pqbs{+)&#!XCO8zzeX-wa<8+G zTuRcjnz&v!cymu5>PJ~i^fxUhooVsG8dWJ>`yo|nL$>@x_o#p#eJz^(Ji9yWe>upC z8fb?nK<{&rc*&L~(WD#Tytb20CUfimJiU7P`Jl)xG8|YyC!ag;?9zb6K#Es z=C!Pme2B=xz}6!-K8IXGKndsblr;&c`Q0g&_4$`^ZUA(5byF+l!KstaJ3Gzd`7Xc! z$G6~?;=V>z^VYZc4ku5Y$5xus?fhv#gH7F#wYKVHOVLzsweFFLlJliGg$L0|u<)l` zjdxm%;)bb6*AmWp4_G(GQ4_zw5QM^!7IjY{b>^CzVX6p+Jc&b2=#27n6dWbz@Yn_N zO{mD2?1IG2GT;cW1mD`J;Ge)8B%askBWwsd`h*~iAvQ%3$SMol(pwplRG-*L#ybVm zV_cNmKa2SCPU3=e)e47gm4TWG9atXjU@sW9xDsAOcQt4BC@hGjS1ABao%rd4#^tm<-p(|N0 zQrh@6Rb)>tjs#P=F$zchqOAT`j&nWR;N;}|@~b+`tf z(Y@;&4LbiuzG1}I3pE1Y$JAS!TkK(~&3B@omX0FDW8F->jUPyU8AIDXO@z$g#lW9K zda3{6^+MSxromLo>)}~(TPgG34;F;eRx)#E>Vx@#8Uh6ep5qZ?L0ED3AG%vMKs>ARq^wt*=)_Hpm zU2RoY>XRU$8HZ4%%n(EuTw>2^E1`G_uw?)AjL2|g6xwmYjcs}gu8cRhQaB@{yS!hb zeH7RCOB5$93((B>=|BU>5d6x>Z`8E@N2bL{QdI_ux&-_}r{32qhqN2(Ui$mJ>W*mB zHCEDn{j_`W`$e#}D1JSD>FeOZT@x*p@;>M{RXZM4{U?%@cU;Q7kykE4;rb((H7uM3 zDO6$lskmRyw7}$e6(9yzCQ&+f5G_ou1>xr>^<_}%%f?Hk<#RRn!YF(XWj@ZpdH^1r z?U^JJnrk5T&N6yYy90tO>1>G`32hDO>&E>9BN{E6@+sKA8^d*If@4T4b2pg0l{0kb zp9ewo!qud`%E;K1E?5|1(F}%rUb-4VahX%~GDU*Pn^LG*SNo<=mpyCpoW#2n!*IGjQyvAgqHe@_^S<4XKu2E*GwLJ=2dQ z7H2&$_fu--LeNnugg)H(gT8j+lSB^Ykyt3^&vc_I!o;in_e+$}fA&5>#z@SbLA}o& zVU(W%cj9QHKZ(}@x1y_c6hrhMOmg~t&0@`x=kVGU3)6(Qa!GjK?8Ve>i$c%YC`vqd z&<%Ml9ey$EVz8`wjKjHpsy&`pJ2I@krd_3wao6-_^& z?n5!B^TJU;NnffPQ8|b@!pC0|qKwMIwOCWVPS;#;ORt50YX{9e__s6&t4ve#qJawC zSvhY{uvy02;Y)_-jwG>p1JTIqVU#fqK26&U#%OALl*t}vfsZl<3%j%00S>ZD(FEPW z#@sE&9cZkLOol{d7P$32z(Dn7?ix;FK_<<&f5s@q*GU;x=KQ5E&@ z0y2$153hA;FtQp6qzwr$vIup~x4>r-BXFVawjQ#}xlx1Y zSK(Uurp#kdTfldy1|IAShoYtg9930Mt$XP`y3%8Yu$no}oh*!*)IL>EJ5^A7OJcF5 zgP%pBdLZ8f!to#~0|Ldr#hYiN@l_Ja#2)P;3nT3}{W~S z>JVJnc#JSx<&aX*YP8=&G2HJ-^gEV@9!AONEkH+FIC>kJ(sc}Zkj|38qyeT76wmBSp&Vui-Wo%{*JuD<*-KN%bcao=-;}ZYUrO_1kh&V zCQVR!7;mU^FAdor&{RV2tT?xqaY)SzbQaf2y`s|-Q-@IfYE!6>zKA0dwH2q`zMmq_ z@9CVzG@KpKngIL)*ex1-5z|*}!%!<&WIyUitVZ1JDs18!r+BFYaukxK4%ih}A6vSX zvT?+AW2lD8E-eJTXcUtL9Nl)VgO&e&P`ZDdq10}_9+0$E&cvCvSqI|*L6|N@Js$&C zY<9ONTN>w39!b2Q#=B}8OO|QP1KDJU7I5fwYyHN8;39}PwOSa=-pUFbB>1T8k~Rfp zMhxfYd_W#SsDH4c;)|`Y>1_(aC7r?bsmEx|W)JCNA*K{`w3gK7`q7=kX;G2x4AHht zxD(NFsn)xp;>R|H&E#lsxm-Nh?HQVrG)Qu-=8lSqyVf_X@9h>74 zdU&-fJBXy5-+ey5_1b4{HF+}qbY6VHV3DP=92=Z7aws&eB z<%EtZIe$9`gtsX#$OVc0_7sT9z%D_wsy4KbW4jow$8l`R!Zwsqkgn`Pi|~N-oXC?e zPEhQleZ=~~AphXwNwQHNO7ve{x;yeXh20!_9NI0A&JKQCSj;w55z8?j{t`}obmWd~ zba*5`Xy^DeKwuwCP=SOp3zI0?V`4_{6}3QycLG8P06W3T583H zRT*`X=&^A6*IzaBw6ciXkIE5#=VpL|N-_yC3u`_oz^}8AE z->7MV*Ll%IlXSxF?|zB}iAThM0FC*i=f~CETtkfbHVjNj{B21gl@#=Ar~s6dD25Cf zS{+P5rD!b;37VyCjy+ii@cq{zcw8 zQU`jSt&Y?YU1h5yfBe_jGE~F9)LSBREcbfLM33WYueFg-u&0xlEf&}SreHO&kSC<$ zOzWKJHafxZCDF5;<8Vd((de!@oWuPY`NryHQATe6%eF4zGmE3p()y?QQ^jisLQyZA za2FR33P`TS{^f~P_t-})Dg}?}o{GB3rSrXH7eD*>J&8%F>n$cWlH&ApEKTOE*kYM@ zA$4}Pt#5r*PQ}jJLOq;v*ikkDT4!m(j-uTNjBUmDDpU)l=2UYp`dUfy$4gfKbOVTfei5Npn)tuXiql#pD!cq%4X8W~k1entPF^;YJwiafceqYm= z$#6Kd&xMg(lKzRXAdMSt83<_2#Q^OXzNRE!sbz1x&Go){^H}>#| zgFB1aROv3clSe0W71LM}A-mR&6m<#W$lSU`P-A!QDlFDvkLn6-Pm_|H(J>l04eRDD znEiZ!zCs}iO2Kb!UT3C)rs&0n$}{~Ux{el&kp$6V00?Y!?`nvxd#9-}jM23PHI1*aGvz9LX7aq6UiK~)Tw&SM zIU<<<_r>+uSUs0mAIQpf!{KJq&p^Z=W>W~GIwgC?R<>k|dGDL-uiUtl4}8@NSonlh zedX5aVyBpLqt^Up8;8kn!$M{C2e$rT()MWkRCh}zz)}-3}o_)c-X2|CO&YfmjMy>Ab&yQv0o0*d4{$e zB_OC0!2lKuB2IJ+i;^t~pmeY#pQ0d$pniE9=z-ulQX$4U8MW z>5^vph+?h>4xp->bx!avcJ><*3Bz_ym~HKRqzpREb*lX#X?H1K9@s+{sm*d}*Epb% z_?-*T2k7ZZ{-p665TWJ|)=wDB)otw4xQR3dM+e7yAlQM9i?QTY5OKos7i_9_63Jr} z$r~??V3&M&{o!ZaE@+148hYCzeRS#lJJHCtf?o)vkHHBsuFfWxmw3}C3Mheiz0YRC z&^`2nB7dBmK)#wYP0Gs-U2rBusZC%Hfg_pmiHYuBPEVkhTmvb0L(%P+qX zd{o_iQwZFWeXgt^bA?ep_bxY}z>`q2pj;vN5cv&@PwCAv6(hK$_RHhVwUy&8C)0~* z=*xSOkq6_rqWf5EqY9p18Tq&(%=9K+p+hSaDNqYjAYZUhg88^GC{#D=wup5R(MO+| zU&JjlASxd1hy7T1&5-2WTjP?&Tni!p5$DKw{{ywFNG4ojP#YEpD47sjTPK5Xlm=ldoM221 z)~)bJ7S|FU$;o=f6Gn-IbWaJ7Leprf`nF)wuiL=om99$Km|KLG%(pW9#P>5xTf9zM zey!7QU|hGBLQtF&H&Z|ST!MOH2bxV#Pwa^D3F-kHM@E6dHS9#@3ZY}kVXl%rj`U?o zb&Jfq`zam=yB-Ih9xsDFGR{1FmOM6IJTfMX;Y;Jovf{fCS&J~UMqW_Bw1#{;v~fXm zFTI}_4NSv^cX70)aRSF~`l<>gS7}zol06LSc3b_UjKMk}A*0PeKf*zOJu*YKqVA}+ z^$ z_CUq4FfK8HUv@;D*pSTnJS(4pjs(j&4Ku zBcl`it8^4$t_=Jkq47`M1}G$y$@w!$Nc=SpnuaCJo&8YLbRDq}(^AmaPZjQWS)wOF za+-MlIr~#67I5z2C#@)m1xN#YA^#(V{lZp4uWr|Ct!DDlt~?vCOrDU{T9XR9f=K)A zM>#F1P-Hto{MO2r*bBraU=B!RELqs_)P$x&u`Wz*=2Zt8o1Yra<=9cm?`W;whw`wvFc|#KdjSlVEVzK8PnMP@%7Y3la z=qXwTpniRz@iUuI{;)KMq!aLD2o%NBJN*62Ih^lr3zjU?>3gn*{^k_Z)%x7`#hi0v zx5w3;NAx37*2)xKWf{O8lopP#(AQ8JttI=$5Yv|^dTIldS`Ke1#^b0?z`n$=syi@O zt929$dEW*gI6h2`rx%O6#>4Lj=P$xygvc3qc4oLgWP^K9EiCVYs22Bf2f`xhruObI zfisRDxIoe$i>wWYdnZ()m^ayTywKAi$KYLExz-jAQ} zK4bmHzua8ieEbPh`8~Pg;NwTqN@eF=%aP%+GIj8~AcZhD8}PVFyeZRs#eXG%`Bsmi zrmN(H`5FkapJ5R6n`m(g-aILw+<~#H#jCz8MXA_D00@m_xdh%ldGT&9KV%u7Lh05< zMUH(*iQnzL$TCFR({uiCadq{c#4EfqQl(qw{JGw9@RPCmYG}IZ#mkbTx;4ZrfGmsY z7h2iCO{r`g@7u5E*H@R{;D{L?uQM+K#rB19Y>=+itr&CSFeR=ul+fcjlb1qRh~su_ z(`VDvc$TE*m`_s+^ltYeSLmJCut)rEVY<8HhvxKO$wf96w$Eh@Zi&4i0SJ5Qi_Z-y z&K~KD=C9#X7`=plqo#V8_@)&6Ab2$20-F@a0k+o1KKVd=8~sIZ6q@TX1vsu(Kp(P( z8dz~5(rhr$CHq*3Um^?d?iLiiZzpvs#HWw1Yg4YT9x8B9$USpSIK18XwzU4bTOvQN z7r0AbzlP*lYyb;C*j1>CTw)Ut@4d7Gh59SEz4WeSzD%Jor1>j!?u@*u))Qo#HYj#r zSBy_7)Rb_%n!iN&i8#0BLPS>lBn#@z1G30#m_E^0<&DN|DK$(#3f~E{849WO!=XQZ zUo)X`n!{7`fpDmNxKlNCy|6W`%AQSSK1!zL)(26feIzc363HX?K$NhE4Q^aS`3wF2 zo`y3qi0|cvlxxqg;}tndH%bZNIK3zv5Jv7qDdRc$P_|6d7qQ9i>0I7j^*=s0EOzq_ zHtX*(o&PQ`uKMph`mC$psGj%5-c?<$d<~?DO)}ip&hVBa?i>H38Zpv@{ESmbvA_1%d5Ocwl3?Dk!Lbo{MyyEN$!5-#<^Ibb!3>iv-ly%x1(fk%orXM1svEVvi542D<>f*@8y z^)CcUpvEi#%0P~YO_YHRl1FUKyW%{xxPOda2Sb;5>wwJQ5;Zh9o~0aH&k}y-(Qpar z^jPcZ%u5J=690jPnx@JdO`zRw=ZPgiDV!E@WUr&yldJ?}RW>NkbrxnlWP7UWq)^~Z zjM!~yH$^{3Zy!BXbIW&ORx07E%+%)EAsSiwMDg=eRk!1)>ZZ=ic&aBC32wV?GEb!# zRBi`nXqwkh#-hZET!v#gA=aXV@;-u{mQ>CWJksw?P0F1J5;1P~2rhz%8)sB}OX8G% z(=k4!>4DQ72l0ON>70cuQFk)brz`U6`E-p%J!HkApB$m;P_xDHyVpKWoxip#A3HmL zT^VvP{QRVW9Fqoe3}$T!$;eCFODvZpAydN58(CMDWgRp45+-3}yVh5Oc2EA`YVz-v z8`oq?gOk+A{^e35z?rF)87H&#?Fx-?Hil5Nqbuw{6<+$&I;Zu91>mX;8;0^%`tjNW z?8t2WC55jx10c=35J%`<4BU}8VKcTSCljLZuj;?dON{C1!a7olF-eDe(f@ps5XUJY z_M9adF}WbIe>zLqziV}pR6NO>!SvKHXCYxsx%27ljPUI2mYCyhS|#bfr?Ot(=Qv8& z_7%lr^*HwyFn|m!tKLUmK33IuNY`RZ-A2>+7DSzmFn!(-hhppEN6>X+B+wY z=Xlxc#!D4*_~GYsxELs?Ws4MTQANK2jI0fw^DEGb zV+2n9_QgV@GD&e>CaG4rWHWPkZYl@<9&W2f6XmH<6E!Go!JlCFBTN%}&tb&=vW|6=%y(wGnP>aVd-h z^C|jvioU%i$=A+N&mtqTwr5K*9$A^xX^dwB(0HPX8>Ejt-nIeg_`+^wh{KG&nZ%+l zWJ6~2;E43l>xZyN3JU(xtT>weocPH{e_`ALxhN0CQ$0}oi}<5GwV;^yK`pd~hW@Dy zTCECBN*L6)7s~pAg{GGh+eYvhB_z0rQsdZbMNd!Qd9m%&5Ep_8Ei9Ac`C)K%W?`?J zmtllG4z!Y%DK%LFpmcJP#@v`Qo&%E?Uq_xIL~=h{2kC}GYQYp?Q|Q)KSV3y8{OXHV ztm!=)qfIXEV3tS1w@1RTN6e{@jYW@uH;;}fkBu8+c*2m@-^eS^-N(QGUC;K1ORcvk+;5R?Z+>Sw4<;212M1 zhtnRmcj0yC886VTS(A;;A!&wIl|Xeg#jd$f!U*@C&VD1Ot5Z`(N z6nYgrf^GjgZRm$UZ2KK%dkC*eqpIO-=g20$w`*#ghplH5qa@&uPnbBRLd^)>&}vn2 zx=ig-?0_kyp{l>!^*+$I(A#wc=uxjA>ew|q--Iw|=Rgg)Lv|9>kUKVKK@Ef>aT*lQ znFY526PJa#(9xITT`dH%1*=AN<#y*RanK%klj1R6#VSvEETs5-*88b(12aGYry0|R zh0z7YO65)x0-%(M4Cg(>=EjbZqVR0qp-l?kVq3QG)%ymcLZZz#w_xrmXfB7qiPZ=L z0hHZ=J^&KW#;9qY_wAu@Nz*a7uOUAeUg*6`!@(kKfrGruJJ8jE%ir)IGie+nR2|U{ zSwGz|ta9Y!$9Q+xQBF-|bCScMrQtBaVMl;NHf10OsM(RV(#`eK1&86ol^Zm~taADR zYnYW!A1KC4x@rNXPlt~4p28D8*1bAJOyDS{5Z$sHY$tF4x54L!xq5=x9E48t8s`9w zjpY$U+pFY`D)PQT{5YcUs6m)J;^5mj!84N3MHI6?!Iys}gOTFnOrCLhOG@wmdndJ+ z5X|Hv3Yxa^miT5cK$o{0c{=s); z^MHH@#O{ln{JqKLBo+Oyy_1N0_;gWeloyq1kU(RzAAYX1I>W!>?KF93ccUNzWQJH> z*~RS-ygXXV>?2_EYMbf1x%r$Glme5=v=~4wf>0sVGW}UfOPe>iB&+>JeCH5}0PpBt zAQbX4PGX@gPFt8%&2I-JkPe-bw(3t5MWDSnDs!HbRH}dF@kk{@hTcRVbuI9gl=$~_ zE(HSEUGRYzWR8Dt;RR4etn>o-pv@QT7TM(yEhYDXQnr&48>Wn-xu*n)2c~K69#K^% zF zjUeVa4N(qfrX=fHiI8u$iZn_EQheX@>70e?N@THAlLq93bPJC-P>#NfXxsXWdl@!`0z8C>^XCzd`A!H2Dqc$D_>Kf_h+o+Atakgo}rO{OPN@ zc{zu?67MBuNdnq^C_M)*Igo|EQV>>BfmDJDZjd_aN>-$LVz~riiZ2kgp z5ClHMy=v!TRS$WbCVI2DBl?osHrPA&mg$Ow@1ceP-UIRTA>2>;CrXkuHol1e+mgaf z#Uha&Uq)%*18@836`n_tA<56#W}i*s&^$-&~@(Rjr;w_MzH{?u?6_hIdSzw)cg+xlyu zOU14&v`0g_PmO)eQh-ju+v2IG-|>Q2KP#?_tcO)Nh23u^fYM0H*NF94jOB=8JC3{} z(8-tPItfq;9S4c~vd#3QK zEcS2J89-94+(FMm6;iEwp>K{u4Dtq{A?4qMPDH=yehq5&nI^bP!|-VCITnzNGb&x6 z|0sge1w57v=(fOp+C8?rj)5lfDqN?=)gsd*T0ojShFTarDHEoI+$@gii1a)18brn= z3(Lf-lBFF{ziXFNsGnQ}Ol+E)RBB4b-H*?`G-Q83tx|m&;ymz(L+T@~9d9f3ims1A z6`(vC_jDG;HsvKTe(m^BSjpc~Ey-B5=|O_Y7J<+*lC6H-49VnnKDRBgx{q?99nA=& zK-B6S(R}_s!J49O06s?17iw96^)Q4<6v5qsmgI>6L}(8gJUc`fs~0-L_pm0W75@&$ z#>LCJ>)?*?5@}I$MTpMuO^kfD4zb*Bp}~q%&GJEmN=aUc{SZ9?EByd`YLS;aX+@ z71pS&+psp&-o*@tzZR1z6cw9H39+o+3Tkx~&qpA~;qvAVE(}34feD5oU)9?gYOl}l z5IDjE1?QimrQ`Z#{lN{=g~g&3T z4GZf_kAP&y;x|EbOjOy|Y;Z^GpfEtq;I%jb=BojVfK6Gmlj21$!kk4fJdWjYVKX_0 z5zg5nmDAY@gPW-lv6sMXMmev*e#c8oC?2_m#-kvVd&GXgu^JEJ%V?HnA@D=CE+~Y> zu^jK(kKYp-`qXcx)fO<&gs?!cbNAZq4k%w9Z?0Q|BP^^?cHR;z2qQbj_eofJhc1&i zJaOcwGrWx6-%?eeG+w4{aQa@VFNB}3*Kzbrf)A92syIu7oF&FH5tsE4;pub*X{x&8 z6ts<&7nqe;#|}X1^)La|j3MAYH@%5_F*kQ$-t#5(KgEexQsB|QJR`( zinWT@FpMtCHy49@35@kF#b{WAM2;u^9zMASW$QXP z-|aY%R?T{iP6FP8mtYfEV>VO|v}Ya!33nt2nkgtuJ}mDKNfxX17TKj{CqhJbN62-qFX$LUbcXCT+td{A)H*T`YS#S(h36+-Q2vLI}HYFhX7Y|qb zl1hJKDo zD)zFB+znvu?+9y>*qyPc;$+2n!U~9*XQ9yJe2F<)lQ@wPg*9A@1GT|9)O!la4y|o- zIT)mX7PP>@oa*an5q%)Dr$r1mnQbo>FX?x6u^4V?ouRT2r7C5d5+A1G-GBM+EaTFF zc%3)R-q&S?sx4rJ#BExt%CrDf?u#tyA_$$W%xngB*r(GJ@gWZ6GGle*owAp+@^N%`(-C`lUVv z!aj%vZ-Yfy)0jm*Xk=uyhG=i-cX~;=2K!yiX5znxy2P~qWi5wu4+2KFj9V9Dh*BO; zAt3K(`QinP?g2fM+3Q^-B>q93cCj{VIhMD^!5jl=DRM~b;%$zhJj`Mty2ZIp#Ddra z{C=?n(-fH>e4B^mp$h=)6IW%Oe70!Mlwi%3sBq=|T zeQ_KF>R`B53m($-T1@Q6l@M(fb)n$v9`XGc!eMaixf~}CM3=l5{eUuGl#dMU7=o)K zd({sdV%eE{;P$KfavftiDCc1aQd9c)E=YLp`!-Pn@kEUS4>p~O8q}tB^o>!v=njM^ z2N@hV>E}BPn@?4d-C;rd=o>e|-U1A~dxM0fR6J%}Gix_Mn&l#bXL2*SO)4O)a&2~C zbJ7AI%BN$n%;=u_7XIrkIQ4C#E!aR_N`E%CTPP37q1rqowMWn>A@hHe1!1lg>cXUe zur{BQV zjlx4Jl&tg+Lnu;G#xu$~B^3zAP^zQ?>5>Q=N+^oPl_x5gv?t??X(tBVM76sIL9Af`L1|{az+L|JA1QMDg}}UxdRAsDRBZF6GscE_3%3 z^`4(%Ge!IjuSUTOT2Z77L46IMLSfa|lx33xuz)46T2_=mW*LlA3I_X(1hX_~>zwaA z9|wsNc)muCxDy{4fRW0Moc@bwwKDdOR{+fL+1f4_-*Az>ImU7nLNLdJKi|`NGdSYG z;Vg}YVv#&pfm6(X7!Ku6rz-;2M%QTA$`W{8 z7JIHD>riaq_>NZT7-V1>&Efi$qFGiO%m#OYz&>HIx+0rppj|^q>1|WuQ?;eKpnU5c zknAN_;8=PCP0FnwyoiE8HzoQEwMW@(a`iDxg;2!jE8|D5ce)YCm!ds9PzhEo3h8AAd2CfHDQLxEQ z0;4S+T$ohNi3cRA4xJPG2lop{dqGuZNGEx<^_9mXuMAmz6H(Q*C|pwFXKp}wnVdk; z`?>V*9sU9c?v-8uAGG;`-NL*)qNPl4LH^!nP9Y2DSnf%x;UC~Cb8Bz5=$F)EI-%Pp z+*?t7REBUb=KUi}j03#YBqn&YC$hh;{casyC>>Jy7Dtc{b!oS_-S@h)t+PHKOYBc} zI04dcobHU9XI2oH-X23`Z5G(LsLw z`vEFNw)2#7QD@L@ZcmpeAJVDCB{S7Tt)npiULG*;lx$T z)tq?=;ZLeP@HAaWW1c`ILpZ+fVA>}Wofpmbwk7i3{bXbFVv4QqOQ$?mSoN`0%W-Qm zZHeT`?V6gmVlD_JI1X9XY$OJ8NV8Q2&IZ+4b;|bAQMPUmDDrv{1?ei}SK*yJLAmIo|Iq{9PT(LFlGip^j5c-45p{G}i6##(UjFU{XjfNbI-OH@7mZI{+s2aE6h#iMi0e%*uvL+p_GoO&bP? zFIj9ACArfElCK{`_wmb;&6Z%qV)$98yIGzhI+#(Nf;v88_xmvE-&C0Tn@8dWwlnL} zPAMauoO9E@-VZ7PLRQJ)Ga6#`cnMOP;8L&e6%XWa+XKSPZO4GNZ^gVujzu+K-BKo0 zT?}Nce-H1Y#De|ARJM8q|A{s+GjD{LgfmZ4$ED&=$*Prb)V6HCBFf46v(UX`2ZjI7 zF$lYkSg1>;RjU#~m2TNWn;=mwv;uI}0H8FdgSU~)P&BF`UB94$=gwI`T@{lq;jpP( zyd42n`*lY1bu_bKLi;B^Py;$durRLbRYQINkK_BCal#wPgO5L}***^y^stByZy4%yXp z%6;Qi={TPYw#IOi{JqKjn-5VGP!cu~-$h$fitN11EN)k2jj>pB-;)#{qZHqGscL%l z!_QZc0EgJko#uqZzX|Wg?$g=~L>A6%I!BKm%Vvj@q?YV@g zSN}jc!6}Jn!e0E>6uRaKW+p$#$Dh8Q>4Npas~3ghf8bIUQhwU2BmpeimCE)&R)I{MNQ3~#oG#OA1VH_5Syg*Dzq$7GaGoB|ndSzen@+YQ z39Cve#CE{yfNt14NKLw6;pZhw1Y2`svL$ZU7)R3SHu#3FojNdMCna~UMher^ntF>d zQlZri$u-n=4Nkz=RC(`3S;l-psiU2eG2Wn_EZ4uGf((rl&BrY^@Kl`_naoLsW{a5Y zD)~56DG`qw5by^$&300NxwQB-J**TuI#(7&kHMQjH^Hnlp`U*RQxbE`iq5m?;U6Lt8KY739_cm z=UMYt)A}QYVUVZ60N9&aXv#UebC!^nA(7XKwR`%o?$h$-FeZ3qF@i|z4Tf>APT&>& z)`k#&RgeTy1*_zFh-%cKp1>gkXpBm-`#Ly|7wd|j%M58dD6H7oTWbf+5{KJC^BbLt zT|({H+U`_U!S7t%N@}f~;B60cKm4MCTc{mqUBNBXj;OTY76jw3E?5LM?BZfoO2<-P zY=iD`lo%_L-Ozgtb-SvLYV5Si7HKSPLA>M4;Y6cf8%*Vgi>n)T>py{WQWXNO42mk~ z$t4p^J-MODGBUj{3G3(MV;%_>LgP!8E7*OAowJ34ofR0@Hv%@M&DFJl4bUII3)>_l zI)L#e@-<9dn|f}%d~)&)Eh|H!`na*0CwJ!<-JRWVl`DUJL1WMfW_G3bzW-PM@ZWl~ z{87nQloz11<$RgCjQoah4v&Bv5SVRD`C9z!PW{`&4;9{Q;-T^Z@1w+bb(t~E`p9Ts zxEhpbDEW5kIfvM_K@={)8Qd4J2eFNaIAzpY5_0|I0EA&P=sdDB$HBoUbT%v|u^o?& z>OVZ(I@9RFXO>u&56PcVl}|9wO*-`Z@Q~#697Qd|EzoSQn98fA#P5)ccW6785xSXNdf#zTid3Du|+Mb-08~o*7BlnT)Zt za%${kQ72C0s~&gVxE#r~bdSKLTub*zoy)aYj?2xQLmZF8xlHpIUC$K+BXmM%04!x- z)}x>#@e1lANX1r|)%N<6+h9y&RZ6RXb*5jjlAbt)P9Cz#XeoZ(zV_GvCg!;ujxS5# z8w@a228%Mh)0$^NM;z zmPY4Z9s;Uo4?F~5Odv_rhL`75gQ+PUIP|+p;e!?H35Tt#qaH}n|Iy{XIzYiB(9Mw31H82VOf%x_qeTG1>vih%P%VjpV-Q?25NH4rew-_uTO;$Kn;JLgM70T~$EQ-L~3#q-C{TS_F82%vw{jRk@e8;gfCpVy73w^dRZ%$k5cxL!!*x3*UMz#`O~oLti2d?-b8S!X zCcR=ApkR(vuV7FY0Ib@2eS@0|-#k`qVgPEPZ0hCx=+ik183GcTp!#%0UIo-O8e&rQ z_{kZr4r45>^)cI)>5?Il9ivW}Ec)NIa@(n%Y}z-Z&ei@E!Ou_WD#wwolAV$x2&$H_ z7%L?pQ}UL5QOrO+34+&BG|NFRQBD15`!r|(zYwc}+qnXQ6~|l8`IBgV z%c8lxLO90z=wO;{T1)>{xWBKhWw?hY&H`NCZ_iTLNA%aV->nXWJ2sVXaRTVTThi|K zOLjX|u7T-|;w7CuX>+=It(b&rs@1j5&RYf+TCkc2i^Z3{qMyjYwg3W^Rcx|klq`v=C#xi*(z~UiH%;VZ%>tWcphzZX}o!Za!weN zg+UyVdTbiB1a+Vm-O^SuP&->osV>5IGt0wIo~|c$pc&Kk#EvLUx*ovzbE6BmhMf>y zA#^NR&{eX>k@{Sq`btn>N-(4$DwT?aV9E-miuqKtZx5a9j%ND`-Q|!KV{yq8zK#wA zZyk^soKMeZe3o(!ML`HZ6N|7U!U{)^tT3$!e}a@Ju`<(i!PV7Nx2_WedY1;lz0CSC zJ`#B$`BK)9qkUzgMgB@+OyJcH0M*b3isHAdRBlTgtrD(X7_tVaommIo9*qQ?QC%jy zZqN;OXj7sITBPo2zNbE!CB|7zj|DGWtR8i-sclTqb8Ubs4%sTx*c0-UgicNo&(g

$1_+Zci_IkD!C){4pt!?kAD!6jl)BjkvcXX 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 z

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..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll True diff --git a/src/GitHub.Api/packages.config b/src/GitHub.Api/packages.config index d5d4862325..6356b0e89c 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/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 1fc0b749c6..8f3a92c09d 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -136,14 +136,6 @@ ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll - True - - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll - True - ..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll True @@ -152,6 +144,14 @@ ..\..\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 + True + + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll + True + diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index 604d5310a1..aadd56f421 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -21,7 +21,7 @@ - + diff --git a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj index 6e5f68f61b..c743cdc468 100644 --- a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj +++ b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj @@ -355,12 +355,12 @@ ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll True diff --git a/src/GitHub.InlineReviews/packages.config b/src/GitHub.InlineReviews/packages.config index d9a1df0beb..9c2010990b 100644 --- a/src/GitHub.InlineReviews/packages.config +++ b/src/GitHub.InlineReviews/packages.config @@ -34,7 +34,7 @@ - + diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 02ef5df6d6..e354aaaf74 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -239,12 +239,12 @@ ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll True diff --git a/src/GitHub.VisualStudio/packages.config b/src/GitHub.VisualStudio/packages.config index c968000021..dfdd36c63a 100644 --- a/src/GitHub.VisualStudio/packages.config +++ b/src/GitHub.VisualStudio/packages.config @@ -35,7 +35,7 @@ - + diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 46c69bdd60..30c2f002fe 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -177,12 +177,12 @@ ..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.dll True - - ..\..\packages\Octokit.GraphQL.0.0.1\lib\netstandard1.1\Octokit.GraphQL.Core.dll + + ..\..\packages\Octokit.GraphQL.0.0.2-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll True diff --git a/test/UnitTests/packages.config b/test/UnitTests/packages.config index 723198e5a5..87bdee5b8d 100644 --- a/test/UnitTests/packages.config +++ b/test/UnitTests/packages.config @@ -31,7 +31,7 @@ - + From 9189d6be68a9ef0df441324ff9c0968889d075b0 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Wed, 4 Apr 2018 10:51:07 +0100 Subject: [PATCH 62/78] Fix navigate from 'View File' to editor --- src/GitHub.App/Services/PullRequestEditorService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 0fe2435b19..544db94bc8 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -102,6 +102,10 @@ public async Task OpenFile( if (!workingDirectory) { AddBufferTag(buffer, session, fullPath, commitSha, null); + + var textView = FindActiveView(); + var file = await session.GetFile(relativePath); + EnableNavigateToEditor(textView, session, file); } } From 1a867f8474b9eb214dae421482139a816061961f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 4 Apr 2018 12:43:53 +0200 Subject: [PATCH 63/78] Validate PR review state before submit. Split the `PullRequestReviewAuthoringViewModel.Submit` command into 2 separate commands and add a `CanExecute` observable for them: - `Approve` can always be submitted - `Comment` needs either a review body or >0 review comments - `RequestChanges` always needs a review body --- ...RequestReviewAuthoringViewModelDesigner.cs | 4 +- .../PullRequestReviewAuthoringViewModel.cs | 26 ++-- .../IPullRequestReviewAuthoringViewModel.cs | 14 +- .../PullRequestReviewAuthoringView.xaml | 8 +- ...ullRequestReviewAuthoringViewModelTests.cs | 129 +++++++++++++++++- 5 files changed, 158 insertions(+), 23 deletions(-) diff --git a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs index 7b2b0c0ad7..a13faba254 100644 --- a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs @@ -47,7 +47,9 @@ public PullRequestReviewAuthoringViewModelDesigner() public string OperationError { get; set; } public IPullRequestModel PullRequestModel { get; set; } public string RemoteRepositoryOwner { get; set; } - public ReactiveCommand Submit { get; } + public ReactiveCommand Approve { get; } + public ReactiveCommand Comment { get; } + public ReactiveCommand RequestChanges { get; } public ReactiveCommand Cancel { get; } public Task InitializeAsync( diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs index cf7b4b6ca0..b0ce4b730a 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs @@ -57,7 +57,16 @@ public PullRequestReviewAuthoringViewModel( .ToProperty(this, x => x.CanApproveRequestChanges); Files = files; - Submit = ReactiveCommand.CreateAsyncTask(DoSubmit); + Approve = ReactiveCommand.CreateAsyncTask(_ => DoSubmit(Octokit.PullRequestReviewEvent.Approve)); + Comment = ReactiveCommand.CreateAsyncTask( + this.WhenAnyValue( + x => x.Body, + x => x.FileComments.Count, + (body, comments) => !string.IsNullOrWhiteSpace(body) || comments > 0), + _ => DoSubmit(Octokit.PullRequestReviewEvent.Comment)); + RequestChanges = ReactiveCommand.CreateAsyncTask( + this.WhenAnyValue(x => x.Body, body => !string.IsNullOrWhiteSpace(body)), + _ => DoSubmit(Octokit.PullRequestReviewEvent.RequestChanges)); Cancel = ReactiveCommand.CreateAsyncTask(DoCancel); } @@ -112,7 +121,9 @@ public IReadOnlyList FileComments } public ReactiveCommand NavigateToPullRequest { get; } - public ReactiveCommand Submit { get; } + public ReactiveCommand Approve { get; } + public ReactiveCommand Comment { get; } + public ReactiveCommand RequestChanges { get; } public ReactiveCommand Cancel { get; } public async Task InitializeAsync( @@ -237,20 +248,15 @@ async Task UpdateFileComments() await Files.InitializeAsync(session, FilterComments); } - async Task DoSubmit(object arg) + async Task DoSubmit(Octokit.PullRequestReviewEvent e) { OperationError = null; IsBusy = true; try { - Octokit.PullRequestReviewEvent e; - - if (Enum.TryParse(arg.ToString(), out e)) - { - await session.PostReview(Body, e); - Close(); - } + await session.PostReview(Body, e); + Close(); } catch (Exception ex) { diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs index 9baa2d49e6..764027f559 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs @@ -68,9 +68,19 @@ public interface IPullRequestReviewAuthoringViewModel : IPanePageViewModel, IDis ReactiveCommand NavigateToPullRequest { get; } /// - /// Gets a command which submits the review. + /// Gets a command which submits the review as an approval. /// - ReactiveCommand Submit { get; } + ReactiveCommand Approve { get; } + + /// + /// Gets a command which submits the review as a comment. + /// + ReactiveCommand Comment { get; } + + /// + /// Gets a command which submits the review requesting changes. + /// + ReactiveCommand RequestChanges { get; } /// /// Gets a command which cancels the review. diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml index 14a4f5a57f..6aeb7c32ab 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml @@ -56,15 +56,13 @@ BorderThickness="1" Padding="4"> - Comment only - Comment only + Approve - Request changes diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs index 5ebbee8eb0..919d372261 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs @@ -209,24 +209,71 @@ public async Task Updates_Model_Id_From_PendingReviewId_When_Session_PullRequest } [Test] - public async Task Submit_Calls_Session_PostReview() + public async Task Approve_Calls_Session_PostReview_And_Closes() { var review = CreateReview(12, "grokys", state: PullRequestReviewState.Pending); var model = CreatePullRequest("shana", review); var session = CreateSession(); + var closed = false; var target = CreateTarget(model, session); await Initialize(target); - target.Body = "Post review"; - target.Submit.Execute(Octokit.PullRequestReviewEvent.Approve); + target.CloseRequested.Subscribe(_ => closed = true); + target.Approve.Execute(null); await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.Approve); + Assert.True(closed); + } + + [Test] + public async Task Comment_Is_Disabled_When_Has_Empty_Body_And_No_File_Comments() + { + var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await Initialize(target); + + Assert.IsFalse(target.Comment.CanExecute(null)); + } + + [Test] + public async Task Comment_Is_Enabled_When_Has_Body() + { + var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await Initialize(target); + target.Body = "Review body"; + + Assert.IsTrue(target.Comment.CanExecute(null)); } [Test] - public async Task Submit_Closes_Page() + public async Task Comment_Is_Enabled_When_Has_File_Comments() + { + var comment = CreateReviewComment(12); + var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest( + authorLogin: "shana", + reviews: new[] { review }, + reviewComments: new[] { comment }); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await Initialize(target); + target.Body = "Review body"; + + Assert.IsTrue(target.Comment.CanExecute(null)); + } + + [Test] + public async Task Comment_Calls_Session_PostReview_And_Closes() { var review = CreateReview(12, "grokys", state: PullRequestReviewState.Pending); var model = CreatePullRequest("shana", review); @@ -237,10 +284,61 @@ public async Task Submit_Closes_Page() await Initialize(target); target.Body = "Post review"; + target.CloseRequested.Subscribe(_ => closed = true); + target.Comment.Execute(null); + + await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.Comment); + Assert.True(closed); + } + [Test] + public async Task RequestChanges_Is_Disabled_When_Has_Empty_Body() + { + var comment = CreateReviewComment(12); + var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest( + authorLogin: "shana", + reviews: new[] { review }, + reviewComments: new[] { comment }); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await Initialize(target); + target.Body = ""; + + Assert.IsFalse(target.RequestChanges.CanExecute(null)); + } + + [Test] + public async Task RequestChanges_Is_Enabled_When_Has_Body() + { + var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await Initialize(target); + target.Body = "Request Changes"; + + Assert.IsTrue(target.RequestChanges.CanExecute(null)); + } + + [Test] + public async Task RequestChanges_Calls_Session_PostReview_And_Closes() + { + var review = CreateReview(12, "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + + await Initialize(target); + target.Body = "Post review"; target.CloseRequested.Subscribe(_ => closed = true); - target.Submit.Execute(Octokit.PullRequestReviewEvent.Approve); + target.RequestChanges.Execute(null); + await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.RequestChanges); Assert.True(closed); } @@ -349,6 +447,27 @@ static PullRequestModel CreatePullRequest( return result; } + static PullRequestModel CreatePullRequest( + string authorLogin = "grokys", + IEnumerable reviews = null, + IEnumerable reviewComments = null) + { + reviews = reviews ?? new IPullRequestReviewModel[0]; + reviewComments = reviewComments ?? new IPullRequestReviewCommentModel[0]; + + var author = Substitute.For(); + author.Login.Returns(authorLogin); + + var result = new PullRequestModel( + 5, + "Pull Request", + author, + DateTimeOffset.Now); + result.Reviews = reviews.ToList(); + result.ReviewComments = reviewComments.ToList(); + return result; + } + static IPullRequestSession CreateSession( string userLogin = "grokys", params IPullRequestSessionFile[] files) From a4fe246eafc94efb96745a04e5ccc45dda5ca045 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Wed, 4 Apr 2018 15:46:55 +0100 Subject: [PATCH 64/78] Make GetAbsolutePath use Windows '\' separator --- src/GitHub.App/Services/PullRequestEditorService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 544db94bc8..633c41db08 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -516,7 +516,9 @@ void ShowInfoMessage(string message) static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFile file) { - return Path.Combine(session.LocalRepository.LocalPath, file.RelativePath); + var localPath = session.LocalRepository.LocalPath; + var relativePath = file.RelativePath.Replace('/', Path.DirectorySeparatorChar); + return Path.Combine(localPath, relativePath); } static IDisposable OpenInProvisionalTab() From 24dec7d880f1bfe0e567fece05704d5ca9ed47b1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 5 Apr 2018 11:19:29 +0200 Subject: [PATCH 65/78] Reuse existing diff tabs. This requires that when we extract a file to a temp file, that the same file at the same commit is always extracted to the same location. To do this, we use a hash of the file's relative path in the repository and the encoding as the temp directory name, and place the commit sha in the temp file's filename. --- .../Services/PullRequestEditorService.cs | 64 +++++++++++++-- src/GitHub.App/Services/PullRequestService.cs | 78 +++++++++++++------ .../Services/PullRequestServiceTests.cs | 29 ++++++- 3 files changed, 139 insertions(+), 32 deletions(-) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 0fe2435b19..132d047bd3 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Threading.Tasks; +using EnvDTE; using GitHub.Commands; using GitHub.Extensions; using GitHub.Models; @@ -16,6 +17,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; @@ -128,8 +130,6 @@ public async Task OpenDiff(IPullRequestSession session, string relativePath, str var file = await session.GetFile(relativePath, headSha ?? "HEAD"); var mergeBase = await pullRequestService.GetMergeBase(session.LocalRepository, session.PullRequest); var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); - var rightPath = file.RelativePath; - var leftPath = await GetBaseFileName(session, file); var rightFile = workingDirectory ? Path.Combine(session.LocalRepository.LocalPath, relativePath) : await pullRequestService.ExtractToTempFile( @@ -138,12 +138,20 @@ await pullRequestService.ExtractToTempFile( relativePath, file.CommitSha, encoding); + + if (FocusExistingDiffViewer(session, mergeBase, rightFile)) + { + return; + } + var leftFile = await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, relativePath, mergeBase, encoding); + var leftPath = await GetBaseFileName(session, file); + var rightPath = file.RelativePath; var leftLabel = $"{leftPath};{session.GetBaseBranchDisplay()}"; var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {session.PullRequest.Number}"; var caption = $"Diff - {Path.GetFileName(file.RelativePath)}"; @@ -173,9 +181,7 @@ await pullRequestService.ExtractToTempFile( (uint)options); } - object docView; - frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); - var diffViewer = ((IVsDifferenceCodeWindow)docView).DifferenceViewer; + var diffViewer = GetDiffViewer(frame); AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, mergeBase, DiffSide.Left); @@ -384,6 +390,45 @@ IVsTextView OpenDocument(string fullPath) return view; } + bool FocusExistingDiffViewer( + IPullRequestSession session, + string mergeBase, + string rightPath) + { + IVsUIHierarchy uiHierarchy; + uint itemID; + IVsWindowFrame windowFrame; + + // Diff documents are indexed by the path on the right hand side of the comparison. + if (VsShellUtilities.IsDocumentOpen( + serviceProvider, + rightPath, + Guid.Empty, + out uiHierarchy, + out itemID, + out windowFrame)) + { + var diffViewer = GetDiffViewer(windowFrame); + + if (diffViewer != null) + { + PullRequestTextBufferInfo leftBufferInfo; + + if (diffViewer.LeftView.TextBuffer.Properties.TryGetProperty( + typeof(PullRequestTextBufferInfo), + out leftBufferInfo) && + leftBufferInfo.Session.PullRequest.Number == session.PullRequest.Number && + leftBufferInfo.CommitSha == mergeBase) + { + windowFrame.Show(); + return true; + } + } + } + + return false; + } + void ShowErrorInStatusBar(string message) { statusBar.ShowMessage(message); @@ -416,7 +461,7 @@ void AddBufferTag( } } - void EnableNavigateToEditor(IWpfTextView textView, IPullRequestSession session, IPullRequestSessionFile file) + void EnableNavigateToEditor(ITextView textView, IPullRequestSession session, IPullRequestSessionFile file) { var view = vsEditorAdaptersFactory.GetViewAdapter(textView); EnableNavigateToEditor(view, session, file); @@ -515,6 +560,13 @@ static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFi return Path.Combine(session.LocalRepository.LocalPath, file.RelativePath); } + static IDifferenceViewer GetDiffViewer(IVsWindowFrame frame) + { + object docView; + frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); + return (docView as IVsDifferenceCodeWindow)?.DifferenceViewer; + } + static IDisposable OpenInProvisionalTab() { return new NewDocumentStateScope( diff --git a/src/GitHub.App/Services/PullRequestService.cs b/src/GitHub.App/Services/PullRequestService.cs index b0edd9bc26..cfcfac7dd8 100644 --- a/src/GitHub.App/Services/PullRequestService.cs +++ b/src/GitHub.App/Services/PullRequestService.cs @@ -16,6 +16,9 @@ using System.Collections.Generic; using LibGit2Sharp; using GitHub.Logging; +using System.Security.Cryptography; +using GitHub.Extensions; +using Serilog; namespace GitHub.Services { @@ -23,6 +26,7 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.Shared)] public class PullRequestService : IPullRequestService { + static readonly ILogger log = LogManager.ForContext(); const string SettingCreatedByGHfVS = "created-by-ghfvs"; const string SettingGHfVSPullRequest = "ghfvs-pr-owner-number"; @@ -409,7 +413,7 @@ public IObservable SwitchToBranch(ILocalRepositoryModel repository, IPullR { var branchName = GetLocalBranchesInternal(repository, repo, pullRequest).FirstOrDefault(); - Log.Assert(branchName != null, "PullRequestService.SwitchToBranch called but no local branch found"); + log.Assert(branchName != null, "PullRequestService.SwitchToBranch called but no local branch found"); if (branchName != null) { @@ -467,11 +471,18 @@ public async Task ExtractToTempFile( string commitSha, Encoding encoding) { - using (var repo = gitService.GetRepository(repository.LocalPath)) + var tempFilePath = CalculateTempFileName(relativePath, commitSha, encoding); + + if (!File.Exists(tempFilePath)) { - var remote = await gitClient.GetHttpRemote(repo, "origin"); - return await ExtractToTempFile(repo, pullRequest.Number, commitSha, relativePath, encoding); + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var remote = await gitClient.GetHttpRemote(repo, "origin"); + await ExtractToTempFile(repo, pullRequest.Number, commitSha, relativePath, encoding, tempFilePath); + } } + + return tempFilePath; } public Encoding GetEncoding(ILocalRepositoryModel repository, string relativePath) @@ -562,39 +573,30 @@ string CreateUniqueRemoteName(IRepository repo, string name) return uniqueName; } - async Task ExtractToTempFile( + async Task ExtractToTempFile( IRepository repo, int pullRequestNumber, string commitSha, - string fileName, - Encoding encoding) + string relativePath, + Encoding encoding, + string tempFilePath) { string contents; - + try { - contents = await gitClient.ExtractFile(repo, commitSha, fileName) ?? string.Empty; + contents = await gitClient.ExtractFile(repo, commitSha, relativePath) ?? string.Empty; } catch (FileNotFoundException) { var pullHeadRef = $"refs/pull/{pullRequestNumber}/head"; var remote = await gitClient.GetHttpRemote(repo, "origin"); await gitClient.Fetch(repo, remote.Name, commitSha, pullHeadRef); - contents = await gitClient.ExtractFile(repo, commitSha, fileName) ?? string.Empty; + contents = await gitClient.ExtractFile(repo, commitSha, relativePath) ?? string.Empty; } - return CreateTempFile(fileName, commitSha, contents, encoding); - } - - static string CreateTempFile(string fileName, string commitSha, string contents, Encoding encoding) - { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var tempFileName = $"{Path.GetFileNameWithoutExtension(fileName)}@{commitSha}{Path.GetExtension(fileName)}"; - var tempFile = Path.Combine(tempDir, tempFileName); - - Directory.CreateDirectory(tempDir); - File.WriteAllText(tempFile, contents, encoding); - return tempFile; + Directory.CreateDirectory(Path.GetDirectoryName(tempFilePath)); + File.WriteAllText(tempFilePath, contents, encoding); } IEnumerable GetLocalBranchesInternal( @@ -674,6 +676,17 @@ static string GetSafeBranchName(string name) } } + static string CalculateTempFileName(string relativePath, string commitSha, Encoding encoding) + { + // The combination of relative path, commit SHA and encoding should be sufficient to uniquely identify a file. + var relativeDir = Path.GetDirectoryName(relativePath) ?? string.Empty; + var key = relativeDir + '|' + encoding.WebName; + var relativePathHash = GetSha256Hash(key); + var tempDir = Path.Combine(Path.GetTempPath(), "GitHubVisualStudio", "FileContents", relativePathHash); + var tempFileName = $"{Path.GetFileNameWithoutExtension(relativePath)}@{commitSha}{Path.GetExtension(relativePath)}"; + return Path.Combine(tempDir, tempFileName); + } + static string BuildGHfVSConfigKeyValue(IPullRequestModel pullRequest) { return pullRequest.Base.RepositoryCloneUrl.Owner + '#' + @@ -700,5 +713,26 @@ static Tuple ParseGHfVSConfigKeyValue(string value) return null; } + + static string GetSha256Hash(string input) + { + Guard.ArgumentNotNull(input, nameof(input)); + + try + { + using (var sha256 = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha256.ComputeHash(bytes); + + return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + } + } + catch (Exception e) + { + log.Error(e, "IMPOSSIBLE! Generating Sha256 hash caused an exception"); + return null; + } + } } } diff --git a/test/UnitTests/GitHub.App/Services/PullRequestServiceTests.cs b/test/UnitTests/GitHub.App/Services/PullRequestServiceTests.cs index dcb7535d8c..35678eafa7 100644 --- a/test/UnitTests/GitHub.App/Services/PullRequestServiceTests.cs +++ b/test/UnitTests/GitHub.App/Services/PullRequestServiceTests.cs @@ -593,7 +593,14 @@ public async Task ExtractsExistingFile() gitClient.ExtractFile(Arg.Any(), "123", "filename").Returns(GetFileTask(fileContent)); var file = await target.ExtractToTempFile(repository, pr, "filename", "123", Encoding.UTF8); - Assert.That(File.ReadAllText(file), Is.EqualTo(fileContent)); + try + { + Assert.That(File.ReadAllText(file), Is.EqualTo(fileContent)); + } + finally + { + File.Delete(file); + } } [Test] @@ -607,7 +614,14 @@ public async Task CreatesEmptyFileForNonExistentFile() gitClient.ExtractFile(Arg.Any(), "123", "filename").Returns(GetFileTask(null)); var file = await target.ExtractToTempFile(repository, pr, "filename", "123", Encoding.UTF8); - Assert.That(File.ReadAllText(file), Is.EqualTo(string.Empty)); + try + { + Assert.That(File.ReadAllText(file), Is.EqualTo(string.Empty)); + } + finally + { + File.Delete(file); + } } // https://github.com/github/VisualStudio/issues/1010 @@ -631,8 +645,15 @@ public async Task CanChangeEncoding(string encodingName) gitClient.ExtractFile(Arg.Any(), "123", "filename").Returns(GetFileTask(fileContent)); var file = await target.ExtractToTempFile(repository, pr, "filename", "123", encoding); - Assert.That(File.ReadAllText(expectedPath), Is.EqualTo(File.ReadAllText(file))); - Assert.That(File.ReadAllBytes(expectedPath), Is.EqualTo(File.ReadAllBytes(file))); + try + { + Assert.That(File.ReadAllText(expectedPath), Is.EqualTo(File.ReadAllText(file))); + Assert.That(File.ReadAllBytes(expectedPath), Is.EqualTo(File.ReadAllBytes(file))); + } + finally + { + File.Delete(file); + } } static IPullRequestModel CreatePullRequest() From 223e904f5048769def8a4e97dedc2c1615cb90ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 5 Apr 2018 11:29:15 +0200 Subject: [PATCH 66/78] Keep things DRY. Move `GetSha256Hash` into `StringExtensions` as it's needed in >1 place. --- src/GitHub.App/Api/ApiClient.cs | 24 ++-------------- src/GitHub.App/Services/PullRequestService.cs | 28 ++----------------- src/GitHub.Extensions/StringExtensions.cs | 21 ++++++++++++++ 3 files changed, 25 insertions(+), 48 deletions(-) diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs index 8aa4873286..978bfe3354 100644 --- a/src/GitHub.App/Api/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -149,30 +149,10 @@ public IObservable GetLicenses() public HostAddress HostAddress { get; } - static string GetSha256Hash(string input) - { - Guard.ArgumentNotEmptyString(input, nameof(input)); - - try - { - using (var sha256 = SHA256.Create()) - { - var bytes = Encoding.UTF8.GetBytes(input); - var hash = sha256.ComputeHash(bytes); - - return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); - } - } - catch (Exception e) - { - log.Error(e, "IMPOSSIBLE! Generating Sha256 hash caused an exception"); - return null; - } - } - static string GetFingerprint() { - return GetSha256Hash(ProductName + ":" + GetMachineIdentifier()); + var fingerprint = ProductName + ":" + GetMachineIdentifier(); + return fingerprint.GetSha256Hash(); } static string GetMachineNameSafe() diff --git a/src/GitHub.App/Services/PullRequestService.cs b/src/GitHub.App/Services/PullRequestService.cs index cfcfac7dd8..04491e870d 100644 --- a/src/GitHub.App/Services/PullRequestService.cs +++ b/src/GitHub.App/Services/PullRequestService.cs @@ -16,9 +16,7 @@ using System.Collections.Generic; using LibGit2Sharp; using GitHub.Logging; -using System.Security.Cryptography; using GitHub.Extensions; -using Serilog; namespace GitHub.Services { @@ -26,7 +24,6 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.Shared)] public class PullRequestService : IPullRequestService { - static readonly ILogger log = LogManager.ForContext(); const string SettingCreatedByGHfVS = "created-by-ghfvs"; const string SettingGHfVSPullRequest = "ghfvs-pr-owner-number"; @@ -413,7 +410,7 @@ public IObservable SwitchToBranch(ILocalRepositoryModel repository, IPullR { var branchName = GetLocalBranchesInternal(repository, repo, pullRequest).FirstOrDefault(); - log.Assert(branchName != null, "PullRequestService.SwitchToBranch called but no local branch found"); + Log.Assert(branchName != null, "PullRequestService.SwitchToBranch called but no local branch found"); if (branchName != null) { @@ -681,7 +678,7 @@ static string CalculateTempFileName(string relativePath, string commitSha, Encod // The combination of relative path, commit SHA and encoding should be sufficient to uniquely identify a file. var relativeDir = Path.GetDirectoryName(relativePath) ?? string.Empty; var key = relativeDir + '|' + encoding.WebName; - var relativePathHash = GetSha256Hash(key); + var relativePathHash = key.GetSha256Hash(); var tempDir = Path.Combine(Path.GetTempPath(), "GitHubVisualStudio", "FileContents", relativePathHash); var tempFileName = $"{Path.GetFileNameWithoutExtension(relativePath)}@{commitSha}{Path.GetExtension(relativePath)}"; return Path.Combine(tempDir, tempFileName); @@ -713,26 +710,5 @@ static Tuple ParseGHfVSConfigKeyValue(string value) return null; } - - static string GetSha256Hash(string input) - { - Guard.ArgumentNotNull(input, nameof(input)); - - try - { - using (var sha256 = SHA256.Create()) - { - var bytes = Encoding.UTF8.GetBytes(input); - var hash = sha256.ComputeHash(bytes); - - return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); - } - } - catch (Exception e) - { - log.Error(e, "IMPOSSIBLE! Generating Sha256 hash caused an exception"); - return null; - } - } } } diff --git a/src/GitHub.Extensions/StringExtensions.cs b/src/GitHub.Extensions/StringExtensions.cs index f7b01a2447..9e3c649e25 100644 --- a/src/GitHub.Extensions/StringExtensions.cs +++ b/src/GitHub.Extensions/StringExtensions.cs @@ -4,8 +4,11 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using GitHub.Logging; +using Splat; namespace GitHub.Extensions { @@ -222,5 +225,23 @@ public static string Humanize(this string s) var combined = String.Join(" ", result); return Char.ToUpper(combined[0], CultureInfo.InvariantCulture) + combined.Substring(1); } + + /// + /// Generates a SHA256 hash for a string. + /// + /// The input string. + /// The SHA256 hash. + public static string GetSha256Hash(this string input) + { + Guard.ArgumentNotNull(input, nameof(input)); + + using (var sha256 = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha256.ComputeHash(bytes); + + return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + } + } } } From e89145ef847dd112e7fc6253df63a2087a8c72e3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 5 Apr 2018 12:04:15 +0200 Subject: [PATCH 67/78] Allow request changes with only file comments. Also fix unit test for `no body/>0 file comments` case - it wasn't correct before as it was assigning a body. --- .../PullRequestReviewAuthoringViewModel.cs | 13 ++++--- ...ullRequestReviewAuthoringViewModelTests.cs | 39 ++++++++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs index b0ce4b730a..38d6f4c176 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs @@ -57,15 +57,18 @@ public PullRequestReviewAuthoringViewModel( .ToProperty(this, x => x.CanApproveRequestChanges); Files = files; + + var hasBodyOrComments = this.WhenAnyValue( + x => x.Body, + x => x.FileComments.Count, + (body, comments) => !string.IsNullOrWhiteSpace(body) || comments > 0); + Approve = ReactiveCommand.CreateAsyncTask(_ => DoSubmit(Octokit.PullRequestReviewEvent.Approve)); Comment = ReactiveCommand.CreateAsyncTask( - this.WhenAnyValue( - x => x.Body, - x => x.FileComments.Count, - (body, comments) => !string.IsNullOrWhiteSpace(body) || comments > 0), + hasBodyOrComments, _ => DoSubmit(Octokit.PullRequestReviewEvent.Comment)); RequestChanges = ReactiveCommand.CreateAsyncTask( - this.WhenAnyValue(x => x.Body, body => !string.IsNullOrWhiteSpace(body)), + hasBodyOrComments, _ => DoSubmit(Octokit.PullRequestReviewEvent.RequestChanges)); Cancel = ReactiveCommand.CreateAsyncTask(DoCancel); } diff --git a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs index 919d372261..86e9f7efc7 100644 --- a/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs +++ b/test/UnitTests/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs @@ -257,17 +257,15 @@ public async Task Comment_Is_Enabled_When_Has_Body() [Test] public async Task Comment_Is_Enabled_When_Has_File_Comments() { - var comment = CreateReviewComment(12); var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); - var model = CreatePullRequest( - authorLogin: "shana", - reviews: new[] { review }, - reviewComments: new[] { comment }); - var session = CreateSession(); + var model = CreatePullRequest("shana", review); + var session = CreateSession( + "grokys", + CreateSessionFile( + CreateInlineCommentThread(CreateReviewComment(12)))); var target = CreateTarget(model, session); await Initialize(target); - target.Body = "Review body"; Assert.IsTrue(target.Comment.CanExecute(null)); } @@ -292,19 +290,14 @@ public async Task Comment_Calls_Session_PostReview_And_Closes() } [Test] - public async Task RequestChanges_Is_Disabled_When_Has_Empty_Body() + public async Task RequestChanges_Is_Disabled_When_Has_Empty_Body_And_No_File_RequestChangess() { - var comment = CreateReviewComment(12); var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); - var model = CreatePullRequest( - authorLogin: "shana", - reviews: new[] { review }, - reviewComments: new[] { comment }); + var model = CreatePullRequest("shana", review); var session = CreateSession(); var target = CreateTarget(model, session); await Initialize(target); - target.Body = ""; Assert.IsFalse(target.RequestChanges.CanExecute(null)); } @@ -318,7 +311,23 @@ public async Task RequestChanges_Is_Enabled_When_Has_Body() var target = CreateTarget(model, session); await Initialize(target); - target.Body = "Request Changes"; + target.Body = "Review body"; + + Assert.IsTrue(target.RequestChanges.CanExecute(null)); + } + + [Test] + public async Task RequestChanges_Is_Enabled_When_Has_File_Comments() + { + var review = CreateReview(12, "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession( + "grokys", + CreateSessionFile( + CreateInlineCommentThread(CreateReviewComment(12)))); + + var target = CreateTarget(model, session); + await Initialize(target); Assert.IsTrue(target.RequestChanges.CanExecute(null)); } From 41850a9e960e1419692702586157cc0c56eaf7bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 5 Apr 2018 12:20:31 +0200 Subject: [PATCH 68/78] Fix CA errors. --- src/GitHub.App/Services/PullRequestEditorService.cs | 12 ++++++++---- src/GitHub.App/Services/PullRequestService.cs | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 132d047bd3..629b8cf012 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -420,8 +420,7 @@ bool FocusExistingDiffViewer( leftBufferInfo.Session.PullRequest.Number == session.PullRequest.Number && leftBufferInfo.CommitSha == mergeBase) { - windowFrame.Show(); - return true; + return ErrorHandler.Succeeded(windowFrame.Show()); } } } @@ -563,8 +562,13 @@ static string GetAbsolutePath(IPullRequestSession session, IPullRequestSessionFi static IDifferenceViewer GetDiffViewer(IVsWindowFrame frame) { object docView; - frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); - return (docView as IVsDifferenceCodeWindow)?.DifferenceViewer; + + if (ErrorHandler.Succeeded(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView))) + { + return (docView as IVsDifferenceCodeWindow)?.DifferenceViewer; + } + + return null; } static IDisposable OpenInProvisionalTab() diff --git a/src/GitHub.App/Services/PullRequestService.cs b/src/GitHub.App/Services/PullRequestService.cs index 04491e870d..a66a36f502 100644 --- a/src/GitHub.App/Services/PullRequestService.cs +++ b/src/GitHub.App/Services/PullRequestService.cs @@ -17,6 +17,7 @@ using LibGit2Sharp; using GitHub.Logging; using GitHub.Extensions; +using static System.FormattableString; namespace GitHub.Services { @@ -680,7 +681,7 @@ static string CalculateTempFileName(string relativePath, string commitSha, Encod var key = relativeDir + '|' + encoding.WebName; var relativePathHash = key.GetSha256Hash(); var tempDir = Path.Combine(Path.GetTempPath(), "GitHubVisualStudio", "FileContents", relativePathHash); - var tempFileName = $"{Path.GetFileNameWithoutExtension(relativePath)}@{commitSha}{Path.GetExtension(relativePath)}"; + var tempFileName = Invariant($"{Path.GetFileNameWithoutExtension(relativePath)}@{commitSha}{Path.GetExtension(relativePath)}"); return Path.Combine(tempDir, tempFileName); } From 02427b6e85adc3051ec41a1c916531ae3443f118 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 5 Apr 2018 14:20:11 +0200 Subject: [PATCH 69/78] Make avatar in PR review summary clickable. --- .../UI/Controls/AccountAvatar.xaml | 24 +++++++++++----- .../UI/Controls/AccountAvatar.xaml.cs | 28 ++++++++++++++++++- .../PullRequestReviewSummaryView.xaml | 8 +++++- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml index 1a3f97dd83..b534d8903b 100644 --- a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml +++ b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml @@ -2,18 +2,28 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Controls" + Name="root" MinWidth="16" MinHeight="16"> - - - - - + + + + + + + + + + - - + + diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs index 753118a14f..b1629a71a4 100644 --- a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs +++ b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs @@ -1,16 +1,24 @@ using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; using GitHub.Models; namespace GitHub.VisualStudio.UI.Controls { - public partial class AccountAvatar : UserControl + public partial class AccountAvatar : UserControl, ICommandSource { public static readonly DependencyProperty AccountProperty = DependencyProperty.Register( nameof(Account), typeof(IAccount), typeof(AccountAvatar)); + public static readonly DependencyProperty CommandProperty = + ButtonBase.CommandProperty.AddOwner(typeof(AccountAvatar)); + public static readonly DependencyProperty CommandParameterProperty = + ButtonBase.CommandParameterProperty.AddOwner(typeof(AccountAvatar)); + public static readonly DependencyProperty CommandTargetProperty = + ButtonBase.CommandTargetProperty.AddOwner(typeof(AccountAvatar)); public AccountAvatar() { @@ -22,5 +30,23 @@ public IAccount Account get { return (IAccount)GetValue(AccountProperty); } set { SetValue(AccountProperty, 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); } + } } } diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml index f3805f77fb..80e4b85d52 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml @@ -57,7 +57,13 @@ - + Date: Thu, 5 Apr 2018 14:52:48 +0200 Subject: [PATCH 70/78] Tweaked comments. --- .../GitHubPane/IPullRequestReviewFileCommentViewModel.cs | 2 +- src/GitHub.UI/Converters/TrimNewlinesConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs index 4b03eaae05..58a7cfb0aa 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs @@ -20,7 +20,7 @@ public interface IPullRequestReviewFileCommentViewModel string RelativePath { get; } /// - /// Gets a comment which opens the comment in a diff view. + /// Gets a command which opens the comment in a diff view. /// ReactiveCommand Open { get; } } diff --git a/src/GitHub.UI/Converters/TrimNewlinesConverter.cs b/src/GitHub.UI/Converters/TrimNewlinesConverter.cs index 9760a2a146..d7dee09fd7 100644 --- a/src/GitHub.UI/Converters/TrimNewlinesConverter.cs +++ b/src/GitHub.UI/Converters/TrimNewlinesConverter.cs @@ -5,7 +5,7 @@ namespace GitHub.UI { /// - /// An that trims newlines from a string and replaces them + /// An that trims newlines and tabs from a string and replaces them /// with spaces. /// public class TrimNewlinesConverter : ValueConverterMarkupExtension From d084591559fd1361ffb325650f5137c056c1e664 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 9 Apr 2018 12:29:43 +0200 Subject: [PATCH 71/78] Set GitHubActionLink.Opacity on disabled. Sigh. Seems that hyperlinks don't respect dependency property precedence, so the `IsEnabled=False` trigger for `Hyperlink.Foreground` doesn't override the foreground `TemplateBinding`. Not sure what we can do here other than set the control's opacity when disabled, so that's what we'll do for now. --- .../Styles/GitHubActionLink.xaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml b/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml index b4a2cd459d..60218fc5ff 100644 --- a/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml @@ -13,6 +13,15 @@ + + + @@ -40,8 +49,7 @@ - - + From ac0116fc1d7a173875dbd4047f4e05cf8e31bbf7 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 10 Apr 2018 13:04:40 +0100 Subject: [PATCH 72/78] Show navigate to editor hint on status bar When a PR is checked out and the diff view gets focus, show a navigate to editor hint on status bar. --- src/GitHub.App/Services/PullRequestEditorService.cs | 6 ++++++ src/GitHub.VisualStudio.UI/Resources.Designer.cs | 9 +++++++++ src/GitHub.VisualStudio.UI/Resources.resx | 3 +++ 3 files changed, 18 insertions(+) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 8ab31ffc78..0817cb2af1 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -468,6 +468,12 @@ void EnableNavigateToEditor(ITextView textView, IPullRequestSession session, IPu { var view = vsEditorAdaptersFactory.GetViewAdapter(textView); EnableNavigateToEditor(view, session, file); + + textView.GotAggregateFocus += (s, e) => + statusBar.ShowMessage(VisualStudio.UI.Resources.NavigateToEditorStatusMessage); + + textView.LostAggregateFocus += (s, e) => + statusBar.ShowMessage(string.Empty); } void EnableNavigateToEditor(IVsTextView textView, IPullRequestSession session, IPullRequestSessionFile file) diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs index 9ef2087be0..fc870b4de4 100644 --- a/src/GitHub.VisualStudio.UI/Resources.Designer.cs +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -519,6 +519,15 @@ public static string nameText { } } + /// + /// Looks up a localized string similar to Press Enter to navigate to Editor. + /// + public static string NavigateToEditorStatusMessage { + get { + return ResourceManager.GetString("NavigateToEditorStatusMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to No repositories. /// diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index f3406dd482..abe8d7ab30 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -416,4 +416,7 @@ Add a single comment + + Press Enter to navigate to Editor + \ No newline at end of file From 39e911ed7aff53bd8f423a61f0dbb81dd0e128a6 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 10 Apr 2018 13:14:38 +0100 Subject: [PATCH 73/78] Show different message when PR not checked out Let the user know they need to check out PR branch before navigating to editor. --- src/GitHub.App/Services/PullRequestEditorService.cs | 4 +++- src/GitHub.VisualStudio.UI/Resources.Designer.cs | 9 +++++++++ src/GitHub.VisualStudio.UI/Resources.resx | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 0817cb2af1..a3374fa2a3 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -469,8 +469,10 @@ void EnableNavigateToEditor(ITextView textView, IPullRequestSession session, IPu var view = vsEditorAdaptersFactory.GetViewAdapter(textView); EnableNavigateToEditor(view, session, file); + var statusMessage = session.IsCheckedOut ? + UI.Resources.NavigateToEditorStatusMessage : UI.Resources.NavigateToEditorNotCheckedOutStatusMessage; textView.GotAggregateFocus += (s, e) => - statusBar.ShowMessage(VisualStudio.UI.Resources.NavigateToEditorStatusMessage); + StatusBarNotificationService.ShowMessage(statusMessage); textView.LostAggregateFocus += (s, e) => statusBar.ShowMessage(string.Empty); diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs index fc870b4de4..e944285de8 100644 --- a/src/GitHub.VisualStudio.UI/Resources.Designer.cs +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -519,6 +519,15 @@ public static string nameText { } } + /// + /// Looks up a localized string similar to Checkout PR branch and press Enter to navigate to Editor. + /// + public static string NavigateToEditorNotCheckedOutStatusMessage { + get { + return ResourceManager.GetString("NavigateToEditorNotCheckedOutStatusMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Press Enter to navigate to Editor. /// diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index abe8d7ab30..1b3774ae70 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -419,4 +419,7 @@ Press Enter to navigate to Editor + + Checkout PR branch and press Enter to navigate to Editor + \ No newline at end of file From 39fb3beb55b1f6371fe9932b9ccb132c22f08a84 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 10 Apr 2018 13:17:14 +0100 Subject: [PATCH 74/78] Add NavigateToEditorNotCheckedOutInfoMessage resource --- src/GitHub.App/Services/PullRequestEditorService.cs | 2 +- src/GitHub.VisualStudio.UI/Resources.Designer.cs | 9 +++++++++ src/GitHub.VisualStudio.UI/Resources.resx | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index a3374fa2a3..1f78d8d04e 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -497,7 +497,7 @@ async Task DoNavigateToEditor(IPullRequestSession session, IPullRequestSessionFi { if (!session.IsCheckedOut) { - ShowInfoMessage("Checkout PR branch before opening file in solution."); + ShowInfoMessage(UI.Resources.NavigateToEditorNotCheckedOutInfoMessage); return; } diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs index e944285de8..0a9fd57f1d 100644 --- a/src/GitHub.VisualStudio.UI/Resources.Designer.cs +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -519,6 +519,15 @@ public static string nameText { } } + /// + /// Looks up a localized string similar to Checkout PR branch before navigating to Editor. + /// + public static string NavigateToEditorNotCheckedOutInfoMessage { + get { + return ResourceManager.GetString("NavigateToEditorNotCheckedOutInfoMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Checkout PR branch and press Enter to navigate to Editor. /// diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index 1b3774ae70..ce22746ddc 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -419,6 +419,9 @@ Press Enter to navigate to Editor + + Checkout PR branch before navigating to Editor + Checkout PR branch and press Enter to navigate to Editor From 2b46a6ce930f62528ef653c3dfc046680778ad62 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 10 Apr 2018 13:33:16 +0100 Subject: [PATCH 75/78] Fix compile errors after rebase --- src/GitHub.App/Resources.Designer.cs | 27 +++++++++++++++++++ src/GitHub.App/Resources.resx | 9 +++++++ .../Services/PullRequestEditorService.cs | 6 ++--- .../Resources.Designer.cs | 27 ------------------- src/GitHub.VisualStudio.UI/Resources.resx | 9 ------- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/GitHub.App/Resources.Designer.cs b/src/GitHub.App/Resources.Designer.cs index 5851e6a658..bb81295462 100644 --- a/src/GitHub.App/Resources.Designer.cs +++ b/src/GitHub.App/Resources.Designer.cs @@ -270,6 +270,33 @@ internal static string MustPullBeforePush { } } + /// + /// Looks up a localized string similar to Checkout PR branch before navigating to Editor. + /// + internal static string NavigateToEditorNotCheckedOutInfoMessage { + get { + return ResourceManager.GetString("NavigateToEditorNotCheckedOutInfoMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checkout PR branch and press Enter to navigate to Editor. + /// + internal static string NavigateToEditorNotCheckedOutStatusMessage { + get { + return ResourceManager.GetString("NavigateToEditorNotCheckedOutStatusMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Press Enter to navigate to Editor. + /// + internal static string NavigateToEditorStatusMessage { + get { + return ResourceManager.GetString("NavigateToEditorStatusMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to No commits to pull. /// diff --git a/src/GitHub.App/Resources.resx b/src/GitHub.App/Resources.resx index 153a52be18..9af5550670 100644 --- a/src/GitHub.App/Resources.resx +++ b/src/GitHub.App/Resources.resx @@ -306,4 +306,13 @@ https://git-scm.com/download/win InProgress + + Press Enter to navigate to Editor + + + Checkout PR branch before navigating to Editor + + + Checkout PR branch and press Enter to navigate to Editor + \ No newline at end of file diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 1f78d8d04e..1fe108899f 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -470,9 +470,9 @@ void EnableNavigateToEditor(ITextView textView, IPullRequestSession session, IPu EnableNavigateToEditor(view, session, file); var statusMessage = session.IsCheckedOut ? - UI.Resources.NavigateToEditorStatusMessage : UI.Resources.NavigateToEditorNotCheckedOutStatusMessage; + App.Resources.NavigateToEditorStatusMessage : App.Resources.NavigateToEditorNotCheckedOutStatusMessage; textView.GotAggregateFocus += (s, e) => - StatusBarNotificationService.ShowMessage(statusMessage); + statusBar.ShowMessage(statusMessage); textView.LostAggregateFocus += (s, e) => statusBar.ShowMessage(string.Empty); @@ -497,7 +497,7 @@ async Task DoNavigateToEditor(IPullRequestSession session, IPullRequestSessionFi { if (!session.IsCheckedOut) { - ShowInfoMessage(UI.Resources.NavigateToEditorNotCheckedOutInfoMessage); + ShowInfoMessage(App.Resources.NavigateToEditorNotCheckedOutInfoMessage); return; } diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs index 0a9fd57f1d..9ef2087be0 100644 --- a/src/GitHub.VisualStudio.UI/Resources.Designer.cs +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -519,33 +519,6 @@ public static string nameText { } } - /// - /// Looks up a localized string similar to Checkout PR branch before navigating to Editor. - /// - public static string NavigateToEditorNotCheckedOutInfoMessage { - get { - return ResourceManager.GetString("NavigateToEditorNotCheckedOutInfoMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checkout PR branch and press Enter to navigate to Editor. - /// - public static string NavigateToEditorNotCheckedOutStatusMessage { - get { - return ResourceManager.GetString("NavigateToEditorNotCheckedOutStatusMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Press Enter to navigate to Editor. - /// - public static string NavigateToEditorStatusMessage { - get { - return ResourceManager.GetString("NavigateToEditorStatusMessage", resourceCulture); - } - } - /// /// Looks up a localized string similar to No repositories. /// diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index ce22746ddc..f3406dd482 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -416,13 +416,4 @@ Add a single comment - - Press Enter to navigate to Editor - - - Checkout PR branch before navigating to Editor - - - Checkout PR branch and press Enter to navigate to Editor - \ No newline at end of file From 1c7bd8d9f0b58f1d18498d834e114178db4784dd Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 10 Apr 2018 14:58:54 +0100 Subject: [PATCH 76/78] Update wording when PR branch not checked out --- src/GitHub.App/Resources.Designer.cs | 2 +- src/GitHub.App/Resources.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.App/Resources.Designer.cs b/src/GitHub.App/Resources.Designer.cs index bb81295462..f9f2856d6a 100644 --- a/src/GitHub.App/Resources.Designer.cs +++ b/src/GitHub.App/Resources.Designer.cs @@ -280,7 +280,7 @@ internal static string NavigateToEditorNotCheckedOutInfoMessage { } /// - /// Looks up a localized string similar to Checkout PR branch and press Enter to navigate to Editor. + /// Looks up a localized string similar to Press Enter to navigate to Editor (PR branch must be checked out). /// internal static string NavigateToEditorNotCheckedOutStatusMessage { get { diff --git a/src/GitHub.App/Resources.resx b/src/GitHub.App/Resources.resx index 9af5550670..aa055d9027 100644 --- a/src/GitHub.App/Resources.resx +++ b/src/GitHub.App/Resources.resx @@ -313,6 +313,6 @@ https://git-scm.com/download/win Checkout PR branch before navigating to Editor - Checkout PR branch and press Enter to navigate to Editor + Press Enter to navigate to Editor (PR branch must be checked out) \ No newline at end of file From 70c283e73742a2811f1e59f1c8b6dadc163d9c66 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 10 Apr 2018 15:09:48 +0100 Subject: [PATCH 77/78] Show status bar hint when using View File Factor out `EnableNavigateStatusBarMessage` method. --- .../Services/PullRequestEditorService.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index 1fe108899f..360fed5db0 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -466,29 +466,37 @@ void AddBufferTag( void EnableNavigateToEditor(ITextView textView, IPullRequestSession session, IPullRequestSessionFile file) { - var view = vsEditorAdaptersFactory.GetViewAdapter(textView); - EnableNavigateToEditor(view, session, file); - - var statusMessage = session.IsCheckedOut ? - App.Resources.NavigateToEditorStatusMessage : App.Resources.NavigateToEditorNotCheckedOutStatusMessage; - textView.GotAggregateFocus += (s, e) => - statusBar.ShowMessage(statusMessage); - - textView.LostAggregateFocus += (s, e) => - statusBar.ShowMessage(string.Empty); + var vsTextView = vsEditorAdaptersFactory.GetViewAdapter(textView); + EnableNavigateToEditor(vsTextView, session, file); } - void EnableNavigateToEditor(IVsTextView textView, IPullRequestSession session, IPullRequestSessionFile file) + void EnableNavigateToEditor(IVsTextView vsTextView, IPullRequestSession session, IPullRequestSessionFile file) { var commandGroup = VSConstants.CMDSETID.StandardCommandSet2K_guid; var commandId = (int)VSConstants.VSStd2KCmdID.RETURN; - new TextViewCommandDispatcher(textView, commandGroup, commandId).Exec += + new TextViewCommandDispatcher(vsTextView, commandGroup, commandId).Exec += async (s, e) => await DoNavigateToEditor(session, file); var contextMenuCommandGroup = new Guid(Guids.guidContextMenuSetString); var goToCommandId = PkgCmdIDList.openFileInSolutionCommand; - new TextViewCommandDispatcher(textView, contextMenuCommandGroup, goToCommandId).Exec += + new TextViewCommandDispatcher(vsTextView, contextMenuCommandGroup, goToCommandId).Exec += async (s, e) => await DoNavigateToEditor(session, file); + + EnableNavigateStatusBarMessage(vsTextView, session); + } + + void EnableNavigateStatusBarMessage(IVsTextView vsTextView, IPullRequestSession session) + { + var textView = vsEditorAdaptersFactory.GetWpfTextView(vsTextView); + + var statusMessage = session.IsCheckedOut ? + App.Resources.NavigateToEditorStatusMessage : App.Resources.NavigateToEditorNotCheckedOutStatusMessage; + + textView.GotAggregateFocus += (s, e) => + statusBar.ShowMessage(statusMessage); + + textView.LostAggregateFocus += (s, e) => + statusBar.ShowMessage(string.Empty); } async Task DoNavigateToEditor(IPullRequestSession session, IPullRequestSessionFile file) From a3bd375cea0a3920741c3789fca51a1f911c292f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Apr 2018 11:02:12 +0200 Subject: [PATCH 78/78] Fix merge error. --- src/GitHub.VisualStudio.UI/Resources.resx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx index 5233bdeb1b..c54e0086db 100644 --- a/src/GitHub.VisualStudio.UI/Resources.resx +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -415,6 +415,7 @@ Add a single comment + Debugging