diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 261600110..a5cab3353 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -503,6 +503,8 @@ message.delete-group-body=You are requesting to delete a group. If you complete message.delete-attribute-title=Delete Attribute? message.delete-attribute-body=You are requesting to delete a custom attribute. If you complete this process the attribute will be removed. This cannot be undone. Do you wish to continue? +message.group-pattern-fail=Pattern must match group url validation pattern. + message.must-be-unique=Must be unique. message.must-be-number=Must be a number. message.name-must-be-unique=Name must be unique. diff --git a/ui/src/app/admin/hooks.js b/ui/src/app/admin/hooks.js index 483f1edeb..f0a768cdc 100644 --- a/ui/src/app/admin/hooks.js +++ b/ui/src/app/admin/hooks.js @@ -22,9 +22,7 @@ export function useGroupUiSchema () { } export function useGroupUiValidator() { - console.log('hi') return (formData, errors) => { - console.log(formData, errors) if (!isNil(formData?.validationRegex)) { const isValid = isValidRegex(formData.validationRegex); if (!isValid) { diff --git a/ui/src/app/admin/hooks.test.js b/ui/src/app/admin/hooks.test.js new file mode 100644 index 000000000..303540317 --- /dev/null +++ b/ui/src/app/admin/hooks.test.js @@ -0,0 +1,9 @@ +import { useGroupUiValidator } from './hooks'; + +it('should validate against a regex', () => { + const validator = useGroupUiValidator(); + const addErrorSpy = jest.fn(); + const fail = validator({ validationRegex: '))(()' }, { validationRegex: { addError: addErrorSpy } }); + expect(addErrorSpy).toHaveBeenCalled(); + expect(validator({validationRegex: '/*'})).toBeUndefined(); +}); \ No newline at end of file diff --git a/ui/src/app/core/user/UserContext.js b/ui/src/app/core/user/UserContext.js index d656ac4a4..064c1e65f 100644 --- a/ui/src/app/core/user/UserContext.js +++ b/ui/src/app/core/user/UserContext.js @@ -7,23 +7,33 @@ const UserContext = React.createContext(); const { Provider, Consumer } = UserContext; const path = '/admin/users/current'; +const group = { + "name": "ADMIN-GROUP", + "resourceId": "admingroup", + "validationRegex": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$", + "ownerType": "GROUP", + "ownerId": "admingroup" +}; /*eslint-disable react-hooks/exhaustive-deps*/ function UserProvider({ children }) { const { get, response } = useFetch(`${API_BASE_PATH}`, { - cacheLife: 10000, - cachePolicy: 'cache-first' + cachePolicy: 'no-cache' }); React.useEffect(() => { loadUser() }, []); async function loadUser() { const user = await get(`${path}`); - if (response.ok) setUser(user); + if (response.ok) setUser({ + ...user, + group + }); } const [user, setUser] = React.useState({}); + return ( {children} ); @@ -50,5 +60,24 @@ function useIsAdminOrInGroup() { return isAdmin || isInGroup; } +function useUserGroup() { + const user = useCurrentUser(); + return user?.group; +} + +function useUserGroupRegexValidator () { + const user = useCurrentUser(); + return user?.group?.validationRegex; +} + -export { UserContext, UserProvider, Consumer as UserConsumer, useCurrentUser, useIsAdmin, useIsAdminOrInGroup }; \ No newline at end of file +export { + UserContext, + UserProvider, + Consumer as UserConsumer, + useCurrentUser, + useIsAdmin, + useIsAdminOrInGroup, + useUserGroupRegexValidator, + useUserGroup +}; \ No newline at end of file diff --git a/ui/src/app/form/component/fields/FilterTargetField.js b/ui/src/app/form/component/fields/FilterTargetField.js index 959306b71..9aaf84b97 100644 --- a/ui/src/app/form/component/fields/FilterTargetField.js +++ b/ui/src/app/form/component/fields/FilterTargetField.js @@ -45,8 +45,13 @@ const FilterTargetField = ({ onChange, errorSchema, formData, + formContext, ...props }) => { + + const { group } = formContext; + const regex = new RegExp(group?.validationRegex || '/*'); + const typeFieldName = `${name}Type`; const type = schema.properties[typeFieldName]; @@ -59,6 +64,8 @@ const FilterTargetField = ({ const [selectedTarget, setSelectedTarget] = React.useState([...(formData.value && !isNil(formData.value) && !isNil(formData.value[0]) ? formData.value : [])]); const [term, setSearchTerm] = React.useState(''); + const [match, setMatch] = React.useState(true); + const [touched, setTouched] = React.useState(false); const [ids, setSearchIds] = React.useState([]); const { get, response } = useFetch(`${API_BASE_PATH}/EntityIds/search`, { @@ -113,6 +120,7 @@ const FilterTargetField = ({ const onEntityIdsChange = (value) => { setSearchTerm(value); + setMatch(regex ? regex.test(value) : true); }; const selectType = (option) => { @@ -172,19 +180,28 @@ const FilterTargetField = ({ onInputChange={onEntityIdsChange} selected={ [term] } onChange={ () => {} } + onBlur={() => setTouched(true)} onSearch={ (query) => setSearchTerm(query) } - renderMenuItemChildren={(option, { options, text }, index) => { - return {option}; - }}> + renderMenuItemChildren={(option, { options, text }, index) => + {option} + }> {({ isMenuShown, toggleMenu }) => ( toggleMenu()} disabled={disabled || readonly} /> )} - - - You must add at least one entity id target and they must each be unique. - - + {(!touched || match) ? + + + You must add at least one entity id target and they must each be unique. + + + : + + + Invalid URL + + + } } { targetType === 'CONDITION_SCRIPT' && @@ -232,7 +249,7 @@ const FilterTargetField = ({