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

Commit 5df40ff

Browse files
author
Meaghan Lewis
authored
Merge branch 'master' into fixes/make-ncrunch-happy
2 parents b417a95 + 428a590 commit 5df40ff

File tree

125 files changed

+5450
-1271
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+5450
-1271
lines changed

CONTRIBUTING.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ This project adheres to the [Open Code of Conduct][code-of-conduct]. By particip
1313

1414
## Submitting a pull request
1515

16-
0. [Fork][] and clone the repository (see Build Instructions in the [README][readme])
17-
0. Create a new branch: `git checkout -b my-branch-name`
18-
0. Make your change, add tests, and make sure the tests still pass
19-
0. Push to your fork and [submit a pull request][pr]
20-
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
16+
1. [Fork][] and clone the repository (see Build Instructions in the [README][readme])
17+
2. Create a new branch: `git checkout -b my-branch-name`
18+
3. Make your change, add tests, and make sure the tests still pass
19+
4. Push to your fork and [submit a pull request][pr]
20+
5. Pat your self on the back and wait for your pull request to be reviewed and merged.
2121

2222
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
2323

24-
- Follow the existing code's style.
25-
- Write tests.
24+
- Follow the style/format of the existing code.
25+
- Write tests for your changes.
2626
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
2727
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
2828

@@ -38,7 +38,7 @@ There are certain areas of the extension that are restricted in what they can do
3838
### Bug Reporting
3939

