Skip to content

Commit 41027e0

Browse files
authored
Show information about parent namespaces on the ID prefix reservation admin panel (#9208)
Progress on NuGet/Engineering#4523
1 parent 9473f50 commit 41027e0

4 files changed

Lines changed: 74 additions & 10 deletions

File tree

src/NuGetGallery/Areas/Admin/Controllers/ReservedNamespaceController.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,28 @@ public JsonResult SearchPrefix(string query)
3939
var prefixQueries = GetPrefixesFromQuery(query);
4040

4141
var foundPrefixes = _reservedNamespaceService.FindReservedNamespacesForPrefixList(prefixQueries);
42+
var valueToNamespace = foundPrefixes.ToDictionary(x => x.Value, StringComparer.OrdinalIgnoreCase);
4243

43-
var notFoundPrefixQueries = prefixQueries.Except(foundPrefixes.Select(p => p.Value), StringComparer.OrdinalIgnoreCase);
44-
var notFoundPrefixes = notFoundPrefixQueries.Select(q => new ReservedNamespace(value: q, isSharedNamespace: false, isPrefix: true));
45-
46-
var resultModel = foundPrefixes.Select(fp => new ReservedNamespaceResultModel(fp, isExisting: true));
47-
resultModel = resultModel.Concat(notFoundPrefixes.Select(nfp => new ReservedNamespaceResultModel(nfp, isExisting: false)).ToList());
44+
var resultModel = new List<ReservedNamespaceResultModel>();
45+
foreach (var value in prefixQueries)
46+
{
47+
bool isExisting = valueToNamespace.TryGetValue(value, out var ns);
48+
if (!isExisting)
49+
{
50+
ns = new ReservedNamespace(value: value, isSharedNamespace: false, isPrefix: true);
51+
}
52+
53+
var existingPrefixes = _reservedNamespaceService.GetReservedNamespacesForId(value);
54+
55+
var parents = existingPrefixes
56+
.Where(x => value.StartsWith(x.Value, StringComparison.OrdinalIgnoreCase)
57+
&& value.Length > x.Value.Length)
58+
.Select(x => x.Value + (x.IsPrefix ? "*" : string.Empty))
59+
.OrderBy(x => x.Length)
60+
.ToArray();
61+
62+
resultModel.Add(new ReservedNamespaceResultModel(ns, isExisting, parents));
63+
}
4864

4965
var results = new ReservedNamespaceSearchResult
5066
{

src/NuGetGallery/Areas/Admin/ViewModels/ReservedNamespaceResultModel.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ public sealed class ReservedNamespaceResultModel
1818

1919
public string[] owners { get; }
2020

21+
public string[] parents { get; }
22+
2123
public ReservedNamespaceResultModel() { }
2224

23-
public ReservedNamespaceResultModel(ReservedNamespace reservedNamespace, bool isExisting)
25+
public ReservedNamespaceResultModel(ReservedNamespace reservedNamespace, bool isExisting, string[] parents)
2426
{
2527
if (reservedNamespace == null)
2628
{
@@ -31,6 +33,7 @@ public ReservedNamespaceResultModel(ReservedNamespace reservedNamespace, bool is
3133
registrations = reservedNamespace.PackageRegistrations?.Select(pr => pr.Id).ToArray();
3234
owners = reservedNamespace.Owners?.Select(u => u.Username).ToArray();
3335
this.isExisting = isExisting;
36+
this.parents = parents;
3437
}
3538
}
3639
}

src/NuGetGallery/Areas/Admin/Views/ReservedNamespace/Index.cshtml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,23 @@
4848
<span style="font-weight: bold">Matching Pattern: </span>
4949
<span data-bind="text: $data.pattern" />
5050
</div>
51+
<!-- ko if: $data.parents.length > 0 -->
52+
<div>
53+
<span style="font-weight: bold">⚠ Parent namespaces:</span>
54+
<ul>
55+
<!-- ko foreach: $data.parents -->
56+
<li data-bind="text: $data"></li>
57+
<!-- /ko -->
58+
</ul>
59+
</div>
60+
<!-- /ko -->
5161
<!-- ko if: $data.isExisting -->
5262
<input style="margin-top: 5px" type="submit" value="Deallocate Namespace" title="Delete this namespace"
53-
data-bind="click: $parent.managePrefix.bind(this, $data.prefix, false)" />
63+
data-bind="click: $parent.managePrefix.bind(this, $data.prefix, false, $data.parents)" />
5464
<!-- /ko -->
5565
<!-- ko ifnot: $data.isExisting -->
5666
<input style="margin-top: 5px" type="submit" value="Reserve Namespace" title="Add this namespace"
57-
data-bind="click: $parent.managePrefix.bind(this, $data.prefix, true)" />
67+
data-bind="click: $parent.managePrefix.bind(this, $data.prefix, true, $data.parents)" />
5868
<!-- /ko -->
5969
</div>
6070
</td>
@@ -243,12 +253,19 @@
243253
});
244254
};
245255
246-
this.managePrefix = function (data, addPrefix) {
256+
this.managePrefix = function (prefixToModify, addPrefix, parents, model, e) {
247257
if (!addPrefix && !confirm('Are you sure you want to delete this namespace?')) {
248258
e.preventDefault();
259+
return;
260+
}
261+
262+
if (addPrefix
263+
&& parents.length > 0
264+
&& !confirm("At least one parent namespace exists. Ensure the owners of the parent namespaces have approved of the new child namespace. Press 'OK' to continue. Existing parent(s):\n" + parents.join("\n"))) {
265+
e.preventDefault();
266+
return;
249267
}
250268
251-
var prefixToModify = data;
252269
var url = addPrefix
253270
? '@Url.Action("AddNamespace", "ReservedNamespace", new { area = "Admin" })'
254271
: '@Url.Action("RemoveNamespace", "ReservedNamespace", new { area = "Admin" })';

tests/NuGetGallery.Facts/Areas/Admin/Controllers/ReservedNamespaceControllerFacts.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,34 @@ public void SearchFindsMatchingPrefixes(string query, int foundCount, int notFou
5151
Assert.Equal(notFoundCount, notFound.Count());
5252
}
5353

54+
55+
[Theory]
56+
[InlineData("Zzz.", "")]
57+
[InlineData("Microsoft.", "")]
58+
[InlineData("Microsoft.Zzz.", "Microsoft.*")]
59+
[InlineData("microsoft.zzz.", "Microsoft.*")]
60+
[InlineData("Microsoft.AspNet.", "Microsoft.*")]
61+
[InlineData("Microsoft.AspNet.Zzz", "Microsoft.*, Microsoft.Aspnet.*")]
62+
[InlineData("jquery.", "")]
63+
[InlineData("jquery.Extentions.Zzz", "jquery.Extentions.*")]
64+
public void IncludesParentNamespaces(string query, string parents)
65+
{
66+
// Arrange.
67+
var namespaces = ReservedNamespaceServiceTestData.GetTestNamespaces();
68+
var reservedNamespaceService = new TestableReservedNamespaceService(reservedNamespaces: namespaces);
69+
var packageRegistrations = new Mock<IEntityRepository<PackageRegistration>>();
70+
var controller = new ReservedNamespaceController(reservedNamespaceService, packageRegistrations.Object);
71+
72+
// Act.
73+
JsonResult jsonResult = controller.SearchPrefix(query);
74+
75+
// Assert
76+
dynamic data = jsonResult.Data;
77+
var resultModelList = data.Prefixes as IEnumerable<ReservedNamespaceResultModel>;
78+
var match = Assert.Single(resultModelList);
79+
Assert.Equal(parents, string.Join(", ", match.parents));
80+
}
81+
5482
[Fact]
5583
public async Task AddNamespaceDoesNotReturnSuccessForInvalidNamespaces()
5684
{

0 commit comments

Comments
 (0)