Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tilovell 908 987 curatedfeed perf and package uniqueness #1002

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 65 additions & 0 deletions Facts/Controllers/CuratedFeedsControllerFacts.cs
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using Moq;
using Xunit;
Expand All @@ -24,10 +25,18 @@ public TestableCuratedFeedsController()
StubCuratedFeedByNameQry
.Setup(stub => stub.Execute(It.IsAny<string>(), It.IsAny<bool>()))
.Returns(StubCuratedFeed);

StubCuratedFeedService = new Mock<ICuratedFeedService>();
CuratedFeedService = StubCuratedFeedService.Object;

StubSearchService = new Mock<ISearchService>();
SearchService = StubSearchService.Object;
}

public CuratedFeed StubCuratedFeed { get; set; }
public Mock<ICuratedFeedByNameQuery> StubCuratedFeedByNameQry { get; private set; }
public Mock<ICuratedFeedService> StubCuratedFeedService { get; private set; }
public Mock<ISearchService> StubSearchService { get; private set; }
public Mock<IIdentity> StubIdentity { get; private set; }

protected override IIdentity Identity
Expand Down Expand Up @@ -165,5 +174,61 @@ public void WillPassTheExcludedPackagesToTheView()
Assert.Equal("theExcludedId", viewModel.ExcludedPackages.First());
}
}

public class TheListPackagesAction
{
[Fact]
public void WillSearchForAPackage()
{
var controller = new TestableCuratedFeedsController();

var redPill = new PackageRegistration
{
Id = "RedPill",
Key = 2,
DownloadCount = 0,
Packages = new []
{
new Package
{
Key = 89932,
}
},
Owners = new []
{
new User
{
Key = 66,
Username = "Morpheus",
}
}
};

redPill.Packages.ElementAt(0).PackageRegistration = redPill;

var mockPackageRegistrations = new [] { redPill }.AsQueryable();
var mockPackages = new[] { redPill.Packages.ElementAt(0) }.AsQueryable();

controller.StubCuratedFeedService
.Setup(stub => stub.GetKey("TheMatrix"))
.Returns(2);

int totalHits;
controller.StubSearchService
.Setup(stub => stub.Search(It.IsAny<SearchFilter>(), out totalHits))
.Returns(mockPackages);

var mockHttpContext = new Mock<HttpContextBase>();
TestUtility.SetupHttpContextMockForUrlGeneration(mockHttpContext, controller);

// Act
var result = controller.ListPackages("TheMatrix", "");

Assert.IsType<ViewResult>(result);
Assert.IsType<PackageListViewModel>(((ViewResult)result).Model);
var model = (result as ViewResult).Model as PackageListViewModel;
Assert.Equal(1, model.Items.Count());
}
}
}
}
2 changes: 1 addition & 1 deletion Facts/Controllers/PackagesControllerFacts.cs
Expand Up @@ -109,7 +109,7 @@ private static Mock<ISearchService> CreateSearchService()
{
var searchService = new Mock<ISearchService>();
int total;
searchService.Setup(s => s.Search(It.IsAny<SearchFilter>(), out total, null)).Returns(
searchService.Setup(s => s.Search(It.IsAny<SearchFilter>(), out total)).Returns(
(IQueryable<Package> p, string searchTerm) => p);

return searchService;
Expand Down
2 changes: 1 addition & 1 deletion Facts/Facts.csproj
Expand Up @@ -164,7 +164,7 @@
<Compile Include="Infrastructure\AnalysisHelperFacts.cs" />
<Compile Include="Infrastructure\CookieTempDataProviderFacts.cs" />
<Compile Include="Infrastructure\Jobs\UpdateStatisticsJobFacts.cs" />
<Compile Include="Infrastructure\LuceneIndexingServiceFacts.cs" />
<Compile Include="Infrastructure\PackageIndexEntityFacts.cs" />
<Compile Include="Infrastructure\LuceneSearchServiceFacts.cs" />
<Compile Include="Infrastructure\NuGetQueryParserFacts.cs" />
<Compile Include="PackageCurators\Windows8PackageCuratorFacts.cs" />
Expand Down
22 changes: 11 additions & 11 deletions Facts/Infrastructure/LuceneSearchServiceFacts.cs
Expand Up @@ -39,7 +39,7 @@ public void IndexAndSearchAPackageByDescription()
new PackageFramework { TargetFramework = "net45" },
}
}
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "awesome");

Expand Down Expand Up @@ -74,7 +74,7 @@ public void IndexAndSearchDavid123For12()
Title = "DavidTest123",
Version = "1.1",
}
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "12");

