From 4e403f9429ced7c5b34c1bcce69318d3af573ca9 Mon Sep 17 00:00:00 2001 From: nulmete Date: Tue, 1 Sep 2026 15:13:29 -0300 Subject: [PATCH 1/5] Differentiate users restricted to specific API endpoints in the users table Rename the users table's "Role" column to "Permissions" and show a badge with the number of API endpoints an API-only user is restricted to. Add an xsmall Tag variant for the inline badges, reveal the actions dropdown on row hover, and navigate to a user's edit page when their row is clicked. --- changes/51602-api-endpoint-restricted-users | 2 + frontend/components/Tag/Tag.stories.tsx | 9 +- frontend/components/Tag/Tag.tests.tsx | 6 + frontend/components/Tag/Tag.tsx | 6 +- frontend/components/Tag/_styles.scss | 7 + .../UsersPage/UsersPageTableConfig.tsx | 2 +- .../pages/admin/ManageUsersPage/_styles.scss | 26 ++- .../components/UsersTable/UsersTable.tsx | 35 +++- .../UsersTable/UsersTableConfig.tests.tsx | 88 ++++++++- .../UsersTable/UsersTableConfig.tsx | 176 ++++++++++-------- 10 files changed, 260 insertions(+), 97 deletions(-) create mode 100644 changes/51602-api-endpoint-restricted-users diff --git a/changes/51602-api-endpoint-restricted-users b/changes/51602-api-endpoint-restricted-users new file mode 100644 index 00000000000..5c5b1bb20b6 --- /dev/null +++ b/changes/51602-api-endpoint-restricted-users @@ -0,0 +1,2 @@ +* Renamed the "Role" column to "Permissions" on the Settings > Users page, and added a badge showing how many API endpoints an API-only user is restricted to. +* Updated the Settings > Users table so the actions dropdown only appears on row hover and clicking anywhere else in a row opens that user's edit page. diff --git a/frontend/components/Tag/Tag.stories.tsx b/frontend/components/Tag/Tag.stories.tsx index 7c8f81b1963..3aa26d67382 100644 --- a/frontend/components/Tag/Tag.stories.tsx +++ b/frontend/components/Tag/Tag.stories.tsx @@ -11,7 +11,7 @@ const meta: Meta = { title: "Components/Tag", argTypes: { children: { control: "text" }, - size: { control: "radio", options: ["large", "small"] }, + size: { control: "radio", options: ["large", "small", "xsmall"] }, disabled: { control: "boolean" }, tooltip: { control: "text" }, className: { control: "text" }, @@ -38,6 +38,13 @@ export const Small: Story = { }, }; +export const XSmall: Story = { + args: { + children: "16 API endpoints", + size: "xsmall", + }, +}; + export const WithTooltip: Story = { args: { children: "Inherited", diff --git a/frontend/components/Tag/Tag.tests.tsx b/frontend/components/Tag/Tag.tests.tsx index 0d7eb11d5ba..f65d4494ab2 100644 --- a/frontend/components/Tag/Tag.tests.tsx +++ b/frontend/components/Tag/Tag.tests.tsx @@ -24,6 +24,12 @@ describe("Tag", () => { expect(screen.getByText("Inherited")).toHaveClass("tag--small"); }); + it("adds the xsmall modifier class when size is set to xsmall", () => { + render(Inherited); + + expect(screen.getByText("Inherited")).toHaveClass("tag--xsmall"); + }); + it("does not wrap the tag in a tooltip when tooltip is omitted", () => { const { container } = render(Inherited); diff --git a/frontend/components/Tag/Tag.tsx b/frontend/components/Tag/Tag.tsx index 2fd3556deda..96c2194f095 100644 --- a/frontend/components/Tag/Tag.tsx +++ b/frontend/components/Tag/Tag.tsx @@ -8,8 +8,9 @@ const baseClass = "tag"; interface ITagBaseProps { children: React.ReactNode; - /** Default: "large" (28px). Per design, use "small" (24px) sparingly. */ - size?: "large" | "small"; + /** Default: "large" (28px). Per design, use "small" (24px) sparingly and + * "xsmall" (19px) only inline with table cell text. */ + size?: "large" | "small" | "xsmall"; className?: string; /** Wraps the tag in a tooltip that shows this content on hover */ tooltip?: JSX.Element | string; @@ -51,6 +52,7 @@ const Tag = (props: ITagProps) => { [`${baseClass}--clickable`]: props.type === "clickable", [`${baseClass}--dismissible`]: props.type === "dismissible", [`${baseClass}--small`]: props.size === "small", + [`${baseClass}--xsmall`]: props.size === "xsmall", }); let content: JSX.Element; diff --git a/frontend/components/Tag/_styles.scss b/frontend/components/Tag/_styles.scss index 32ac51cb9af..b4bb7e338b1 100644 --- a/frontend/components/Tag/_styles.scss +++ b/frontend/components/Tag/_styles.scss @@ -19,6 +19,13 @@ height: 24px; } + &--xsmall { + height: 19px; + padding: 0 $pad-xsmall; + gap: $pad-xsmall; + font-size: $xxx-small; + } + &--clickable { background: none; cursor: pointer; diff --git a/frontend/pages/admin/ManageFleetsPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx b/frontend/pages/admin/ManageFleetsPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx index 5072f42ba22..f0716a58baf 100644 --- a/frontend/pages/admin/ManageFleetsPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx +++ b/frontend/pages/admin/ManageFleetsPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx @@ -74,7 +74,7 @@ export const renderApiUserIndicator = () => { /> } - size="small" + size="xsmall" > API diff --git a/frontend/pages/admin/ManageUsersPage/_styles.scss b/frontend/pages/admin/ManageUsersPage/_styles.scss index fd7e1343de8..2d52bd60852 100644 --- a/frontend/pages/admin/ManageUsersPage/_styles.scss +++ b/frontend/pages/admin/ManageUsersPage/_styles.scss @@ -9,8 +9,7 @@ &.actions__header { padding-left: 0; } - &.status__header, - &.role__header { + &.status__header { width: 86px; // set to prevent expanding } } @@ -19,7 +18,6 @@ tbody { // need specificity to override datatable css td.name__cell, - td.role__cell, td.teams__cell, td.status__cell, td.email__cell { @@ -27,8 +25,14 @@ white-space: nowrap; } + // Wider than its siblings so the role plus the API endpoint count tag + // fit on one line + td.permissions__cell { + max-width: $col-md; + } + td.status__cell, - td.role__cell { + td.permissions__cell { white-space: nowrap; // Prevent No access from wrapping } @@ -83,8 +87,8 @@ } @media (max-width: ($break-mobile-sm - 1)) { - .role__header, - .role__cell { + .permissions__header, + .permissions__cell { display: none; width: 0; } @@ -138,6 +142,15 @@ } } +.users-table { + // Not to be confused with .permissions__cell, the react-table this sits in + &__permissions-content { + display: flex; + align-items: center; + gap: $pad-small; + } +} + .create-user-page, .create-api-user-page, .edit-user-page { @@ -292,4 +305,3 @@ } } } - diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTable.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTable.tsx index 03028d37984..29cf25be104 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTable.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTable.tsx @@ -1,5 +1,6 @@ import React, { useState, useCallback, useContext, useMemo } from "react"; import { InjectedRouter } from "react-router"; +import { Row } from "react-table"; import { useQuery } from "react-query"; import PATHS from "router/paths"; @@ -48,6 +49,10 @@ const EmptyUsersTable = () => ( /> ); +interface IRowProps extends Row { + original: IUserTableData; +} + interface IUsersTableProps { router: InjectedRouter; // v3 } @@ -121,16 +126,27 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { // FUNCTIONS + const goToEditUser = useCallback( + (user: IUserTableData) => { + if (user.type === "user" && user.apiId === currentUser?.id) { + router.push(PATHS.ACCOUNT); + return; + } + const editPath = PATHS.ADMIN_USERS_EDIT(user.apiId); + router.push( + user.type === "invite" ? `${editPath}?type=invite` : editPath + ); + }, + [router, currentUser?.id] + ); + const onActionSelect = useCallback( (value: string, user: IUserTableData) => { switch (value) { - case "edit": { - const editPath = PATHS.ADMIN_USERS_EDIT(user.apiId); - router.push( - user.type === "invite" ? `${editPath}?type=invite` : editPath - ); + case "edit": + case "editMyAccount": + goToEditUser(user); break; - } case "delete": toggleDeleteUserModal(user); break; @@ -140,16 +156,13 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { case "resetSessions": toggleResetSessionsUserModal(user); break; - case "editMyAccount": - router.push(PATHS.ACCOUNT); - break; default: return null; } return null; }, [ - router, + goToEditUser, toggleDeleteUserModal, toggleResetPasswordUserModal, toggleResetSessionsUserModal, @@ -347,6 +360,8 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { isAllPagesSelected={false} isClientSidePagination renderCount={renderUsersCount} + disableMultiRowSelect + onClickRow={(row: IRowProps) => goToEditUser(row.original)} /> )} {showDeleteUserModal && renderDeleteUserModal()} diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx index eac26ffd5fe..35206046e63 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx @@ -1,11 +1,36 @@ +import { render, screen } from "@testing-library/react"; + import createMockUser from "__mocks__/userMock"; import { IInvite } from "interfaces/invite"; +import { IApiEndpointRef } from "interfaces/api_endpoint"; -import { combineDataSets } from "./UsersTableConfig"; +import { + combineDataSets, + generateTableHeaders, + IUserTableData, +} from "./UsersTableConfig"; const daysAgo = (days: number): string => new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); +const mockEndpoints = (count: number): IApiEndpointRef[] => + Array.from({ length: count }, (_unused, i) => ({ + method: "GET", + path: `/api/v1/fleet/endpoint-${i}`, + })); + +const renderPermissionsCell = (row: IUserTableData) => { + const column = generateTableHeaders(jest.fn(), true).find( + (c) => c.accessor === "permissions" + ); + const Cell = column?.Cell as (props: { + cell: { value: string }; + row: { original: IUserTableData }; + }) => JSX.Element; + + render(Cell({ cell: { value: row.permissions }, row: { original: row } })); +}; + const createMockInvite = (overrides?: Partial): IInvite => ({ created_at: daysAgo(1), updated_at: daysAgo(1), @@ -56,3 +81,64 @@ describe("UsersTableConfig - combineDataSets", () => { expect(row.status).toBe("No access"); }); }); + +describe("UsersTableConfig - API endpoint restrictions", () => { + it("counts the endpoints a user is restricted to", () => { + const users = [ + createMockUser({ api_only: true, api_endpoints: mockEndpoints(3) }), + ]; + const [row] = combineDataSets(users, [], 99); + expect(row.apiEndpointCount).toBe(3); + }); + + it("counts zero endpoints for a user with unrestricted API access", () => { + const users = [createMockUser({ api_only: true })]; + const [row] = combineDataSets(users, [], 99); + expect(row.apiEndpointCount).toBe(0); + }); + + it("counts zero endpoints for invites", () => { + const [row] = combineDataSets([], [createMockInvite()], 99); + expect(row.apiEndpointCount).toBe(0); + }); + + it("names the role column 'Permissions'", () => { + const column = generateTableHeaders(jest.fn(), true).find( + (c) => c.accessor === "permissions" + ); + expect(column?.title).toBe("Permissions"); + expect(column?.Header).toBe("Permissions"); + }); + + it("shows a badge with the endpoint count in the Permissions cell", () => { + const users = [ + createMockUser({ api_only: true, api_endpoints: mockEndpoints(16) }), + ]; + const [row] = combineDataSets(users, [], 99); + + renderPermissionsCell(row); + + expect(screen.getByText("Admin")).toBeInTheDocument(); + expect(screen.getByText("16 API endpoints")).toBeInTheDocument(); + }); + + it("singularizes the badge when the user is restricted to one endpoint", () => { + const users = [ + createMockUser({ api_only: true, api_endpoints: mockEndpoints(1) }), + ]; + const [row] = combineDataSets(users, [], 99); + + renderPermissionsCell(row); + + expect(screen.getByText("1 API endpoint")).toBeInTheDocument(); + }); + + it("omits the badge for a user with unrestricted API access", () => { + const users = [createMockUser({ api_only: true })]; + const [row] = combineDataSets(users, [], 99); + + renderPermissionsCell(row); + + expect(screen.queryByText(/API endpoint/)).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx index bee6474f676..09841e79ffb 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx @@ -22,14 +22,20 @@ import { import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import ActionsDropdown from "../../../../../components/ActionsDropdown"; +const baseClass = "users-table"; + const renderApiUserIndicator = () => { return ( - + API ); }; +const renderApiEndpointCount = (count: number) => ( + {`${count} API endpoint${count === 1 ? "" : "s"}`} +); + interface IHeaderProps { column: { title: string; @@ -73,7 +79,7 @@ export interface IUserTableData { teams: string; teamNames: string[]; roleGroups: { role: string; names: string[] }[]; - role: UserRole; + permissions: UserRole; actions: IDropdownOption[]; /** Prefixed ID used as a unique react-table row key (e.g. "user-3", "invite-1") */ id: string; @@ -81,6 +87,8 @@ export interface IUserTableData { apiId: number; type: string; api_only: boolean; + /** Number of API endpoints this user is restricted to; 0 means unrestricted */ + apiEndpointCount: number; } // The inactivity window is enforced server-side (see UserInactiveAfter in @@ -106,8 +114,68 @@ const generateInviteStatus = (invite: IInvite): string => ? "No access" : "Invite pending"; -// NOTE: cellProps come from react-table -// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties +const renderRole = (cellProps: ICellProps) => { + if (cellProps.cell.value === "GitOps") { + return ( + + The GitOps role is only available for API-only +
+ users. This user has no access to the UI. + + } + > + GitOps +
+ ); + } + if (cellProps.cell.value === "Observer+") { + return ( + + Users with the Observer+ role have access to all of +
+ the same functions as an Observer, with the added +
+ ability to run any live report against all hosts. + + } + > + {cellProps.cell.value} +
+ ); + } + if (cellProps.cell.value === ROLE_VARIOUS) { + const { roleGroups } = cellProps.row.original; + return ( + ( + + {role}: {names.join(", ")} +
+
+ ))} + underline={false} + showArrow + position="top" + tipOffset={10} + fixedPositionStrategy + > + +
+ ); + } + return ( + + ); +}; + const generateTableHeaders = ( actionSelectHandler: (value: string, user: IUserTableData) => void, isPremiumTier: boolean | undefined @@ -133,69 +201,18 @@ const generateTableHeaders = ( }, }, { - title: "Role", - Header: "Role", - accessor: "role", + title: "Permissions", + Header: "Permissions", + accessor: "permissions", disableSortBy: true, Cell: (cellProps: ICellProps) => { - if (cellProps.cell.value === "GitOps") { - return ( - - The GitOps role is only available for API-only -
- users. This user has no access to the UI. - - } - > - GitOps -
- ); - } - if (cellProps.cell.value === "Observer+") { - return ( - - Users with the Observer+ role have access to all of -
- the same functions as an Observer, with the added -
- ability to run any live report against all hosts. - - } - > - {cellProps.cell.value} -
- ); - } - if (cellProps.cell.value === ROLE_VARIOUS) { - const { roleGroups } = cellProps.row.original; - return ( - ( - - {role}: {names.join(", ")} -
-
- ))} - underline={false} - showArrow - position="top" - tipOffset={10} - fixedPositionStrategy - > - -
- ); - } + const { apiEndpointCount } = cellProps.row.original; + return ( - +
+ {renderRole(cellProps)} + {apiEndpointCount > 0 && renderApiEndpointCount(apiEndpointCount)} +
); }, }, @@ -249,15 +266,22 @@ const generateTableHeaders = ( disableSortBy: true, accessor: "actions", Cell: (cellProps: IActionsDropdownProps) => ( - - actionSelectHandler(value, cellProps.row.original) - } - placeholder="Actions" - menuAlign="right" - variant="secondary" - /> +
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + > + + actionSelectHandler(value, cellProps.row.original) + } + placeholder="Actions" + menuAlign="right" + variant="secondary" + /> +
), }, ]; @@ -370,7 +394,7 @@ const enhanceUserData = ( teams: generateTeam(user.teams, user.global_role), teamNames: generateTeamNames(user.teams), roleGroups: generateRoleGroups(user.teams), - role: generateRole(user.teams, user.global_role), + permissions: generateRole(user.teams, user.global_role), actions: generateActionDropdownOptions( user.id === currentUserId, false, @@ -381,6 +405,7 @@ const enhanceUserData = ( apiId: user.id, type: "user", api_only: user.api_only, + apiEndpointCount: user.api_endpoints?.length ?? 0, }; }); }; @@ -394,7 +419,7 @@ const enhanceInviteData = (invites: IInvite[]): IUserTableData[] => { teams: generateTeam(invite.teams, invite.global_role), teamNames: generateTeamNames(invite.teams), roleGroups: generateRoleGroups(invite.teams), - role: generateRole(invite.teams, invite.global_role), + permissions: generateRole(invite.teams, invite.global_role), actions: generateActionDropdownOptions( false, true, @@ -405,6 +430,7 @@ const enhanceInviteData = (invites: IInvite[]): IUserTableData[] => { apiId: invite.id, type: "invite", api_only: false, // api only users are created through fleetctl and not invites + apiEndpointCount: 0, }; }); }; From 7ae7b78369fd76def1f5dd1ede411cfac7c75f5c Mon Sep 17 00:00:00 2001 From: nulmete Date: Tue, 1 Sep 2026 18:30:25 -0300 Subject: [PATCH 2/5] adjust font-size and height --- frontend/components/Tag/_styles.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/Tag/_styles.scss b/frontend/components/Tag/_styles.scss index b4bb7e338b1..e0d9a712150 100644 --- a/frontend/components/Tag/_styles.scss +++ b/frontend/components/Tag/_styles.scss @@ -20,10 +20,10 @@ } &--xsmall { - height: 19px; + height: 20px; padding: 0 $pad-xsmall; gap: $pad-xsmall; - font-size: $xxx-small; + font-size: $xx-small; } &--clickable { From 42188790a9aa0bafb1d47c7a0bacf90bdb4a8b66 Mon Sep 17 00:00:00 2001 From: nulmete Date: Tue, 1 Sep 2026 18:34:58 -0300 Subject: [PATCH 3/5] Keep `role` as the users table row key The Permissions column now sets react-table's `id` so the cell and header DOM classes stay `permissions__*` while the row field remains `role`. --- frontend/components/Tag/Tag.tsx | 2 +- .../UsersTable/UsersTableConfig.tests.tsx | 6 +++--- .../components/UsersTable/UsersTableConfig.tsx | 13 +++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/frontend/components/Tag/Tag.tsx b/frontend/components/Tag/Tag.tsx index 96c2194f095..f0697093610 100644 --- a/frontend/components/Tag/Tag.tsx +++ b/frontend/components/Tag/Tag.tsx @@ -9,7 +9,7 @@ const baseClass = "tag"; interface ITagBaseProps { children: React.ReactNode; /** Default: "large" (28px). Per design, use "small" (24px) sparingly and - * "xsmall" (19px) only inline with table cell text. */ + * "xsmall" (20px) only inline with table cell text. */ size?: "large" | "small" | "xsmall"; className?: string; /** Wraps the tag in a tooltip that shows this content on hover */ diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx index 35206046e63..dd883dc94c5 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx @@ -21,14 +21,14 @@ const mockEndpoints = (count: number): IApiEndpointRef[] => const renderPermissionsCell = (row: IUserTableData) => { const column = generateTableHeaders(jest.fn(), true).find( - (c) => c.accessor === "permissions" + (c) => c.id === "permissions" ); const Cell = column?.Cell as (props: { cell: { value: string }; row: { original: IUserTableData }; }) => JSX.Element; - render(Cell({ cell: { value: row.permissions }, row: { original: row } })); + render(Cell({ cell: { value: row.role }, row: { original: row } })); }; const createMockInvite = (overrides?: Partial): IInvite => ({ @@ -104,7 +104,7 @@ describe("UsersTableConfig - API endpoint restrictions", () => { it("names the role column 'Permissions'", () => { const column = generateTableHeaders(jest.fn(), true).find( - (c) => c.accessor === "permissions" + (c) => c.id === "permissions" ); expect(column?.title).toBe("Permissions"); expect(column?.Header).toBe("Permissions"); diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx index 09841e79ffb..c7095bfe0de 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tsx @@ -65,6 +65,7 @@ interface IDataColumn { title: string; Header: ((props: IHeaderProps) => JSX.Element) | string; accessor: string; + id?: string; Cell: | ((props: ICellProps) => JSX.Element) | ((props: IActionsDropdownProps) => JSX.Element); @@ -79,7 +80,7 @@ export interface IUserTableData { teams: string; teamNames: string[]; roleGroups: { role: string; names: string[] }[]; - permissions: UserRole; + role: UserRole; actions: IDropdownOption[]; /** Prefixed ID used as a unique react-table row key (e.g. "user-3", "invite-1") */ id: string; @@ -203,7 +204,11 @@ const generateTableHeaders = ( { title: "Permissions", Header: "Permissions", - accessor: "permissions", + accessor: "role", + // react-table derives the cell/header DOM classes and the sort key from + // `id`, so this keeps them as `permissions__*` without renaming the + // underlying row field. + id: "permissions", disableSortBy: true, Cell: (cellProps: ICellProps) => { const { apiEndpointCount } = cellProps.row.original; @@ -394,7 +399,7 @@ const enhanceUserData = ( teams: generateTeam(user.teams, user.global_role), teamNames: generateTeamNames(user.teams), roleGroups: generateRoleGroups(user.teams), - permissions: generateRole(user.teams, user.global_role), + role: generateRole(user.teams, user.global_role), actions: generateActionDropdownOptions( user.id === currentUserId, false, @@ -419,7 +424,7 @@ const enhanceInviteData = (invites: IInvite[]): IUserTableData[] => { teams: generateTeam(invite.teams, invite.global_role), teamNames: generateTeamNames(invite.teams), roleGroups: generateRoleGroups(invite.teams), - permissions: generateRole(invite.teams, invite.global_role), + role: generateRole(invite.teams, invite.global_role), actions: generateActionDropdownOptions( false, true, From ba10d968c9057dade8fc3add8aa45c36b6c6b109 Mon Sep 17 00:00:00 2001 From: Nico <32375741+nulmete@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:48:29 -0300 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../components/UsersTable/UsersTableConfig.tests.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx index dd883dc94c5..b54ed5e9b05 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx @@ -28,7 +28,7 @@ const renderPermissionsCell = (row: IUserTableData) => { row: { original: IUserTableData }; }) => JSX.Element; - render(Cell({ cell: { value: row.role }, row: { original: row } })); + render(); }; const createMockInvite = (overrides?: Partial): IInvite => ({ From 17654254bc37b1c41f720bdf44633a5f55918f3b Mon Sep 17 00:00:00 2001 From: nulmete Date: Tue, 1 Sep 2026 18:53:26 -0300 Subject: [PATCH 5/5] import React in test file --- .../components/UsersTable/UsersTableConfig.tests.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx index b54ed5e9b05..96832d7524d 100644 --- a/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx +++ b/frontend/pages/admin/ManageUsersPage/components/UsersTable/UsersTableConfig.tests.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { render, screen } from "@testing-library/react"; import createMockUser from "__mocks__/userMock";