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

Commit e46c054

Browse files
committed
Merge pull request #201 from github/shana/notifications-service
Notifications service
2 parents 2967814 + 2376ace commit e46c054

26 files changed

+361
-155
lines changed

src/GitHub.App/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GitHub.App/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@
153153
<data name="PublishToTitle" xml:space="preserve">
154154
<value>Publish repository to {0}</value>
155155
</data>
156+
<data name="RepositoryCloneFailedNoSelectedRepo" xml:space="preserve">
157+
<value>No selected repository.</value>
158+
</data>
156159
<data name="RepositoryCreationFailedAlreadyExists" xml:space="preserve">
157160
<value>Repository '{0}/{1}' already exists.</value>
158161
</data>

src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class RepositoryCloneViewModel : BaseViewModel, IRepositoryCloneViewModel
2727
readonly IRepositoryHost repositoryHost;
2828
readonly IRepositoryCloneService cloneService;
2929
readonly IOperatingSystem operatingSystem;
30-
readonly IVSServices vsServices;
30+
readonly INotificationService notificationService;
3131
readonly IReactiveCommand<IReadOnlyList<IRepositoryModel>> loadRepositoriesCommand;
3232
readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create();
3333
readonly ObservableAsPropertyHelper<bool> isLoading;
@@ -41,20 +41,20 @@ public class RepositoryCloneViewModel : BaseViewModel, IRepositoryCloneViewModel
4141
IConnectionRepositoryHostMap connectionRepositoryHostMap,
4242
IRepositoryCloneService repositoryCloneService,
4343
IOperatingSystem operatingSystem,
44-
IVSServices vsServices)
45-
: this(connectionRepositoryHostMap.CurrentRepositoryHost, repositoryCloneService, operatingSystem, vsServices)
44+
INotificationService notificationService)
45+
: this(connectionRepositoryHostMap.CurrentRepositoryHost, repositoryCloneService, operatingSystem, notificationService)
4646
{ }
4747

4848
public RepositoryCloneViewModel(
4949
IRepositoryHost repositoryHost,
5050
IRepositoryCloneService cloneService,
5151
IOperatingSystem operatingSystem,
52-
IVSServices vsServices)
52+
INotificationService notificationService)
5353
{
5454
this.repositoryHost = repositoryHost;
5555
this.cloneService = cloneService;
5656
this.operatingSystem = operatingSystem;
57-
this.vsServices = vsServices;
57+
this.notificationService = notificationService;
5858

5959
Title = string.Format(CultureInfo.CurrentCulture, Resources.CloneTitle, repositoryHost.Title);
6060
Repositories = new ReactiveList<IRepositoryModel>();
@@ -120,6 +120,11 @@ IObservable<Unit> OnCloneRepository(object state)
120120
{
121121
var repository = SelectedRepository;
122122
Debug.Assert(repository != null, "Should not be able to attempt to clone a repo when it's null");
123+
if (repository == null)
124+
{
125+
notificationService.ShowError(Resources.RepositoryCloneFailedNoSelectedRepo);
126+
return Observable.Return(Unit.Default);
127+
}
123128
// The following is a noop if the directory already exists.
124129
operatingSystem.Directory.CreateDirectory(BaseRepositoryPath);
125130
return cloneService.CloneRepository(repository.CloneUrl, repository.Name, BaseRepositoryPath);
@@ -129,7 +134,7 @@ IObservable<Unit> OnCloneRepository(object state)
129134
{
130135
var repository = SelectedRepository;
131136
Debug.Assert(repository != null, "Should not be able to attempt to clone a repo when it's null");
132-
vsServices.ShowError(e.GetUserFriendlyErrorMessage(ErrorType.ClonedFailed, repository.Name));
137+
notificationService.ShowError(e.GetUserFriendlyErrorMessage(ErrorType.ClonedFailed, repository.Name));
133138
return Observable.Return(Unit.Default);
134139
});
135140
}

src/GitHub.App/ViewModels/RepositoryPublishViewModel.cs

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.ComponentModel.Composition;
55
using System.Globalization;
66
using System.Linq;
7-
using System.Reactive;
87
using System.Reactive.Linq;
98
using GitHub.Exports;
109
using GitHub.Extensions;
@@ -27,7 +26,7 @@ public class RepositoryPublishViewModel : RepositoryFormViewModel, IRepositoryPu
2726

