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

Allow different rules and action for delete user #2714

Merged
merged 10 commits into from
Dec 11, 2024
Merged

Conversation

jyeshe
Copy link
Member

@jyeshe jyeshe commented Nov 26, 2024

Description

This PR changes the rendering of the ProfileLive.Edit and UserLive.Index (superuser) liveviews to allow them to be customized on three aspects when deleting a user:

  • UI rendering
  • Validation rules
  • Action processing

Its strategy was similar to the Projects customization, where the liveview is injected on the router.

The change consists on making the respective edit.html.heex and index.html.heex to rely completely on components so that the Saas page can reuse them and implement different rules on the liveview modules.

EDIT: As suggested by the LV docs, the metadata live/4 opt can be used to customize a LiveView which may be invoked from different routes.

Refs #2500

Validation steps

ProfileLive.Edit:

  1. Login with a new user (without project activity)
  2. Navigate User profile and click on Delete account button at the bottom of the page
  3. A popup shows a message: "Your account and credential data ..."
  4. Type the user's email, confirm and the user shall be logged out. On an attempt to login it shows:
    image

UserLive.Index:

  1. Login with a superuser, enter on Admin Menu and click on Users menu
  2. Click on the button to delete the Viewer user
    A popup shows a message: "This user's account and credential ..."
  3. Type [email protected], confirm and it shall show the user as scheduled for deletion:
    image
  4. Repeat the operation for the same user and the Viewer user is deleted (doesn't appear anymore on the table).

Additional notes for the reviewer

As a helper note on the refactoring, LightningWeb.ProfileLive.MfaComponent and LightningWeb.ProfileLive.GithubComponent aren't directly related to Delete User and wouldn't need to be extracted from the ProfileLive.FormComponent. However they are not part of the <form> and their actions/events are handled by their own components themselves (as expected) justifying the extraction to the same level as the ProfileLive.FormComponent (and not inside).

AI Usage

Please disclose how you've used AI in this work (it's cool, we just want to know!):

  • Code generation (copilot but not intellisense)
  • Learning or fact checking
  • Strategy / design
  • Optimisation / refactoring
  • Translation / spellchecking / doc gen
  • Other
  • I have not used AI

You can read more details in our Responsible AI Policy

Pre-submission checklist

  • I have performed a self-review of my code.
  • I have implemented and tested all related authorization policies. (e.g., :owner, :admin, :editor, :viewer)
  • I have updated the changelog.
  • I have ticked a box in "AI usage" in this PR

@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 4 times, most recently from 4e32c32 to 7d06f11 Compare November 26, 2024 13:01
@jyeshe jyeshe changed the title Allow polimorphic rules and action for delete user Allow different rules and action for delete user Nov 26, 2024
@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 4 times, most recently from 1418886 to 79a937d Compare November 26, 2024 13:13
@jyeshe jyeshe self-assigned this Nov 26, 2024
@jyeshe jyeshe linked an issue Nov 26, 2024 that may be closed by this pull request
@jyeshe jyeshe force-pushed the polimorphic-delete-user branch from 79a937d to ed7793f Compare November 26, 2024 14:31
@jyeshe jyeshe marked this pull request as ready for review November 26, 2024 14:31
@jyeshe jyeshe requested review from stuartc, midigofrank, elias-ba and taylordowns2000 and removed request for midigofrank November 26, 2024 14:31
@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 2 times, most recently from 02ce262 to 4dfd455 Compare November 26, 2024 14:54
Copy link

codecov bot commented Nov 26, 2024

Codecov Report

Attention: Patch coverage is 97.82609% with 2 lines in your changes missing coverage. Please review.

Project coverage is 91.25%. Comparing base (ca68969) to head (61c2715).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...ghtning_web/live/components/user_deletion_modal.ex 66.66% 1 Missing ⚠️
lib/lightning_web/live/user_live/index.ex 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2714      +/-   ##
==========================================
+ Coverage   91.22%   91.25%   +0.03%     
==========================================
  Files         333      335       +2     
  Lines       11869    11933      +64     
==========================================
+ Hits        10828    10890      +62     
- Misses       1041     1043       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@elias-ba elias-ba left a comment

Choose a reason for hiding this comment

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

Hey @jyeshe, this is really a great piece of work. I learnt a lot while reviewing it, thank you. I left few comments (my reactions and curiosity questions), I would appreciate to hear your thoughts on em.

@@ -0,0 +1,90 @@
defmodule LightningWeb.ProfileLive.CommonComponents do
Copy link
Contributor

Choose a reason for hiding this comment

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

@jyeshe CommonComponents is really a great name and I like it. But we usually name these modules as Common or Components, I am wondering if we shouldn't keep that naming for consistency ?

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Comment on lines 35 to 67
def action_cards(assigns) do
~H"""
<div id={"user-#{@current_user.id}"} class="md:col-span-2">
<.live_component
:if={@live_action == :delete}
module={@user_deletion_modal}
id={@current_user.id}
user={@current_user}
logout={true}
return_to={~p"/profile"}
/>
<.live_component
module={LightningWeb.ProfileLive.FormComponent}
id={@current_user.id}
title={@page_title}
action={@live_action}
user={@current_user}
return_to={~p"/profile"}
/>
<.live_component
module={LightningWeb.ProfileLive.MfaComponent}
id={"#{@current_user.id}_mfa_section"}
user={@current_user}
/>
<.live_component
module={LightningWeb.ProfileLive.GithubComponent}
id={"#{@current_user.id}_github_section"}
user={@current_user}
/>
<.delete_user_card url={@delete_user_url} />
</div>
"""
end
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice

@@ -0,0 +1,113 @@
defmodule LightningWeb.UserLive.CommonComponents do
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question for the naming here ? Again, I really think that CommonComponents is a great name, I am just wondering about module naming consistency.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

attr :users, :list, required: true
attr :live_action, :atom, required: true

attr :user_deletion_modal, :atom,
Copy link
Contributor

Choose a reason for hiding this comment

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

:atom ? You maybe mean :any (or :map) ?

Copy link
Member Author

@jyeshe jyeshe Nov 27, 2024

Choose a reason for hiding this comment

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

The atom here is the module name

assign(socket, :users, list_users())
|> assign(:active_menu_item, :users),
layout: {LightningWeb.Layouts, :settings}}
modal = Map.get(socket, :private, %{})[:delete_modal]
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice ❤️

@@ -98,7 +98,6 @@ defmodule LightningWeb.UserLive.CommonComponents do
Cancel deletion
</.link>
</span>
|
Copy link
Member Author

Choose a reason for hiding this comment

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

👍

@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 2 times, most recently from 20ebc32 to 6528aac Compare November 27, 2024 18:12
@jyeshe
Copy link
Member Author

jyeshe commented Nov 27, 2024

Changed to use metadata as the private works only on the connection.

@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 4 times, most recently from 17f55c0 to a93a560 Compare November 28, 2024 11:04
@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 5 times, most recently from 918236f to 5159368 Compare November 28, 2024 20:23
@jyeshe jyeshe requested a review from midigofrank November 29, 2024 09:35
@jyeshe jyeshe force-pushed the polimorphic-delete-user branch 10 times, most recently from 3380fa4 to fc4aba7 Compare December 10, 2024 16:00
@jyeshe jyeshe force-pushed the polimorphic-delete-user branch from fc4aba7 to a95852f Compare December 10, 2024 16:30
@@ -106,7 +107,7 @@ defmodule LightningWeb.Components.UserDeletionModal do
</:title>
<div class="">
<p class="text-sm text-gray-500">
This user cannot be deleted until their auditable activities have also been purged.
<%= if @is_superuser_menu, do: "This user", else: "Your account" %> cannot be deleted until their auditable activities have also been purged.
Copy link
Member

Choose a reason for hiding this comment

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

Would it not be better to have the comparison based on the current user vs. the user selected for deletion? Then the component (upstream) wouldn't need to know about its location in the component tree?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have opted to use the page that uses the modal as a direct and more explicit condition to choose the language:

  • 3rd person on the users page (accessed via superuser menu)
  • 1st person on the user profile

Maybe I should have used the page name instead of the menu as a context.

Copy link
Member Author

@jyeshe jyeshe Dec 11, 2024

Choose a reason for hiding this comment

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

The condition you propose is valid now that we've blocked the deletion of super users and also referring to the superuser on the 3rd person, when deleting his/her own account wouldn't harm much as well. The main reason was to make the condition as clearer as possible (kind of giving names from user story/business perspective).

Copy link
Member

Choose a reason for hiding this comment

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

But the 3rd and 1st person perspective can be determined by the current user and the selected user, I'm not seeing a good enough reason to bring the layout into the mix when we have the current user available everywhere.

If the @is_superuser_menu option is purely for the copy, I would suggest an attr that controls it lives outside, like is_current_user or something. Components really should avoid knowing about the place they are mounted from.

Copy link
Member Author

Choose a reason for hiding this comment

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

I get your point of sufficient existing conditions but it is not rare that find components that behave differently according to a context.
On the other hand, is_current_user is very straightforward and clear as well. Thanks

end

defp apply_action(socket, :delete, %{"id" => id}) do
socket
|> assign(:page_title, "Users")
|> assign(:user, Accounts.get_user!(id))
|> assign(:delete_user, Accounts.get_user!(id))
Copy link
Member

Choose a reason for hiding this comment

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

Why the change? It's called user downstream in the table component as well. I could imagine it's because we're wanting to signify that this user is selected for deletion, but the component doesn't really know about that, it's just a 'selected user' in my mind and is only deletable when paired with an action/event.

Copy link
Member Author

@jyeshe jyeshe Dec 11, 2024

Choose a reason for hiding this comment

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

The renaming was for three reasons:

  • It's not used anymore directly neither on the LV nor on the heex, it's passed down to 3rd level component.
  • Same idea as you've mentioned the selected user that will be used somewhere for some operation
  • Given the 2 above, I wanted to make it easier for the review and maintenance, in a way that it's easy to search and quickly find it by a unique name:
    image

@stuartc stuartc merged commit c9cad42 into main Dec 11, 2024
8 checks passed
@stuartc stuartc deleted the polimorphic-delete-user branch December 11, 2024 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Scheduled deletion of users account does not work
3 participants