Skip to content

Adding roles table for Audit user page#106

Open
jacobo-dominguez-wgu wants to merge 7 commits intoopenedx:masterfrom
WGU-Open-edX:audit-user
Open

Adding roles table for Audit user page#106
jacobo-dominguez-wgu wants to merge 7 commits intoopenedx:masterfrom
WGU-Open-edX:audit-user

Conversation

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor

@jacobo-dominguez-wgu jacobo-dominguez-wgu commented Mar 23, 2026

Description

Creating the roles table on the audit user page. It includes:

  • New roles assignments table replacing the card roles.
  • Different background color for rows including roles from django.
  • Table pagination.
  • Minimal modifications to the header to match the ui design.

It closes #86
Figma design

Pending things:

Warning

Will be on draft status until the required endpoints are completed.
This PR requires some the endpoints proposed in the section "Proposed Endpoint for M2"
openedx/openedx-authz#230

image image

How to test it

Pre - requisites

A running local tutor instance.
It requires the endpoint created on this PR openedx/openedx-authz#230 (already merged)
Since the role assignation wizard is currently on development, we can still use the old library specific access manager and assign some roles there http://apps.local.openedx.io:2025/admin-console/authz/libraries/:libraryId
or, can assign roles by directly using the api endpoint http://local.openedx.io:8000/api-docs/#/authz/authz_v1_roles_users_update

1 - Go to the new audit user page -> http://apps.local.openedx.io:2025/admin-console/authz/user/:username (select a user with many roles assigned to get a more populated table) (NOTE: The team members table will redirect to this page, currently in progress #124)
2 - Validate all the data loads properly, the UI matches the design and acceptance criteria is fulfilled.

NOTE: Expand all permissions and delete functionality are on different prs.
NOTE 2: The "Assign Role" button redirects to the new role assignation wizard (in development here #109 and #111).

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Mar 23, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented Mar 23, 2026

Thanks for the pull request, @jacobo-dominguez-wgu!

This repository is currently maintained by @openedx/committers-frontend.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Mar 23, 2026
@jacobo-dominguez-wgu jacobo-dominguez-wgu marked this pull request as draft March 23, 2026 16:53
@mphilbrick211 mphilbrick211 added the mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). label Mar 23, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Mar 23, 2026
@jacobo-dominguez-wgu jacobo-dominguez-wgu force-pushed the audit-user branch 3 times, most recently from 2151f75 to ef7bf9b Compare March 31, 2026 01:30
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 98.31461% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.78%. Comparing base (3125965) to head (2035a1d).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
src/authz-module/data/api.ts 85.71% 2 Missing ⚠️
src/index.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #106      +/-   ##
==========================================
+ Coverage   95.35%   95.78%   +0.42%     
==========================================
  Files          53       59       +6     
  Lines        1055     1209     +154     
  Branches      208      233      +25     
==========================================
+ Hits         1006     1158     +152     
- Misses         46       48       +2     
  Partials        3        3              

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

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

I started by trying to test this. The page renders correctly:

Image

But when I try to assign roles by clicking on "Assign Role", I get a bunch of errors seemingly stemming from 404s. Have the endpoints not been implemented, yet? I'm using the very latest edx-platform as of the time of this review.

If that's the case, please add the backend PRs that this depends on to the description. Otherwise, if there's some other way to test this, please outline the steps.

In the meantime, this is what Claude found when looking at the code. I think all points are worth addressing:

Bugs

1. navigate() called during render instead of in an effect

In src/authz-module/audit-user/index.tsx#L29-L31:

if (!user && !isLoadingUser) {
    navigate(AUTHZ_HOME_PATH);
}

This is a side effect executed during render. React will warn about state updates during render, and the component continues executing (building navLinks, columns, etc.) after the navigate call, which is wasted work at best and can lead to errors if it references state that becomes inconsistent mid-render.

The existing LibrariesUserManager.tsx handles the same pattern correctly:

useEffect(() => {
    if (!isFetchingMember) {
        if (!isLoadingTeamMember && !user?.username) {
            navigate(teamMembersPath);
        }
    }
}, [isFetchingMember, isLoadingTeamMember, user?.username]);

2. Pagination uses results.length instead of API count

In src/authz-module/audit-user/index.tsx#L74 and #L102:

const pageCount = userAssignments?.length
    ? Math.ceil(userAssignments.length / TABLE_DEFAULT_PAGE_SIZE) : 1;
...
itemCount={userAssignments?.length || 0}

The table is configured with manualPagination, meaning the backend handles pagination and the frontend must tell the table how many total items exist. But itemCount and pageCount are derived from userAssignments.length - which is the current page of results, not the total. The API response includes a count field for the total, but the component destructuring on line 27 doesn't extract it:

const { data: { results: userAssignments } = { results: [] } } = useUserAssignedRoles(...);