2827
readonly IRepositoryHosts hosts;
2928
readonly IRepositoryPublishService repositoryPublishService;
30-
readonly IVSServices vsServices;
29+
readonly INotificationService notificationService;
3130
readonly ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts;
3231
readonly ObservableAsPropertyHelper<bool> isHostComboBoxVisible;
3332
readonly ObservableAsPropertyHelper<bool> canKeepPrivate;
@@ -38,10 +37,10 @@ public class RepositoryPublishViewModel : RepositoryFormViewModel, IRepositoryPu
3837
public RepositoryPublishViewModel(
3938
IRepositoryHosts hosts,
4039
IRepositoryPublishService repositoryPublishService,
41-
IVSServices vsServices,
40+
INotificationService notificationService,
4241
IConnectionManager connectionManager)
4342
{
44-
this.vsServices = vsServices;
43+
this.notificationService = notificationService;
4544
this.hosts = hosts;
4645

4746
title = this.WhenAny(
@@ -56,9 +55,7 @@ public RepositoryPublishViewModel(
5655
this.repositoryPublishService = repositoryPublishService;
5756

5857
if (Connections.Any())
59-
{
6058
SelectedConnection = Connections.FirstOrDefault(x => x.HostAddress.IsGitHubDotCom()) ?? Connections[0];
61-
}
6259

6360
accounts = this.WhenAny(x => x.SelectedConnection, x => x.Value != null ? hosts.LookupHost(x.Value.HostAddress) : RepositoryHosts.DisconnectedRepositoryHost)
6461
.Where(x => !(x is DisconnectedRepositoryHost))
@@ -72,9 +69,7 @@ public RepositoryPublishViewModel(
7269
.Subscribe(accts => {
7370
var selectedAccount = accts.FirstOrDefault();
7471
if (selectedAccount != null)
75-
{
7672
SelectedAccount = accts.FirstOrDefault();
77-
}
7873
});
7974

8075
isHostComboBoxVisible = this.WhenAny(x => x.Connections, x => x.Value)
@@ -95,9 +90,7 @@ public RepositoryPublishViewModel(
9590

9691
var defaultRepositoryName = repositoryPublishService.LocalRepositoryName;
9792
if (!string.IsNullOrEmpty(defaultRepositoryName))
98-
{
99-
DefaultRepositoryName = defaultRepositoryName;
100-
}
93+
DefaultRepositoryName = defaultRepositoryName;
10194

10295
this.WhenAny(x => x.SelectedConnection, x => x.SelectedAccount,
10396
(a,b) => true)
@@ -157,18 +150,14 @@ IObservable<ProgressState> OnPublishRepository(object arg)
157150
var account = SelectedAccount;
158151

159152
return repositoryPublishService.PublishRepository(newRepository, account, SelectedHost.ApiClient)
160-
.Select(_ =>
161-
{
162-
vsServices.ShowMessage("Repository published successfully.");
163-
return ProgressState.Success;
164-
})
153+
.Select(_ => ProgressState.Success)
165154
.Catch<ProgressState, Exception>(ex =>
166155
{
167156
if (!ex.IsCriticalException())
168157
{
169158
log.Error(ex);
170159
var error = new PublishRepositoryUserError(ex.Message);
171-
vsServices.ShowError((error.ErrorMessage + Environment.NewLine + error.ErrorCauseOrResolution).TrimEnd());
160+
notificationService.ShowError((error.ErrorMessage + Environment.NewLine + error.ErrorCauseOrResolution).TrimEnd());
172161
}
173162
return Observable.Return(ProgressState.Fail);
174163
});
@@ -191,21 +180,6 @@ void InitializeValidation()
191180
var parsedReference = GetSafeRepositoryName(repoName);
192181
return parsedReference != repoName ? String.Format(CultureInfo.CurrentCulture, Resources.SafeRepositoryNameWarning, parsedReference) : null;
193182
});
194-
195-
this.WhenAny(x => x.SafeRepositoryNameWarningValidator.ValidationResult, x => x.Value)
196-
.WhereNotNull() // When this is instantiated, it sends a null result.
197-
.Select(result => result?.Message)
198-
.Subscribe(message =>
199-
{
200-
if (!string.IsNullOrEmpty(message))
201-
{
202-
vsServices.ShowWarning(message);
203-
}
204-
else
205-
{
206-
vsServices.ClearNotifications();
207-
}
208-
});
209183
}
210184
}
211185
}

