diff --git a/src/AccountDeleter/EmptyFeatureFlagService.cs b/src/AccountDeleter/EmptyFeatureFlagService.cs
index 93a549f4cf..ee700dd835 100644
--- a/src/AccountDeleter/EmptyFeatureFlagService.cs
+++ b/src/AccountDeleter/EmptyFeatureFlagService.cs
@@ -75,7 +75,10 @@ public bool IsDisplayNuGetPackageExplorerLinkEnabled()
{
throw new NotImplementedException();
}
-
+ public bool IsDisplayNuGetTrendsLinksEnabled()
+ {
+ throw new NotImplementedException();
+ }
public bool IsDisplayTargetFrameworkEnabled(User user)
{
throw new NotImplementedException();
diff --git a/src/GitHubVulnerabilities2Db/Fakes/FakeFeatureFlagService.cs b/src/GitHubVulnerabilities2Db/Fakes/FakeFeatureFlagService.cs
index 8799c8e50c..f79d756d39 100644
--- a/src/GitHubVulnerabilities2Db/Fakes/FakeFeatureFlagService.cs
+++ b/src/GitHubVulnerabilities2Db/Fakes/FakeFeatureFlagService.cs
@@ -65,6 +65,11 @@ public bool IsDisplayFuGetLinksEnabled()
throw new NotImplementedException();
}
+ public bool IsDisplayNuGetTrendsLinksEnabled()
+ {
+ throw new NotImplementedException();
+ }
+
public bool IsDisplayVulnerabilitiesEnabled()
{
throw new NotImplementedException();
@@ -309,4 +314,4 @@ public bool IsFrameworkFilteringEnabled(User user) {
throw new NotImplementedException();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs b/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs
index 8363e83e06..508026de3a 100644
--- a/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs
+++ b/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs
@@ -30,6 +30,7 @@ public class FeatureFlagService : IFeatureFlagService
private const string ManagePackagesVulnerabilitiesFeatureName = GalleryPrefix + "ManagePackagesVulnerabilities";
private const string DisplayFuGetLinksFeatureName = GalleryPrefix + "DisplayFuGetLinks";
private const string DisplayNuGetPackageExplorerLinkFeatureName = GalleryPrefix + "DisplayNuGetPackageExplorerLink";
+ private const string DisplayNuGetTrendsLinkFeatureName = GalleryPrefix + "DisplayNuGetTrendsLink";
private const string ODataReadOnlyDatabaseFeatureName = GalleryPrefix + "ODataReadOnlyDatabase";
private const string PackagesAtomFeedFeatureName = GalleryPrefix + "PackagesAtomFeed";
private const string SearchSideBySideFlightName = GalleryPrefix + "SearchSideBySide";
@@ -170,6 +171,11 @@ public bool IsDisplayNuGetPackageExplorerLinkEnabled()
return _client.IsEnabled(DisplayNuGetPackageExplorerLinkFeatureName, defaultValue: false);
}
+ public bool IsDisplayNuGetTrendsLinksEnabled()
+ {
+ return _client.IsEnabled(DisplayNuGetTrendsLinkFeatureName, defaultValue: false);
+ }
+
public bool AreEmbeddedIconsEnabled(User user)
{
return _client.IsEnabled(EmbeddedIconFlightName, user, defaultValue: false);
@@ -404,4 +410,4 @@ public bool IsFrameworkFilteringEnabled(User user) {
return _client.IsEnabled(FrameworkFilteringFeatureName, user, defaultValue: false);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs b/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs
index 865202edb8..b3d34c83fc 100644
--- a/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs
+++ b/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs
@@ -77,6 +77,11 @@ public interface IFeatureFlagService
///
bool IsDisplayFuGetLinksEnabled();
+ ///
+ /// Whether or not a nugettrends.com link is visible on a package's details page.
+ ///
+ bool IsDisplayNuGetTrendsLinksEnabled();
+
///
/// Whether or not a nuget.info (NuGet Package Explorer) link is visible on a package's details page.
///
diff --git a/src/NuGetGallery/App_Data/Files/Content/flags.json b/src/NuGetGallery/App_Data/Files/Content/flags.json
index 2f2cb0fe6f..7ae1eedaf6 100644
--- a/src/NuGetGallery/App_Data/Files/Content/flags.json
+++ b/src/NuGetGallery/App_Data/Files/Content/flags.json
@@ -26,6 +26,7 @@
"NuGetGallery.ManagePackagesVulnerabilities": "Enabled",
"NuGetGallery.DisplayFuGetLinks": "Enabled",
"NuGetGallery.DisplayNuGetPackageExplorerLink": "Enabled",
+ "NuGetGallery.DisplayNuGetTrendsLink": "Enabled",
"NuGetGallery.PatternSetTfmHeuristics": "Enabled",
"NuGetGallery.EmbeddedIcons": "Enabled",
"NuGetGallery.MarkdigMdRendering": "Enabled",
@@ -134,4 +135,4 @@
"Domains": []
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NuGetGallery/Content/gallery/img/nuget-trends-32x32.png b/src/NuGetGallery/Content/gallery/img/nuget-trends-32x32.png
new file mode 100644
index 0000000000..b16c254340
Binary files /dev/null and b/src/NuGetGallery/Content/gallery/img/nuget-trends-32x32.png differ
diff --git a/src/NuGetGallery/Content/gallery/img/nuget-trends.svg b/src/NuGetGallery/Content/gallery/img/nuget-trends.svg
new file mode 100644
index 0000000000..1275d78e71
--- /dev/null
+++ b/src/NuGetGallery/Content/gallery/img/nuget-trends.svg
@@ -0,0 +1,27 @@
+
+
+
diff --git a/src/NuGetGallery/Controllers/PackagesController.cs b/src/NuGetGallery/Controllers/PackagesController.cs
index c991ac3c31..5eac48c04e 100644
--- a/src/NuGetGallery/Controllers/PackagesController.cs
+++ b/src/NuGetGallery/Controllers/PackagesController.cs
@@ -952,6 +952,7 @@ public virtual async Task DisplayPackage(string id, string version
model.IsPackageVulnerabilitiesEnabled = isPackageVulnerabilitiesEnabled;
model.IsFuGetLinksEnabled = _featureFlagService.IsDisplayFuGetLinksEnabled();
model.IsNuGetPackageExplorerLinkEnabled = _featureFlagService.IsDisplayNuGetPackageExplorerLinkEnabled();
+ model.IsNuGetTrendsLinksEnabled = _featureFlagService.IsDisplayNuGetTrendsLinksEnabled();
model.IsPackageRenamesEnabled = _featureFlagService.IsPackageRenamesEnabled(currentUser);
model.IsPackageDependentsEnabled = _featureFlagService.IsPackageDependentsEnabled(currentUser);
model.IsRecentPackagesNoIndexEnabled = _featureFlagService.IsRecentPackagesNoIndexEnabled();
diff --git a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs
index 780e0d0a54..9a88de6b93 100644
--- a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs
+++ b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs
@@ -179,6 +179,12 @@ private DisplayPackageViewModel SetupCommon(
viewModel.NuGetPackageExplorerUrl = nugetPackageExplorerReadyUrl;
}
+ var nugetTrendsUrl = $"https://nugettrends.com/packages?ids={package.Id}";
+ if (PackageHelper.TryPrepareUrlForRendering(nugetTrendsUrl, out string nugetTrendsReadyUrl))
+ {
+ viewModel.NuGetTrendsUrl = nugetTrendsReadyUrl;
+ }
+
viewModel.EmbeddedLicenseType = package.EmbeddedLicenseType;
viewModel.LicenseExpression = package.LicenseExpression;
@@ -264,4 +270,4 @@ private static string GetPushedBy(Package package, User currentUser, Dictionary<
return pushedByCache[userPushedBy];
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs b/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs
index a05d32983b..f4269f05e7 100644
--- a/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs
+++ b/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs
@@ -38,6 +38,7 @@ public class DisplayPackageViewModel : ListPackageItemViewModel
public bool IsPackageDeprecationEnabled { get; set; }
public bool IsPackageVulnerabilitiesEnabled { get; set; }
public bool IsFuGetLinksEnabled { get; set; }
+ public bool IsNuGetTrendsLinksEnabled { get; set; }
public bool IsNuGetPackageExplorerLinkEnabled { get; set; }
public bool IsPackageRenamesEnabled { get; set; }
public bool IsGitHubUsageEnabled { get; set; }
@@ -88,6 +89,7 @@ public bool HasNewerRelease
public string ProjectUrl { get; set; }
public string LicenseUrl { get; set; }
public string FuGetUrl { get; set; }
+ public string NuGetTrendsUrl { get; set; }
public string NuGetPackageExplorerUrl { get; set; }
public IReadOnlyCollection LicenseNames { get; set; }
public string LicenseExpression { get; set; }
@@ -146,6 +148,11 @@ public bool CanDisplayFuGetLink()
return IsFuGetLinksEnabled && !string.IsNullOrEmpty(FuGetUrl) && Available;
}
+ public bool CanDisplayNuGetTrendsLink()
+ {
+ return IsNuGetTrendsLinksEnabled && !string.IsNullOrEmpty(NuGetTrendsUrl) && Available;
+ }
+
public bool CanDisplayTargetFrameworks()
{
return IsDisplayTargetFrameworkEnabled && !Deleted && !IsDotnetNewTemplatePackageType;
@@ -166,4 +173,4 @@ public enum RepositoryKind
GitHub,
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml
index 207be8a2e2..f20c900080 100644
--- a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml
+++ b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml
@@ -1166,6 +1166,23 @@
}
+ @if (Model.CanDisplayNuGetTrendsLink() && Model.ShowDetailsAndLinks)
+ {
+ var disclaimer = "nugettrends.com is a 3rd party website, not controlled by Microsoft. This link is made available to you per the NuGet Terms of Use.";
+
+
+
+
+ Open in NuGet Trends
+
+
+ }
+
@if (!Model.CanReportAsOwner && Model.Available && Model.ShowDetailsAndLinks)
{
diff --git a/src/VerifyMicrosoftPackage/Fakes/FakeFeatureFlagService.cs b/src/VerifyMicrosoftPackage/Fakes/FakeFeatureFlagService.cs
index 7663a1aa96..3c2eaa2e05 100644
--- a/src/VerifyMicrosoftPackage/Fakes/FakeFeatureFlagService.cs
+++ b/src/VerifyMicrosoftPackage/Fakes/FakeFeatureFlagService.cs
@@ -114,6 +114,8 @@ public class FakeFeatureFlagService : IFeatureFlagService
public bool IsDisplayNuGetPackageExplorerLinkEnabled() => throw new NotImplementedException();
+ public bool IsDisplayNuGetTrendsLinksEnabled() => throw new NotImplementedException();
+
public bool IsDisplayTargetFrameworkEnabled(User user) => throw new NotImplementedException();
public bool IsComputeTargetFrameworkEnabled() => throw new NotImplementedException();
diff --git a/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs
index 68eab89692..ad47cc0c01 100644
--- a/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs
+++ b/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs
@@ -294,6 +294,76 @@ public void CannotDisplayFuGetLinkWhenInvalid(bool isEnabled, string url, bool i
Assert.False(model.CanDisplayFuGetLink());
}
+ [Fact]
+ public void ItInitializesNuGetTrendsUrl()
+ {
+ var package = new Package
+ {
+ Version = "1.0.0",
+ NormalizedVersion = "1.0.0",
+ PackageRegistration = new PackageRegistration
+ {
+ Id = "foo",
+ Owners = Enumerable.Empty().ToList(),
+ Packages = Enumerable.Empty().ToList()
+ }
+ };
+
+ var model = CreateDisplayPackageViewModel(package, currentUser: null, packageKeyToDeprecation: null, readmeHtml: null);
+ Assert.Equal("https://nugettrends.com/packages?ids=foo", model.NuGetTrendsUrl);
+ }
+
+ [Fact]
+ public void CanDisplayNuGetTrendsLinkWhenValid()
+ {
+ var package = new Package
+ {
+ Version = "1.0.0",
+ NormalizedVersion = "1.0.0",
+ PackageRegistration = new PackageRegistration
+ {
+ Id = "foo",
+ Owners = Enumerable.Empty().ToList(),
+ Packages = Enumerable.Empty().ToList()
+ }
+ };
+
+ var model = CreateDisplayPackageViewModel(package, currentUser: null, packageKeyToDeprecation: null, readmeHtml: null);
+
+ model.IsNuGetTrendsLinksEnabled = true;
+ model.Available = true;
+
+ Assert.True(model.CanDisplayNuGetTrendsLink());
+ }
+
+ [Theory]
+ [InlineData(false, "https://nugettrends.com/packages?ids=foo", true)]
+ [InlineData(true, "", true)]
+ [InlineData(true, null, true)]
+ [InlineData(true, "https://nugettrends.com/packages?ids=foo", false)]
+ public void CannotDisplayNuGetTrendsLinkWhenInvalid(bool isEnabled, string url, bool isAvailable)
+ {
+ var package = new Package
+ {
+ Version = "1.0.0",
+ NormalizedVersion = "1.0.0",
+ PackageRegistration = new PackageRegistration
+ {
+ Id = "foo",
+ Owners = Enumerable.Empty().ToList(),
+ Packages = Enumerable.Empty().ToList()
+ }
+ };
+
+ var model = CreateDisplayPackageViewModel(package, currentUser: null, packageKeyToDeprecation: null, readmeHtml: null);
+
+ model.IsNuGetTrendsLinksEnabled = isEnabled;
+ model.NuGetTrendsUrl = url;
+ model.Available = isAvailable;
+
+ Assert.False(model.CanDisplayNuGetTrendsLink());
+ }
+
[Theory]
[InlineData(true, true, true)]
[InlineData(false, true, true)]
@@ -1195,4 +1265,4 @@ private static DisplayPackageViewModel CreateDisplayPackageViewModel(
readmeResult: new RenderedMarkdownResult { Content = readmeHtml });
}
}
-}
\ No newline at end of file
+}