When a user has more than 10 role assignments, the table will display only the first page with no way to navigate further.

3. UserAccount type uses snake_case but camelCaseObject is applied

src/data/api.ts#L14-L17 applies camelCaseObject(data) to the API response, but the UserAccount type defines fields in snake_case (profile_image, date_joined, is_active, etc.). The actual runtime data has camelCase keys (profileImage, dateJoined, isActive), so the TypeScript types are incorrect.

This doesn't break the two fields actually used - username and email - since they're unchanged by camelCasing. But it's a type safety gap. The test mock data in src/data/hooks.test.tsx already uses camelCase (profileImage, dateJoined), confirming the mismatch. Either the type should use camelCase or camelCaseObject should be removed.

Issues

4. Route ordering in AuthZModule

In src/authz-module/index.tsx#L33-L36, the AUDIT_USER_PATH route is declared after the path="*" catch-all:

<Route path="*" element={<NotFoundError />} />
<Route path={ROUTES.AUDIT_USER_PATH} element={<AuditUserPage />} />

React Router v6 ranks routes by specificity so this works correctly at runtime (/user/:username is more specific than *). But conventionally catch-all routes go last - having it in the middle makes the routing table harder to read and will mislead anyone unfamiliar with v6's ranking behavior.

5. CSS breakpoint gap for hr styling

