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

Account delete user flow #4977

Merged
merged 6 commits into from
Nov 16, 2017
Merged

Account delete user flow #4977

merged 6 commits into from
Nov 16, 2017

Conversation

cristinamanum
Copy link
Contributor

@cristinamanum cristinamanum commented Nov 9, 2017

pack1
user2
user1
owners

The user flow for account delete.

@@ -647,6 +647,32 @@ public void SendPackageAddedNotice(Package package, string packageUrl, string pa
}
}

public void SendAccountDeleteNotice(MailAddress mailAddress, string account)
{
string body = @"We received a request to delete your account {0}. If you did not initiate this request please contact the {1} team.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting here could be much better. Also there's a couple oddly worded phrases in here.

You should also make similar modifications to the page itself.

We received a request to delete your account {0}. If you did not initiate this request please contact the {1} team immediately.

When your account will be deleted, we will:
- revoke your API key(s)
- remove your ownership from any packages you own
- remove your ownership from any ID prefix reservations and delete any ID prefix reservations that you were the only owner of

We will not delete any packages associated with your account.

Thanks,
The {1} Team

For comparison, it is currently:

We received a request to delete your account {0}. If you did not initiate this request please contact the {1} team.
When your account will be deleted, we will:revoke your API key(s), 
remove you as the owner for any package you own, dissociate all previously existing ID prefix reservation with this account. If you were the only account tied to a particular ID prefix reservation the prefix will be available to anyone.
We will not delete the NuGet packages associated with the account.

Thanks,
The {1} Team

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@ViewHelpers.AlertWarning(
@<text>
Sorry, the owners of this package do not allow contacting them through this form.
if ((bool)ViewData["HasOwners"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in the Model, don't use ViewData.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

{
@ViewHelpers.AlertWarning(
@<text>
Sorry, this package has no owners and is not being actively maintained. You can try to find a contact for "@Model.PackageId" through its <a href="@Model.ProjectUrl">Project Site</a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check that the package has a ProjectUrl before you ask them to go to it like above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@@ -626,17 +626,24 @@

<h2>Owners</h2>
<ul class="list-unstyled owner-list">
@foreach (var owner in Model.Owners)
@if (Model.Owners.Count() == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use .Any() instead of .Count() == 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@owner.Username
</a>
</li>
@ViewHelpers.AlertWarning(@<text>@Strings.PackageHasNoOwnersMessage</text>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use strings for other user-facing strings in the .cshtml, no need to put it in a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is good to target having them in resource file. It will make easier to localize the site. Do you see any disadvantages?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The disadvantages are
1 - if we actually were to localize the site, it would require a huge effort anyway because this convention isn't used anywhere else
2 - it's harder to work with (you have to work with the .resx file instead of the rest of the code and you have to recompile if you want to change the string unlike the rest of razor) with little gain

If we reused this string somewhere else I'd agree that it was probably worth, but given that we don't, I don't think it's worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

{
return HttpNotFound("User not found.");
}
bool pendingRequest = _supportRequestService.GetIssues(assignedTo: null, reason: null, issueStatusId: null, galleryUsername: user.Username)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone cancels a request to delete their account, won't this still find the existing request?

It seems a bit awkward to query the support request service for whether or not a user requested deletion. Additionally, when we move off our support request system, this will need to be changed. I would rather store this in the database somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first question - no they will not find it because it was not created.
For the second question, the result of the user action to submit the request to have the package deleted is that a new support request is created.
As discussed offline updated to use the issuekey.

{
@Html.AntiForgeryToken()
<p>
<strong>By proceeding, I understand that I am relinquishing ownership of the package/s listed above, and any existent package ID reservation/s will be dissociated with this account.</strong>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use less verbose terms than relinquish and dissociate? losing should be perfectly understandable

Copy link
Contributor

@loic-sharma loic-sharma Nov 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there is no need for the passive voice here: "am relinquishing" -> "will relinquish" (or "will lose").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is as per spec. Following up with the spec owner.

{
@Html.AntiForgeryToken()
<p>
<strong>By proceeding, I understand that I am relinquishing ownership of the package/s listed above, and any existent package ID reservation/s will be dissociated with this account.</strong>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should show all the reserved namespaces associated with the account in the same way we show packages. This will make it clearer for users which reserved namespaces will be deleted and which ones have multiple users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation is as spec-ed. In think the info and warnings that are showed are enough for the user.

public void DeleteAccountRequestView (bool withPendingIssues)
{
// Arrange
string userName = "DeletedUser";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test (and the rest of the account deletion tests) should use Fakes

var fakes = Get<Fakes>();
var userToDelete = fakes.Owner;
var packageToDelete = fakes.Package;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the benefits of getting the user through fakes versus creating it directly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fakes is used in some of our tests to make it easier to get users. Unless your test requires that you create special users (for example, those that own multiple packages or are in multiple organizations), if your test is in a file that uses Fakes, your test should use Fakes.

[Theory]
[InlineData(false)]
[InlineData(true)]
public void DeleteAccountRequestView (bool withPendingIssues)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a test for the TheDeleteAccountAction, so it should have its own method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

{
@ViewHelpers.AlertWarning(@<text>
<b class="keywords">Request submitted.</b> <br />
It may take up to 5 business days to process Account deletion requests.<a href="https://github.com/NuGet/Home/wiki/NuGet-Account-Deletion-Workflow">Read more.</a><br />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a space between the period and the link here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit: the capitalization on "Account" seems weird - could we leave it lower cased?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is implemented as spec-ed. IMO it looks good with capital letter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. You should still add a space between "requests." and the "Read more." link though :)

@@ -280,6 +280,11 @@ public static void RegisterUIRoutes(RouteCollection routes)
new { controller = "Users", action = "Delete" });

routes.MapRoute(
RouteName.UserDeleteAccount,
"account/delete",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing looks off here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -775,6 +775,7 @@ public virtual ActionResult ContactOwners(string id)
CopySender = true,
};

ViewData["HasOwners"] = package.PackageRegistration.Owners.Count > 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.Owners.Count > 0 -> .Owners.Any()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}
bool pendingRequest = _supportRequestService.GetIssues(assignedTo: null, reason: null, issueStatusId: null, galleryUsername: user.Username)
.Any(issue => string.Equals(issue.IssueTitle, Strings.AccountDelete_SupportRequestTitle, StringComparison.CurrentCultureIgnoreCase) &&
issue.IssueStatus.Key != 3 &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does issue.IssueStatus.Key != 3 mean? A constant for 3 could help explain this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code was updated. It represents the status for a specific issue. ( 3 == resolved)


List<Package> userPackages = new List<Package>() { userPackage };
List<Issue> issues = new List<Issue>();
if(withPendingIssues)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A space is missing between the if and (

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> SendAccountDeleteNotificationAsync()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestAccountDeletionAsync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we use Async in controller action names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async suffix was added to follow the naming convention for the async methods. The observation that @scottbommarito made is correct. The name was changed to RequestAccountDeletion to be in sync with the current pattern.

public virtual async Task<ActionResult> SendAccountDeleteNotificationAsync()
{
var user = GetCurrentUser();
bool createSupportRequestStatus = await _supportRequestService.AddNewSupportRequestAsync(Strings.AccountDelete_SupportRequestTitle,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no checks here:
if (user == null || user.IsDeleted)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -58,6 +58,11 @@ public IReadOnlyCollection<Issue> GetIssues(int? assignedTo = null, string reaso
return queryable.ToList();
}

public IReadOnlyCollection<Issue> GetOpenIssues( Func<Issue,bool> filter )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to have a Func<Issue, bool> as a parameter when you can just return an IQueryable<Issue> or IEnumerable<Issue> instead. Is there any substantial benefit to returning IReadOnlyCollection<Issue>?

Another option is to add parameters for the user the request is for and the title of the support request so that you can just specify those values instead and you can still return IReadOnlyCollection<Issue>

One last thing--you could make the key of the issue a parameter of the function so that you don't need to make functions for GetResolvedIssues, GetWaitingForCustomerIssues, etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issues key is private and I preferred to not change it as I think that was a reason for making it private. Why is weird to have a predicate that enables filtering?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said in another comment, IssueKeys should be public. There's no reason to have "magic values" around the code when we can just make the enum public and get rid of them. I think it was probably private before because it wasn't needed outside of that class, but now it is so it should be changed.

It's weird to have a predicate because
1 - syntactically, predicates aren't as elegant or powerful as LINQ
2 - it requires every caller to write a filtering function when the filtering is likely done on the same fields (the user the request is for and the title, for example)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to make IssuesKeys public and use them on this code path.

return HttpNotFound("User not found.");
}

bool createSupportRequestStatus = await _supportRequestService.AddNewSupportRequestAsync(Strings.AccountDelete_SupportRequestTitle,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think bool isSupportRequestCreated is a better name for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

@@ -647,6 +647,36 @@ public void SendPackageAddedNotice(Package package, string packageUrl, string pa
}
}

public void SendAccountDeleteNotice(MailAddress mailAddress, string account)
{
string body = @"We received a request to delete your account {0}. If you did not initiate this request please contact the {1} team imediatelly.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We received a request -> We have received a request

your account {0} -> your account, '{0}'

imediatelly -> immediately

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

public void SendAccountDeleteNotice(MailAddress mailAddress, string account)
{
string body = @"We received a request to delete your account {0}. If you did not initiate this request please contact the {1} team imediatelly.
{2}When your account will be deleted, we will:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't needed to use Environment.NewLines in previous emails. Have you tested that the email sent looks correctly and doesn't have extra newlines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the email looks good(attached).
email

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it looks good.

However, it might not look consistent with the rest of our emails, because they don't have newlines between every line.

@@ -618,4 +618,13 @@ For more information, please contact '{2}'.</value>
<data name="AccountDelete_Success" xml:space="preserve">
<value>The account:{0} was deleted succesfully.</value>
</data>
<data name="AccountDelete_CreateSupportRequestFails" xml:space="preserve">
<value>The request failed to be submitted. Please try again or contact nuget support.</value>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things
1 - nuget -> NuGet
2 - This should use IAppConfiguration.Brand so that if somebody deploys the gallery themselves and changes the brand, it doesn't say NuGet support anyway

An easy alternative may be just remove the use of nuget and just show contact support.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to not use NuGet in the notification.

{
@ViewHelpers.AlertWarning(
@<text>
Sorry, this package has no owners and is not being actively maintained.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say it's not being actively maintained ON the NuGet Gallery. Someone might still be working on it on GitHub or some other site, but is not maintaining this package on NuGet.

is not being actively maintained on @Config.Brand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


@if (!string.IsNullOrEmpty(Model.ProjectUrl))
{
@:You can try contacting the package owners of "@Model.PackageId" through its <a href="@Model.ProjectUrl">Project Site</a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just said it doesn't have any package owners, so we should say

You can try contacting someone maintaining "@Model.PackageId" through its <a href="@Model.ProjectUrl">Project Site</a>

Also in the else

You'll have to find someone maintaining the package through other means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first is as spec-ed. The second, the scenario is about finding the owners of the package. I think that the message should refer to the owners.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a minor grammatical change. I think it's ok to change despite how it was spec'ed

My confusion is just that the statement above says "this package has no owners" (and not "this package has no owners on NuGet.org"), but then we are talking about it having owners again. It's not too big of a deal but I think changing the phrasing as detailed would improve the clarity of the statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code updated to be : Sorry, this package has no owners and is not being actively maintained on NuGet.org

{
@ViewHelpers.AlertWarning(@<text>
<b class="keywords">Request submitted.</b> <br />
It may take up to 5 business days to process Account deletion requests. <a href="https://go.microsoft.com/fwlink/?linkid=862770">Read more.</a><br />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Account -> account

(I don't think account should be capitalized here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree - updated.

{
// Arrange
string userName = "DeletedUser";
string emailAddress = $"{userName}@coldmail.com";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coldmail.com...that's brilliant hahaha

{
IssueTitle = Strings.AccountDelete_SupportRequestTitle,
OwnerEmail = emailAddress,
IssueStatus = new IssueStatus() { Key = 1, Name = "OneIssue" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the IssueKeys constant instead of 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IssuesKeys is private. I prefer to not change it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IssueKeys should be public...there's no reason to have "magic values" around the code when we can just make the enum public and get rid of them.

Copy link
Contributor Author

@cristinamanum cristinamanum Nov 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to make the keys public and use these values here instead of 1.

@cristinamanum cristinamanum merged commit 9036b12 into dev Nov 16, 2017
@shishirx34 shishirx34 mentioned this pull request Nov 17, 2017
10 tasks
@cristinamanum cristinamanum deleted the cmanu-userdeleteaccount branch January 11, 2018 18:09
scottbommarito pushed a commit that referenced this pull request Jul 22, 2019
* Account delete user flow

* PR feedback

* PR feedback

* PR feedback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants