diff --git a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx index 779daeff58..7a80e938b9 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx @@ -1,10 +1,15 @@ +import React, { useMemo, useCallback, MutableRefObject, memo } from 'react' import { FormGroup, Col } from 'Components' import { Typeahead } from 'react-bootstrap-typeahead' +import type { TypeaheadRef } from 'react-bootstrap-typeahead' import GluuLabel from '../Gluu/GluuLabel' import Typography from '@mui/material/Typography' import { createTheme, ThemeProvider } from '@mui/material/styles' import { useTranslation } from 'react-i18next' import customColors from '@/customColors' +import { FormikContextType } from 'formik' + +type Option = string | Record const theme = createTheme({ typography: { @@ -14,7 +19,32 @@ const theme = createTheme({ }, }) -function GluuTypeAhead({ +interface GluuTypeAheadProps { + label: string + labelKey?: string + name: string + value?: Option[] + options: Option[] + formik?: FormikContextType> | null + required?: boolean + doc_category?: string + doc_entry?: string + forwardRef?: MutableRefObject | null + onChange?: ((selected: Option[]) => void) | null + lsize?: number + rsize?: number + disabled?: boolean + showError?: boolean + errorMessage?: string + allowNew?: boolean + isLoading?: boolean + multiple?: boolean + hideHelperMessage?: boolean + minLength?: number + emptyLabel?: string +} + +const GluuTypeAhead = memo(function GluuTypeAhead({ label, labelKey, name, @@ -37,8 +67,30 @@ function GluuTypeAhead({ hideHelperMessage = false, minLength = 0, emptyLabel = 'fields.nothingToShowInTheList', -}: any) { +}: GluuTypeAheadProps) { const { t } = useTranslation() + + const selectedValue = useMemo( + () => (value !== undefined ? value : (formik?.values?.[name] as Option[]) || []), + [value, formik, name], + ) + + const handleChange = useCallback( + (selected: Option[]) => { + if (formik) { + formik.setFieldValue(name, selected) + if (onChange) { + onChange(selected) + } + } else if (onChange) { + onChange(selected) + } + }, + [formik, name, onChange], + ) + + const resolvedLabelKey = useMemo(() => labelKey || name, [labelKey, name]) + return ( {required ? ( @@ -63,23 +115,14 @@ function GluuTypeAhead({ disabled={disabled} ref={forwardRef} emptyLabel={t(emptyLabel)} - labelKey={labelKey || name} + labelKey={resolvedLabelKey} isLoading={isLoading} minLength={minLength} - onChange={(selected) => { - if (formik) { - formik.setFieldValue(name, selected) - if (onChange) { - onChange(selected) - } - } else if (onChange) { - onChange(selected) - } - }} + onChange={handleChange} id={name} data-testid={name} multiple={multiple} - selected={value || []} + selected={selectedValue} options={options} /> {!hideHelperMessage && ( @@ -93,5 +136,6 @@ function GluuTypeAhead({ ) -} +}) + export default GluuTypeAhead diff --git a/admin-ui/plugins/admin/components/Mapping/MappingAddDialogForm.js b/admin-ui/plugins/admin/components/Mapping/MappingAddDialogForm.js index 43b393a6c4..5639cd25be 100644 --- a/admin-ui/plugins/admin/components/Mapping/MappingAddDialogForm.js +++ b/admin-ui/plugins/admin/components/Mapping/MappingAddDialogForm.js @@ -22,15 +22,32 @@ const MappingAddDialogForm = ({ handler, modal, onAccept, roles, mapping = [] }) const theme = useContext(ThemeContext) const selectedTheme = theme.state.theme - const getPermissionsForSearch = () => { - const filteredArr = [] + useEffect(() => { + if (!modal) { + setApiRole('') + setSelectedPermissions([]) + setActive(false) + } + }, [modal]) + + const getPermissionsForSearch = (role = '') => { + const fullPermissions = [] for (const i in permissions) { - filteredArr.push(permissions[i].permission) + if (permissions[i]?.permission) { + fullPermissions.push(permissions[i].permission) + } } - setSearchAblePermissions(filteredArr) + if (role) { + const roleMapping = (mapping || []).find((m) => m?.role === role) + const assigned = new Set(roleMapping?.permissions || []) + const filtered = fullPermissions.filter((p) => !assigned.has(p)) + setSearchAblePermissions(filtered) + return + } + setSearchAblePermissions(fullPermissions) } useEffect(() => { - getPermissionsForSearch() + getPermissionsForSearch(apiRole) }, [permissions]) useEffect(() => { @@ -42,19 +59,18 @@ const MappingAddDialogForm = ({ handler, modal, onAccept, roles, mapping = [] }) }, [apiRole, selectedPermissions]) useEffect(() => { - const addedRoles = [] - for (const i in mapping) { - addedRoles.push(mapping[i].role) - } + getPermissionsForSearch(apiRole) + }, [apiRole, mapping]) + useEffect(() => { const rolesArr = [] for (const i in roles) { - if (!addedRoles.includes(roles[i].role)) { + if (roles[i]?.role) { rolesArr.push(roles[i].role) } } setAutoCompleteRoles(rolesArr) - }, [roles, mapping]) + }, [roles]) function handleAccept() { const roleData = {} @@ -90,8 +106,9 @@ const MappingAddDialogForm = ({ handler, modal, onAccept, roles, mapping = [] }) setSelectedPermissions(selected) }} options={searchablePermissions} + allowNew={false} + value={selectedPermissions} required={false} - value={[]} forwardRef={autocompleteRef} doc_category={'Mapping'} > @@ -103,7 +120,8 @@ const MappingAddDialogForm = ({ handler, modal, onAccept, roles, mapping = [] }) style={applicationStyle.buttonStyle} onClick={handleAccept} > - {t('actions.yes')} + + {t('actions.add')} )}{' '} diff --git a/admin-ui/plugins/admin/components/Mapping/MappingItem.js b/admin-ui/plugins/admin/components/Mapping/MappingItem.js index b636682268..a947c6c9ec 100644 --- a/admin-ui/plugins/admin/components/Mapping/MappingItem.js +++ b/admin-ui/plugins/admin/components/Mapping/MappingItem.js @@ -100,7 +100,7 @@ function MappingItem({ candidate, roles }) { getPermissionsForSearch() }, [permissions, candidate?.permissions?.length, cedarPermissions]) - const initialValues = {} + const initialValues = { mappingAddPermissions: [] } const handleAddPermission = (values, { resetForm }) => { if (values?.mappingAddPermissions?.length) { @@ -200,7 +200,6 @@ function MappingItem({ candidate, roles }) { formik={formik} options={searchablePermissions} required={false} - value={[]} forwardRef={autocompleteRef} doc_category={'Mapping'} allowNew={false} diff --git a/admin-ui/plugins/admin/components/Mapping/MappingPage.js b/admin-ui/plugins/admin/components/Mapping/MappingPage.js index 40da193ee7..335a5952cd 100644 --- a/admin-ui/plugins/admin/components/Mapping/MappingPage.js +++ b/admin-ui/plugins/admin/components/Mapping/MappingPage.js @@ -61,10 +61,14 @@ function MappingPage() { } function onAddConfirmed(mappingData) { - buildPayload(userAction, 'Add new mapping', mappingData) - dispatch(addNewRolePermissions({ data: mappingData })) + const existing = (mapping || []).find((m) => m?.role === mappingData.role) + const mergedPermissions = Array.from( + new Set([...(existing?.permissions || []), ...(mappingData?.permissions || [])]), + ) + const payload = { role: mappingData.role, permissions: mergedPermissions } + buildPayload(userAction, 'Add new mapping', payload) + dispatch(addNewRolePermissions({ data: payload })) toggle() - // doFetchList() } function doFetchList() {