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

feat: [M3-7490] - Improve NodeBalancer Restricted User Experience #10095

Merged
merged 16 commits into from
Jan 31, 2024

Conversation

jaalah-akamai
Copy link
Contributor

@jaalah-akamai jaalah-akamai commented Jan 22, 2024

Description 📝

To prevent unauthorized access to specific flows and provide clearer guidance, we aim to restrict entry to users without the required permissions while providing new notices.

Tooltips specifically designed for disabled buttons will persist within action menus. These tooltips prove valuable in scenarios where a restricted user has permission to delete their personal resources but lacks the permission to delete those belonging to the admin user. In such cases, we refrain from displaying notices on the page.

For restricted users:

  • Error Notices:
    • Drawers - Error notifications without an icon:
    • Create Pages - Error notifications with an icon
  • Warning Notices w/ Icon:
    • Detail Pages
  • Disabled Create Button
    • Empty State Landing Pages
    • Landing

Changes 🔄

  • Added useIsResourceRestricted hook that verifies if a user with restricted access can edit specific cloud resources (Linodes, NodeBalancers, Volumes, etc.) Admin-created resources may be restricted, but those created by the restricted user are editable.
  • Changed entityNameMap to grantTypeMap and added as const in order to generate a union of the mapped names to provide type-safety to useIsResourceRestricted and allow for capitalization/plurality in the names.
  • Added StyledTagButton to replace our one-off usages of adding tags.
  • Added getRestrictedResourceText utility to help generate resource-specific notices.
  • Added isRestrictedGlobalGrantType util which can be used to throughout Cloud Manager (+15 occurrences we can update in the future)

Preview 📷

Note

We changed the banner slightly from the screenshots. The text will now say You don't have permissions to ${action} ${resourceType}. Please contact your account administrator to request the necessary permissions.

Page Before After
Empty State Landing Screenshot 2024-01-26 at 9 48 38 AM Screenshot 2024-01-26 at 9 48 15 AM
Landing: Unable to delete admin NodeBalancer Screenshot 2024-01-26 at 9 50 34 AM Screenshot 2024-01-26 at 9 49 50 AM Screenshot 2024-01-26 at 9 43 44 AM
Page Before After
Landing Screenshot 2024-01-23 at 9 05 46 PM No Restrictions Screenshot 2024-01-23 at 8 43 22 PM No Notice, Can Add Linodes, Can Add NodeBalancers Screenshot 2024-01-23 at 8 45 26 PM
Create Page cloud linode com_nodebalancers_create (1) localhost_3000_nodebalancers_create
Configurations Screenshot 2024-01-22 at 1 22 29 PM Screenshot 2024-01-22 at 1 29 01 PM
Settings Screenshot 2024-01-22 at 1 37 43 PM Screenshot 2024-01-22 at 1 37 15 PM
Summary: Tags Screenshot 2024-01-22 at 10 51 17 AM Screenshot 2024-01-22 at 10 49 29 AM
Before After
Screenshot 2024-01-22 at 10 09 07 AM Screenshot 2024-01-22 at 10 08 53 AM

How to test 🧪

Prerequisites

(How to setup test environment)

  • Log into two accounts side by side:
    • An unrestricted admin user account: full access
    • A restricted user account (use Incognito for this)
      • Start with Read Only for everything
      • After we'll be toggling permissions

Reproduction steps

Restricted User Perm: "Read Only"

  • Empty State Landing:
    • Start with no NodeBalancers and perms set to "Read Only" on restricted account
    • Observe as restricted user, you cannot create a NodeBalancer
    • Change perm to include "Can add Linodes" and "Can add NodeBalancers"
    • Observe as restricted user, you can create a NodeBalancer
    • Reset settings for restricted user back to "Read Only"
  • Create:
    • As admin user, create a Nodebalancer
    • Observe as restricted user, you shouldn't be able to reach this page except by changing URL
    • Observe that when changing URL, all fields and actions are disabled (including firewalls)
  • Landing:
    • Observe as restricted user, notice shows and you cannot delete or create NodeBalancers
  • Detail:
    • Observe as restricted user, notice shows
    • Observe on Summary tab, you cannot create tags
    • Observe on Configurations tab, all fields and actions are disabled
    • Observe on Settings tab, all fields and actions are disabled