Expand Down Expand Up @@ -107,7 +107,7 @@ public void IndexAndSearchWithWordStemming()
Title = "SuperzipLib",
Version = "1.1.2",
}
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "compressed");

Expand Down Expand Up @@ -158,7 +158,7 @@ public void SearchUsingCombinedIdAndGeneralTerms()
Title = "Red Herring",
Version = "1.1.2",
},
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "Id:Red Death");

Expand Down Expand Up @@ -213,7 +213,7 @@ public void SearchUsingExactPackageId()
Title = "SomeotherNuGet.Core.SimilarlyNamedPackage",
Version = "1.5.20902.9026",
}
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

// simple query
var results = IndexAndSearch(packageSource, "NuGet.Core");
Expand Down Expand Up @@ -289,7 +289,7 @@ public void SearchForNuGetCoreWithExactField(string field, string term)
Version = "1.5.20902.9026",
Tags = "javascript"
}
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

// query targeted specifically against id field should work equally well
var results = IndexAndSearch(packageSource, field + ":" + term);
Expand Down Expand Up @@ -323,7 +323,7 @@ public void SearchForJQueryUICombinedWithPartialId()
Title = "JQuery UI (Combined Blobbary)",
Tags = "web javascript",
},
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "id:JQuery.ui");
Assert.NotEmpty(results);
Expand Down Expand Up @@ -355,7 +355,7 @@ public void SearchForDegenerateSingleQuoteQuery()
Title = "JQuery UI (Combined Blobbary)",
Tags = "web javascript",
},
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "\"");
Assert.NotEmpty(results);
Expand Down Expand Up @@ -407,7 +407,7 @@ public void SearchUsesPackageRegistrationDownloadCountsToPrioritize()
Title = "JQuery UI (Combined Blobbary)",
Tags = "web javascript",
},
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "");
Assert.NotEmpty(results);
Expand Down Expand Up @@ -464,7 +464,7 @@ public void IndexAndSearchRetrievesCanDriveV2Feed()
Version = "3.4 RC",
};

packageSource.Setup(x => x.GetPackagesForIndexing(null)).Returns(new Package[] {p}.AsQueryable());
packageSource.Setup(x => x.GetPackagesForIndexing(null)).Returns(new Package[] { p }.Select(x => new PackageIndexEntity(x)).AsQueryable());
var results = IndexAndSearch(packageSource, "");
var r = results.AsQueryable().ToV2FeedPackageQuery("http://www.nuget.org/").First();

Expand Down Expand Up @@ -525,7 +525,7 @@ public void SearchWorksAroundLuceneQuerySyntaxExceptions()
Title = "NuGet.Core",
Version = "1.5.20902.9026",
},
}.AsQueryable());
}.Select(p => new PackageIndexEntity(p)).AsQueryable());

var results = IndexAndSearch(packageSource, "*Core"); // Lucene parser throws for leading asterisk in searches
Assert.NotEmpty(results);
Expand Down
Expand Up @@ -5,7 +5,7 @@

