Skip to content

Commit a406276

Browse files
tomaszgolebiowskiTomasz Gołębiowski
andauthored
Support for notifications (#355)
This PR adds support for notifications from the agent. They can appear as text in the status bar or as a window popping up in the lower right corner of the main window. The window version appears when the notification contains actions. In this case, they appear as buttons in the window. ## Test plan 1. Log out of Cody 2. Log in again 3. A notification should appear in the status bar: "Signed in to `url`" <!-- REQUIRED; info at https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles --> --------- Co-authored-by: Tomasz Gołębiowski <tgolebiowski@virtuslab.com>
1 parent 1b82b22 commit a406276

File tree

10 files changed

+464
-16
lines changed

10 files changed

+464
-16
lines changed

src/Cody.Core/Agent/NotificationHandlers.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public class NotificationHandlers : INotificationHandler
1919
private readonly Task<IInfobarNotifications> _infobarNotificationsAsync;
2020
private readonly ILog _logger;
2121
private readonly IUserSettingsService _settingsService;
22-
22+
private readonly IStatusbarService _statusbarService;
23+
private readonly IToastNotificationService _toastNotificationService;
2324
public IAgentService agentClient;
2425

2526
public delegate Task PostWebMessageAsJsonDelegate(string message);
@@ -39,13 +40,17 @@ public NotificationHandlers(
3940
ILog logger,
4041
IDocumentService documentService,
4142
ISecretStorageService secretStorage,
42-
Task<IInfobarNotifications> infobarNotificationsAsync)
43+
Task<IInfobarNotifications> infobarNotificationsAsync,
44+
IStatusbarService statusbarService,
45+
IToastNotificationService toastNotificationService)
4346
{
4447
_settingsService = settingsService;
4548
_secretStorage = secretStorage;
4649
_infobarNotificationsAsync = infobarNotificationsAsync;
4750
_logger = logger;
4851
_messageFilter = new WebviewMessageHandler(documentService, () => OnOptionsPageShowRequest?.Invoke(this, EventArgs.Empty), _logger);
52+
_statusbarService = statusbarService;
53+
_toastNotificationService = toastNotificationService;
4954
}
5055

5156
public void SetAgentClient(IAgentService client)
@@ -235,19 +240,37 @@ public async Task<string> ShowMessage(ShowWindowMessageParams param)
235240
{
236241
// TODO: supports only single auto-edit notification for now
237242
// because how the code handles enabling/disabling auto-edits via UserSettingsService
238-
if (!param.Message.Contains("You have been enrolled to Cody Auto-edit")) return null;
243+
if (param.Message.Contains("You have been enrolled to Cody Auto-edit"))
244+
{
245+
246+
var notifications = await _infobarNotificationsAsync;
239247

240-
var notifications = await _infobarNotificationsAsync;
248+
_logger.Debug($"ℹ ShowMessage:{param.Message}");
249+
var selectedValue = await notifications.Show(param);
241250

242-
_logger.Debug($"ℹ ShowMessage:{param.Message}");
243-
var selectedValue = await notifications.Show(param);
251+
_logger.Debug($"Selected value: '{selectedValue}'");
244252

245-
_logger.Debug($"Selected value: '{selectedValue}'");
253+
if (selectedValue == null) // an user want to stay on auto-edits
254+
_settingsService.EnableAutoEdit = true;
246255

247-
if (selectedValue == null) // an user want to stay on auto-edits
248-
_settingsService.EnableAutoEdit = true;
256+
return selectedValue;
257+
}
258+
else if (param.Message.StartsWith("Edit applied to"))
259+
{
260+
_statusbarService.SetText(param.Message);
261+
}
262+
else if (param.Items != null && param.Items.Count > 0 && !string.IsNullOrEmpty(param.Items[0]))
263+
{
264+
var result = await _toastNotificationService.ShowNotification(
265+
param.Severity, param.Message, param.Options?.Detail, param.Items);
266+
return result;
267+
}
268+
else
269+
{
270+
_statusbarService.SetText(param.Message);
271+
}
249272

250-
return selectedValue;
273+
return null;
251274
}
252275
}
253276
}

src/Cody.Core/Cody.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
<Compile Include="Infrastructure\IFileDialogService.cs" />
129129
<Compile Include="Infrastructure\ISecretStorageService.cs" />
130130
<Compile Include="Infrastructure\IProgressService.cs" />
131+
<Compile Include="Infrastructure\IToastNotificationService.cs" />
131132
<Compile Include="Logging\ISentryLog.cs" />
132133
<Compile Include="Logging\ITestLogger.cs" />
133134
<Compile Include="Logging\SentryLog.cs" />
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Cody.Core.Agent.Protocol;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace Cody.Core.Infrastructure
9+
{
10+
public interface IToastNotificationService
11+
{
12+
Task<string> ShowNotification(SeverityEnum severity, string message, string details, IEnumerable<string> actions);
13+
}
14+
}

