-
Notifications
You must be signed in to change notification settings - Fork 365
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
Changes from all commits
b69c6c1
40c7d96
3109cd7
b6ac3e4
4a3f190
87e34e1
3d98268
924986d
87a9451
ed92441
35bf9f9
b15eea4
c6e93b6
8154589
bb2a422
c5acd0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Tech Stories | ||
--- | ||
|
||
Improve NodeBalancer Restricted User Experience ([#10095](https://github.com/linode/manager/pull/10095)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { action } from '@storybook/addon-actions'; | ||
import React from 'react'; | ||
|
||
import { StyledPlusIcon, StyledTagButton } from './StyledTagButton'; | ||
|
||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
const meta: Meta<typeof StyledTagButton> = { | ||
args: { | ||
children: 'Tag', | ||
disabled: false, | ||
onClick: () => null, | ||
}, | ||
component: StyledTagButton, | ||
title: 'Components/TagButton', | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof StyledTagButton>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
buttonType: 'outlined', | ||
children: 'Tag', | ||
disabled: false, | ||
onClick: action('onClick'), | ||
}, | ||
render: (args) => ( | ||
<StyledTagButton | ||
{...args} | ||
endIcon={<StyledPlusIcon disabled={args.disabled} />} | ||
> | ||
Add a Tag | ||
</StyledTagButton> | ||
), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { styled } from '@mui/material/styles'; | ||
|
||
import Plus from 'src/assets/icons/plusSign.svg'; | ||
|
||
import { Button } from './Button'; | ||
|
||
/** | ||
* A button for Tags. Eventually this treatment will go away, | ||
* but the sake of the MUI migration we need to keep it around for now, and as a styled component in order to get rid of | ||
* spreading excessive styles for everywhere this is used. | ||
* | ||
*/ | ||
export const StyledTagButton = styled(Button, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
label: 'StyledTagButton', | ||
})(({ theme, ...props }) => ({ | ||
border: 'none', | ||
fontSize: '0.875rem', | ||
...(!props.disabled && { | ||
'&:hover, &:focus': { | ||
backgroundColor: theme.color.tagButton, | ||
border: 'none', | ||
}, | ||
backgroundColor: theme.color.tagButton, | ||
color: theme.textColors.linkActiveLight, | ||
}), | ||
})); | ||
|
||
export const StyledPlusIcon = styled(Plus, { | ||
label: 'StyledPlusIcon', | ||
})(({ theme, ...props }) => ({ | ||
color: props.disabled | ||
? theme.name === 'dark' | ||
? '#5c6470' | ||
: theme.color.disabledText | ||
: theme.color.tagIcon, | ||
height: '10px', | ||
width: '10px', | ||
})); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import { Autocomplete } from '../Autocomplete/Autocomplete'; | |
import { LinkButton } from '../LinkButton'; | ||
|
||
interface Props { | ||
disabled?: boolean; | ||
entityType: FirewallDeviceEntityType | undefined; | ||
handleFirewallChange: (firewallID: number) => void; | ||
helperText: JSX.Element; | ||
|
@@ -21,6 +22,7 @@ interface Props { | |
|
||
export const SelectFirewallPanel = (props: Props) => { | ||
const { | ||
disabled, | ||
entityType, | ||
handleFirewallChange, | ||
helperText, | ||
|
@@ -69,6 +71,7 @@ export const SelectFirewallPanel = (props: Props) => { | |
onChange={(_, selection) => { | ||
handleFirewallChange(selection?.value ?? -1); | ||
}} | ||
disabled={disabled} | ||
errorText={error?.[0].reason} | ||
label="Assign Firewall" | ||
loading={isLoading} | ||
|
@@ -78,7 +81,7 @@ export const SelectFirewallPanel = (props: Props) => { | |
value={selectedFirewall} | ||
/> | ||
<StyledLinkButtonBox> | ||
<LinkButton onClick={handleCreateFirewallClick}> | ||
<LinkButton isDisabled={disabled} onClick={handleCreateFirewallClick}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
Create Firewall | ||
</LinkButton> | ||
</StyledLinkButtonBox> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { grantTypeMap } from 'src/features/Account/constants'; | ||
|
||
// A useful type for getting the values of an object | ||
export type ObjectValues<T> = T[keyof T]; | ||
|
||
export type GrantTypeMap = ObjectValues<typeof grantTypeMap>; | ||
abailly-akamai marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,38 @@ | ||
import { getStorage, setStorage } from 'src/utilities/storage'; | ||
|
||
import type { Token } from '@linode/api-v4'; | ||
import type { GlobalGrantTypes, Grants, Profile, Token } from '@linode/api-v4'; | ||
import type { GrantTypeMap } from 'src/features/Account/types'; | ||
|
||
type ActionType = 'create' | 'delete' | 'edit' | 'view'; | ||
|
||
interface GetRestrictedResourceText { | ||
action?: ActionType; | ||
isSingular?: boolean; | ||
resourceType: GrantTypeMap; | ||
} | ||
export const getRestrictedResourceText = ({ | ||
action = 'edit', | ||
isSingular = true, | ||
resourceType, | ||
}: GetRestrictedResourceText): string => { | ||
const resource = isSingular | ||
? 'this ' + resourceType.replace(/s$/, '') | ||
: resourceType; | ||
|
||
return `You don't have permissions to ${action} ${resource}. Please contact your account administrator to request the necessary permissions.`; | ||
}; | ||
|
||
export const isRestrictedGlobalGrantType = ({ | ||
globalGrantType, | ||
grants, | ||
profile, | ||
}: { | ||
globalGrantType: GlobalGrantTypes; | ||
grants: Grants | undefined; | ||
profile: Profile | undefined; | ||
}): boolean => { | ||
return Boolean(profile?.restricted) && !grants?.global[globalGrantType]; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's clear from the name however
|
||
|
||
// TODO: Parent/Child: FOR MSW ONLY, REMOVE WHEN API IS READY | ||
// ================================================================ | ||
|
There was a problem hiding this comment.
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