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

Follow Packages #978

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7aded75
Favorites Feature part 1 (before branch switch) - Entities update, Fo…
Mar 19, 2013
b6d4a6e
Get the font awesome and javasript stuff on DisplayPackage working. A…
Mar 22, 2013
a2bf704
Get view data for binding favorite stars in list packages view.
Mar 26, 2013
6812dc8
Fix EF migration for favorites wich was screwed up...
Mar 26, 2013
dbcab94
Part way through making changes to ListPackages to use javascript. Ja…
Mar 27, 2013
a4063f8
Got interactive add/remove favorite buttons working on Packages List …
Mar 28, 2013
ab23f5e
Add a 'Manage Favorite Packages' page, and start displaying user's fa…
Mar 29, 2013
ad56c48
Fix code analysis warnings.
Mar 29, 2013
5b8afe2
incorp code review (re: url)
johnataylor Apr 4, 2013
049c563
Fix compile errors and regenerate the EF migration for favorites sinc…
Apr 5, 2013
e653a10
Addressing a bunch of good code review feedback on the favorites feat…
Apr 5, 2013
ae11734
After debugging - fix various javascript bugs, edge cases, fxcop comp…
Apr 6, 2013
ece0a4f
More idiomatic use of Razor in html button ids.
Apr 8, 2013
1c644a2
Update button styling, remove unnecessary javascript scripts, simplif…
Apr 8, 2013
7b30a2c
Rename 'UserFollowsPackage' to 'PackageFollow' according to @anurse's…
Apr 9, 2013
6727f83
Fix a bug in ColumnLengthOfPackageTable Down() migration found while …
Apr 9, 2013
a2fee76
A few more styling tweaks to favorites
Apr 9, 2013
666fefb
Add a unit test that covers the new controller. Also adding in an ICu…
Apr 3, 2013
11709bc
Add a favorite count on DisplayPackage page. Refactor and rename 'fol…
Apr 10, 2013
657e02c
Add per-user favorites feeds, which will be broken when I merge in th…
Apr 10, 2013
abd1ce2
Cherry-Merge pull request #1023 from NuGet/tilovell-987-curatedfeed_perf
TimLovellSmith Apr 10, 2013
9fb1180
Get searchable favorite package feeds working. And updated synchronou…
Apr 10, 2013
5ec9032
Adjust the 'favorite' terminology to 'follow' terminology. Revert a l…
Apr 11, 2013
ead1dd3
Revert a few more accidental changes that became obvious in the pull …
Apr 11, 2013
1d0ce28
Fix a bunch of other accidental changes caught with self-review. Rege…
Apr 11, 2013
32e0461
Fix bugs I just introduced.
Apr 11, 2013
294d12c
Remove the indexing in response to favoriting, while it's a nice idea…
Apr 11, 2013
329188c
Add a Followers page. Also resolve some misc merge issues, fxcop issu…
Apr 12, 2013
0ff8c73
Fix indentation of 'my followed packages' on the account page. Fix mi…
Apr 15, 2013
fa8208d
Stylistic fixes for followed packages and temporarily disable the fol…
Apr 15, 2013
f169567
Fixup issues caused by rebasing on dev-start.
Apr 16, 2013
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
2 changes: 1 addition & 1 deletion Facts/Controllers/ApiControllerFacts.cs
Expand Up @@ -837,4 +837,4 @@ public async void VerifyRecentPopularityStatsDownloadsCount()
}
}
}
}
}
2 changes: 1 addition & 1 deletion Facts/Controllers/CuratedFeedsControllerFacts.cs
Expand Up @@ -231,4 +231,4 @@ public void WillSearchForAPackage()
}
}
}
}
}
1 change: 1 addition & 0 deletions Facts/Controllers/JsonApiControllerFacts.cs
Expand Up @@ -22,6 +22,7 @@ public class JsonApiControllerFacts

var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(c => c.User).Returns(currentUser.Object);

var controller = new JsonApiController(packageService.Object, userService.Object, repository.Object, messageService.Object);
TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, controller);
return controller;
Expand Down
2 changes: 1 addition & 1 deletion Facts/Services/MessageServiceFacts.cs
Expand Up @@ -275,4 +275,4 @@ public void WillSendInstructions()
}
}
}
}
}
2 changes: 2 additions & 0 deletions Facts/Services/PackageServiceFacts.cs
Expand Up @@ -98,6 +98,7 @@ private static Mock<INupkg> CreateNuGetPackage(Action<Mock<IPackageMetadata>> se
packageRepository = packageRepository ?? new Mock<IEntityRepository<Package>>();
packageStatsRepo = packageStatsRepo ?? new Mock<IEntityRepository<PackageStatistics>>();
packageOwnerRequestRepo = packageOwnerRequestRepo ?? new Mock<IEntityRepository<PackageOwnerRequest>>();
var followsRepository = new Mock<IEntityRepository<PackageFollow>>();
indexingService = indexingService ?? new Mock<IIndexingService>();

var packageService = new Mock<PackageService>(
Expand All @@ -106,6 +107,7 @@ private static Mock<INupkg> CreateNuGetPackage(Action<Mock<IPackageMetadata>> se
packageRepository.Object,
packageStatsRepo.Object,
packageOwnerRequestRepo.Object,
followsRepository.Object,
indexingService.Object);

packageService.CallBase = true;
Expand Down
64 changes: 60 additions & 4 deletions Facts/Services/UserServiceFacts.cs
Expand Up @@ -18,11 +18,15 @@ private static UserService CreateMockUserService(Action<Mock<UserService>> setup

cryptoService = cryptoService ?? new Mock<ICryptographyService>();
userRepo = userRepo ?? new Mock<IEntityRepository<User>>();

var followsRepo = new Mock<IEntityRepository<PackageFollow>>();
var packageRegRepo = new Mock<IEntityRepository<PackageRegistration>>();

var userService = new Mock<UserService>(
config.Object,
cryptoService.Object,
userRepo.Object)
userRepo.Object,
followsRepo.Object,
packageRegRepo.Object)
{
CallBase = true
};
Expand Down Expand Up @@ -763,21 +767,73 @@ public void ThrowsArgumentExceptionForNullUser()
}
}