src/Cody.UI/Cody.UI.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,17 @@
6464
<Compile Include="ViewModels\EditCodeViewModel.cs" />
6565
<Compile Include="ViewModels\GeneralOptionsViewModel.cs" />
6666
<Compile Include="ViewModels\MainViewModel.cs" />
67+
<Compile Include="ViewModels\ToastViewModel.cs" />
6768
<Compile Include="Views\EditCodeView.xaml.cs">
6869
<DependentUpon>EditCodeView.xaml</DependentUpon>
6970
</Compile>
7071
<Compile Include="Views\MainView.xaml.cs">
7172
<DependentUpon>MainView.xaml</DependentUpon>
7273
</Compile>
7374
<Compile Include="Controls\WebviewController.cs" />
75+
<Compile Include="Views\ToastView.xaml.cs">
76+
<DependentUpon>ToastView.xaml</DependentUpon>
77+
</Compile>
7478
</ItemGroup>
7579
<ItemGroup>
7680
<Page Include="Controls\Options\GeneralOptionsControl.xaml">
@@ -92,6 +96,10 @@
9296
<SubType>Designer</SubType>
9397
<Generator>MSBuild:Compile</Generator>
9498
</Page>
99+
<Page Include="Views\ToastView.xaml">
100+
<SubType>Designer</SubType>
101+
<Generator>MSBuild:Compile</Generator>
102+
</Page>
95103
</ItemGroup>
96104
<ItemGroup>
97105
<ProjectReference Include="..\Cody.Core\Cody.Core.csproj">
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using Cody.Core.Agent.Protocol;
2+
using Cody.UI.MVVM;
3+
using Microsoft.VisualStudio.Imaging;
4+
using Microsoft.VisualStudio.Imaging.Interop;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Collections.ObjectModel;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using System.Windows;
12+
using System.Windows.Input;
13+
14+
namespace Cody.UI.ViewModels
15+
{
16+
public class ToastViewModel : NotifyPropertyChangedBase
17+
{
18+
private Window window;
19+
20+
public ToastViewModel(Window window, SeverityEnum severity, string message, string details, IEnumerable<string> actions)
21+
{
22+
this.window = window;
23+
Severity = severity;
24+
Message = message;
25+
Details = details;
26+
Actions = new ObservableCollection<string>(actions);
27+
}
28+
29+
public ObservableCollection<string> Actions { get; set; }
30+
31+
private string message;
32+
public string Message
33+
{
34+
get => message;
35+
set => SetProperty(ref message, value);
36+
}
37+
38+
private string details;
39+
public string Details
40+
{
41+
get => details;
42+
set => SetProperty(ref details, value);
43+
}
44+
45+
private SeverityEnum severity;
46+
public SeverityEnum Severity
47+
{
48+
get => severity;
49+
set => SetProperty(ref severity, value);
50+
}
51+
52+
private DelegateCommand<string> actionCommand;
53+
public DelegateCommand<string> ActionCommand
54+
{
55+
get { return actionCommand = actionCommand ?? new DelegateCommand<string>(OnActionButtonClick); }
56+
}
57+
58+
private void OnActionButtonClick(string actionName)
59+
{
60+
SelectedAction = actionName;
61+
window.Close();
62+
}
63+
64+
private DelegateCommand closeCommand;
65+
public DelegateCommand CloseCommand
66+
{
67+
get { return closeCommand = closeCommand ?? new DelegateCommand(OnCloseButtonClick); }
68+
}
69+
70+
private void OnCloseButtonClick() => window.Close();
71+
72+
public ImageMoniker Moniker
73+
{
74+
get
75+
{
76+
switch (severity)
77+
{
78+
case SeverityEnum.Error: return KnownMonikers.StatusError;
79+
case SeverityEnum.Warning: return KnownMonikers.StatusWarning;
80+
case SeverityEnum.Information:
81+
default:
82+
return KnownMonikers.StatusInformation;
83+
}
84+
}
85+
}
86+
87+
public string SelectedAction { get; protected set; }
88+
}
89+
}

