From 6a57ca65117576800bea77b4046b89d4321597ea Mon Sep 17 00:00:00 2001 From: Harsh Vador Date: Sun, 26 Apr 2026 13:46:48 +0530 Subject: [PATCH 1/2] Fix server-side role search behavior across role selectors --- .../e2e/Features/SSOConfiguration.spec.ts | 16 ++++- .../e2e/Flow/AddRoleAndAssignToUser.spec.ts | 4 ++ .../ui/playwright/e2e/Pages/Domains.spec.ts | 2 + .../playwright/e2e/Pages/UserDetails.spec.ts | 13 ++++ .../resources/ui/playwright/utils/user.ts | 4 +- .../Bot/BotDetails/BotDetails.component.tsx | 54 +++++++++++--- .../Bot/BotDetails/BotDetails.test.tsx | 9 +++ .../Users/CreateUser/CreateUser.component.tsx | 68 ++++++++++++++---- .../Users/CreateUser/CreateUser.interface.ts | 2 - .../Users/CreateUser/CreateUser.test.tsx | 11 ++- .../UserProfileRoles.component.tsx | 71 +++++++++++++------ .../UserProfileRoles.test.tsx | 7 +- .../SsoRolesSelectField.test.tsx | 61 +++++++--------- .../SsoRolesSelectField.tsx | 46 ++++++++++-- .../LdapRoleMappingWidget.test.tsx | 41 ++++------- .../LdapRoleMappingWidget.tsx | 59 +++++++++++++-- .../common/RolesCard/RolesCard.component.tsx | 6 ++ .../common/RolesCard/RolesCard.interfaces.ts | 2 + .../CreateUserPage.component.tsx | 28 +------- .../main/resources/ui/src/rest/rolesAPIV1.ts | 18 +++++ 20 files changed, 367 insertions(+), 155 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts index e0937e4cc2fd..b2e6aaa06b4d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts @@ -680,6 +680,7 @@ test.describe('SSO Configuration Tests', () => { page, }) => { await selectSSOProvider(page, 'ldap'); + await page.waitForResponse('/api/v1/roles/search?*'); const addMappingButton = page.getByTestId('add-mapping-btn'); const ldapGroupInputs = page.locator( @@ -747,6 +748,7 @@ test.describe('SSO Configuration Tests', () => { page, }) => { await selectSSOProvider(page, 'ldap'); + await page.waitForResponse('/api/v1/roles/search?*'); const field = page.getByTestId( 'sso-configuration-form-array-field-template-authReassignRoles' @@ -760,8 +762,13 @@ test.describe('SSO Configuration Tests', () => { // Opening the dropdown shows API-fetched role options await field.click(); await expect(dropdown).toBeVisible(); + await field.locator('input').fill(''); + await page.waitForResponse('/api/v1/roles/search?*'); await expect(dropdown.locator('.ant-select-item-option')).not.toHaveCount( - 0 + 0, + { + timeout: 15000, + } ); // Select the first available role — it appears as a selection tag @@ -780,15 +787,20 @@ test.describe('SSO Configuration Tests', () => { // Typing filters the visible options await field.click(); await field.locator('input').fill('Data'); + await page.waitForResponse('/api/v1/roles/search?*'); await expect( dropdown.locator( '.ant-select-item-option:not(.ant-select-item-option-disabled)' ) - ).not.toHaveCount(0); + ).not.toHaveCount(0, { timeout: 15000 }); // Pressing Enter on a non-existent value does not create an arbitrary tag await field.locator('input').clear(); + const missingRoleSearchResponse = page.waitForResponse( + '/api/v1/roles/search?*' + ); await field.locator('input').fill('NonExistentRoleXYZ123'); + await missingRoleSearchResponse; await field.locator('input').press('Enter'); await expect(field.locator('.ant-select-selection-item')).toHaveCount(0); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts index 28a7e74d7e72..a8026937738b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts @@ -79,7 +79,9 @@ test.describe.serial('Add role and assign it to the user', () => { test('Create new user and assign new role to him', async ({ page }) => { await settingClick(page, GlobalSettingOptions.USERS); + const initialRolesResponse = page.waitForResponse('/api/v1/roles/search?*'); await page.click('[data-testid="add-user"]'); + await initialRolesResponse; await page.fill('[data-testid="email"]', user.email); await page.fill('[data-testid="displayName"]', userDisplayName); @@ -96,7 +98,9 @@ test.describe.serial('Add role and assign it to the user', () => { await page.locator('.ant-select-dropdown').waitFor({ state: 'visible', }); + const rolesSearchResponse = page.waitForResponse('/api/v1/roles/search?*'); await page.fill('#roles', roleName); + await rolesSearchResponse; await page.click(`[title="${roleName}"]`); await page.keyboard.press('Escape'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts index fbadd237db9f..0d0c9b920c50 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts @@ -2714,7 +2714,9 @@ test.describe('Domains Rbac', () => { // Add domain role to the user await visitUserProfilePage(page, user1.responseData.name); + const initialRolesResponse = page.waitForResponse('/api/v1/roles/search?*'); await page.getByTestId('edit-roles-button').click(); + await initialRolesResponse; await page.locator('[data-testid="user-profile-edit-popover"]').isVisible(); const rolesCombobox = page.locator('input[role="combobox"]').nth(1); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts index aa91c801aa27..b9b7a6bf06b0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts @@ -501,7 +501,11 @@ test.describe('User with different Roles', () => { await expect(adminPage.getByTestId('user-profile-roles')).toBeVisible(); + const initialRolesResponse = adminPage.waitForResponse( + '/api/v1/roles/search?*' + ); await adminPage.getByTestId('edit-roles-button').click(); + await initialRolesResponse; await expect( adminPage.getByTestId('profile-edit-roles-select') @@ -511,6 +515,15 @@ test.describe('User with different Roles', () => { state: 'visible', }); + await adminPage + .getByTestId('profile-edit-roles-select') + .locator('input') + .fill('Application'); + await adminPage.waitForResponse('/api/v1/roles/search?*'); + await adminPage + .locator('.ant-select-item-option-content') + .getByText('Application bot role', { exact: true }) + .waitFor({ state: 'visible' }); await adminPage .locator('.ant-select-item-option-content') .getByText('Application bot role', { exact: true }) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts index ecd114126fa2..caac21e988be 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts @@ -710,7 +710,7 @@ export const addUser = async ( await waitForAllLoadersToDisappear(page); await page.click('[data-testid="add-user"]'); - await page.waitForResponse('/api/v1/roles?default=false&limit=100&fields='); + await page.waitForResponse('/api/v1/roles/search?*'); await page.fill('[data-testid="email"]', email); await page.fill('[data-testid="displayName"]', name); @@ -726,7 +726,9 @@ export const addUser = async ( .getByRole('combobox'); await expect(rolesCombobox).toBeVisible({ timeout: 120000 }); await rolesCombobox.click(); + const rolesSearchResponse = page.waitForResponse('/api/v1/roles/search?*'); await rolesCombobox.fill(role); + await rolesSearchResponse; const roleOption = page .locator('.ant-select-item-option-content') .filter({ hasText: new RegExp(`^${role}$`) }) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.component.tsx index 1b3dee972fb6..367844008658 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.component.tsx @@ -14,16 +14,16 @@ import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; import { Button, Card, Col, Input, Row, Typography } from 'antd'; import { AxiosError } from 'axios'; -import { toLower } from 'lodash'; -import { FC, useEffect, useMemo, useState } from 'react'; +import { debounce, toLower, uniqBy } from 'lodash'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as IconBotProfile } from '../../../../assets/svg/bot-profile.svg'; -import { PAGE_SIZE_LARGE, TERM_ADMIN } from '../../../../constants/constants'; +import { TERM_ADMIN } from '../../../../constants/constants'; import { GlobalSettingOptions } from '../../../../constants/GlobalSettings.constants'; import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore'; import { EntityType } from '../../../../enums/entity.enum'; import { Role } from '../../../../generated/entity/teams/role'; -import { getAllRoles } from '../../../../rest/rolesAPIV1'; +import { searchRoles } from '../../../../rest/rolesAPIV1'; import { getEntityName } from '../../../../utils/EntityUtils'; import { getSettingPath } from '../../../../utils/RouterUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; @@ -48,6 +48,7 @@ const BotDetails: FC = ({ const [isDisplayNameEdit, setIsDisplayNameEdit] = useState(false); const [selectedRoles, setSelectedRoles] = useState>([]); const [roles, setRoles] = useState>([]); + const [isRolesLoading, setIsRolesLoading] = useState(false); const { getResourceLimit, config } = useLimitStore(); const [disableFields, setDisableFields] = useState(['token']); @@ -74,15 +75,29 @@ const BotDetails: FC = ({ } }; - const fetchRoles = async () => { + const fetchRoles = useCallback(async (query = '') => { + setIsRolesLoading(true); + try { - const data = await getAllRoles('', false, PAGE_SIZE_LARGE); - setRoles(data); + const data = await searchRoles(query); + setRoles((prevRoles) => { + const selectedRoleOptions = prevRoles.filter((role) => + selectedRoles.includes(role.id) + ); + + return uniqBy([...selectedRoleOptions, ...data], 'id'); + }); } catch (err) { - setRoles([]); showErrorToast(err as AxiosError); + } finally { + setIsRolesLoading(false); } - }; + }, [selectedRoles]); + + const debouncedFetchRoles = useMemo( + () => debounce(fetchRoles, 300), + [fetchRoles] + ); const onDisplayNameChange = (e: React.ChangeEvent) => { setDisplayName(e.target.value); @@ -190,7 +205,9 @@ const BotDetails: FC = ({ setSelectedRoles(selectedRoles) @@ -211,8 +228,27 @@ const BotDetails: FC = ({ initLimits(); }, []); + useEffect(() => { + return () => { + debouncedFetchRoles.cancel(); + }; + }, [debouncedFetchRoles]); + useEffect(() => { prepareSelectedRoles(); + setRoles((prevRoles) => + uniqBy( + [ + ...prevRoles, + ...((botUserData.roles ?? []).map((role) => ({ + id: role.id, + name: role.name ?? '', + displayName: role.displayName, + })) as Role[]), + ], + 'id' + ) + ); }, [botUserData]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.test.tsx index 1ac3b8620a4e..268cac3d4cad 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Bot/BotDetails/BotDetails.test.tsx @@ -14,6 +14,7 @@ import { act, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { OperationPermission } from '../../../../context/PermissionProvider/PermissionProvider.interface'; +import { searchRoles } from '../../../../rest/rolesAPIV1'; import { getAuthMechanismForBotUser } from '../../../../rest/userAPI'; import AccessTokenCard from '../../Users/AccessTokenCard/AccessTokenCard.component'; import BotDetails from './BotDetails.component'; @@ -93,6 +94,10 @@ jest.mock('../../../../utils/PermissionsUtils', () => ({ checkPermission: jest.fn().mockReturnValue(true), })); +jest.mock('../../../../rest/rolesAPIV1', () => ({ + searchRoles: jest.fn().mockResolvedValue([]), +})); + const mockGetResourceLimit = jest.fn().mockResolvedValue({ configuredLimit: { disabledFields: [] }, }); @@ -146,6 +151,10 @@ jest.mock('../../../../context/LimitsProvider/useLimitsStore', () => ({ })); describe('Test BotsDetail Component', () => { + beforeEach(() => { + (searchRoles as jest.Mock).mockResolvedValue([]); + }); + it('Should render all child elements', async () => { await act(async () => { render(, { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/CreateUser/CreateUser.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/CreateUser/CreateUser.component.tsx index 32fa737ec8fb..c4afeafaa573 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/CreateUser/CreateUser.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/CreateUser/CreateUser.component.tsx @@ -24,8 +24,8 @@ import { Switch, } from 'antd'; import { AxiosError } from 'axios'; -import { compact, isEmpty, isUndefined, map, trim } from 'lodash'; -import { useEffect, useMemo, useState } from 'react'; +import { compact, debounce, isEmpty, isUndefined, map, trim, uniqBy } from 'lodash'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; import { ReactComponent as IconSync } from '../../../../assets/svg/ic-sync.svg'; @@ -55,8 +55,8 @@ import { } from '../../../../interface/FormUtils.interface'; import { generateRandomPwd } from '../../../../rest/auth-API'; import { getAllPersonas } from '../../../../rest/PersonaAPI'; +import { searchRoles } from '../../../../rest/rolesAPIV1'; import { getJWTTokenExpiryOptions } from '../../../../utils/BotsUtils'; -import { handleSearchFilterOption } from '../../../../utils/CommonUtils'; import { getEntityName, getEntityReferenceListFromEntities, @@ -72,7 +72,6 @@ import TeamsSelectable from '../../Team/TeamsSelectable/TeamsSelectable'; import { CreateUserProps } from './CreateUser.interface'; const CreateUser = ({ - roles, isLoading, onCancel, onSave, @@ -92,6 +91,10 @@ const CreateUser = ({ const [selectedTeams, setSelectedTeams] = useState< Array >([]); + const [roleOptions, setRoleOptions] = useState< + Array<{ label: string; value: string }> + >([]); + const [isRolesLoading, setIsRolesLoading] = useState(false); const [isPasswordGenerating, setIsPasswordGenerating] = useState(false); const { activeDomainEntityRef } = useDomainStore(); const selectedDomain = @@ -135,12 +138,33 @@ const CreateUser = ({ const selectedRoles = Form.useWatch('roles', form); const selectedPersonas = Form.useWatch('personas', form); - const roleOptions = useMemo(() => { - return map(roles, (role) => ({ - label: getEntityName(role), - value: role.id, - })); - }, [roles]); + const fetchRoleOptions = useCallback(async (searchText = '') => { + setIsRolesLoading(true); + + try { + const roles = await searchRoles(searchText); + const nextOptions = map(roles, (role) => ({ + label: getEntityName(role), + value: role.id, + })); + + setRoleOptions((prevOptions) => { + const selectedRoleOptions = prevOptions.filter((option) => + (selectedRoles ?? []).includes(String(option.value)) + ); + + return uniqBy([...selectedRoleOptions, ...nextOptions], 'value'); + }); + } catch (error) { + showErrorToast( + error as AxiosError, + t('server.entity-fetch-error', { entity: t('label.role-plural') }) + ); + setRoleOptions([]); + } finally { + setIsRolesLoading(false); + } + }, [selectedRoles, t]); const fetchPersonaOptions = async (_searchText: string, page?: number) => { try { @@ -264,6 +288,23 @@ const CreateUser = ({ generateRandomPassword(); }, []); + useEffect(() => { + if (!forceBot && !isAdminPage) { + fetchRoleOptions(); + } + }, [forceBot, isAdminPage]); + + const debouncedFetchRoleOptions = useMemo( + () => debounce(fetchRoleOptions, 300), + [fetchRoleOptions] + ); + + useEffect(() => { + return () => { + debouncedFetchRoleOptions.cancel(); + }; + }, [debouncedFetchRoleOptions]); + return (
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx index 17b1c40a7473..55c058ce6c10 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx @@ -49,6 +49,7 @@ const UserProfileRoles = ({ const [isLoading, setIsLoading] = useState(false); const [isRolesLoading, setIsRolesLoading] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const selectedRolesRef = useRef([]); const useRolesOption = useMemo(() => { const options = roles?.map((role) => ({ @@ -66,29 +67,32 @@ const UserProfileRoles = ({ return options; }, [roles, isUserAdmin, getEntityName]); - const fetchRoles = useCallback(async (query = '') => { - setIsRolesLoading(true); - - try { - const response = await searchRoles(query); - setRoles((prevRoles) => { - const selectedRoleOptions = prevRoles.filter((role) => - selectedRoles.includes(role.id) + const fetchRoles = useCallback( + async (query = '') => { + setIsRolesLoading(true); + + try { + const response = await searchRoles(query); + setRoles((prevRoles) => { + const selectedRoleOptions = prevRoles.filter((role) => + selectedRolesRef.current.includes(role.id) + ); + + return uniqBy([...selectedRoleOptions, ...response], 'id'); + }); + } catch (err) { + showErrorToast( + err as AxiosError, + t('server.entity-fetch-error', { + entity: t('label.role-plural'), + }) ); - - return uniqBy([...selectedRoleOptions, ...response], 'id'); - }); - } catch (err) { - showErrorToast( - err as AxiosError, - t('server.entity-fetch-error', { - entity: t('label.role-plural'), - }) - ); - } finally { - setIsRolesLoading(false); - } - }, [selectedRoles, t]); + } finally { + setIsRolesLoading(false); + } + }, + [t] + ); const debouncedFetchRoles = useMemo( () => debounce(fetchRoles, 300), @@ -155,6 +159,10 @@ const UserProfileRoles = ({ setUserRoles(); }, [setUserRoles]); + useEffect(() => { + selectedRolesRef.current = selectedRoles; + }, [selectedRoles]); + useEffect(() => { setUserRoles(); }, [setUserRoles]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SettingsSso/SSOConfigurationForm/SsoRolesSelectField.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SettingsSso/SSOConfigurationForm/SsoRolesSelectField.tsx index dbaf7984d3a5..ca6646412767 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SettingsSso/SSOConfigurationForm/SsoRolesSelectField.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SettingsSso/SSOConfigurationForm/SsoRolesSelectField.tsx @@ -16,7 +16,7 @@ import { Col, Row, Select, Typography } from 'antd'; import { AxiosError } from 'axios'; import classNames from 'classnames'; import { debounce, startCase } from 'lodash'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { searchRoles } from '../../../rest/rolesAPIV1'; import { showErrorToast } from '../../../utils/ToastUtils'; @@ -28,6 +28,18 @@ const SsoRolesSelectField = (props: FieldProps) => { const [roleOptions, setRoleOptions] = useState< { label: string; value: string }[] >([]); + const id = props.idSchema.$id; + const value: string[] = props.formData ?? []; + const hasError = props.rawErrors && props.rawErrors.length > 0; + + const placeholder = + props.uiSchema?.['ui:placeholder'] ?? t('label.select-field'); + const searchStateRef = useRef({ + roleOptions: [] as { label: string; value: string }[], + value: [] as string[], + }); + + searchStateRef.current = { roleOptions, value }; useEffect(() => { searchRoles('') @@ -51,7 +63,8 @@ const SsoRolesSelectField = (props: FieldProps) => { label: role.displayName || role.name, value: role.name, })); - const selectedSet = new Set(value || []); + const { roleOptions, value } = searchStateRef.current; + const selectedSet = new Set(value); const kept = roleOptions.filter((option) => selectedSet.has(option.value) ); @@ -64,7 +77,7 @@ const SsoRolesSelectField = (props: FieldProps) => { showErrorToast(err as AxiosError); } }, 300), - [roleOptions, value] + [] ); useEffect(() => { @@ -73,13 +86,6 @@ const SsoRolesSelectField = (props: FieldProps) => { }; }, [debouncedSearchRoles]); - const id = props.idSchema.$id; - const value: string[] = props.formData ?? []; - const hasError = props.rawErrors && props.rawErrors.length > 0; - - const placeholder = - props.uiSchema?.['ui:placeholder'] ?? t('label.select-field'); - const handleChange = useCallback( (newValue: string[]) => { props.onChange(newValue); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/LdapRoleMappingWidget/LdapRoleMappingWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/LdapRoleMappingWidget/LdapRoleMappingWidget.tsx index 3beeb2eb4e78..d95d30731616 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/LdapRoleMappingWidget/LdapRoleMappingWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/LdapRoleMappingWidget/LdapRoleMappingWidget.tsx @@ -77,6 +77,12 @@ const LdapRoleMappingWidget: FC = (props) => { ); const isInitialMount = useRef(true); + const searchStateRef = useRef({ + availableRoles: [] as RoleOption[], + mappings: [] as RoleMappingEntry[], + }); + + searchStateRef.current = { availableRoles, mappings }; useEffect(() => { if (isInitialMount.current) { @@ -223,6 +229,7 @@ const LdapRoleMappingWidget: FC = (props) => { const results = await searchRoles(searchText); setSearchResults((prev) => { const next = new Map(prev); + const { availableRoles, mappings } = searchStateRef.current; const currentMapping = mappings.find( (mapping) => mapping.id === mappingId ); @@ -250,7 +257,7 @@ const LdapRoleMappingWidget: FC = (props) => { showErrorToast(err as AxiosError); } }, 300), - [availableRoles, mappings] + [] ); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/RolesCard/RolesCard.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/RolesCard/RolesCard.component.tsx index cb78a8ec80bd..b6aa3807f97b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/RolesCard/RolesCard.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/RolesCard/RolesCard.component.tsx @@ -104,6 +104,7 @@ const RolesCard = ({ {isRolesEdit ? (