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: [UIE-8139] - IAM RBAC: add new assigned entities table component part 1 #11588

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

aaleksee-akamai
Copy link
Contributor

@aaleksee-akamai aaleksee-akamai commented Jan 30, 2025

Description 📝

IAM RBAC - new assigned entities table component.

Changes 🔄

List any change(s) relevant to the reviewer.

  • Table for the assigned entities
  • Filtering roles

do not include (will be introduced in the next PR):

  • Any action in the action menu
  • Redirecting to specific entity page by clicking on the entity name

Target release date 🗓️

2/11/25 (dev)

Preview 📷

Include a screenshot or screen recording of the change.

🔒 Use the Mask Sensitive Data setting for security.

💡 Use <video src="" /> tag when including recordings in table.

Before After
Screenshot 2025-01-31 at 10 33 34 AM image

How to test 🧪

Prerequisites

(How to setup test environment)

  • Ensure the Identity and Access Beta flag is enabled in dev tools
  • Ensure the MSW is enabled in dev tools
  • Click on the any username in the users table and go to the tab Assigned Entities or click on the user's menu View Assigned Entities

Verification steps

(How to verify changes)

  • Confirm table renders.
  • Confirm table renders data according to the applied filters
Author Checklists

As an Author, to speed up the review process, I considered 🤔

👀 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


  • I have read and considered all applicable items listed above.

As an Author, before moving this PR from Draft to Open, I confirmed ✅

  • All unit tests are passing
  • TypeScript compilation succeeded without errors
  • Code passes all linting rules

@aaleksee-akamai aaleksee-akamai requested a review from a team as a code owner January 30, 2025 13:43
@aaleksee-akamai aaleksee-akamai requested review from hkhalil-akamai and pmakode-akamai and removed request for a team January 30, 2025 13:43
@aaleksee-akamai aaleksee-akamai self-assigned this Jan 30, 2025
@aaleksee-akamai aaleksee-akamai force-pushed the UIE-8139-iam-rbac-assigned-entities-table-part-1 branch from 1ca7141 to f2f22e7 Compare January 30, 2025 13:45

export const StyledTypography = styled(Typography, {
label: 'StyledTypography',
})(({ theme }) => ({
color:
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 believe we don't need it, as @jaalah-akamai mentioned earlier

Copy link

github-actions bot commented Jan 30, 2025

Coverage Report:
Base Coverage: 78.94%
Current Coverage: 78.94%

roles: EntitiesRole[] | RoleMap[];
}

export const getFilteredRoles = (
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 have the same functionality in two components (AssignedRolesTable and AssignedEntitiesTable), with the only difference being the searchableFields. So, I’ve combined the logic for both components and passed searchableFields as a parameter

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we make getSearchableFields an optional field in the options?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we could make getSearchableFields optional, but for that, we need a default implementation of searchableFields. Right now, this function is only used in two components (that I mentioned above), so making it optional would add flexibility, but it’s worth considering whether it’s necessary at this stage. If we expect to expand its usage further, introducing a default getSearchableFields function could make sense. Otherwise, keeping it explicitly required for now might be a simpler and more controlled approach

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay. At least we should make it part of the options object, no need to have multiple arguments to this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, it makes sense, thanks @hkhalil-akamai

value?: string;
}

export const mapEntityTypes = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same situation here — the only difference is the ending

@cpathipa cpathipa requested review from cpathipa and removed request for pmakode-akamai January 30, 2025 14:48
@jaalah-akamai jaalah-akamai self-requested a review January 31, 2025 15:29
Comment on lines 146 to 152
<DebouncedSearchTextField
clearable
debounceTime={250}
hideLabel
label="Filter"
onSearch={setQuery}
placeholder="Search"
sx={{ marginRight: 2, width: 410 }}
value={query}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the search doesn't involve any network requests, do we need to debounce 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.

yep, remove it, thanks

roles: EntitiesRole[] | RoleMap[];
}

export const getFilteredRoles = (
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay. At least we should make it part of the options object, no need to have multiple arguments to this function.

@hkhalil-akamai
Copy link
Contributor

hkhalil-akamai commented Jan 31, 2025

Does this project have an API spec yet? I noticed that the return type of API getAccountResources is incorrect as the mocked response is an array of type IamAccountResource[].

Also, the accountResourcesFactory is not written according to convention. It should be defined as a single object of type IamAccountResource, not an array. The .buildList() factory method can be used to produce an array for the mock endpoint.

Copy link
Contributor

@jaalah-akamai jaalah-akamai left a comment

Choose a reason for hiding this comment

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

PR looks really good, nice job

The failing test is just due to the name change View User Roles > View Assigned Roles

},
];

