8000 Merge pull request #1312 from github/feature/pull-request-filtering · github/VisualStudio@51aedf2 · GitHub
[go: up one dir, main page]

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

Commit 51aedf2

Browse files
authored
Merge pull request #1312 from github/feature/pull-request-filtering
Pull Request Filtering
2 parents 0f2f016 + 1bed9e3 commit 51aedf2

File tree

8 files changed

+238
-33
lines changed

8 files changed

+238
-33
lines changed

src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public PullRequestListViewModelDesigner()
5353
Authors = new ObservableCollection<IAccount>(prs.Select(x => x.Author));
5454
SelectedAssignee = Assignees.ElementAt(1);
5555
SelectedAuthor = Authors.ElementAt(1);
56+
57+
IsLoaded = true;
5658
}
5759

5860
public IReadOnlyList<IRemoteRepositoryModel> Repositories { get; }
@@ -68,6 +70,8 @@ public PullRequestListViewModelDesigner()
6870
public IAccount SelectedAuthor { get; set; }
6971
public bool RepositoryIsFork { get; set; } = true;
7072
public bool ShowPullRequestsForFork { get; set; }
73+
public string SearchQuery { get; set; }
74+
public bool IsLoaded { get; }
7175

7276
public ObservableCollection<IAccount> Assignees { get; set; }
7377
public IAccount SelectedAssignee { get; set; }

src/GitHub.App/ViewModels/PullRequestListViewModel.cs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
44
using System.ComponentModel.Composition;
5+
using System.Globalization;
56
using System.Linq;
67
using System.Reactive.Linq;
78
using System.Reactive.Subjects;
@@ -11,6 +12,7 @@
1112
using GitHub.Collections;
1213
using GitHub.Exports;
1314
using GitHub.Extensions;
15+
using GitHub.Helpers;
1416
using GitHub.Logging;
1517
using GitHub.Models;
1618
using GitHub.Services;
@@ -93,19 +95,19 @@ public PullRequestListViewModel(
9395

9496
this.WhenAny(x => x.SelectedState, x => x.Value)
9597
.Where(x => PullRequests != null)
96-
.Subscribe(s => UpdateFilter(s, SelectedAssignee, SelectedAuthor));
98+
.Subscribe(s => UpdateFilter(s, SelectedAssignee, SelectedAuthor, SearchQuery));
9799

98100
this.WhenAny(x => x.SelectedAssignee, x => x.Value)
99101
.Where(x => PullRequests != null && x != EmptyUser)
100-
.Subscribe(a => UpdateFilter(SelectedState, a, SelectedAuthor));
102+
.Subscribe(a => UpdateFilter(SelectedState, a, SelectedAuthor, SearchQuery));
101103

102104
this.WhenAny(x => x.SelectedAuthor, x => x.Value)
103105
.Where(x => PullRequests != null && x != EmptyUser)
104-
.Subscribe(a => UpdateFilter(SelectedState, SelectedAssignee, a));
106+
.Subscribe(a => UpdateFilter(SelectedState, SelectedAssignee, a, SearchQuery));
105107

106-
this.WhenAnyValue(x => x.SelectedRepository)
107-
.Skip(1)
108-
.Subscribe(_ => ResetAndLoad());
108+
this.WhenAny(x => x.SearchQuery, x => x.Value)
109+
.Where(x => PullRequests != null)
110+
.Subscribe(f => UpdateFilter(SelectedState, SelectedAssignee, SelectedAuthor, f));
109111

110112
SelectedState = States.FirstOrDefault(x => x.Name == listSettings.SelectedState) ?? States[0];
111113
OpenPullRequest = ReactiveCommand.Create();
@@ -173,19 +175,50 @@ async Task Load()
173175
}
174176

175177
IsBusy = false;
176-
UpdateFilter(SelectedState, SelectedAssignee, SelectedAuthor);
178+
UpdateFilter(SelectedState, SelectedAssignee, SelectedAuthor, SearchQuery);
177179
});
178180
}
179181

180-
void UpdateFilter(PullRequestState state, IAccount ass, IAccount aut)
182+
void UpdateFilter(PullRequestState state, IAccount ass, IAccount aut, string filText)
181183
{
182184
if (PullRequests == null)
183185
return;
184-
pullRequests.Filter = (pr, i, l) =>
185-
(!state.IsOpen.HasValue || state.IsOpen == pr.IsOpen) &&
186-
(ass == null || ass.Equals(pr.Assignee)) &&
187-
(aut == null || aut.Equals(pr.Author));
188-
SaveSettings();
186+
187+
var filterTextIsNumber = false;
188+
var filterTextIsString = false;
189+
var filterPullRequestNumber = 0;
190+
191+
if (filText != null)
192+
{
193+
filText = filText.Trim();
194+
195+
var hasText = !string.IsNullOrEmpty(filText);
196+
197+
if (hasText && filText.StartsWith("#", StringComparison.CurrentCultureIgnoreCase))
198+
{
199+
filterTextIsNumber = int.TryParse(filText.Substring(1), out filterPullRequestNumber);
200+
}
201+
else
202+
{
203+
filterTextIsNumber = int.TryParse(filText, out filterPullRequestNumber);
204+
}
205+
206+
filterTextIsString = hasText && !filterTextIsNumber;
207+
}
208+
209+
pullRequests.Filter = (pullRequest, index, list) =>
210+
(!state.IsOpen.HasValue || state.IsOpen == pullRequest.IsOpen) &&
211+
(ass == null || ass.Equals(pullRequest.Assignee)) &&
212+
(aut == null || aut.Equals(pullRequest.Author)) &&
213+
(filterTextIsNumber == false || pullRequest.Number == filterPullRequestNumber) &&
214+
(filterTextIsString == false || pullRequest.Title.ToUpperInvariant().Contains(filText.ToUpperInvariant()));
215+
}
216+
217+
string searchQuery;
218+
public string SearchQuery
219+
{
220+
get { return searchQuery; }
221+
set { this.RaiseAndSetIfChanged(ref searchQuery, value); }
189222
}
190223

