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

WIP: Issues #1957

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/GitHub.App/SampleData/IssueListItemViewModelDesigner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Diagnostics.CodeAnalysis;
using GitHub.ViewModels;
using GitHub.ViewModels.GitHubPane;

namespace GitHub.SampleData
{
[ExcludeFromCodeCoverage]
public class IssueListItemViewModelDesigner : ViewModelBase, IIssueListItemViewModel
{
public string Id { get; set; }
public IActorViewModel Author { get; set; }
public int CommentCount { get; set; }
public int Number { get; set; }
public string Title { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
}
}
69 changes: 69 additions & 0 deletions src/GitHub.App/SampleData/IssueListViewModelDesigner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Data;
using GitHub.Models;
using GitHub.ViewModels;
using GitHub.ViewModels.GitHubPane;
using ReactiveUI;

namespace GitHub.SampleData
{
[ExcludeFromCodeCoverage]
public class IssueListViewModelDesigner : PanePageViewModelBase, IIssueListViewModel
{
public IssueListViewModelDesigner()
{
Items = new[]
{
new IssueListItemViewModelDesigner
{
Number = 1855,
Title = "Do something sensible when things go wrong",
Author = new ActorViewModelDesigner("jcansdale"),
UpdatedAt = DateTimeOffset.Now - TimeSpan.FromDays(1),
},
new IssueListItemViewModelDesigner
{
Number = 1865,
Title = "Installer hangs after download of GHfVS is complete",
Author = new ActorViewModelDesigner("meaghanlewis"),
CommentCount = 7,
UpdatedAt = DateTimeOffset.Now - TimeSpan.FromMinutes(2),
},
new IssueListItemViewModelDesigner
{
Number = 1908,
Title = "Unicode error can occur when viewing diff for PR files",
Author = new ActorViewModelDesigner("StanleyGoldman"),
CommentCount = 0,
UpdatedAt = DateTimeOffset.Now - TimeSpan.FromHours(5),
},
};

ItemsView = CollectionViewSource.GetDefaultView(Items);
States = new[] { "Open", "Closed", "All" };
SelectedState = "Open";
}

public IUserFilterViewModel AuthorFilter { get; set; }
public IReadOnlyList<IIssueListItemViewModelBase> Items { get; }
public ICollectionView ItemsView { get; }
public LocalRepositoryModel LocalRepository { get; set; }
public IssueListMessage Message { get; set; }
public RepositoryModel RemoteRepository { get; set; }
public IReadOnlyList<RepositoryModel> Forks { get; }
public string SearchQuery { get; set; }
public string SelectedState { get; set; }
public string StateCaption { get; set; }
public IReadOnlyList<string> States { get; }
public Uri WebUrl => null;
public ReactiveCommand<IIssueListItemViewModelBase, Unit> OpenItem { get; }
public ReactiveCommand<IIssueListItemViewModel, IIssueListItemViewModel> OpenItemInBrowser { get; }

public Task InitializeAsync(LocalRepositoryModel repository, IConnection connection) => Task.CompletedTask;
}
}
27 changes: 27 additions & 0 deletions src/GitHub.App/Services/FromGraphQlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using CheckAnnotationLevel = GitHub.Models.CheckAnnotationLevel;
using CheckConclusionState = GitHub.Models.CheckConclusionState;
using CheckStatusState = GitHub.Models.CheckStatusState;
using IssueState = GitHub.Models.IssueState;
using PullRequestReviewState = GitHub.Models.PullRequestReviewState;
using StatusState = GitHub.Models.StatusState;

Expand Down Expand Up @@ -38,6 +39,19 @@ public static class FromGraphQlExtensions
}
}