public class TheWhereIsFollowingMethod
{
[Fact]
public void GetsAListOfPackageIds()
{
var user = new User { EmailAddress = "old@example.com", Key = 1400, Username = "Bill" };
var allUsers = (new[] { user });
var allFollows = new []
{
new PackageFollow
{
UserKey = 1400,
PackageRegistrationKey = 1,
PackageRegistration = new PackageRegistration
{
Key = 1,
Id = "Package1",
},
User = user,
IsFollowing = true,
}
};

var service = new TestableUserService();
service.MockUserRepository
.Setup(repo => repo.GetAll())
.Returns(allUsers.AsQueryable());
service.MockFollowsRepository
.Setup(repo => repo.GetAll())
.Returns(allFollows.AsQueryable());

string[] results = service.WhereIsFollowing("Bill", new[] { "Package1", "Package2" }).ToArray();
Assert.Equal(1, results.Length);
Assert.Equal("Package1", results[0]);
}

[Fact]
public void CanThrowUserNotFound()
{
var service = new TestableUserService();
service.MockUserRepository
.Setup(repo => repo.GetAll())
.Returns(new User[0].AsQueryable());

Assert.Throws<UserNotFoundException>(() =>
service.WhereIsFollowing("Bill", new[] { "Package1" })
);
}
}

public class TestableUserService : UserService
{
public Mock<ICryptographyService> MockCrypto { get; protected set; }
public Mock<IConfiguration> MockConfig { get; protected set; }
public Mock<IEntityRepository<User>> MockUserRepository { get; protected set; }
public Mock<IEntityRepository<PackageFollow>> MockFollowsRepository { get; protected set; }

public TestableUserService()
{
Crypto = (MockCrypto = new Mock<ICryptographyService>()).Object;
CryptoService = (MockCrypto = new Mock<ICryptographyService>()).Object;
Config = (MockConfig = new Mock<IConfiguration>()).Object;
UserRepository = (MockUserRepository = new Mock<IEntityRepository<User>>()).Object;
FollowsRepository = (MockFollowsRepository = new Mock<IEntityRepository<PackageFollow>>()).Object;

// Set ConfirmEmailAddress to a default of true
MockConfig.Setup(c => c.ConfirmEmailAddresses).Returns(true);
}
}
}
}
}
2 changes: 1 addition & 1 deletion Facts/TestUtils/TestUtility.cs
Expand Up @@ -86,4 +86,4 @@ public static T GetAnonymousPropertyValue<T>(Object source, string propertyName)
return (T)property.GetValue(source, null);
}
}
}
}
18 changes: 9 additions & 9 deletions Website/App_Code/ViewHelpers.cshtml
Expand Up @@ -102,17 +102,14 @@
<li>
@OwnerGravatar(owner, size, url, showName)
</li>
}
}
</ul>
}

@helper OwnerGravatar(User owner, int size, UrlHelper url, bool showName = true)
{
<a class="owner" href="@url.User(owner)" title="@owner.Username">
@if (!String.IsNullOrEmpty(owner.EmailAddress))
{
@GravatarImage(owner.EmailAddress, owner.Username, size)
}
@GravatarImage(owner.EmailAddress, owner.Username, size)
@if (showName)
{<text>@owner.Username</text>
}
Expand All @@ -121,12 +118,15 @@

@helper GravatarImage(string email, string username, int size)
{
var gravatarHtml = Gravatar.GetHtml(email, size, "retro", GravatarRating.G, attributes: new { width = size, height = size, title = username, @class = "owner-image" });
if (gravatarHtml != null && Request.IsSecureConnection)
if (!String.IsNullOrWhiteSpace(email))
{
gravatarHtml = new HtmlString(gravatarHtml.ToHtmlString().Replace("http://www.gravatar.com/", "https://secure.gravatar.com/"));
var gravatarHtml = Gravatar.GetHtml(email, size, "retro", GravatarRating.G, attributes: new { width = size, height = size, title = username, @class = "owner-image" });
if (gravatarHtml != null && Request.IsSecureConnection)
{
gravatarHtml = new HtmlString(gravatarHtml.ToHtmlString().Replace("http://www.gravatar.com/", "https://secure.gravatar.com/"));
}
@gravatarHtml
}
@gravatarHtml
}

@helper ReleaseTag()
Expand Down
4 changes: 4 additions & 0 deletions Website/App_Start/ContainerBindings.cs
Expand Up @@ -115,6 +115,10 @@ public override void Load()
.To<EntityRepository<PackageStatistics>>()
.InRequestScope();

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

Bind<ICuratedFeedService>()
.To<CuratedFeedService>()
.InRequestScope();
Expand Down
11 changes: 11 additions & 0 deletions Website/App_Start/Routes.cs
Expand Up @@ -76,6 +76,11 @@ public static void RegisterRoutes(RouteCollection routes)
"packages/cancel-upload",
new { controller = MVC.Packages.Name, action = "CancelUpload"});