4040
Here are a few helpful tips when reporting a bug:
41-
- Verify the bug resides in the GitHub for Visual Studio extension
41+
- Verify that the bug resides in the GitHub for Visual Studio extension
4242
- A lot of functionality provided by this extension resides in the Team Explorer pane, alongside other non-GitHub tools to manage and collaborate on source code, including Visual Studio's Git support, which is owned by Microsoft.
4343
- If this bug not is related to the GitHub extension, visit the [Visual Studio support page](https://www.visualstudio.com/support/support-overview-vs) for help
4444
- Screenshots are very helpful in diagnosing bugs and understanding the state of the extension when it's experiencing problems. Please include them whenever possible.

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
os: Visual Studio 2017
2-
version: '2.5.4.{build}'
2+
version: '2.5.5.{build}'
33
skip_tags: true
44
install:
55
- ps: |

docs/developer/how-viewmodels-are-turned-into-views.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ This is the basis for converting view models to views.
6565

6666
There are currently two top-level controls for our UI:
6767

68-
- [GitHubDialogWindow](../src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml) for the dialog which shows the login, clone, etc views
69-
- [GitHubPaneView](../src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml) for the GitHub pane
68+
- [GitHubDialogWindow](../../src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml) for the dialog which shows the login, clone, etc views
69+
- [GitHubPaneView](../../src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml) for the GitHub pane
7070

7171
In the resources for each of these top-level controls we define a `DataTemplate` like so:
7272

@@ -77,10 +77,10 @@ In the resources for each of these top-level controls we define a `DataTemplate`
7777
</DataTemplate>
7878
```
7979

80-
The `DataTemplate.DataType` here applies the template to all classes inherited from [`GitHub.ViewModels.ViewModelBase`](../src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs) [1]. The template defines a single `ContentControl` whose contents are created by a `ViewLocator`.
80+
The `DataTemplate.DataType` here applies the template to all classes inherited from [`GitHub.ViewModels.ViewModelBase`](../../src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs) [1]. The template defines a single `ContentControl` whose contents are created by a `ViewLocator`.
8181

82-
The [`ViewLocator`](../src/GitHub.VisualStudio/Views/ViewLocator.cs) class is an `IValueConverter` which then creates an instance of the appropriate view for the view model using MEF.
82+
The [`ViewLocator`](../../src/GitHub.VisualStudio/Views/ViewLocator.cs) class is an `IValueConverter` which then creates an instance of the appropriate view for the view model using MEF.
8383

8484
And thus a view model becomes a view.
8585

86-
[1]: it would be nice to make it apply to all classes that inherit `IViewModel` but unfortunately WPF's `DataTemplate`s don't work with interfaces.
86+
[1]: it would be nice to make it apply to all classes that inherit `IViewModel` but unfortunately WPF's `DataTemplate`s don't work with interfaces.
-176 KB
Binary file not shown.

src/GitHub.Api/GitHub.Api.csproj

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,14 @@
4747
</PropertyGroup>
4848
<Import Project="$(SolutionDir)\src\common\signing.props" />
4949
<ItemGroup>
50-
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
51-
<HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
52-
<Private>True</Private>
50+
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
51+
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
5352
</Reference>
54-
<Reference Include="Octokit.GraphQL, Version=0.0.4.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
55-
<HintPath>..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
56-
<Private>True</Private>
53+
<Reference Include="Octokit.GraphQL, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
54+
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
5755
</Reference>
58-
<Reference Include="Octokit.GraphQL.Core, Version=0.0.4.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
59-
<HintPath>..\..\packages\Octokit.GraphQL.0.0.4-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
60-
<Private>True</Private>
56+
<Reference Include="Octokit.GraphQL.Core, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
57+
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
6158
</Reference>
6259
<Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
6360
<HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath>

src/GitHub.Api/packages.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3-
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
4-
<package id="Octokit.GraphQL" version="0.0.4-alpha" targetFramework="net461" />
3+
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
4+
<package id="Octokit.GraphQL" version="0.1.0-beta" targetFramework="net461" />
55
<package id="Serilog" version="2.5.0" targetFramework="net461" />
66
</packages>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Threading.Tasks;
5+
6+
namespace GitHub.Collections
7+
{
8+
/// <summary>
9+
/// A loader for a virtualizing list.
10+
/// </summary>
11+
/// <typeparam name="T">The item type.</typeparam>
12+
/// <remarks>
13+
/// This interface is used by the <see cref="VirtualizingList{T}"/> class to load pages of data.
14+
/// </remarks>
15+
public interface IVirtualizingListSource<T> : IDisposable, INotifyPropertyChanged
16+
{
17+
/// <summary>
18+
/// Gets a value that indicates where loading is in progress.
19+
/// </summary>
20+
bool IsLoading { get; }
21+
22+
/// <summary>
23+
/// Gets the page size of the list source.
24+
/// </summary>
25+
int PageSize { get; }
26+
27+
/// <summary>
28+
/// Gets the total number of items in the list.
29+
/// </summary>
30+
/// <returns>A task returning the count.</returns>
31+
Task<int> GetCount();
32+
33+
/// <summary>
34+
/// Gets the numbered page of items.
35+
/// </summary>
36+
/// <param name="pageNumber">The page number.</param>
37+
/// <returns>A task returning the page contents.</returns>
38+
Task<IReadOnlyList<T>> GetPage(int pageNumber);
39+
}
40+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using System.Windows;
6+
using System.Windows.Threading;
7+
using GitHub.Logging;
8+
using GitHub.Models;
9+
using ReactiveUI;
10+
using Serilog;
11+
12+
namespace GitHub.Collections
13+
{
14+
/// <summary>
15+
/// An <see cref="IVirtualizingListSource{T}"/> that loads GraphQL pages sequentially, and
16+
/// transforms items into a view model after reading.
17+
/// </summary>
18+
/// <typeparam name="TModel">The type of the model read from the remote data source.</typeparam>
19+
/// <typeparam name="TViewModel">The type of the transformed view model.</typeparam>
20+
/// <remarks>
21+
/// GraphQL can only read pages of data sequentally, so in order to read item 450 (assuming a
22+
/// page size of 100), the list source must read pages 1, 2, 3 and 4 in that order. Classes
23+
/// deriving from this class only need to implement <see cref="LoadPage(string)"/> to load a
24+
/// single page and this class will handle the rest.
25+
///
26+
/// In addition, items will usually need to be transformed into a view model after reading. The
27+
/// implementing class overrides <see cref="CreateViewModel(TModel)"/> to carry out that
28+
/// transformation.
29+
/// </remarks>
30+
public abstract class SequentialListSource<TModel, TViewModel> : ReactiveObject, IVirtualizingListSource<TViewModel>
31+
{
32+
static readonly ILogger log = LogManager.ForContext<SequentialListSource<TModel, TViewModel>>();
33+
34+
readonly Dispatcher dispatcher;
35+
readonly object loadLock = new object();
36+
Dictionary<int, Page<TModel>> pages = new Dictionary<int, Page<TModel>>();
37+
Task loading = Task.CompletedTask;
38+
bool disposed;
39+
bool isLoading;
40+
int? count;
41+
int nextPage;
42+
int loadTo;
43+
string after;
44+
45+
/// <summary>
46+
/// Initializes a new instance of the <see cref="SequentialListSource{TModel, TViewModel}"/> class.
47+
/// </summary>
48+
public SequentialListSource()
49+
{
50+
dispatcher = Application.Current?.Dispatcher;
51+
}
52+
53+
/// <inheritdoc/>
54+
public bool IsLoading
55+
{
56+
get { return isLoading; }
57+
private set { this.RaiseAndSetIfChanged(ref isLoading, value); }
58+
}
59+
60+
/// <inheritdoc/>
61+
public virtual int PageSize => 100;
62+
63+
event EventHandler PageLoaded;
64+
65+
public void Dispose() => disposed = true;
66+
67+
/// <inheritdoc/>
68+
public async Task<int> GetCount()
69+
{
70+
dispatcher?.VerifyAccess();
71+
72+
if (!count.HasValue)
73+
{
74+
count = (await EnsureLoaded(0).ConfigureAwait(false)).TotalCount;
75+
}
76+
77+
return count.Value;
78+
}
79+
80+
/// <inheritdoc/>
81+
public async Task<IReadOnlyList<TViewModel>> GetPage(int pageNumber)
82+
{
83+
dispatcher?.VerifyAccess();
84+
85+
var page = await EnsureLoaded(pageNumber);
86+
87+
if (page == null)
88+
{
89+
return null;
90+
}
91+
92+
var result = page.Items
93+
.Select(CreateViewModel)
94+
.ToList();
95+
pages.Remove(pageNumber);
96+
return result;
97+
}
98+
99+
/// <summary>
100+
/// When overridden in a derived class, transforms a model into a view model after loading.
101+
/// </summary>
102+
/// <param name="model">The model.</param>
103+
/// <returns>The view model.</returns>
104+
protected abstract TViewModel CreateViewModel(TModel model);
105+
106+
/// <summary>
107+
/// When overridden in a derived class reads a page of results from GraphQL.
108+
/// </summary>
109+
/// <param name="after">The GraphQL after cursor.</param>
110+
/// <returns>A task which returns the page of results.</returns>
111+
protected abstract Task<Page<TModel>> LoadPage(string after);
112+
113+
/// <summary>
114+
/// Called when the source begins loading pages.
115+
/// </summary>
116+
protected virtual void OnBeginLoading()
117+
{
118+
IsLoading = true;
119+
}
120+
121+
/// <summary>
122+
/// Called when the source finishes loading pages.
123+
/// </summary>
124+
protected virtual void OnEndLoading()
125+
{
126+
IsLoading = false;
127+
}
128+
129+
async Task<Page<TModel>> EnsureLoaded(int pageNumber)
130+
{
131+
if (pageNumber < nextPage)
132+
{
133+
return pages[pageNumber];
134+
}
135+
136+
var pageLoaded = WaitPageLoaded(pageNumber);
137+
loadTo = Math.Max(loadTo, pageNumber);
138+
139+
while (!disposed)
140+
{
141+
lock (loadLock)
142+
{
143+
if (loading.IsCompleted)
144+
{
145+
loading = Load();
146+
}
147+
}
148+
149+
var completed = await Task.WhenAny(loading, pageLoaded).ConfigureAwait(false);
150+
151+
if (completed.IsFaulted)
152+
{
153+
throw completed.Exception;
154+
}
155+
156+
if (pageLoaded.IsCompleted)
157+
{
158+
// A previous waiting task may have already returned the page. If so, return null.
159+
pages.TryGetValue(pageNumber, out var result);
160+
return result;
161+
}
162+
}
163+
164+
return null;
165+
}
166+
167+
Task WaitPageLoaded(int page)
168+
{
169+
var tcs = new TaskCompletionSource<bool>();
170+
EventHandler handler = null;
171+
handler = (s, e) =>
172+
{
173+
if (nextPage > page)
174+
{
175+
tcs.SetResult(true);
176+
PageLoaded -= handler;
177+
}
178+
};
179+
PageLoaded += handler;
180+
return tcs.Task;
181+
}
182+
183+
async Task Load()
184+
{
185+
OnBeginLoading();
186+
187+
try
188+
{
189+
while (nextPage <= loadTo && !disposed)
190+
{
191+
await LoadNextPage().ConfigureAwait(false);
192+
PageLoaded?.Invoke(this, EventArgs.Empty);
193+
}
194+
}
195+
finally
196+
{
197+
OnEndLoading();
198+
}
199+
}
200+
201+
async Task LoadNextPage()
202+
{
203+
log.Debug("Loading page {Number} of {ModelType}", nextPage, typeof(TModel));
204+
205+
var page = await LoadPage(after).ConfigureAwait(false);
206+
pages[nextPage++] = page;
207+
after = page.EndCursor;
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)