diff --git a/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs b/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs
index ea99fce84d..645edbe64e 100644
--- a/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs
+++ b/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs
@@ -7,13 +7,13 @@
namespace NuGetGallery.Frameworks
{
- public class FrameworkCompatibilityService : IFrameworkCompatibilityService
+ public static class FrameworkCompatibilityService
{
private static readonly IFrameworkCompatibilityProvider CompatibilityProvider = DefaultCompatibilityProvider.Instance;
private static readonly IReadOnlyList AllSupportedFrameworks = SupportedFrameworks.AllSupportedNuGetFrameworks;
private static readonly IReadOnlyDictionary> CompatibilityMatrix = GetCompatibilityMatrix();
- public ISet GetCompatibleFrameworks(IEnumerable packageFrameworks)
+ public static ISet GetCompatibleFrameworks(IEnumerable packageFrameworks)
{
if (packageFrameworks == null)
{
diff --git a/src/NuGetGallery.Core/Frameworks/IFrameworkCompatibilityService.cs b/src/NuGetGallery.Core/Frameworks/IFrameworkCompatibilityService.cs
deleted file mode 100644
index e3a61409ba..0000000000
--- a/src/NuGetGallery.Core/Frameworks/IFrameworkCompatibilityService.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using NuGet.Frameworks;
-
-namespace NuGetGallery.Frameworks
-{
- public interface IFrameworkCompatibilityService
- {
- ///
- /// Computes a set of compatible target frameworks from a list of target frameworks.
- ///
- /// List of frameworks.
- /// A set of computed compatible target frameworks.
- ///
- /// Every element on the returned set is compatible with at least one of the target frameworks from the input.
- ///
- ISet GetCompatibleFrameworks(IEnumerable frameworks);
- }
-}
\ No newline at end of file
diff --git a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs
index 12a90a32ae..7eaae5a658 100644
--- a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs
+++ b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs
@@ -22,13 +22,6 @@ public class PackageFrameworkCompatibilityFactory : IPackageFrameworkCompatibili
private readonly NuGetFrameworkSorter Sorter = new NuGetFrameworkSorter();
private readonly int NetStartingMajorVersion = 5;
- private readonly IFrameworkCompatibilityService _compatibilityService;
-
- public PackageFrameworkCompatibilityFactory(IFrameworkCompatibilityService compatibilityService)
- {
- _compatibilityService = compatibilityService ?? throw new ArgumentNullException();
- }
-
public PackageFrameworkCompatibility Create(ICollection packageFrameworks)
{
if (packageFrameworks == null)
@@ -53,7 +46,7 @@ public PackageFrameworkCompatibility Create(ICollection packag
private IReadOnlyDictionary> CreateFrameworkCompatibilityTable(ICollection filteredPackageFrameworks)
{
- var compatibleFrameworks = _compatibilityService.GetCompatibleFrameworks(filteredPackageFrameworks);
+ var compatibleFrameworks = FrameworkCompatibilityService.GetCompatibleFrameworks(filteredPackageFrameworks);
var table = new Dictionary>();
diff --git a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs
index dc2d9c2604..5a5d01fac3 100644
--- a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs
+++ b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs
@@ -13,7 +13,7 @@ public class PackageFrameworkCompatibilityTableData
public NuGetFramework Framework { get; set; }
///
- /// if the was computed from .
+ /// if the was computed from .
/// if the was retrieved from the package asset frameworks.
///
public bool IsComputed { get; set; }
diff --git a/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs b/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs
index e852c86a23..f001a03f86 100644
--- a/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs
+++ b/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs
@@ -471,11 +471,6 @@ protected override void Load(ContainerBuilder builder)
.As()
.SingleInstance();
- builder.RegisterType()
- .AsSelf()
- .As()
- .SingleInstance();
-
builder.RegisterType()
.AsSelf()
.As()
diff --git a/src/NuGetGallery/Areas/Admin/Controllers/CorrectIsLatestController.cs b/src/NuGetGallery/Areas/Admin/Controllers/CorrectIsLatestController.cs
new file mode 100644
index 0000000000..a96318afa9
--- /dev/null
+++ b/src/NuGetGallery/Areas/Admin/Controllers/CorrectIsLatestController.cs
@@ -0,0 +1,108 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+using NuGetGallery.Areas.Admin.Models;
+using NuGetGallery.Areas.Admin.ViewModels;
+
+namespace NuGetGallery.Areas.Admin.Controllers
+{
+ public class CorrectIsLatestController : AdminControllerBase
+ {
+ private readonly IPackageService _packageService;
+ private readonly IEntitiesContext _entitiesContext;
+ private readonly IPackageFileService _packageFileService;
+ private readonly ITelemetryService _telemetryService;
+
+ public CorrectIsLatestController(IPackageService packageService, IEntitiesContext entitiesContext, IPackageFileService packageFileService, ITelemetryService telemetryService)
+ {
+ _packageService = packageService ?? throw new ArgumentNullException(nameof(packageService));
+ _entitiesContext = entitiesContext ?? throw new ArgumentNullException(nameof(entitiesContext));
+ _packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService));
+ _telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService));
+ }
+
+ [HttpGet]
+ public ActionResult Index()
+ {
+ return View();
+ }
+
+ [HttpGet]
+ public ActionResult CorrectIsLatestPackages()
+ {
+ var result = _entitiesContext
+ .PackageRegistrations
+ .Where(pr => pr.Packages.Any(p => p.IsLatest || p.IsLatestStable || p.IsLatestSemVer2 || p.IsLatestStableSemVer2))
+ .Select(pr => new CorrectIsLatestPackage()
+ {
+ Id = pr.Id,
+ Version = pr.Packages
+ .Where(p => p.IsLatest || p.IsLatestStable || p.IsLatestSemVer2 || p.IsLatestStableSemVer2)
+ .FirstOrDefault()
+ .Version,
+ IsLatestCount = pr.Packages.Where(p => p.IsLatest).Count(),
+ IsLatestStableCount = pr.Packages.Where(p => p.IsLatestStable).Count(),
+ IsLatestSemVer2Count = pr.Packages.Where(p => p.IsLatestSemVer2).Count(),
+ IsLatestStableSemVer2Count = pr.Packages.Where(p => p.IsLatestStableSemVer2).Count(),
+ HasIsLatestUnlisted = pr.Packages.Any(p =>
+ !p.Listed
+ && (p.IsLatest
+ || p.IsLatestStable
+ || p.IsLatestSemVer2
+ || p.IsLatestStableSemVer2))
+ })
+ .Where(pr => pr.IsLatestCount > 1
+ || pr.IsLatestStableCount > 1
+ || pr.IsLatestSemVer2Count > 1
+ || pr.IsLatestStableSemVer2Count > 1
+ || pr.HasIsLatestUnlisted)
+ .OrderBy(pr => pr.Id)
+ .ToList();
+
+ return Json(result, JsonRequestBehavior.AllowGet);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task ReflowPackages(CorrectIsLatestRequest request)
+ {
+ if (request == null || request.Packages == null || request.Packages.Count == 0)
+ {
+ return Json(HttpStatusCode.BadRequest, "Packages cannot be null or empty.", JsonRequestBehavior.AllowGet);
+ }
+
+ var reflowPackageService = new ReflowPackageService(
+ _entitiesContext,
+ (PackageService)_packageService,
+ _packageFileService,
+ _telemetryService);
+
+ var totalPackagesReflowed = 0;
+ var totalPackagesFailReflowed = 0;
+
+ foreach (var package in request.Packages)
+ {
+ try
+ {
+ await reflowPackageService.ReflowAsync(package.Id, package.Version);
+ totalPackagesReflowed++;
+ }
+ catch (Exception ex)
+ {
+ ex.Log();
+ totalPackagesFailReflowed++;
+ }
+ }
+
+ var reflowedPackagesMessage = totalPackagesReflowed == 1 ? $"{totalPackagesReflowed} package reflowed" : $"{totalPackagesReflowed} packages reflowed";
+ var failedPackagesMessage = totalPackagesFailReflowed == 1 ? $"{totalPackagesFailReflowed} package fail reflow" : $"{totalPackagesFailReflowed} packages fail reflow";
+
+ return Json(HttpStatusCode.OK, $"{reflowedPackagesMessage}, {failedPackagesMessage}.", JsonRequestBehavior.AllowGet);
+ }
+ }
+}
diff --git a/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs b/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs
index d8d77fbf59..fbe6ae9b15 100644
--- a/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs
+++ b/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs
@@ -98,37 +98,55 @@ public ActionResult ValidateInputs(string packagesFromInput, string packagesToIn
}
// create validated input result
- var input = new PopularityTransferItem(CreatePackageSearchResult(packageFrom.Packages.First()),
- CreatePackageSearchResult(packageTo.Packages.First()),
- packageFrom.Key,
- packageTo.Key);
+ result.ValidatedInputs.Add(new PopularityTransferItem(packageFrom, packageTo));
- result.ValidatedInputs.Add(input);
+ // checking for existing entries in the PackageRenames table
+ // 1. 'From' input that already has a 'From' entry in the PackageRenames table -- Conflict
+ var existingRenames = _packageRenameService.GetPackageRenames(packageFrom);
- // check for existing entries in the PackageRename table for the 'From' packages
- var existingRenamesFrom = _packageRenameService.GetPackageRenames(packageFrom);
-
- if (existingRenamesFrom.Any())
+ if (existingRenames.Any())
{
- if (existingRenamesFrom.Count == 1)
+ if (existingRenames.Count == 1)
{
- var existingRenamesMessage = $"{packageFrom.Id} already has 1 entry in the PackageRenames table. This will be removed with this operation.";
- result.ExistingPackageRenames.Add(existingRenamesMessage);
+ result.ExistingPackageRenamesMessagesConflict.Add($"{packageFrom.Id} already has 1 entry in the PackageRenames table. This will be removed with this operation.");
}
else
{
- var existingRenamesMessage = $"{packageFrom.Id} already has {existingRenamesFrom.Count} entries in the PackageRenames table. These will be removed with this operation.";
- result.ExistingPackageRenames.Add(existingRenamesMessage);
+ result.ExistingPackageRenamesMessagesConflict.Add($"{packageFrom.Id} already has {existingRenames.Count} entries in the PackageRenames table. These will be removed with this operation.");
+ }
+
+ foreach (var existingRename in existingRenames)
+ {
+ result.ExistingPackageRenamesConflict.Add(new PopularityTransferItem(existingRename.FromPackageRegistration, existingRename.ToPackageRegistration));
+ }
+ }
+
+ // 2. 'From' input that already has a 'To' entry in the PackageRenames table -- Transitive
+ existingRenames = _packageRenameService.GetPackageRenamesTo(packageFrom);
+
+ if (existingRenames.Any())
+ {
+ var existingRenamesMessage = $"{packageFrom.Id} already has entries in the PackageRenames table. This popularity transfer will result in a new transitive relationship. Please look at the PackageRenames table and verify your input before proceeding.";
+ result.ExistingPackageRenamesMessagesTransitive.Add(existingRenamesMessage);
+
+ foreach (var existingRename in existingRenames)
+ {
+ result.ExistingPackageRenamesTransitive.Add(new PopularityTransferItem(existingRename.FromPackageRegistration, existingRename.ToPackageRegistration));
}
}
- // check for existing entries in the PackageRename table for the 'To' packages
- var existingRenamesTo = _packageRenameService.GetPackageRenames(packageTo);
+ // 3. 'To' input that already has a 'From' entry in the PackageRenames table -- Transitive
+ existingRenames = _packageRenameService.GetPackageRenames(packageTo);
- if (existingRenamesTo.Any())
+ if (existingRenames.Any())
{
var existingRenamesMessage = $"{packageTo.Id} already has entries in the PackageRenames table. This popularity transfer will result in a new transitive relationship. Please look at the PackageRenames table and verify your input before proceeding.";
- result.ExistingPackageRenames.Insert(0, existingRenamesMessage);
+ result.ExistingPackageRenamesMessagesTransitive.Add(existingRenamesMessage);
+
+ foreach (var existingRename in existingRenames)
+ {
+ result.ExistingPackageRenamesTransitive.Add(new PopularityTransferItem(existingRename.FromPackageRegistration, existingRename.ToPackageRegistration));
+ }
}
}
diff --git a/src/NuGetGallery/Areas/Admin/Models/CorrectIsLatestPackage.cs b/src/NuGetGallery/Areas/Admin/Models/CorrectIsLatestPackage.cs
new file mode 100644
index 0000000000..fa0e96ff08
--- /dev/null
+++ b/src/NuGetGallery/Areas/Admin/Models/CorrectIsLatestPackage.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace NuGetGallery.Areas.Admin.Models
+{
+ public class CorrectIsLatestPackage
+ {
+ public string Id { get; set; }
+ public string Version { get; set; }
+ public int IsLatestCount { get; set; }
+ public int IsLatestStableCount { get; set; }
+ public int IsLatestSemVer2Count { get; set; }
+ public int IsLatestStableSemVer2Count { get; set; }
+ public bool HasIsLatestUnlisted { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/Areas/Admin/ViewModels/CorrectIsLatestRequest.cs b/src/NuGetGallery/Areas/Admin/ViewModels/CorrectIsLatestRequest.cs
new file mode 100644
index 0000000000..d5e80b69d1
--- /dev/null
+++ b/src/NuGetGallery/Areas/Admin/ViewModels/CorrectIsLatestRequest.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+
+using System.Collections.Generic;
+
+namespace NuGetGallery.Areas.Admin.ViewModels
+{
+ public class CorrectIsLatestRequest
+ {
+ public ICollection Packages { get; set; }
+ }
+
+ public class CorrectIsLatestPackageRequest
+ {
+ public string Id { get; set; }
+ public string Version { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs b/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs
index d68dfffed3..1a1acc2413 100644
--- a/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs
+++ b/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs
@@ -1,9 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.Collections.Generic;
+using System.Linq;
using System.Web;
using System.Web.Mvc;
+using NuGet.Services.Entities;
namespace NuGetGallery.Areas.Admin.ViewModels
{
@@ -12,12 +15,18 @@ public class PopularityTransferViewModel
public PopularityTransferViewModel()
{
ValidatedInputs = new List();
- ExistingPackageRenames = new List();
+ ExistingPackageRenamesConflict = new List();
+ ExistingPackageRenamesTransitive = new List();
+ ExistingPackageRenamesMessagesConflict = new List();
+ ExistingPackageRenamesMessagesTransitive = new List();
SuccessMessage = string.Empty;
}
public List ValidatedInputs { get; set; }
- public List ExistingPackageRenames { get; set; }
+ public List ExistingPackageRenamesConflict { get; set; }
+ public List ExistingPackageRenamesTransitive { get; set; }
+ public List ExistingPackageRenamesMessagesConflict { get; set; }
+ public List ExistingPackageRenamesMessagesTransitive { get; set; }
public string SuccessMessage { get; set; } = string.Empty;
}
@@ -28,35 +37,49 @@ public PopularityTransferItem()
FromOwners = new List();
ToOwners = new List();
}
-
- public PopularityTransferItem(
- PackageSearchResult packageFrom,
- PackageSearchResult packageTo,
- int fromKey,
- int toKey)
+
+ public PopularityTransferItem(PackageRegistration packageFrom, PackageRegistration packageTo)
{
- FromId = packageFrom.PackageId;
- FromUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageFrom.PackageId);
- FromDownloads = packageFrom.DownloadCount;
- FromOwners = packageFrom.Owners;
- FromKey = fromKey;
-
- ToId = packageTo.PackageId;
- ToUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageTo.PackageId);
- ToDownloads = packageTo.DownloadCount;
- ToOwners = packageTo.Owners;
- ToKey = toKey;
+ FromId = packageFrom.Id;
+ FromUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageFrom.Id);
+ FromDownloads = packageFrom.DownloadCount.ToNuGetNumberString();
+ FromOwners = packageFrom
+ .Owners
+ .Select(u => u.Username)
+ .OrderBy(u => u, StringComparer.OrdinalIgnoreCase)
+ .Select(u => new UserViewModel
+ {
+ Username = u,
+ ProfileUrl = UrlHelperExtensions.User(new UrlHelper(HttpContext.Current.Request.RequestContext), u),
+ })
+ .ToList();
+ FromKey = packageFrom.Key;
+
+ ToId = packageTo.Id;
+ ToUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageTo.Id);
+ ToDownloads = packageTo.DownloadCount.ToNuGetNumberString();
+ ToOwners = packageTo
+ .Owners
+ .Select(u => u.Username)
+ .OrderBy(u => u, StringComparer.OrdinalIgnoreCase)
+ .Select(u => new UserViewModel
+ {
+ Username = u,
+ ProfileUrl = UrlHelperExtensions.User(new UrlHelper(HttpContext.Current.Request.RequestContext), u),
+ })
+ .ToList();
+ ToKey = packageTo.Key;
}
public string FromId { get; set; }
public string FromUrl { get; set; }
- public long FromDownloads { get; set; }
+ public string FromDownloads { get; set; }
public IReadOnlyList FromOwners { get; set; } = new List();
public int FromKey { get; set; }
public string ToId { get; set; }
public string ToUrl { get; set; }
- public long ToDownloads { get; set; }
+ public string ToDownloads { get; set; }
public IReadOnlyList ToOwners { get; set; } = new List();
public int ToKey { get; set; }
}
diff --git a/src/NuGetGallery/Areas/Admin/Views/CorrectIsLatest/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/CorrectIsLatest/Index.cshtml
new file mode 100644
index 0000000000..328c7b09a6
--- /dev/null
+++ b/src/NuGetGallery/Areas/Admin/Views/CorrectIsLatest/Index.cshtml
@@ -0,0 +1,193 @@
+@model IReadOnlyList
+@{
+ ViewBag.Title = "Correct IsLatest packages";
+}
+@ViewHelpers.AjaxAntiForgeryToken(Html)
+
+
+ Correct IsLatest packages
+
+ Get correct IsLatest packages
+
+
+ @ViewHelpers.AlertInfo(@Loading packages... )
+
+
+
No packages with incorrect IsLatest found.
+
+
+ @using (Html.BeginForm())
+ {
+ @Html.AntiForgeryToken()
+
+
+ @ViewHelpers.Alert(@ , "info", "Info")
+
+
+ @ViewHelpers.AlertInfo(@The packages are being reflowed. It may take a while for this change to propagate through our system. )
+
+
+ @ViewHelpers.AlertDanger(@ )
+
+
+
+
+ }
+
+
+@section BottomScripts {
+
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml
index ca73cc8509..200abcd523 100644
--- a/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml
+++ b/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml
@@ -255,6 +255,17 @@
Transfer popularity from one package to another.
+
+
+
+ Correct IsLatest state from packages.
+
+
diff --git a/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml
index 2513d665fe..3e4bc89d27 100644
--- a/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml
+++ b/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml
@@ -3,6 +3,36 @@
}
@ViewHelpers.AjaxAntiForgeryToken(Html)
+@helper AddPopularityTransferItemTable(string inputName, string tableId, string tableLabel)
+{
+
+}
+