routes.MapRoute(
RouteName.PackageFollowers,
"packages/{id}/followers",
new { controller = MVC.Packages.Name, action = "Followers" });

routes.MapRoute(
RouteName.PackageOwnerConfirmation,
"packages/{id}/owners/{username}/confirm/{token}",
Expand Down Expand Up @@ -329,6 +334,12 @@ public static void RegisterServiceRoutes(RouteCollection routes)
"api/v2/curated-feed",
typeof(V2CuratedFeed));

// Disabling followed packages feed until we work out where we want it.
// routes.MapServiceRoute(
// "v2" + RouteName.FollowedPackagesFeed,
// "api/v2/followed-packages",
// typeof(V2FollowedPackagesFeed));

routes.MapServiceRoute(
RouteName.V2ApiFeed,
"api/v2/",
Expand Down
91 changes: 84 additions & 7 deletions Website/Content/Site.css
Expand Up @@ -524,26 +524,31 @@ hgroup.page-heading h2 { padding-left: 7px; }
#actions li h2 { font-size: 1.5em; }

#actions li#uploadPackage h2 {
background: url("Images/uploadPackage.png") no-repeat 0 50%;
background: url(../Content/Images/uploadPackage.png) no-repeat 0 50%;
font-size: 1.7em;
padding-left: 20px;
}

#actions li#changePassword h2 {
background: url("Images/changePassword.png") no-repeat 0 50%;
#actions li#managePackages h2 {
background: url(../Content/Images/managePackages.png) no-repeat 0 50%;
padding-left: 20px;
}

#actions li#managePackages h2 {
background: url("Images/managePackages.png") no-repeat 0 50%;
#actions li#managedFollowedPackages h2 {
background: url(../Content/Images/managePackages.png) no-repeat 0 50%;
padding-left: 20px;
}

#actions li#editProfile h2 {
background: url("Images/editProfile.png") no-repeat 0 50%;
background: url(../Content/Images/editProfile.png) no-repeat 0 50%;
padding-left: 20px;
}


#actions li#changePassword h2 {
background: url(../Content/Images/changePassword.png) no-repeat 0 50%;
padding-left: 20px;
}

/* Account/API Key */

#apiKey h1 { font-size: 2em; }
Expand Down Expand Up @@ -784,6 +789,66 @@ fieldset.search label {
padding: 0;
}

/* Profile Page FollowedPackages */

#followedPackages {
list-style: none;
margin: 0;
padding: 0;
}

/* Follow Buttons */

a.followbtn,
a.unfollowbtn,
a.followloginbtn {
font-family: FontAwesome;
display: table;
font-size: 12px;
font-weight: bold;
font-style: normal;
text-decoration: none;
-webkit-font-smoothing: antialiased;
border: thin;
border-color: lightgray;
padding: 2px 2px 2px 2px;
border-radius: 2px;
box-shadow: 1px 1px 3px #222;
display: none;
}

a.followbtn {
background-color: #e4f1f7;
}

a.unfollowbtn {
background-color: #fffedc;
box-shadow: 0px 0px 3px #222;
}

a.followloginbtn {
display: inline;
}

a.followbtn i:before,
a.followloginbtn i:before {
content: "\f006";
}

a.unfollowbtn i:before {
content: "\f005";
}

a.followbtn i,
a.unfollowbtn i,
a.followloginbtn i {
font-family: FontAwesome;
font-weight: inherit;
font-style: inherit;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
}

/* List Package */

section.package {
Expand Down Expand Up @@ -825,6 +890,14 @@ section.package .downloads {
font-weight: 600;
}

section.package div.follows {
border-left: 1px solid #ccc;
display: inline;
margin-left: 10px;
padding-left: 10px;
min-height: 16px;
}

section.package div.tags {
border-left: 1px solid #ccc;
display: inline;
Expand Down Expand Up @@ -1278,6 +1351,10 @@ footer#footer div.license {
font-size: .7em;
}

.footer-acknowledgements {
font-size: .7em
}

/* async upload */
form .async-upload-panel
{
Expand Down
22 changes: 22 additions & 0 deletions Website/Content/font-awesome-ie7.min.css

Large diffs are not rendered by default.