From 6ee3fb9af19120245e5ca44dce3296d1e0419dbf Mon Sep 17 00:00:00 2001 From: "cursor[bot]" <206951365+cursor[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 01:23:39 +0200 Subject: [PATCH] Fix membership policy edit action navigation (#36690) Automatic Merge --- .../edit_action_navigation.spec.ts | 179 ++++++++++++++++++ .../access_control/policies.test.tsx | 24 +++ .../admin_console/access_control/policies.tsx | 2 +- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 e2e-tests/playwright/specs/functional/system_console/abac/policy_management/edit_action_navigation.spec.ts diff --git a/e2e-tests/playwright/specs/functional/system_console/abac/policy_management/edit_action_navigation.spec.ts b/e2e-tests/playwright/specs/functional/system_console/abac/policy_management/edit_action_navigation.spec.ts new file mode 100644 index 00000000000..84d827cd85e --- /dev/null +++ b/e2e-tests/playwright/specs/functional/system_console/abac/policy_management/edit_action_navigation.spec.ts @@ -0,0 +1,179 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, test, enableABAC, navigateToABACPage} from '@mattermost/playwright-lib'; + +import {createBasicPolicy, getPolicyIdByName} from '../support'; + +/** + * @objective E2E coverage for membership policy edit action navigation: + * - Clicking Edit in the policy row action menu navigates to the membership policy editor + * + * @reference MM-68958: Fix membership policy edit action navigation + */ +test.describe('ABAC Policy Management - Edit Action Navigation', () => { + /** + * MM-68958: Edit action in policy row menu navigates to membership policy editor + * + * Steps: + * 1. Enable ABAC and create a membership policy + * 2. Navigate to System Console > Membership Policies + * 3. Open a policy row's three-dot action menu + * 4. Click Edit + * 5. Verify the URL is /admin_console/system_attributes/membership_policies/edit_policy/ + */ + test('MM-68958 Edit action navigates to membership policy editor', async ({pw}) => { + test.setTimeout(120000); + + await pw.skipIfNoLicense(); + + const {adminUser, adminClient, team} = await pw.initSetup(); + + // Create a test channel for the policy + const channelName = `abac-edit-nav-test-${pw.random.id()}`; + const privateChannel = await adminClient.createChannel({ + team_id: team.id, + name: channelName.toLowerCase().replace(/[^a-z0-9-]/g, ''), + display_name: channelName, + type: 'P', + }); + + const {systemConsolePage} = await pw.testBrowser.login(adminUser); + const page = systemConsolePage.page; + + await navigateToABACPage(page); + await enableABAC(page); + + // Create a basic membership policy + const policyName = `Edit-Nav-Test-${pw.random.id()}`; + + await createBasicPolicy(page, { + name: policyName, + attribute: 'Department', + operator: '==', + value: 'Engineering', + autoSync: false, + channels: [privateChannel.display_name], + }); + + // Get the policy ID from the backend + const policyId = await getPolicyIdByName(adminClient, policyName); + expect(policyId, 'Policy should be created and have an ID').toBeTruthy(); + + // Navigate to Membership Policies list page + await page.goto('/admin_console/system_attributes/membership_policies', {waitUntil: 'networkidle'}); + await page.waitForTimeout(1000); + + // Search for the policy to ensure it's visible + const policySearchInput = page.locator('input[placeholder*="Search" i]').first(); + if (await policySearchInput.isVisible({timeout: 3000})) { + await policySearchInput.fill(policyName); + await page.waitForTimeout(1000); + } + + // Find the policy row + const policyRowLocator = page.locator('tr.clickable, .DataGrid_row').filter({hasText: policyName}).first(); + await policyRowLocator.waitFor({state: 'visible', timeout: 10000}); + + // Open the three-dot action menu for the policy + const actionMenuButton = policyRowLocator + .locator('button[aria-label*="Actions" i], button:has(i.icon-dots-vertical)') + .first(); + await actionMenuButton.waitFor({state: 'visible', timeout: 5000}); + await actionMenuButton.click(); + await page.waitForTimeout(500); + + // Click the Edit menu item + const editMenuItem = page.locator(`[id*="policy-menu-edit-${policyId}"]`).first(); + await editMenuItem.waitFor({state: 'visible', timeout: 5000}); + + // Click Edit and wait for navigation + await editMenuItem.click(); + + // Wait for the URL to change to the edit policy page + await page.waitForURL(`**/admin_console/system_attributes/membership_policies/edit_policy/${policyId}`, { + timeout: 10000, + }); + + // Verify we're on the edit policy page by checking the URL + const currentURL = page.url(); + expect(currentURL).toContain(`/admin_console/system_attributes/membership_policies/edit_policy/${policyId}`); + + // Additional verification: Check that the policy editor is loaded + // The policy editor should have the policy name visible + const policyNameInput = page.locator('input[placeholder*="name" i], input[value*="Edit-Nav-Test"]').first(); + await expect(policyNameInput).toBeVisible({timeout: 10000}); + }); + + /** + * MM-68958: Row click also navigates to membership policy editor + * + * This test verifies that clicking the row (not just the Edit action) also navigates correctly. + * This behavior should have been working before, but we verify it still works after the fix. + */ + test('Row click navigates to membership policy editor', async ({pw}) => { + test.setTimeout(120000); + + await pw.skipIfNoLicense(); + + const {adminUser, adminClient, team} = await pw.initSetup(); + + // Create a test channel for the policy + const channelName = `abac-row-click-test-${pw.random.id()}`; + const privateChannel = await adminClient.createChannel({ + team_id: team.id, + name: channelName.toLowerCase().replace(/[^a-z0-9-]/g, ''), + display_name: channelName, + type: 'P', + }); + + const {systemConsolePage} = await pw.testBrowser.login(adminUser); + const page = systemConsolePage.page; + + await navigateToABACPage(page); + await enableABAC(page); + + // Create a basic membership policy + const policyName = `Row-Click-Test-${pw.random.id()}`; + + await createBasicPolicy(page, { + name: policyName, + attribute: 'Department', + operator: '==', + value: 'Sales', + autoSync: false, + channels: [privateChannel.display_name], + }); + + // Get the policy ID from the backend + const policyId = await getPolicyIdByName(adminClient, policyName); + expect(policyId, 'Policy should be created and have an ID').toBeTruthy(); + + // Navigate to Membership Policies list page + await page.goto('/admin_console/system_attributes/membership_policies', {waitUntil: 'networkidle'}); + await page.waitForTimeout(1000); + + // Search for the policy to ensure it's visible + const policySearchInput = page.locator('input[placeholder*="Search" i]').first(); + if (await policySearchInput.isVisible({timeout: 3000})) { + await policySearchInput.fill(policyName); + await page.waitForTimeout(1000); + } + + // Find the policy row + const policyRowLocator = page.locator('tr.clickable, .DataGrid_row').filter({hasText: policyName}).first(); + await policyRowLocator.waitFor({state: 'visible', timeout: 10000}); + + // Click the row (not the action menu) + await policyRowLocator.click(); + + // Wait for the URL to change to the edit policy page + await page.waitForURL(`**/admin_console/system_attributes/membership_policies/edit_policy/${policyId}`, { + timeout: 10000, + }); + + // Verify we're on the edit policy page + const currentURL = page.url(); + expect(currentURL).toContain(`/admin_console/system_attributes/membership_policies/edit_policy/${policyId}`); + }); +}); diff --git a/webapp/channels/src/components/admin_console/access_control/policies.test.tsx b/webapp/channels/src/components/admin_console/access_control/policies.test.tsx index c4ffb43c115..b1c88ba12d4 100644 --- a/webapp/channels/src/components/admin_console/access_control/policies.test.tsx +++ b/webapp/channels/src/components/admin_console/access_control/policies.test.tsx @@ -143,6 +143,30 @@ describe('components/admin_console/access_control/PolicyList', () => { expect(screen.getByText('Delete')).toBeInTheDocument(); }); + test('Edit menu item should navigate to membership policy editor', async () => { + mockSearchPolicies.mockResolvedValue({ + data: { + policies: [ + {id: 'policy1', name: 'Policy 1'} as AccessControlPolicy, + ], + total: 1, + }, + } as ActionResult); + renderWithContext(); + await waitFor(() => { + expect(screen.getByText('Policy 1')).toBeInTheDocument(); + }); + + const menuButton = document.getElementById('policy-menu-policy1')!; + await userEvent.click(menuButton); + const editItem = document.getElementById('policy-menu-edit-policy1')!; + await userEvent.click(editItem); + + await waitFor(() => { + expect(mockHistoryPushInternal).toHaveBeenCalledWith('/admin_console/system_attributes/membership_policies/edit_policy/policy1'); + }); + }); + test('Delete menu item is disabled when a policy contains masked values', async () => { // The "--------" sentinel in a rule expression means the caller can't // see at least one referenced value. Server enforces a 403 on delete diff --git a/webapp/channels/src/components/admin_console/access_control/policies.tsx b/webapp/channels/src/components/admin_console/access_control/policies.tsx index 795566a585a..47778585de3 100644 --- a/webapp/channels/src/components/admin_console/access_control/policies.tsx +++ b/webapp/channels/src/components/admin_console/access_control/policies.tsx @@ -252,7 +252,7 @@ export default function PolicyList(props: Props): JSX.Element { if (props.onPolicySelected) { props.onPolicySelected(policy); } else { - history.push(`/admin_console/system_attributes/attribute_based_access_control/edit_policy/${policy.id}`); + history.push(`/admin_console/system_attributes/membership_policies/edit_policy/${policy.id}`); } }} leadingElement={}