const memoizedTableItems = React.useMemo(() => {
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 remove this useMemo all together. There's nothing in here that would greatly benefit from it. We wouldn't even need to worry about memoizing getFilteredRoles since that's a pure function and memoization is more for equality checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cpathipa suggested using memo for the same logic in mine previous PR. So, there is a question - do we need to memoize this to avoid recalculating filtered roles on every render or not? Or maybe I got it wrong, and it needs to be done in a different way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaalah-akamai , @cpathipa , just circling back on this — would appreciate your thoughts. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

I originally suggested using useMemo in the previous PR to avoid recalculating on every render. I think we are using map with a map there. The idea was to optimize performance, but if it's not providing significant benefits, I’m open to revisiting that decision.

if (resource) {
resourceRole.roles.forEach((r: RoleType) => {
result.push({
id: r + '-' + resourceRole.resource_id,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: lets use template strings

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, done


if (resource) {
resourceRole.roles.forEach((r: RoleType) => {
result.push({
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: using a map here would a little more straightforward

data: assignedRoles,
error: assignedRolesError,
isLoading: assignedRolesLoading,
} = useAccountUserPermissions(username ?? '');
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like the current implementation of useAccountUserPermissions does not handle cases where username is undefined or empty, which can lead to unnecessary API calls or API failures.

We might need to update useAccountUserPermissions hook accordingly
example:

export const useAccountUserPermissions = (username?: string) => {
  return useQuery<IamUserPermissions, APIError[]>(
    iamQueries.user(username ?? '')._ctx.permissions,
    { enabled: Boolean(username) }
  );
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks for noticing that @cpathipa , I've had a look at the code and it seems like a lot of cases are missing that check

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure! In CM, we generally use the enabled flag in most queries. We can always improve and update the missing ones as we revisit them.

@aaleksee-akamai aaleksee-akamai force-pushed the UIE-8139-iam-rbac-assigned-entities-table-part-1 branch from f2f22e7 to bda1fdf Compare February 4, 2025 16:12

if (resource) {
result.push(
...resourceRole.roles.map((r: RoleType) => ({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaalah-akamai , please check if this is what you mean with using map

Copy link
Contributor

Choose a reason for hiding this comment

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

@aaleksee-akamai I was more saying that by using a map you don't need to have the results variable or push anything to the array since the map returns an array of the values already. Spreading the results of the map and pushing that into the array is redundant.

const addResourceNamesToRoles = (
  assignedRoles: IamUserPermissions,
  resources: IamAccountResource
): EntitiesRole[] => {
  const resourcesRoles = assignedRoles.resource_access;
  const resourcesArray: IamAccountResource[] = Object.values(resources);

  return resourcesRoles.flatMap((resourceRole: ResourceAccess) => {
    const resourceByType = resourcesArray.find(
      (r: IamAccountResource) => r.resource_type === resourceRole.resource_type
    );

    if (resourceByType) {
      const resource = resourceByType.resources.find(
        (res: Resource) => res.id === resourceRole.resource_id
      );

      if (resource) {
        return resourceRole.roles.map((r: RoleType) => ({
          id: r + '-' + resourceRole.resource_id,
          resource_id: resourceRole.resource_id,
          resource_name: resource.name,
          resource_type: resourceRole.resource_type,
          role_name: r,
        }));
      }
    }

    return [];
  });
};

export const useAccountUserPermissions = (username?: string) => {
return useQuery<IamUserPermissions, APIError[]>({
...iamQueries.user(username ?? '')._ctx.permissions,
enabled: Boolean(username),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cpathipa , please check if it's a correct implementation

Copy link
Contributor

Choose a reason for hiding this comment

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

yea, LGTM!

data: assignedRoles,
error: assignedRolesError,
isLoading: assignedRolesLoading,
} = useAccountUserPermissions(username ?? '');
Copy link
Contributor

Choose a reason for hiding this comment

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

Sure! In CM, we generally use the enabled flag in most queries. We can always improve and update the missing ones as we revisit them.

},
];

const memoizedTableItems = React.useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I originally suggested using useMemo in the previous PR to avoid recalculating on every render. I think we are using map with a map there. The idea was to optimize performance, but if it's not providing significant benefits, I’m open to revisiting that decision.

export const useAccountUserPermissions = (username?: string) => {
return useQuery<IamUserPermissions, APIError[]>({
...iamQueries.user(username ?? '')._ctx.permissions,
enabled: Boolean(username),
Copy link
Contributor

Choose a reason for hiding this comment

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

yea, LGTM!

@aaleksee-akamai aaleksee-akamai force-pushed the UIE-8139-iam-rbac-assigned-entities-table-part-1 branch from bda1fdf to 0d1b863 Compare February 7, 2025 10:47
@linode-gh-bot
Copy link
Collaborator

Cloud Manager UI test results

🔺 1 failing test on test run #4 ↗︎

❌ Failing✅ Passing↪️ Skipped🕐 Duration
1 Failing499 Passing2 Skipped119m 33s

Details

Failing Tests
SpecTest
create-volume.spec.tsvolume create flow » creates a volume from an existing Linode

Troubleshooting

Use this command to re-run the failing tests:

yarn cy:run -s "cypress/e2e/core/volumes/create-volume.spec.ts"

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

Successfully merging this pull request may close these issues.

5 participants