Skip to content

Commit 490b2d4

Browse files
Updated symbol generation
[release]
1 parent b81046a commit 490b2d4

File tree

3 files changed

+226
-0
lines changed

3 files changed

+226
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using System;
2+
using TestExtension.ToolWindows;
3+
using System.Collections.Concurrent;
4+
using System.Diagnostics;
5+
using System.Globalization;
6+
using System.Threading.Tasks;
7+
using Community.VisualStudio.Toolkit;
8+
using System.Windows.Controls;
9+
using Microsoft.VisualStudio.Imaging;
10+
using Microsoft.VisualStudio.PlatformUI;
11+
using Microsoft.VisualStudio.Shell;
12+
using Task = System.Threading.Tasks.Task;
13+
14+
namespace TestExtension.ToolWindows
15+
{
16+
/// <summary>
17+
/// A standardized way to prompt the user to rate and review the extension
18+
/// </summary>
19+
public class RatingPrompt
20+
{
21+
private const string _urlFormat = "https://marketplace.visualstudio.com/items?itemName={0}&ssr=false#review-details";
22+
private const int _minutesVisible = 2;
23+
private static readonly ConcurrentDictionary<string, bool> _hasAlreadyRequested = new();
24+
25+
/// <summary>
26+
/// Creates a new instance of the rating prompt.
27+
/// </summary>
28+
/// <param name="marketplaceId">The unique Marketplace id found at the end of the Marketplace URL. For instance: "MyName.MyExtensions".</param>
29+
/// <param name="extensionName">The name of the extension to show in the prompt.</param>
30+
/// <param name="config">Likely a options page that implements the <see cref="IRatingConfig"/> interface. This is used to keep track of how many times the prompt was requested and if the user has already rated.</param>
31+
/// <param name="requestsBeforePrompt">Indicates how many successful requests it takes before the user is prompted to rate.</param>
32+
/// <exception cref="ArgumentNullException">None of the parameters passed in can be null.</exception>
33+
/// <exception cref="ArgumentException">The Marketplace ID has to be valid so an absolute URI can be constructed.</exception>
34+
public RatingPrompt(string marketplaceId, string extensionName, IRatingConfig config = null, int requestsBeforePrompt = 5)
35+
{
36+
MarketplaceId = marketplaceId ?? throw new ArgumentNullException(nameof(marketplaceId));
37+
ExtensionName = extensionName ?? throw new ArgumentNullException(nameof(extensionName));
38+
Config = config;
39+
RequestsBeforePrompt = requestsBeforePrompt;
40+
41+
string ratingUrl = string.Format(CultureInfo.InvariantCulture, _urlFormat, MarketplaceId);
42+
43+
if (!Uri.TryCreate(ratingUrl, UriKind.Absolute, out Uri parsedUrl))
44+
{
45+
throw new ArgumentException($"{RatingUrl} is not a valid URL", nameof(marketplaceId));
46+
}
47+
48+
RatingUrl = parsedUrl;
49+
}
50+
51+
/// <summary>
52+
/// The Marketplace ID is the unique last part of the URL. For instance: "MyName.MyExtension".
53+
/// </summary>
54+
public virtual string MarketplaceId { get; }
55+
56+
/// <summary>
57+
/// The name of the extension. It's shown in the prompt so the user knows which extension to rate.
58+
/// </summary>
59+
public virtual string ExtensionName { get; }
60+
61+
/// <summary>
62+
/// The configuration/options object used to store the information related to the rating prompt.
63+
/// </summary>
64+
public virtual IRatingConfig? Config { get; }
65+
66+
/// <summary>
67+
/// The Marketplace URL the users are taken to when prompted.
68+
/// </summary>
69+
public virtual Uri RatingUrl { get; }
70+
71+
/// <summary>
72+
/// Indicates how many successful requests it takes before the user is prompted to rate.
73+
/// </summary>
74+
public virtual int RequestsBeforePrompt { get; set; }
75+
76+
/// <summary>
77+
/// Registers successful usage of the extension. Only one is registered per Visual Studio session.
78+
/// When the number of usages matches <see cref="RequestsBeforePrompt"/>, the prompt will be shown.
79+
/// </summary>
80+
public virtual void RegisterSuccessfulUsage()
81+
{
82+
if (Config == null)
83+
{
84+
throw new NullReferenceException("The Config property has not been set.");
85+
}
86+
87+
if (_hasAlreadyRequested.TryAdd(MarketplaceId, true) && Config.RatingRequests < RequestsBeforePrompt)
88+
{
89+
IncrementAsync().FireAndForget();
90+
}
91+
}
92+
93+
/// <summary>
94+
/// Resets the count of successful usages and starts over.
95+
/// </summary>
96+
public virtual async Task ResetAsync()
97+
{
98+
_hasAlreadyRequested.TryRemove(MarketplaceId, out _);
99+
100+
if (Config != null)
101+
{
102+
Config.RatingRequests = 0;
103+
await Config.SaveAsync();
104+
}
105+
}
106+
107+
private async Task IncrementAsync()
108+
{
109+
await Task.Yield(); // Yield to allow any shutdown procedure to continue
110+
111+
if (VsShellUtilities.ShellIsShuttingDown || Config == null)
112+
{
113+
return;
114+
}
115+
116+
Config.RatingRequests += 1;
117+
await Config.SaveAsync();
118+
119+
if (Config.RatingRequests == RequestsBeforePrompt)
120+
{
121+
PromptAsync().FireAndForget();
122+
}
123+
}
124+
125+
/// <summary>
126+
/// Prompts the user to rate the extension.
127+
/// </summary>
128+
public async Task PromptAsync()
129+
{
130+
var window = new RatingPromptWindow(this);
131+
var result = window.ShowDialog();
132+
133+
if (window.Choice == RateChoice.RateNow)
134+
{
135+
Process.Start(RatingUrl.OriginalString);
136+
}
137+
else if (window.Choice == RateChoice.RateLater)
138+
{
139+
ResetAsync().FireAndForget();
140+
}
141+
}
142+
}
143+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<platform:DialogWindow x:Class="TestExtension.ToolWindows.RatingPromptWindow"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:platform="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
7+
xmlns:image="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
8+
xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
9+
xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
10+
toolkit:Themes.UseVsTheme="True"
11+
mc:Ignorable="d"
12+
Width="450"
13+
Height="150" WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" ShowInTaskbar="False">
14+
<Grid>
15+
16+
<Grid.RowDefinitions>
17+
<RowDefinition Height="*" />
18+
<RowDefinition Height="auto" />
19+
</Grid.RowDefinitions>
20+
21+
<Grid.ColumnDefinitions>
22+
<ColumnDefinition Width="auto"/>
23+
<ColumnDefinition Width="*"/>
24+
</Grid.ColumnDefinitions>
25+
26+
<image:CrispImage Margin="10" Width="80" Height="80" Moniker="{x:Static catalog:KnownMonikers.Rating}" />
27+
<TextBlock TextWrapping="Wrap" Grid.Column="1" Name="lblText" Margin="10" Text="Are you enjoying XYZ extension?" VerticalAlignment="Center" />
28+
29+
<StackPanel Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="1">
30+
<Button Name="btnRate" Content="Rate ait now" Margin="10" Click="btnRate_Click" IsDefault="True" />
31+
<Button Name="btnPostpone" Content="Remind me later" Margin="10" Click="btnPostpone_Click" IsCancel="True" />
32+
<Button Name="btnNoThanks" Content="No, thanks" Margin="10" Click="btnNoThanks_Click" />
33+
</StackPanel>
34+
</Grid>
35+
</platform:DialogWindow>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Diagnostics;
2+
using Microsoft.VisualStudio.PlatformUI;
3+
using Microsoft.VisualStudio.Shell;
4+
5+
namespace TestExtension.ToolWindows
6+
{
7+
public partial class RatingPromptWindow : DialogWindow
8+
{
9+
private readonly RatingPrompt _prompt;
10+
11+
public RatingPromptWindow(RatingPrompt prompt)
12+
{
13+
InitializeComponent();
14+
15+
lblText.Text = $"Are you enjoying the {prompt.ExtensionName} extension? Help spread the word by leaving a review.";
16+
_prompt = prompt;
17+
}
18+
19+
public RateChoice Choice { get; private set; }
20+
21+
private void btnRate_Click(object sender, System.Windows.RoutedEventArgs e)
22+
{
23+
Choice = RateChoice.RateNow;
24+
Close();
25+
}
26+
27+
private void btnPostpone_Click(object sender, System.Windows.RoutedEventArgs e)
28+
{
29+
Choice = RateChoice.RateLater;
30+
Close();
31+
}
32+
33+
private void btnNoThanks_Click(object sender, System.Windows.RoutedEventArgs e)
34+
{
35+
Choice = RateChoice.NoRate;
36+
Close();
37+
}
38+
39+
40+
}
41+
42+
public enum RateChoice
43+
{
44+
RateNow,
45+
RateLater,
46+
NoRate,
47+
}
48+
}

0 commit comments

Comments
 (0)