Restricted User Perm: "Read Only" w/ Can add Linodes, Can add NodeBalancers

  • Create:
    • Observe as restricted user, you should be able to create a NodeBalancer
    • Observe that there's no notices
  • Landing:
    • Observe as restricted user, you should be able to create a NodeBalancer
    • Observe there's no notices
    • Observe you cannot delete the admin user's NodeBalancer
    • Observe you can delete your own NodeBalancer
  • Detail:
    • Observe no notices show for your own NodeBalancer, but that they do for admin user's NodeBalancer
    • Observe you can edit your own NodeBalancer, but that you cannot edit the admin user's NodeBalancer

As an Author I have considered 🤔

Check all that apply

  • 👀 Doing a self review
  • ❔ Our contribution guidelines
  • 🤏 Splitting feature into small PRs
  • ➕ Adding a changeset
  • 🧪 Providing/Improving test coverage
  • 🔐 Removing all sensitive information from the code and PR description
  • 🚩 Using a feature flag to protect the release
  • 👣 Providing comprehensive reproduction steps
  • 📑 Providing or updating our documentation
  • 🕛 Scheduling a pair reviewing session
  • 📱 Providing mobile support
  • ♿ Providing accessibility support

'& :hover': {
color: '#4d99f1',
},
'&& .MuiSvgIcon-root': {
fill: theme.color.disabledText,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We only display the tooltip for when things are disabled

* spreading excessive styles for everywhere this is used.
*
*/
export const StyledTagButton = styled(Button, {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can use this in other places as well, but I didn't want to expand the scope of this PR too much.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 that being said we should both add a unit test and add/modify a story if it is meant to stay

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with this new component and thanks for creating a test/story as per @abailly-akamai. Should we open a ticket for the other places?

@@ -78,7 +81,7 @@ export const SelectFirewallPanel = (props: Props) => {
value={selectedFirewall}
/>
<StyledLinkButtonBox>
<LinkButton onClick={handleCreateFirewallClick}>
<LinkButton isDisabled={disabled} onClick={handleCreateFirewallClick}>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To disable this link in create flows for restricted users, probably should've been passed in to begin with.

@@ -87,6 +88,7 @@ const StyledConfigsButton = styled(Button, {
}));

interface Props {
grants: Grants | undefined;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing in grants due to the fact this is a class component and it was available from it's parent.

@@ -123,7 +123,6 @@ export const CreateBucketDrawer = (props: Props) => {
{isRestrictedUser && (
<Notice
data-qa-permissions-notice
important
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't want the icon version for drawers anymore.

firewall: 'Firewalls',
database: 'Databases',
vpc: 'VPCs',
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to /src/features/Account/constants

@jaalah-akamai jaalah-akamai added the Restricted User Access Improve UX surrounding restricted access to features label Jan 23, 2024
@jaalah-akamai jaalah-akamai marked this pull request as ready for review January 24, 2024 02:25
@jaalah-akamai jaalah-akamai requested a review from a team as a code owner January 24, 2024 02:25
@jaalah-akamai jaalah-akamai requested review from bnussman-akamai and abailly-akamai and removed request for a team January 24, 2024 02:25
Copy link

github-actions bot commented Jan 24, 2024

Coverage Report:
Base Coverage: 81.19%
Current Coverage: 81.15%

@bnussman-akamai
Copy link
Member

I'm not a huge fan of how we force a restricted user to look at a big yellow banner on landing pages. I think it would be nice if we had a more subtile way to indicate they have read-only permissions for example.

For the load balancer empty state, should we disable the "Create" button with a tooltip instead of putting a yellow banner at the top?

I still feel like "Access is restricted" is too generic. I think it would be nice to let the user know they have "read only permission" or that they "do not have permission to create"...

Those are just my opinions on the UX aspect of this, will continue to review regardless.

@jaalah-akamai
Copy link
Contributor Author

@bnussman-akamai I'll circle back to UX about the wording, but if you can review the functionality that would be awesome!

Copy link
Member

@bnussman-akamai bnussman-akamai left a comment

Choose a reason for hiding this comment

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

General functionality looks good! Have a few comments/questions

const disabled =
Boolean(profile?.restricted) && !grants?.global.add_nodebalancers;
Boolean(profile?.restricted) &&
!(grants?.global.add_nodebalancers && grants?.global.add_linodes);
Copy link
Member

Choose a reason for hiding this comment

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

I confirmed I was able to create a NodeBalancer as a restricted user without add_linodes permission.

I understand why we might want to take add_linodes into account for UX reasons, but I'd prefer if we just check add_nodebalancers and not add_linodes so we are in parity with how the API works.

Screen.Recording.2024-01-26.at.10.32.01.AM.mov

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 this. Any reason why the Add Linodes permission should be required for adding/modifying NodeBalancers?

Copy link
Contributor

Choose a reason for hiding this comment

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

if that's the case we should document this in code more thouroughly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh yea I was under the impression that linodes with read_only perms couldn't be added to NodeBalancers, I guess that's not the case.

packages/manager/src/hooks/useIsResourceRestricted.ts Outdated Show resolved Hide resolved
packages/manager/src/hooks/useIsResourceRestricted.ts Outdated Show resolved Hide resolved
packages/manager/src/features/Account/utils.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@abailly-akamai abailly-akamai left a comment

Choose a reason for hiding this comment

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

Nice work! Added comments to clean things up and some questions. I'll give this a more thorough review once code improvements from reviewers have been addressed 👍

* spreading excessive styles for everywhere this is used.
*
*/
export const StyledTagButton = styled(Button, {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 that being said we should both add a unit test and add/modify a story if it is meant to stay

const disabled =
Boolean(profile?.restricted) && !grants?.global.add_nodebalancers;
Boolean(profile?.restricted) &&
!(grants?.global.add_nodebalancers && grants?.global.add_linodes);
Copy link
Contributor

Choose a reason for hiding this comment

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

if that's the case we should document this in code more thouroughly

@@ -628,6 +645,8 @@ class NodeBalancerConfigurations extends React.Component<CombinedProps, State> {
? parseInt(expandedConfigId, 10) === config.id
: false;

const isRestricted = this.isRestrictedUser();

const L = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not related to this PR, but const L 👎

Copy link
Member

Choose a reason for hiding this comment

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

ramda 😡

@jaalah-akamai
Copy link
Contributor Author

jaalah-akamai commented Jan 30, 2024

  • Added storybook for StyledTagButton (No unit tests since it essentially inherits from Button and I'll be working with UX to make this comply to a standard button style)
  • Added isRestrictedGlobalGrantType util which can be used to throughout Cloud Manager (+15 occurrences we can update in the future)
  • getRestrictedResourceText now supports singular or plural instances
  • For useIsResourceRestricted hook, removed default for grantLevel and useEffect. Updated variable name to isNodeBalancerReadOnly

Copy link
Contributor

@hkhalil-akamai hkhalil-akamai left a comment

Choose a reason for hiding this comment

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

Verified testing steps, LGTM! Thanks for considering others' feedback.

* spreading excessive styles for everywhere this is used.
*
*/
export const StyledTagButton = styled(Button, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with this new component and thanks for creating a test/story as per @abailly-akamai. Should we open a ticket for the other places?

Copy link
Contributor

@abailly-akamai abailly-akamai left a comment

Choose a reason for hiding this comment

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

Reads so much better, thanks for the changes!

Feature behaves as described in the PR and looks great from my vantage point ✅

profile: Profile | undefined;
}) => {
return Boolean(profile?.restricted) && !grants?.global[globalGrantType];
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it's clear from the name however

  1. can we add return types here?
  2. i think it's always super useful to complement those utils with JSDocs

@jaalah-akamai
Copy link
Contributor Author

jaalah-akamai commented Jan 31, 2024

Agree with this new component and thanks for creating a test/story as per @abailly-akamai. Should we open a ticket for the other places?

I updated it here, it was only one reference.

@jaalah-akamai jaalah-akamai merged commit b11bff9 into linode:develop Jan 31, 2024
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ready for Review Restricted User Access Improve UX surrounding restricted access to features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants