From 8c154d4ba6724c704d24cc2a53fb612ee88391d2 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 9 Jan 2025 12:20:01 -0600 Subject: [PATCH] Display View button if user can list roles (#50888) This fixes a regression that caused users who could list/read roles but NOT edit them to be unable to view the details of the role. This PR will change the text to "view details" if the user can _only_ view and not edit. The edit button in the role editor already has logic to prevent and tell the user they do not have permissions to edit, so no further changes need to be made there. --- .../teleport/src/Roles/RoleList/RoleList.tsx | 11 +++- .../teleport/src/Roles/Roles.test.tsx | 50 ++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/web/packages/teleport/src/Roles/RoleList/RoleList.tsx b/web/packages/teleport/src/Roles/RoleList/RoleList.tsx index baf15b596c534..3fec1ddd63867 100644 --- a/web/packages/teleport/src/Roles/RoleList/RoleList.tsx +++ b/web/packages/teleport/src/Roles/RoleList/RoleList.tsx @@ -39,6 +39,7 @@ export function RoleList({ serversidePagination: SeversidePagination; rolesAcl: Access; }) { + const canView = rolesAcl.list && rolesAcl.read; const canEdit = rolesAcl.edit; const canDelete = rolesAcl.remove; @@ -72,6 +73,7 @@ export function RoleList({ altKey: 'options-btn', render: (role: RoleResource) => ( onEdit(role.id)} @@ -87,18 +89,23 @@ export function RoleList({ } const ActionCell = (props: { + canView: boolean; canEdit: boolean; canDelete: boolean; onEdit(): void; onDelete(): void; }) => { - if (!(props.canEdit || props.canDelete)) { + if (!(props.canView || props.canDelete)) { return ; } return ( - {props.canEdit && Edit} + {props.canView && ( + + {props.canEdit ? 'Edit' : 'View Details'} + + )} {props.canDelete && ( Delete )} diff --git a/web/packages/teleport/src/Roles/Roles.test.tsx b/web/packages/teleport/src/Roles/Roles.test.tsx index 1a4338cac036e..071c1e10eea3d 100644 --- a/web/packages/teleport/src/Roles/Roles.test.tsx +++ b/web/packages/teleport/src/Roles/Roles.test.tsx @@ -120,13 +120,13 @@ describe('Roles list', () => { expect(menuItems).toHaveLength(2); }); - test('hides edit button if no access', async () => { + test('hides view/edit button if no access', async () => { const ctx = createTeleportContext(); const testState = { ...defaultState, rolesAcl: { ...defaultState.rolesAcl, - edit: false, + list: false, }, }; @@ -147,12 +147,15 @@ describe('Roles list', () => { fireEvent.click(optionsButton); const menuItems = screen.queryAllByRole('menuitem'); expect(menuItems).toHaveLength(1); - expect(menuItems.every(item => item.textContent.includes('Edit'))).not.toBe( - true - ); + expect( + menuItems.every( + item => + item.textContent.includes('View') || item.textContent.includes('Edit') + ) + ).not.toBe(true); }); - test('hides delete button if no access', async () => { + test('hides delete button if user does not have permission to delete', async () => { const ctx = createTeleportContext(); const testState = { ...defaultState, @@ -184,12 +187,14 @@ describe('Roles list', () => { ).not.toBe(true); }); - test('hides Options button if no permissions to edit or delete', async () => { + test('displays Options button if user has permission to list/read roles', async () => { const ctx = createTeleportContext(); const testState = { ...defaultState, rolesAcl: { - ...defaultState.rolesAcl, + list: true, + read: true, + create: false, remove: false, edit: false, }, @@ -203,6 +208,35 @@ describe('Roles list', () => { ); + await waitFor(() => { + expect(screen.getByText('cool-role')).toBeInTheDocument(); + }); + const optionsButton = screen.getByRole('button', { name: /options/i }); + fireEvent.click(optionsButton); + const menuItems = screen.queryAllByRole('menuitem'); + expect(menuItems).toHaveLength(1); + expect(menuItems[0]).toHaveTextContent('View'); + }); + + test('hides Options button if no permissions to view or delete', async () => { + const ctx = createTeleportContext(); + const testState = { + ...defaultState, + rolesAcl: { + ...defaultState.rolesAcl, + remove: false, + list: false, + }, + }; + + render( + + + + + + ); + await waitFor(() => { expect(screen.getByText('cool-role')).toBeInTheDocument(); });