Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit e0c04fa

Browse files
committed
Added drafts to PR review authoring.
1 parent 832ee25 commit e0c04fa

File tree

3 files changed

+146
-5
lines changed

3 files changed

+146
-5
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using GitHub.ViewModels.GitHubPane;
2+
3+
namespace GitHub.Models.Drafts
4+
{
5+
/// <summary>
6+
/// Stores a draft for a <see cref="PullRequestReviewAuthoringViewModel"/>.
7+
/// </summary>
8+
public class PullRequestReviewDraft
9+
{
10+
/// <summary>
11+
/// Gets or sets the draft review body.
12+
/// </summary>
13+
public string Body { get; set; }
14+
}
15+
}

src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
using System.ComponentModel.Composition;
44
using System.Linq;
55
using System.Reactive;
6+
using System.Reactive.Concurrency;
67
using System.Reactive.Linq;
78
using System.Threading.Tasks;
89
using GitHub.Extensions;
910
using GitHub.Factories;
1011
using GitHub.Logging;
1112
using GitHub.Models;
13+
using GitHub.Models.Drafts;
14+
using GitHub.Primitives;
1215
using GitHub.Services;
1316
using ReactiveUI;
1417
using Serilog;
@@ -24,7 +27,9 @@ public class PullRequestReviewAuthoringViewModel : PanePageViewModelBase, IPullR
2427

