diff --git a/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs b/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs index 2dcf460823..62c7fa87ef 100644 --- a/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs +++ b/src/NuGetGallery.Services/Configuration/FeatureFlagService.cs @@ -26,6 +26,7 @@ public class FeatureFlagService : IFeatureFlagService private const string ManageDeprecationForManyVersionsFeatureName = GalleryPrefix + "ManageDeprecationMany"; private const string ManageDeprecationApiFeatureName = GalleryPrefix + "ManageDeprecationApi"; private const string DisplayVulnerabilitiesFeatureName = GalleryPrefix + "DisplayVulnerabilities"; + private const string DisplayFuGetLinksFeatureName = GalleryPrefix + "DisplayFuGetLinks"; private const string ODataReadOnlyDatabaseFeatureName = GalleryPrefix + "ODataReadOnlyDatabase"; private const string PackagesAtomFeedFeatureName = GalleryPrefix + "PackagesAtomFeed"; private const string SearchSideBySideFlightName = GalleryPrefix + "SearchSideBySide"; @@ -133,6 +134,11 @@ public bool IsDisplayVulnerabilitiesEnabled() return _client.IsEnabled(DisplayVulnerabilitiesFeatureName, defaultValue: false); } + public bool IsDisplayFuGetLinksEnabled() + { + return _client.IsEnabled(DisplayFuGetLinksFeatureName, defaultValue: false); + } + public bool AreEmbeddedIconsEnabled(User user) { return _client.IsEnabled(EmbeddedIconFlightName, user, defaultValue: false); diff --git a/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs b/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs index 50eb7fd90b..9fb7b91211 100644 --- a/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs +++ b/src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs @@ -61,6 +61,11 @@ public interface IFeatureFlagService /// bool IsDisplayVulnerabilitiesEnabled(); + /// + /// Whether or not a fuget.org link is visible on a package's details page. + /// + bool IsDisplayFuGetLinksEnabled(); + /// /// Whether the user is allowed to publish packages with an embedded icon. /// diff --git a/src/NuGetGallery/App_Data/Files/Content/flags.json b/src/NuGetGallery/App_Data/Files/Content/flags.json index d4dd2d2e84..90405890e6 100644 --- a/src/NuGetGallery/App_Data/Files/Content/flags.json +++ b/src/NuGetGallery/App_Data/Files/Content/flags.json @@ -23,7 +23,8 @@ "NuGetGallery.ODataV2FindPackagesByIdCountNonHijacked": "Enabled", "NuGetGallery.ODataV2SearchNonHijacked": "Enabled", "NuGetGallery.ODataV2SearchCountNonHijacked": "Enabled", - "NuGetGallery.DisplayVulnerabilities": "Enabled" + "NuGetGallery.DisplayVulnerabilities": "Enabled", + "NuGetGallery.DisplayFuGetLinks": "Enabled" }, "Flights": { "NuGetGallery.TyposquattingFlight": { diff --git a/src/NuGetGallery/Content/gallery/img/fuget-32x32.png b/src/NuGetGallery/Content/gallery/img/fuget-32x32.png new file mode 100644 index 0000000000..64d6695505 Binary files /dev/null and b/src/NuGetGallery/Content/gallery/img/fuget-32x32.png differ diff --git a/src/NuGetGallery/Content/gallery/img/fuget.svg b/src/NuGetGallery/Content/gallery/img/fuget.svg new file mode 100644 index 0000000000..65e1cf893a --- /dev/null +++ b/src/NuGetGallery/Content/gallery/img/fuget.svg @@ -0,0 +1,13 @@ + + + + fuget-icon + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/PackagesController.cs b/src/NuGetGallery/Controllers/PackagesController.cs index 146f183f3a..b78d9fbf56 100644 --- a/src/NuGetGallery/Controllers/PackagesController.cs +++ b/src/NuGetGallery/Controllers/PackagesController.cs @@ -902,6 +902,7 @@ public virtual async Task DisplayPackage(string id, string version model.IsAtomFeedEnabled = _featureFlagService.IsPackagesAtomFeedEnabled(); model.IsPackageDeprecationEnabled = _featureFlagService.IsManageDeprecationEnabled(currentUser, allVersions); model.IsPackageVulnerabilitiesEnabled = _featureFlagService.IsDisplayVulnerabilitiesEnabled(); + model.IsFuGetLinksEnabled = _featureFlagService.IsDisplayFuGetLinksEnabled(); model.IsPackageRenamesEnabled = _featureFlagService.IsPackageRenamesEnabled(currentUser); model.IsPackageDependentsEnabled = _featureFlagService.IsPackageDependentsEnabled(currentUser); diff --git a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs index d8d3882d79..fcb96a39de 100644 --- a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs +++ b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs @@ -163,6 +163,12 @@ private DisplayPackageViewModel SetupCommon( viewModel.ProjectUrl = projectUrl; } + var fugetUrl = $"https://www.fuget.org/packages/{package.Id}/{package.NormalizedVersion}"; + if (PackageHelper.TryPrepareUrlForRendering(fugetUrl, out string fugetReadyUrl)) + { + viewModel.FuGetUrl = fugetReadyUrl; + } + viewModel.EmbeddedLicenseType = package.EmbeddedLicenseType; viewModel.LicenseExpression = package.LicenseExpression; diff --git a/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs b/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs index c770b9f3d0..9f8a01dcb4 100644 --- a/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs +++ b/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs @@ -34,6 +34,7 @@ public class DisplayPackageViewModel : ListPackageItemViewModel public bool IsAtomFeedEnabled { get; set; } public bool IsPackageDeprecationEnabled { get; set; } public bool IsPackageVulnerabilitiesEnabled { get; set; } + public bool IsFuGetLinksEnabled { get; set; } public bool IsPackageRenamesEnabled { get; set; } public bool IsGitHubUsageEnabled { get; set; } public bool IsPackageDependentsEnabled { get; set; } @@ -77,6 +78,7 @@ public bool HasNewerRelease public RepositoryKind RepositoryType { get; private set; } public string ProjectUrl { get; set; } public string LicenseUrl { get; set; } + public string FuGetUrl { get; set; } public IReadOnlyCollection LicenseNames { get; set; } public string LicenseExpression { get; set; } public IReadOnlyCollection LicenseExpressionSegments { get; set; } diff --git a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml index aec4239c44..a1c26675e5 100644 --- a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml +++ b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml @@ -1029,7 +1029,24 @@ View validations } - + + @if (Model.IsFuGetLinksEnabled && !string.IsNullOrEmpty(Model.FuGetUrl)) + { + var disclaimer = "fuget.org is a 3rd party website, not controlled by Microsoft. This link is made available to you per the NuGet Terms of Use."; + +
  • + + + Open in FuGet Package Explorer + +
  • + } + @if (Model.LicenseNames.AnySafe()) {

    License info provided by Sonatype.

    diff --git a/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs index b5808d4663..eb324c5eda 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DisplayPackageViewModelFacts.cs @@ -104,6 +104,27 @@ public void ItInitializesProjectUrl(string projectUrl, string expected) Assert.Equal(expected, model.ProjectUrl); } + [Theory] + [InlineData("foo", "1.0.0", "https://www.fuget.org/packages/foo/1.0.0")] + [InlineData("foo", "1.1.0", "https://www.fuget.org/packages/foo/1.1.0")] + [InlineData("Foo.Bar", "1.1.0-bETa", "https://www.fuget.org/packages/Foo.Bar/1.1.0-bETa")] + public void ItInitializesFuGetUrl(string packageId, string packageVersion, string expected) + { + var package = new Package + { + Version = packageVersion, + NormalizedVersion = packageVersion, + PackageRegistration = new PackageRegistration + { + Id = packageId, + Owners = Enumerable.Empty().ToList(), + Packages = Enumerable.Empty().ToList() + } + }; + + var model = CreateDisplayPackageViewModel(package, currentUser: null, packageKeyToDeprecation: null, readmeHtml: null); + Assert.Equal(expected, model.FuGetUrl); + } [Theory] [InlineData(null, null)]