The SCSS was refactored from a desktop-up approach to a mobile-first approach. On master, hr defaults to horizontal and switches to vertical at min-width-lg. In the new code (index.scss#L7-L10 and #L54-L63), it defaults to vertical and only switches to horizontal at max-width-sm. Screens between sm and lg breakpoints will now see vertical hr dividers where they previously saw horizontal ones. If this is intentional to match the new Figma designs, no issue - just flagging it since it affects the existing library pages too (via the class rename from .authz-libraries to .authz-module).

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

I started by trying to test this. The page renders correctly

If that's the case, please add the backend PRs that this depends on to the description. Otherwise, if there's some other way to test this, please outline the steps.

In the meantime, this is what Claude found when looking at the code. I think all points are worth addressing:

Bugs

1. navigate() called during render instead of in an effect
2. Pagination uses results.length instead of API count
3. UserAccount type uses snake_case but camelCaseObject is applied
4. Route ordering in AuthZModule
5. CSS breakpoint gap for hr styling

Hi @arbrandes, thanks for your review. The "Assign Role" button is not depending in any endpoint, it redirects to the role assignation wizard currently in development (here #109 and #111), I just added the redirection to that page but it does not exist yet thats why it shows 404, (Should I remove the button and add it when the wizard is done? So it does not become a blocker for this pr).

Also I have addressed the rest of the points:

  1. navigate() moved to a useEffect
  2. I had already replaced the results.length with count but I guess I mixed it up during a merge conflicts resolution, I did it again.
  3. Changed all the fields on UserAccount to camelCase.
  4. Route ordering fixed.
  5. This is an intentional change so the ui does not break on small screens and matches the figma design.

@arbrandes
Copy link
Copy Markdown

Should I remove the button and add it when the wizard is done?

No need, but I still need a way to add data so I can see it working. How do I do that?

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

Should I remove the button and add it when the wizard is done?

No need, but I still need a way to add data so I can see it working. How do I do that?

Since the wizard is currently on development, you can still use the old library specific access manager and assign some roles there http://apps.local.openedx.io:2025/admin-console/authz/libraries/:libraryId

Or, can assign roles by directly using the api endpoint http://local.openedx.io:8000/api-docs/#/authz/authz_v1_roles_users_update

I will add it to the testing instructions in the description.

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

Can you give another look @arbrandes, @dcoa?

@arbrandes
Copy link
Copy Markdown

Ok, thank you! I tried it out, and here were the results.

With the version of the MFE in master, I successfully added all four roles to a user named erik using the library interface at http://apps.local.openedx.io:2025/admin-console/authz/libraries/lib:Axim:TL01:

image

I then checked out this PR and went to http://apps.local.openedx.io:2025/admin-console/authz/user/erik, but the spinner appears for a long time and in the end I see nothing:

image

I am on an up-to-date edx-platform.

Claude also caught a couple of remaining issues:

1. ProfileImage type still uses snake_case

The previous review flagged that the UserAccount type used snake_case despite camelCaseObject being applied in src/data/api.ts#L17. The UserAccount fields were correctly converted to camelCase, but the nested ProfileImage type was missed:

export type ProfileImage = {
  has_image: boolean;
  image_url_full: string;
  image_url_large: string;
  image_url_medium: string;
  image_url_small: string;
};

camelCaseObject transforms nested objects recursively, so the runtime data has hasImage, imageUrlFull, etc. The test mock data in src/data/hooks.test.tsx#L53-L59 already uses the correct camelCase keys, confirming the mismatch.

2. CSS class rename missed in authz-home

The SCSS file renames .authz-libraries to .authz-module (src/authz-module/index.scss#L3). The class name was updated in LibrariesTeamManager.tsx and LibrariesUserManager.tsx, but src/authz-module/authz-home/index.tsx#L17 still uses the old name:

<div className="authz-libraries">

Since .authz-libraries no longer exists in the stylesheet, the authz-home page loses all styles scoped under that class: hr divider styling, .tab-content background, .collapsible-card and .collapsible-trigger overrides, .permission-chip sizing, .permission-table line height, and the responsive @media rules.

@jacobo-dominguez-wgu
Copy link
Copy Markdown
Contributor Author

jacobo-dominguez-wgu commented Apr 16, 2026

Ok, thank you! I tried it out, and here were the results.
I then checked out this PR and went to http://apps.local.openedx.io:2025/admin-console/authz/user/erik, but the spinner appears for a long time and in the end I see nothing:

All the required backend prs are merged into main now for openedx-authz, but I am guessing openedx-platform does not have the latest version of openedx-authz, I tried mounting main branch of openedx-authz and the table loaded for me, would you mind trying?

Update:
It is included on openedx-platform now openedx/openedx-platform#38348

1. ProfileImage type still uses snake_case

This one is addressed.

2. CSS class rename missed in authz-home

Solved

Thanks @arbrandes.

@dcoa
Copy link
Copy Markdown
Contributor

dcoa commented Apr 17, 2026

I can validate all the acceptance criteria, except this:

The table is paginated with 10 rows per page, with previous/next arrows and a page selector using the Paragon reduce variant.

There is not page selector in the table. There is a reason for that? @jacobo-dominguez-wgu

I also can see some errors that were addressed here #124 , so I suggest merge that and rebase this before approved. In general looks good.

Comment thread src/authz-module/data/hooks.ts Outdated
Comment on lines +127 to +135
export const useUserAssignedRoles = (username: string, querySettings: QuerySettings) => {
const result = useQuery<GetUserAssignmentsResponse, Error>({
queryKey: authzQueryKeys.userRoles(username, querySettings),
queryFn: () => getUserAssignedRoles(username, querySettings),
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
refetchOnWindowFocus: false,
});
return result;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
export const useUserAssignedRoles = (username: string, querySettings: QuerySettings) => {
const result = useQuery<GetUserAssignmentsResponse, Error>({
queryKey: authzQueryKeys.userRoles(username, querySettings),
queryFn: () => getUserAssignedRoles(username, querySettings),
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
refetchOnWindowFocus: false,
});
return result;
};
export const useUserAssignedRoles = (username: string, querySettings: QuerySettings) => useQuery<GetUserAssignmentsResponse, Error>({
queryKey: authzQueryKeys.userRoles(username, querySettings),
queryFn: () => getUserAssignedRoles(username, querySettings),
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
refetchOnWindowFocus: false,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there is a reason for this values?

    staleTime: 1000 * 60 * 30, // refetch after 30 minutes
    refetchOnWindowFocus: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you this change has been addressed!
about this

staleTime: 1000 * 60 * 30, // refetch after 30 minutes

I think is the time Jacobo considered correct for the data to be current
do you have any suggestions? or can we leave it this way?

Copy link
Copy Markdown

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

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

Ok, I finally got this to work (I had to rebuild my openedx-dev image from scratch to get the latest openedx-authz), and it looks good! The only weird thing I saw is a blank role with asterisks:

Image

Not a blocker, just curious. (I suspect it's just stale data.)

@jesusbalderramawgu
Copy link
Copy Markdown
Contributor

Ok, I finally got this to work (I had to rebuild my openedx-dev image from scratch to get the latest openedx-authz), and it looks good! The only weird thing I saw is a blank role with asterisks:

Image Not a blocker, just curious. (I suspect it's just stale data.)

Thank you Adolfo, that issue is gonna be solved in one of my PR's later

@jesusbalderramawgu
Copy link
Copy Markdown
Contributor

I can validate all the acceptance criteria, except this:

The table is paginated with 10 rows per page, with previous/next arrows and a page selector using the Paragon reduce variant.

There is not page selector in the table. There is a reason for that? @jacobo-dominguez-wgu

I also can see some errors that were addressed here #124 , so I suggest merge that and rebase this before approved. In general looks good.

Thanks for your feedback, I checked and I see the page selector correctly but is not visible if we have less than 10 records
we have right now this constants
TABLE_DEFAULT_PAGE_SIZE = 10
so if we have 10 or less records the page selector won't be visible unless we have more records to show
also I checked and looks exactly like in the design.

Screenshot 2026-04-17 at 9 16 16 a m

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

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

Task - RBAC AuthZ - M2.6 View the audit detail for a specific user - Replace roles card view with roles table

6 participants