namespace NuGetGallery.Infrastructure
{
public class LuceneIndexingServiceFacts
public class PackageIndexEntityFacts
{
[Theory]
[InlineData("NHibernate", new[] { "NHibernate" })]
Expand All @@ -24,7 +24,7 @@ public class LuceneIndexingServiceFacts
public void CamelCaseTokenizer(string term, IEnumerable<string> tokens)
{
// Act
var result = LuceneIndexingService.TokenizeId(term);
var result = PackageIndexEntity.TokenizeId(term);

// Assert
Assert.Equal(tokens.OrderBy(p => p), result.OrderBy(p => p));
Expand All @@ -38,7 +38,7 @@ public void CamelCaseTokenizer(string term, IEnumerable<string> tokens)
[InlineData("JQuery.UI.Combined", "JQuery UI Combined")]
public void IdSplitter(string term, string tokens)
{
var result = LuceneIndexingService.SplitId(term);
var result = PackageIndexEntity.SplitId(term);
Assert.Equal(result, tokens);
}

Expand All @@ -54,7 +54,7 @@ public void IdSplitter(string term, string tokens)
[InlineData("SignalR.Hosting.AspNet", "SignalR Hosting Asp Net")]
public void CamelIdSplitter(string term, string tokens)
{
var result = LuceneIndexingService.CamelSplitId(term);
var result = PackageIndexEntity.CamelSplitId(term);
Assert.Equal(result, tokens);
}
}
Expand Down
2 changes: 1 addition & 1 deletion Facts/Services/FeedServiceFacts.cs
Expand Up @@ -74,7 +74,7 @@ public void V1FeedSearchDoesNotReturnPrereleasePackages()
configuration.Setup(c => c.GetSiteRoot(It.IsAny<bool>())).Returns("https://localhost:8081/");
var searchService = new Mock<ISearchService>(MockBehavior.Strict);
int total;
searchService.Setup(s => s.Search(It.IsAny<SearchFilter>(), out total, null)).Returns
searchService.Setup(s => s.Search(It.IsAny<SearchFilter>(), out total)).Returns
<IQueryable<Package>, string>((_, __) => _);
var v1Service = new TestableV1Feed(repo.Object, configuration.Object, searchService.Object);

Expand Down
12 changes: 12 additions & 0 deletions Website/App_Start/ContainerBindings.cs
Expand Up @@ -90,6 +90,14 @@ public override void Load()
.To<EntityRepository<User>>()
.InRequestScope();

Bind<IEntityRepository<CuratedFeed>>()
.To<EntityRepository<CuratedFeed>>()
.InRequestScope();

Bind<IEntityRepository<CuratedPackage>>()
.To<EntityRepository<CuratedPackage>>()
.InRequestScope();

Bind<IEntityRepository<PackageRegistration>>()
.To<EntityRepository<PackageRegistration>>()
.InRequestScope();
Expand All @@ -110,6 +118,10 @@ public override void Load()
.To<EntityRepository<PackageStatistics>>()
.InRequestScope();

Bind<ICuratedFeedService>()
.To<CuratedFeedService>()
.InRequestScope();

Bind<IUserService>()
.To<UserService>()
.InRequestScope();
Expand Down
5 changes: 5 additions & 0 deletions Website/App_Start/Routes.cs
Expand Up @@ -137,6 +137,11 @@ public static void RegisterRoutes(RouteCollection routes)
"curated-feeds/{name}",
new { controller = CuratedFeedsController.ControllerName, action = "CuratedFeed" });

routes.MapRoute(
RouteName.CuratedFeedListPackages,
"curated-feeds/{curatedFeedName}/packages",
MVC.CuratedFeeds.ListPackages());

routes.MapRoute(
RouteName.CreateCuratedPackageForm,
"forms/add-package-to-curated-feed",
Expand Down
65 changes: 64 additions & 1 deletion Website/Controllers/CuratedFeedsController.cs
@@ -1,5 +1,9 @@
using System.Linq;
using System;
using System.Data.Entity;
using System.Linq;
using System.Web.Mvc;
using NuGet;
using NuGetGallery;

namespace NuGetGallery
{
Expand All @@ -8,6 +12,18 @@ public partial class CuratedFeedsController : AppController
{
public const string ControllerName = "CuratedFeeds";

public ICuratedFeedService CuratedFeedService { get; protected set; }
public ISearchService SearchService { get; protected set; }

protected CuratedFeedsController() { }

public CuratedFeedsController(ICuratedFeedService curatedFeedService,
ISearchService searchService)
{
CuratedFeedService = curatedFeedService;
SearchService = searchService;
}

[HttpGet]
public virtual ActionResult CuratedFeed(string name)
{
Expand Down Expand Up @@ -38,5 +54,52 @@ public virtual ActionResult CuratedFeed(string name)
.Select(cp => cp.PackageRegistration.Id),
});
}

[HttpGet]
public virtual ActionResult ListPackages(string curatedFeedName, string q, string sortOrder = null, int page = 1, bool prerelease = false)
{
if (page < 1)
{
page = 1;
}

q = (q ?? "").Trim();

if (String.IsNullOrEmpty(sortOrder))
{
// Determine the default sort order. If no query string is specified, then the sortOrder is DownloadCount
// If we are searching for something, sort by relevance.
sortOrder = q.IsEmpty() ? Constants.PopularitySortOrder : Constants.RelevanceSortOrder;
}

var searchFilter = SearchAdaptor.GetSearchFilter(q, sortOrder, page, prerelease);
searchFilter.CuratedFeedKey = CuratedFeedService.GetKey(curatedFeedName);
if (searchFilter.CuratedFeedKey == 0)
{
return HttpNotFound();
}

int totalHits;
IQueryable<Package> packageVersions = SearchService.Search(searchFilter, out totalHits);
if (page == 1 && !packageVersions.Any())
{
// In the event the index wasn't updated, we may get an incorrect count.
totalHits = 0;
}

var viewModel = new PackageListViewModel(
packageVersions,
q,
sortOrder,
totalHits,
page - 1,
Constants.DefaultPackageListPageSize,
Url,
prerelease);

ViewBag.SearchTerm = q;

return View("~/Views/Packages/ListPackages.cshtml", viewModel);
}
}
}