2528
readonly IPullRequestEditorService editorService;
2629
readonly IPullRequestSessionManager sessionManager;
30+
readonly IMessageDraftStore draftStore;
2731
readonly IPullRequestService pullRequestService;
32+
readonly IScheduler timerScheduler;
2833
IPullRequestSession session;
2934
IDisposable sessionSubscription;
3035
PullRequestReviewModel model;
@@ -39,15 +44,31 @@ public PullRequestReviewAuthoringViewModel(
3944
IPullRequestService pullRequestService,
4045
IPullRequestEditorService editorService,
4146
IPullRequestSessionManager sessionManager,
47+
IMessageDraftStore draftStore,
4248
IPullRequestFilesViewModel files)
49+
: this(pullRequestService, editorService, sessionManager,draftStore, files, DefaultScheduler.Instance)
50+
{
51+
}
52+
53+
public PullRequestReviewAuthoringViewModel(
54+
IPullRequestService pullRequestService,
55+
IPullRequestEditorService editorService,
56+
IPullRequestSessionManager sessionManager,
57+
IMessageDraftStore draftStore,
58+
IPullRequestFilesViewModel files,
59+
IScheduler timerScheduler)
4360
{
4461
Guard.ArgumentNotNull(editorService, nameof(editorService));
4562
Guard.ArgumentNotNull(sessionManager, nameof(sessionManager));
63+
Guard.ArgumentNotNull(draftStore, nameof(draftStore));
4664
Guard.ArgumentNotNull(files, nameof(files));
65+
Guard.ArgumentNotNull(timerScheduler, nameof(timerScheduler));
4766

4867
this.pullRequestService = pullRequestService;
4968
this.editorService = editorService;
5069
this.sessionManager = sessionManager;
70+
this.draftStore = draftStore;
71+
this.timerScheduler = timerScheduler;
5172

5273
canApproveRequestChanges = this.WhenAnyValue(
5374
x => x.Model,
@@ -148,8 +169,25 @@ public async Task InitializeAsync(
148169
{
149170
LocalRepository = localRepository;
150171
RemoteRepositoryOwner = owner;
151-
session = await sessionManager.GetSession(owner, repo, pullRequestNumber);
152-
await Load(session.PullRequest);
172+
session = await sessionManager.GetSession(owner, repo, pullRequestNumber).ConfigureAwait(true);
173+
await Load(session.PullRequest).ConfigureAwait(true);
174+
175+
if (LocalRepository?.CloneUrl != null)
176+
{
177+
var key = GetDraftKey();
178+
179+
if (string.IsNullOrEmpty(Body))
180+
{
181+
var draft = await draftStore.GetDraft<PullRequestReviewDraft>(key, string.Empty)
182+
.ConfigureAwait(true);
183+
Body = draft?.Body;
184+
}
185+
186+
this.WhenAnyValue(x => x.Body)
187+
.Throttle(TimeSpan.FromSeconds(1), timerScheduler)
188+
.Select(x => new PullRequestReviewDraft { Body = x })
189+
.Subscribe(x => draftStore.UpdateDraft(key, string.Empty, x));
190+
}
153191
}
154192
finally
155193
{
@@ -182,6 +220,20 @@ public override async Task Refresh()
182220
}
183221
}
184222

223+
public static string GetDraftKey(
224+
UriString cloneUri,
225+
int pullRequestNumber)
226+
{
227+
return Invariant($"pr-review|{cloneUri}|{pullRequestNumber}");
228+
}
229+
230+
protected string GetDraftKey()
231+
{
232+
return GetDraftKey(
233+
LocalRepository.CloneUrl.WithOwner(RemoteRepositoryOwner),
234+
PullRequestModel.Number);
235+
}
236+
185237
async Task Load(PullRequestDetailModel pullRequest)
186238
{
187239
try
@@ -252,8 +304,9 @@ async Task DoSubmit(Octokit.PullRequestReviewEvent e)
252304

253305
try
254306
{
255-
await session.PostReview(Body, e);
307+
await session.PostReview(Body, e).ConfigureAwait(true);
256308
Close();
309+
await draftStore.DeleteDraft(GetDraftKey(), string.Empty).ConfigureAwait(true);
257310
}
258311
catch (Exception ex)
259312
{
@@ -285,6 +338,7 @@ async Task DoCancel()
285338
Close();
286339
}
287340

341+
await draftStore.DeleteDraft(GetDraftKey(), string.Empty).ConfigureAwait(true);
288342
}
289343
catch (Exception ex)
290344
{

test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Reactive.Concurrency;
45
using System.Reactive.Linq;
56
using System.Reactive.Subjects;
67
using System.Threading.Tasks;
78
using GitHub.Factories;
89
using GitHub.Models;
10+
using GitHub.Models.Drafts;
11+
using GitHub.Primitives;
912
using GitHub.Services;
1013
using GitHub.ViewModels.GitHubPane;
1114
using NSubstitute;
@@ -404,6 +407,68 @@ public async Task Cancel_Just_Closes_When_Has_No_Pending_Review_Async()
404407
Assert.True(closed);
405408
}
406409

410+
[Test]
411+
public async Task Loads_Draft()
412+
{
413+
var draftStore = Substitute.For<IMessageDraftStore>();
414+
draftStore.GetDraft<PullRequestReviewDraft>("pr-review|https://github.com/owner/repo|5", string.Empty)
415+
.Returns(new PullRequestReviewDraft
416+
{
417+
Body = "This is a review.",
418+
});
419+
420+
var target = CreateTarget(draftStore: draftStore);
421+
await InitializeAsync(target);
422+
423+
Assert.That(target.Body, Is.EqualTo("This is a review."));
424+
}
425+
426+
[Test]
427+
public async Task Updates_Draft_When_Body_Changes()
428+
{
429+
var scheduler = new HistoricalScheduler();
430+
var draftStore = Substitute.For<IMessageDraftStore>();
431+
var target = CreateTarget(draftStore: draftStore, timerScheduler: scheduler);
432+
await InitializeAsync(target);
433+
434+
target.Body = "Body changed.";
435+
436+
await draftStore.DidNotReceiveWithAnyArgs().UpdateDraft<PullRequestReviewDraft>(null, null, null);
437+
438+
scheduler.AdvanceBy(TimeSpan.FromSeconds(1));
439+
440+
await draftStore.Received().UpdateDraft(
441+
"pr-review|https://github.com/owner/repo|5",
442+
string.Empty,
443+
Arg.Is<PullRequestReviewDraft>(x => x.Body == "Body changed."));
444+
}
445+
446+
[Test]
447+
public async Task Deletes_Draft_When_Review_Approved()
448+
{
449+
var scheduler = new HistoricalScheduler();
450+
var draftStore = Substitute.For<IMessageDraftStore>();
451+
var target = CreateTarget(draftStore: draftStore, timerScheduler: scheduler);
452+
await InitializeAsync(target);
453+
454+
await target.Approve.Execute();
455+
456+
await draftStore.Received().DeleteDraft("pr-review|https://github.com/owner/repo|5", string.Empty);
457+
}
458+
459+
[Test]
460+
public async Task Deletes_Draft_When_Canceled()
461+
{
462+
var scheduler = new HistoricalScheduler();
463+
var draftStore = Substitute.For<IMessageDraftStore>();
464+
var target = CreateTarget(draftStore: draftStore, timerScheduler: scheduler);
465+
await InitializeAsync(target);
466+
467+
await target.Cancel.Execute();
468+
469+
await draftStore.Received().DeleteDraft("pr-review|https://github.com/owner/repo|5", string.Empty);
470+
}
471+
407472
static PullRequestReviewAuthoringViewModel CreateTarget(
408473
PullRequestDetailModel model,
409474
IPullRequestSession session = null,
@@ -420,17 +485,23 @@ static PullRequestReviewAuthoringViewModel CreateTarget(
420485
IPullRequestService pullRequestService = null,
421486
IPullRequestEditorService editorService = null,
422487
IPullRequestSessionManager sessionManager = null,
423-
IPullRequestFilesViewModel files = null)
488+
IMessageDraftStore draftStore = null,
489+
IPullRequestFilesViewModel files = null,
490+
IScheduler timerScheduler = null)
424491
{
425492
editorService = editorService ?? Substitute.For<IPullRequestEditorService>();
426493
sessionManager = sessionManager ?? CreateSessionManager();
494+
draftStore = draftStore ?? Substitute.For<IMessageDraftStore>();
427495
files = files ?? Substitute.For<IPullRequestFilesViewModel>();
496+
timerScheduler = timerScheduler ?? DefaultScheduler.Instance;
428497

429498
return new PullRequestReviewAuthoringViewModel(
430499
pullRequestService,
431500
editorService,
432501
sessionManager,
433-
files);
502+
draftStore,
503+
files,
504+
timerScheduler);
434505
}
435506

436507
static PullRequestReviewModel CreateReview(
@@ -537,6 +608,7 @@ static IPullRequestSessionManager CreateSessionManager(
537608
static ILocalRepositoryModel CreateLocalRepositoryModel()
538609
{
539610
var result = Substitute.For<ILocalRepositoryModel>();
611+
result.CloneUrl.Returns(new UriString("https://github.com/owner/repo"));
540612
result.Owner.Returns("owner");
541613
result.Name.Returns("repo");
542614
return result;

0 commit comments

Comments
 (0)