191224
bool isBusy;
@@ -271,6 +304,8 @@ public IAccount EmptyUser
271304
get { return emptyUser; }
272305
}
273306

307+
public bool IsSearchEnabled => true;
308+
274309
readonly Subject<ViewWithData> navigate = new Subject<ViewWithData>();
275310
public IObservable<ViewWithData> Navigate => navigate;
276311

@@ -308,7 +343,7 @@ void CreatePullRequests()
308343
void ResetAndLoad()
309344
{
310345
CreatePullRequests();
311-
UpdateFilter(SelectedState, SelectedAssignee, SelectedAuthor);
346+
UpdateFilter(SelectedState, SelectedAssignee, SelectedAuthor, SearchQuery);
312347
Load().Forget();
313348
}
314349

src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override string ToString()
2727
}
2828
}
2929

30-
public interface IPullRequestListViewModel : IViewModel, ICanNavigate, IHasBusy
30+
public interface IPullRequestListViewModel : ISearchablePanePageViewModel, ICanNavigate, IHasBusy
3131
{
3232
IReadOnlyList<IRemoteRepositoryModel> Repositories { get; }
3333
IRemoteRepositoryModel SelectedRepository { get; set; }

src/GitHub.Exports/GitHub.Exports.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,10 @@
152152
<Compile Include="Models\UsageData.cs" />
153153
<Compile Include="Services\IUsageService.cs" />
154154
<Compile Include="Settings\PkgCmdID.cs" />
155+
<Compile Include="ViewModels\IGitHubPaneViewModel.cs" />
155156
<Compile Include="ViewModels\IHasErrorState.cs" />
156157
<Compile Include="ViewModels\IHasLoading.cs" />
158+
<Compile Include="ViewModels\ISearchablePageViewModel.cs" />
157159
<Compile Include="ViewModels\IPanePageViewModel.cs" />
158160
<Compile Include="ViewModels\IViewModel.cs" />
159161
<None Include="..\common\settings.json">
@@ -258,7 +260,6 @@
258260
<Compile Include="Services\IGitHubServiceProvider.cs" />
259261
<Compile Include="Services\IWikiProbe.cs" />
260262
<Compile Include="Services\WikiProbe.cs" />
261-
<Compile Include="ViewModels\IGitHubPaneViewModel.cs" />
262263
<Compile Include="Primitives\HostAddress.cs" />
263264
<Compile Include="UI\IUIController.cs" />
264265
<Compile Include="Models\IPullRequestModel.cs" />
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Collections.ObjectModel;
2-
using System.Windows.Input;
3-
using GitHub.UI;
1+
using GitHub.UI;
42

53
namespace GitHub.ViewModels
64
{
@@ -10,5 +8,15 @@ public interface IGitHubPaneViewModel : IViewModel
108
IView Control { get; }
119
string Message { get; }
1210
MessageType MessageType { get; }
11+
12+
/// <summary>
13+
/// Gets a value indicating whether search is available on the current page.
14+
/// </summary>
15+
bool IsSearchEnabled { get; }
16+
17+
/// <summary>
18+
/// Gets or sets the search query for the current page.
19+
/// </summary>
20+
string SearchQuery { get; set; }
1321
}
1422
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace GitHub.ViewModels
4+
{
5+
/// <summary>
6+
/// A view model that represents a searchable page in the GitHub pane.
7+
/// </summary>
8+
public interface ISearchablePanePageViewModel : IPanePageViewModel
9+
{
10+
/// <summary>
11+
/// Gets or sets the current search query.
12+
/// </summary>
13+
string SearchQuery { get; set; }
14+
}
15+
}

src/GitHub.VisualStudio/UI/GitHubPane.cs

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
using System;
2-
using System.Runtime.InteropServices;
3-
using Microsoft.VisualStudio.Shell;
4-
using Microsoft.VisualStudio.Shell.Interop;
52
using System.ComponentModel.Design;
6-
using System.Windows.Controls;
7-
using GitHub.Services;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Reactive.Linq;
5+
using System.Runtime.InteropServices;
86
using GitHub.Extensions;
9-
using GitHub.Models;
7+
using GitHub.Logging;
8+
using GitHub.Services;
109
using GitHub.UI;
1110
using GitHub.ViewModels;
12-
using System.Diagnostics;
13-
using GitHub.Logging;
14-
using GitHub.VisualStudio.UI.Views;
11+
using Microsoft.VisualStudio.Shell;
12+
using Microsoft.VisualStudio.Shell.Interop;
13+
using ReactiveUI;
1514

1615
namespace GitHub.VisualStudio.UI
1716
{
@@ -32,11 +31,27 @@ public class GitHubPane : ToolWindowPane, IServiceProviderAware, IViewHost
3231
{
3332
public const string GitHubPaneGuid = "6b0fdc0a-f28e-47a0-8eed-cc296beff6d2";
3433
bool initialized = false;
34+
IDisposable viewSubscription;
3535

3636
IView View
3737
{
3838
get { return Content as IView; }
39-
set { Content = value; }
39+
set
40+
{
41+
viewSubscription?.Dispose();
42+
viewSubscription = null;
43+
44+
Content = value;
45+
46+
viewSubscription = value.WhenAnyValue(x => x.ViewModel)
47+
.SelectMany(x =>
48+
{
49+
var pane = x as IGitHubPaneViewModel;
50+
return pane?.WhenAnyValue(p => p.IsSearchEnabled, p => p.SearchQuery)
51+
?? Observable.Return(Tuple.Create<bool, string>(false, null));
52+
})
53+
.Subscribe(x => UpdateSearchHost(x.Item1, x.Item2));
54+
}
4055
}
4156

4257
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
@@ -56,6 +71,8 @@ public GitHubPane() : base(null)
5671
View = uiProvider.GetView(Exports.UIViewType.GitHubPane);
5772
}
5873

74+
public override bool SearchEnabled => true;
75+
5976
protected override void Initialize()
6077
{
6178
base.Initialize();
@@ -78,5 +95,89 @@ public void ShowView(ViewWithData data)
7895
{
7996
View.ViewModel?.Initialize(data);
8097
}
98+
99+
[SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Justification = "WTF CA, I'm overriding!")]
100+
public override IVsSearchTask CreateSearch(uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback)
101+
{
102+
var pane = View.ViewModel as IGitHubPaneViewModel;
103+
104+
if (pane != null)
105+
{
106+
return new SearchTask(pane, dwCookie, pSearchQuery, pSearchCallback);
107+
}
108+
109+
return null;
110+
}
111+
112+
public override void ClearSearch()
113+
{
114+
var pane = View.ViewModel as IGitHubPaneViewModel;
115+
116+
if (pane != null)
117+
{
118+
pane.SearchQuery = null;
119+
}
120+
}
121+
122+
public override void OnToolWindowCreated()
123+
{
124+
base.OnToolWindowCreated();
125+
126+
Marshal.ThrowExceptionForHR(((IVsWindowFrame)Frame)?.SetProperty(
127+
(int)__VSFPROPID5.VSFPROPID_SearchPlacement,
128+
__VSSEARCHPLACEMENT.SP_STRETCH) ?? 0);
129+
130+
var pane = View.ViewModel as IGitHubPaneViewModel;
131+
UpdateSearchHost(pane?.IsSearchEnabled ?? false, pane?.SearchQuery);
132+
}
133+
134+
void UpdateSearchHost(bool enabled, string query)
135+
{
136+
if (SearchHost != null)
137+
{
138+
SearchHost.IsEnabled = enabled;
139+
140+
if (SearchHost.SearchQuery?.SearchString != query)
141+
{
142+
SearchHost.SearchAsync(query != null ? new SearchQuery(query) : null);
143+
}
144+
}
145+
}
146+
147+
class SearchTask : VsSearchTask
148+
{
149+
readonly IGitHubPaneViewModel viewModel;
150+
151+
public SearchTask(
152+
IGitHubPaneViewModel viewModel,
153+
uint dwCookie,
154+
IVsSearchQuery pSearchQuery,
155+
IVsSearchCallback pSearchCallback)
156+
: base(dwCookie, pSearchQuery, pSearchCallback)
157+
{
158+
this.viewModel = viewModel;
159+
}
160+
161+
protected override void OnStartSearch()
162+
{
163+
viewModel.SearchQuery = SearchQuery.SearchString;
164+
base.OnStartSearch();
165+
}
166+
167+
protected override void OnStopSearch() => viewModel.SearchQuery = null;
168+
}
169+
170+
class SearchQuery : IVsSearchQuery
171+
{
172+
public SearchQuery(string query)
173+
{
174+
SearchString = query;
175+
}
176+
177+
public uint ParseError => 0;
178+
public string SearchString { get; }
179+
180+
public uint GetTokens(uint dwMaxTokens, IVsSearchToken[] rgpSearchTokens) => 0;
181+
}
81182
}
82183
}

0 commit comments

Comments
 (0)
0