src/Cody.UI/Views/ToastView.xaml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<Window xmlns:platformui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
2+
xmlns:theme="clr-namespace:Community.VisualStudio.Toolkit"
3+
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
4+
xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
5+
xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
6+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
7+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
8+
x:Class="Cody.UI.Views.ToastView"
9+
Height="105"
10+
Width="350"
11+
WindowStyle="None"
12+
BorderThickness="1"
13+
BorderBrush="{DynamicResource {x:Static vsshell:VsBrushes.ToolWindowBorderKey}}"
14+
WindowStartupLocation="Manual"
15+
ResizeMode="NoResize"
16+
AllowsTransparency="True"
17+
ShowInTaskbar="False"
18+
SizeToContent="Height"
19+
theme:Themes.UseVsTheme="True"
20+
>
21+
<FrameworkElement.Resources>
22+
<Style x:Key="IgnoreButtonStyle" TargetType="{x:Type Button}">
23+
<Setter Property="Control.BorderThickness" Value="0"/>
24+
<Setter Property="Control.HorizontalContentAlignment" Value="Center"/>
25+
<Setter Property="Control.VerticalContentAlignment" Value="Center"/>
26+
<Setter Property="FrameworkElement.UseLayoutRounding" Value="True"/>
27+
<Setter Property="Template">
28+
<Setter.Value>
29+
<ControlTemplate TargetType="{x:Type Button}">
30+
<Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
31+
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
32+
</Border>
33+
</ControlTemplate>
34+
</Setter.Value>
35+
</Setter>
36+
<Style.Triggers>
37+
<Trigger Property="IsMouseOver" Value="True">
38+
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static vsshell:VsBrushes.ToolWindowButtonHoverInactiveBorderKey}}"/>
39+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vsshell:VsBrushes.ToolWindowButtonHoverInactiveGlyphKey}}"/>
40+
<Setter Property="Background" Value="{DynamicResource {x:Static vsshell:VsBrushes.ToolWindowButtonHoverInactiveKey}}"/>
41+
<Setter Property="BorderThickness" Value="1"/>
42+
</Trigger>
43+
<Trigger Property="IsMouseOver" Value="False">
44+
<Setter Property="BorderThickness" Value="0"/>
45+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vsshell:VsBrushes.ToolWindowButtonInactiveGlyphKey}}"/>
46+
<Setter Property="Background" Value="Transparent"/>
47+
</Trigger>
48+
</Style.Triggers>
49+
</Style>
50+
<Style x:Key="DismissGlyphStyleKey" TargetType="{x:Type Path}">
51+
<Setter Property="Shape.Fill">
52+
<Setter.Value>
53+
<Binding Path="(TextElement.Foreground)" RelativeSource="{RelativeSource Self}"/>
54+
</Setter.Value>
55+
</Setter>
56+
<Setter Property="FrameworkElement.Height" Value="10"/>
57+
<Setter Property="FrameworkElement.Width" Value="10"/>
58+
<Setter Property="Shape.Stretch" Value="Uniform"/>
59+
</Style>
60+
<Path x:Key="DismissGlyphPathKey" x:Name="DismissGlyph" x:Uid="DismissGlyph"
61+
Data="F1 M 0,0 L 2,0 5,3 8,0 10,0 6,4 10,8 8,8 5,5 2,8 0,8 4,4 0,0 Z"
62+
Style="{StaticResource DismissGlyphStyleKey}"/>
63+
</FrameworkElement.Resources>
64+
<StackPanel Margin="12">
65+
<Grid>
66+
<Grid.ColumnDefinitions>
67+
<ColumnDefinition Width="32"/>
68+
<ColumnDefinition Width="*"/>
69+
<ColumnDefinition Width="Auto"/>
70+
</Grid.ColumnDefinitions>
71+
72+
<imaging:CrispImage Grid.Column="0"
73+
Width="24"
74+
Height="24"
75+
Margin="2,2,0,0"
76+
VerticalAlignment="Top"
77+
Moniker="{Binding Moniker}"/>
78+
79+
80+
<StackPanel Grid.Column="1"
81+
Margin="8,0,20,0"
82+
Orientation="Vertical"
83+
VerticalAlignment="Top"
84+
HorizontalAlignment="Left">
85+
<TextBlock Margin="0,0,0,8" FontWeight="SemiBold" Text="{Binding Message}" TextWrapping="Wrap" Visibility="Visible">
86+
</TextBlock>
87+
<TextBlock Margin="0" Text="{Binding Details}" TextWrapping="Wrap">
88+
<TextBlock.Style>
89+
<Style TargetType="TextBlock">
90+
<Style.Triggers>
91+
<DataTrigger Binding="{Binding Details}" Value="{x:Null}">
92+
<Setter Property="Visibility" Value="Collapsed"/>
93+
</DataTrigger>
94+
</Style.Triggers>
95+
</Style>
96+
</TextBlock.Style>
97+
</TextBlock>
98+
</StackPanel>
99+
100+
<Button Grid.Column="2"
101+
Width="14"
102+
Height="14"
103+
Margin="0,-6,-6,0"
104+
HorizontalAlignment="Right"
105+
VerticalAlignment="Top"
106+
IsCancel="True"
107+
Focusable="False"
108+
Command="{Binding CloseCommand}"
109+
Style="{StaticResource IgnoreButtonStyle}">
110+
<StaticResourceExtension ResourceKey="DismissGlyphPathKey"/>
111+
</Button>
112+
113+
</Grid>
114+
<StackPanel x:Name="ActionButtonPanel" Margin="46,17,0,0" HorizontalAlignment="Right" Orientation="Horizontal">
115+
<ItemsControl ItemsSource="{Binding Actions}">
116+
<ItemsControl.ItemsPanel>
117+
<ItemsPanelTemplate>
118+
<StackPanel Orientation="Horizontal"/>
119+
</ItemsPanelTemplate>
120+
</ItemsControl.ItemsPanel>
121+
<ItemsControl.ItemTemplate>
122+
<DataTemplate>
123+
<Button Margin="0,0,6,0"
124+
Content="{Binding}"
125+
Command="{Binding DataContext.ActionCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
126+
CommandParameter="{Binding}"/>
127+
</DataTemplate>
128+
</ItemsControl.ItemTemplate>
129+
</ItemsControl>
130+
</StackPanel>
131+
</StackPanel>
132+
</Window>

0 commit comments

Comments
 (0)