public static IssueState FromGraphQl(this Octokit.GraphQL.Model.IssueState value)
{
switch (value)
{
case Octokit.GraphQL.Model.IssueState.Open:
return IssueState.Open;
case Octokit.GraphQL.Model.IssueState.Closed:
return IssueState.Closed;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}

public static Models.PullRequestState FromGraphQl(this Octokit.GraphQL.Model.PullRequestState value)
{
switch (value)
Expand Down Expand Up @@ -121,5 +135,18 @@ public static CheckAnnotationLevel FromGraphQl(this Octokit.GraphQL.Model.CheckA
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}

public static Octokit.GraphQL.Model.IssueState ToGraphQL(this IssueState value)
{
switch (value)
{
case IssueState.Open:
return Octokit.GraphQL.Model.IssueState.Open;
case IssueState.Closed:
return Octokit.GraphQL.Model.IssueState.Closed;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
}
163 changes: 163 additions & 0 deletions src/GitHub.App/Services/IssueService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using GitHub.Api;
using GitHub.Models;
using GitHub.Primitives;
using Octokit.GraphQL;
using Octokit.GraphQL.Model;
using static Octokit.GraphQL.Variable;
using IssueState = GitHub.Models.IssueState;

namespace GitHub.Services
{
[Export(typeof(IIssueService))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class IssueService : IIssueService
{
static ICompiledQuery<Page<ActorModel>> readAssignableUsers;
static ICompiledQuery<Page<IssueListItemModel>> readIssues;
static ICompiledQuery<IssueDetailModel> readIssue;

readonly IGraphQLClientFactory graphqlFactory;

[ImportingConstructor]
public IssueService(IGraphQLClientFactory graphqlFactory)
{
this.graphqlFactory = graphqlFactory;
}

public async Task<Page<IssueListItemModel>> ReadIssues(
HostAddress address,
string owner,
string name,
string after,
IssueState[] states)
{
if (readIssues == null)
{
readIssues = new Query()
.Repository(owner: Var(nameof(owner)), name: Var(nameof(name)))
.Issues(
first: 100,
after: Var(nameof(after)),
orderBy: new IssueOrder { Direction = OrderDirection.Desc, Field = IssueOrderField.CreatedAt },
states: Var(nameof(states)))
.Select(page => new Page<IssueListItemModel>
{
EndCursor = page.PageInfo.EndCursor,
HasNextPage = page.PageInfo.HasNextPage,
TotalCount = page.TotalCount,
Items = page.Nodes.Select(issue => new IssueListItemModel
{
Id = issue.Id.Value,
Author = new ActorModel
{
Login = issue.Author.Login,
AvatarUrl = issue.Author.AvatarUrl(null),
},
CommentCount = issue.Comments(0, null, null, null).TotalCount,
Number = issue.Number,
State = issue.State.FromGraphQl(),
Title = issue.Title,
UpdatedAt = issue.UpdatedAt,
}).ToList(),
}).Compile();
}

var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false);
var vars = new Dictionary<string, object>
{
{ nameof(owner), owner },
{ nameof(name), name },
{ nameof(after), after },
{ nameof(states), states.Select(x => x.ToGraphQL()).ToList() },
};

return await graphql.Run(readIssues, vars).ConfigureAwait(false);
}

public async Task<IssueDetailModel> ReadIssue(HostAddress address, string owner, string name, int number)
{
if (readIssue == null)
{
readIssue = new Query()
.Repository(owner: Var(nameof(owner)), name: Var(nameof(name)))
.Issue(Var(nameof(number)))
.Select(issue => new IssueDetailModel
{
Id = issue.Id.Value,
Number = issue.Number,
Author = new ActorModel
{
Login = issue.Author.Login,
AvatarUrl = issue.Author.AvatarUrl(null),
},
Title = issue.Title,
Body = issue.Body,
UpdatedAt = issue.UpdatedAt,
Comments = issue.Comments(null, null, null, null).AllPages().Select(comment => new CommentModel
{
Id = comment.Id.Value,
DatabaseId = comment.DatabaseId.Value,
Author = new ActorModel
{
Login = comment.Author.Login,
AvatarUrl = comment.Author.AvatarUrl(null),
},
Body = comment.Body,
CreatedAt = comment.CreatedAt,
Url = comment.Url,
}).ToList(),
}).Compile();
}

var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false);
var vars = new Dictionary<string, object>
{
{ nameof(owner), owner },
{ nameof(name), name },
{ nameof(number), number },
};

return await graphql.Run(readIssue, vars).ConfigureAwait(false);
}

public async Task<Page<ActorModel>> ReadAssignableUsers(
HostAddress address,
string owner,
string name,
string after)
{
if (readAssignableUsers == null)
{
readAssignableUsers = new Query()
.Repository(owner: Var(nameof(owner)), name: Var(nameof(name)))
.AssignableUsers(first: 100, after: Var(nameof(after)))
.Select(connection => new Page<ActorModel>
{
EndCursor = connection.PageInfo.EndCursor,
HasNextPage = connection.PageInfo.HasNextPage,
TotalCount = connection.TotalCount,
Items = connection.Nodes.Select(user => new ActorModel
{
AvatarUrl = user.AvatarUrl(30),
Login = user.Login,
}).ToList(),
}).Compile();
}

var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false);
var vars = new Dictionary<string, object>
{
{ nameof(owner), owner },
{ nameof(name), name },
{ nameof(after), after },
};

return await graphql.Run(readAssignableUsers, vars).ConfigureAwait(false);
}
}
}
11 changes: 11 additions & 0 deletions src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
readonly ObservableAsPropertyHelper<string> title;
readonly ReactiveCommand<Unit, Unit> refresh;
readonly ReactiveCommand<Unit, Unit> showPullRequests;
readonly ReactiveCommand<Unit, Unit> showIssues;
readonly ReactiveCommand<Unit, Unit> openInBrowser;
readonly ReactiveCommand<Unit, Unit> help;
IDisposable connectionSubscription;
Expand Down Expand Up @@ -152,6 +153,9 @@ public GitHubPaneViewModel(
ShowPullRequests,
this.WhenAny(x => x.Content, x => x.Value == navigator));

showIssues = ReactiveCommand.CreateFromTask(
ShowIssues,
this.WhenAny(x => x.Content, x => x.Value == navigator));
openInBrowser = ReactiveCommand.Create(
() =>
{
Expand Down Expand Up @@ -303,6 +307,12 @@ public Task ShowCreatePullRequest()
return NavigateTo<IPullRequestCreationViewModel>(x => x.InitializeAsync(LocalRepository, Connection));
}

/// <inheritdoc/>
public Task ShowIssues()
{
return NavigateTo<IIssueListViewModel>(x => x.InitializeAsync(LocalRepository, Connection));
}

/// <inheritdoc/>
public Task ShowPullRequests()
{
Expand Down Expand Up @@ -373,6 +383,7 @@ async Task CreateInitializeTask(IServiceProvider paneServiceProvider)

var menuService = (IMenuCommandService)paneServiceProvider.GetService(typeof(IMenuCommandService));
BindNavigatorCommand(menuService, PkgCmdIDList.pullRequestCommand, showPullRequests);
BindNavigatorCommand(menuService, PkgCmdIDList.issuesCommand, showIssues);
BindNavigatorCommand(menuService, PkgCmdIDList.backCommand, navigator.NavigateBack);
BindNavigatorCommand(menuService, PkgCmdIDList.forwardCommand, navigator.NavigateForward);
BindNavigatorCommand(menuService, PkgCmdIDList.refreshCommand, refresh);
Expand Down
43 changes: 43 additions & 0 deletions src/GitHub.App/ViewModels/GitHubPane/IssueListItemViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using GitHub.Models;

namespace GitHub.ViewModels.GitHubPane
{
/// <summary>
/// A view model which displays an item in a <see cref="IssueListViewModel"/>.
/// </summary>
public class IssueListItemViewModel : ViewModelBase, IIssueListItemViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="IssueListItemViewModel"/> class.
/// </summary>
/// <param name="model">The underlying issue item model.</param>
public IssueListItemViewModel(IssueListItemModel model)
{
Id = model.Id;
Author = new ActorViewModel(model.Author);
CommentCount = model.CommentCount;
Number = model.Number;
Title = model.Title;
UpdatedAt = model.UpdatedAt;
}

/// <inheritdoc/>
public string Id { get; }

/// <inheritdoc/>
public IActorViewModel Author { get; }

/// <inheritdoc/>
public int CommentCount { get; }

/// <inheritdoc/>
public int Number { get; }

/// <inheritdoc/>
public string Title { get; }

/// <inheritdoc/>
public DateTimeOffset UpdatedAt { get; }
}
}
Loading