src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<ItemGroup>
4949
<Reference Include="PresentationCore" />
5050
<Reference Include="System" />
51+
<Reference Include="System.ComponentModel.Composition" />
5152
<Reference Include="System.Core" />
5253
<Reference Include="System.Net.Http" />
5354
<Reference Include="Microsoft.TeamFoundation.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
@@ -91,6 +92,7 @@
9192
<Compile Include="Models\IConnectionRepositoryHostMap.cs" />
9293
<Compile Include="Models\IRepositoryHosts.cs" />
9394
<Compile Include="Services\IModelService.cs" />
95+
<Compile Include="Services\NotificationDispatcher.cs" />
9496
<Compile Include="ViewModels\ILoginControlViewModel.cs" />
9597
<Compile Include="ViewModels\ILoginToGitHubForEnterpriseViewModel.cs" />
9698
<Compile Include="ViewModels\ILoginToGitHubViewModel.cs" />
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel.Composition;
4+
using System.Reactive.Linq;
5+
using System.Reactive.Subjects;
6+
using System.Windows.Input;
7+
8+
namespace GitHub.Services
9+
{
10+
[Export(typeof(INotificationDispatcher))]
11+
[Export(typeof(INotificationService))]
12+
[PartCreationPolicy(CreationPolicy.Shared)]
13+
public sealed class NotificationDispatcher : INotificationDispatcher, IDisposable
14+
{
15+
Subject<Notification> notifications;
16+
Stack<INotificationService> notificationHandlers;
17+
18+
public NotificationDispatcher()
19+
{
20+
notifications = new Subject<Notification>();
21+
notificationHandlers = new Stack<INotificationService>();
22+
}
23+
24+
public IObservable<Notification> Listen()
25+
{
26+
return notifications;
27+
}
28+
29+
public void AddListener(INotificationService handler)
30+
{
31+
notificationHandlers.Push(handler);
32+
}
33+
34+
public void RemoveListener()
35+
{
36+
notificationHandlers.Pop();
37+
}
38+
39+
public void RemoveListener(INotificationService handler)
40+
{
41+
Stack<INotificationService> handlers = new Stack<INotificationService>();
42+
while(notificationHandlers.TryPeek() != handler)
43+
handlers.Push(notificationHandlers.Pop());
44+
if (notificationHandlers.Count > 0)
45+
notificationHandlers.Pop();
46+
while (handlers.Count > 0)
47+
notificationHandlers.Push(handlers.Pop());
48+
}
49+
50+
public void ShowMessage(string message)
51+
{
52+
notifications.OnNext(new Notification(message, Notification.NotificationType.Message));
53+
var handler = notificationHandlers.TryPeek();
54+
handler?.ShowMessage(message);
55+
}
56+
57+
public void ShowMessage(string message, ICommand command)
58+
{
59+
notifications.OnNext(new Notification(message, Notification.NotificationType.Message, command));
60+
var handler = notificationHandlers.TryPeek();
61+
handler?.ShowMessage(message, command);
62+
}
63+
64+
public void ShowWarning(string message)
65+
{
66+
notifications.OnNext(new Notification(message, Notification.NotificationType.Warning));
67+
var handler = notificationHandlers.TryPeek();
68+
handler.ShowWarning(message);
69+
}
70+
71+
public void ShowError(string message)
72+
{
73+
notifications.OnNext(new Notification(message, Notification.NotificationType.Error));
74+
var handler = notificationHandlers.TryPeek();
75+
handler?.ShowError(message);
76+
}
77+
78+
bool disposed; // To detect redundant calls
79+
void Dispose(bool disposing)
80+
{
81+
if (disposing)
82+
{
83+
if (disposed) return;
84+
disposed = true;
85+
notifications.Dispose();
86+
}
87+
}
88+
89+
public void Dispose()
90+
{
91+
Dispose(true);
92+
GC.SuppressFinalize(this);
93+
}
94+
}
95+
}

src/GitHub.Exports/GitHub.Exports.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
<Compile Include="Collections\ICopyable.cs" />
100100
<Compile Include="ExceptionExtensions.cs" />
101101
<Compile Include="Extensions\VSExtensions.cs" />
102+
<Compile Include="GlobalSuppressions.cs" />
102103
<Compile Include="Helpers\INotifyPropertySource.cs" />
103104
<Compile Include="Extensions\PropertyNotifierExtensions.cs" />
104105
<Compile Include="Extensions\SimpleRepositoryModelExtensions.cs" />
@@ -114,9 +115,13 @@
114115
<Compile Include="Services\IGitService.cs" />
115116
<Compile Include="Services\IMenuHandler.cs" />
116117
<Compile Include="Services\IMenuProvider.cs" />
118+
<Compile Include="Services\INotificationDispatcher.cs" />
119+
<Compile Include="Services\ITeamExplorerServices.cs" />
117120
<Compile Include="Services\ITeamExplorerServiceHolder.cs" />
121+
<Compile Include="Services\INotificationService.cs" />
118122
<Compile Include="Services\Logger.cs" />
119123
<Compile Include="Services\Services.cs" />
124+
<Compile Include="Services\TeamExplorerServices.cs" />
120125
<Compile Include="Services\VSServices.cs" />
121126
<Compile Include="Models\IConnection.cs" />
122127
<Compile Include="Properties\AssemblyInfo.cs" />
1.68 KB
Binary file not shown.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Windows.Input;
3+
4+
namespace GitHub.Services
5+
{
6+
public struct Notification
7+
{
8+
public enum NotificationType
9+
{
10+
Message,
11+
MessageCommand,
12+
Warning,
13+
Error
14+
}
15+
public string Message { get; }
16+
public NotificationType Type { get; }
17+
public ICommand Command { get; }
18+
19+
public Notification(string message, NotificationType type, ICommand command = null)
20+
{
21+
Message = message;
22+
Type = type;
23+
Command = command;
24+
}
25+
}
26+
27+
public interface INotificationDispatcher : INotificationService
28+
{
29+
IObservable<Notification> Listen();
30+
void AddListener(INotificationService notificationHandler);
31+
void RemoveListener();
32+
void RemoveListener(INotificationService notificationHandler);
33+
}
34+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Windows.Input;
2+
3+
namespace GitHub.Services
4+
{
5+
public interface INotificationService
6+
{
7+
void ShowMessage(string message);
8+
void ShowMessage(string message, ICommand command);
9+
void ShowWarning(string message);
10+
void ShowError(string message);
11+
}
12+
}

0 commit comments

Comments
 (0)