From ba8cf1aaf233a92760f3e012471a311f268ce457 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Fri, 4 Dec 2020 16:28:48 -0500 Subject: [PATCH 1/3] Add all LDAP (Default-5) setting forms --- .../src/screens/Setting/LDAP/LDAP.test.jsx | 18 +- .../Setting/LDAP/LDAPEdit/LDAPEdit.jsx | 229 +++++++++++++++-- .../Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx | 232 +++++++++++++++++- .../shared/data.ldapDefaultSettings.json | 134 ++++++++++ .../Setting/shared/data.ldapSettings.json | 2 +- 5 files changed, 587 insertions(+), 28 deletions(-) create mode 100644 awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAP.test.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAP.test.jsx index bcc6c60be0..88ca337048 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAP.test.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAP.test.jsx @@ -6,12 +6,13 @@ import { waitForElement, } from '../../../../testUtils/enzymeHelpers'; import { SettingsAPI } from '../../../api'; +import { SettingsProvider } from '../../../contexts/Settings'; +import mockAllOptions from '../shared/data.allSettingOptions.json'; +import mockLDAP from '../shared/data.ldapSettings.json'; import LDAP from './LDAP'; jest.mock('../../../api/models/Settings'); -SettingsAPI.readCategory.mockResolvedValue({ - data: {}, -}); +SettingsAPI.readCategory.mockResolvedValue({ data: mockLDAP }); describe('', () => { let wrapper; @@ -39,9 +40,14 @@ describe('', () => { initialEntries: ['/settings/ldap/default/edit'], }); await act(async () => { - wrapper = mountWithContexts(, { - context: { router: { history } }, - }); + wrapper = mountWithContexts( + + + , + { + context: { router: { history } }, + } + ); }); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); expect(wrapper.find('LDAPEdit').length).toBe(1); diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx index 084df200ed..4e6dbe4e6e 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx @@ -1,25 +1,218 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { Button } from '@patternfly/react-core'; -import { CardBody, CardActionsRow } from '../../../../components/Card'; +import React, { useCallback, useEffect } from 'react'; +import { useHistory, useRouteMatch } from 'react-router-dom'; +import { Formik } from 'formik'; +import { Form } from '@patternfly/react-core'; +import { CardBody } from '../../../../components/Card'; +import ContentError from '../../../../components/ContentError'; +import ContentLoading from '../../../../components/ContentLoading'; +import { FormSubmitError } from '../../../../components/FormField'; +import { FormColumnLayout } from '../../../../components/FormLayout'; +import { useSettings } from '../../../../contexts/Settings'; +import { RevertAllAlert, RevertFormActionGroup } from '../../shared'; +import { + BooleanField, + ChoiceField, + EncryptedField, + InputField, + ObjectField, +} from '../../shared/SharedFields'; +import { formatJson } from '../../shared/settingUtils'; +import useModal from '../../../../util/useModal'; +import useRequest from '../../../../util/useRequest'; +import { SettingsAPI } from '../../../../api'; + +function LDAPEdit() { + const history = useHistory(); + const { isModalOpen, toggleModal, closeModal } = useModal(); + const { PUT: options } = useSettings(); + const { + params: { category }, + } = useRouteMatch('/settings/ldap/:category/edit'); + const ldapCategory = + category === 'default' ? 'AUTH_LDAP_' : `AUTH_LDAP_${category}_`; + + const { isLoading, error, request: fetchLDAP, result: ldap } = useRequest( + useCallback(async () => { + const { data } = await SettingsAPI.readCategory('ldap'); + + const mergedData = {}; + Object.keys(data).forEach(key => { + if (!options[key]) { + return; + } + mergedData[key] = options[key]; + mergedData[key].value = data[key]; + }); + + return mergedData; + }, [options]), + null + ); + + useEffect(() => { + fetchLDAP(); + }, [fetchLDAP]); + + const { error: submitError, request: submitForm } = useRequest( + useCallback( + async values => { + await SettingsAPI.updateAll(values); + history.push(`/settings/ldap/${category}/details`); + }, + [history, category] + ), + null + ); + + const handleSubmit = async form => { + await submitForm({ + [`${ldapCategory}BIND_DN`]: form[`${ldapCategory}BIND_DN`], + [`${ldapCategory}BIND_PASSWORD`]: form[`${ldapCategory}BIND_PASSWORD`], + [`${ldapCategory}DENY_GROUP`]: form[`${ldapCategory}DENY_GROUP`], + [`${ldapCategory}GROUP_TYPE`]: form[`${ldapCategory}GROUP_TYPE`], + [`${ldapCategory}REQUIRE_GROUP`]: form[`${ldapCategory}REQUIRE_GROUP`], + [`${ldapCategory}SERVER_URI`]: form[`${ldapCategory}SERVER_URI`], + [`${ldapCategory}START_TLS`]: form[`${ldapCategory}START_TLS`], + [`${ldapCategory}USER_DN_TEMPLATE`]: form[ + `${ldapCategory}USER_DN_TEMPLATE` + ], + [`${ldapCategory}GROUP_SEARCH`]: formatJson( + form[`${ldapCategory}GROUP_SEARCH`] + ), + [`${ldapCategory}GROUP_TYPE_PARAMS`]: formatJson( + form[`${ldapCategory}GROUP_TYPE_PARAMS`] + ), + [`${ldapCategory}ORGANIZATION_MAP`]: formatJson( + form[`${ldapCategory}ORGANIZATION_MAP`] + ), + [`${ldapCategory}TEAM_MAP`]: formatJson(form[`${ldapCategory}TEAM_MAP`]), + [`${ldapCategory}USER_ATTR_MAP`]: formatJson( + form[`${ldapCategory}USER_ATTR_MAP`] + ), + [`${ldapCategory}USER_FLAGS_BY_GROUP`]: formatJson( + form[`${ldapCategory}USER_FLAGS_BY_GROUP`] + ), + [`${ldapCategory}USER_SEARCH`]: formatJson( + form[`${ldapCategory}USER_SEARCH`] + ), + }); + }; + + const handleRevertAll = async () => { + const defaultValues = Object.assign( + ...Object.entries(ldap).map(([key, value]) => ({ + [key]: value.default, + })) + ); + await submitForm(defaultValues); + closeModal(); + }; + + const handleCancel = () => { + history.push(`/settings/ldap/${category}/details`); + }; + + const initialValues = fields => + Object.keys(fields).reduce((acc, key) => { + if (fields[key].type === 'list' || fields[key].type === 'nested object') { + const emptyDefault = fields[key].type === 'list' ? '[]' : '{}'; + acc[key] = fields[key].value + ? JSON.stringify(fields[key].value, null, 2) + : emptyDefault; + } else { + acc[key] = fields[key].value ?? ''; + } + return acc; + }, {}); -function LDAPEdit({ i18n }) { return ( - {i18n._(t`Edit form coming soon :)`)} - - - + {isLoading && } + {!isLoading && error && } + {!isLoading && ldap && ( + + {formik => ( +
+ + + + + + + + + + + + + + + + + {submitError && } + + + {isModalOpen && ( + + )} + + )} +
+ )}
); } -export default withI18n()(LDAPEdit); +export default LDAPEdit; diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx index 12ac75a6ed..c74fa67064 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx @@ -1,16 +1,242 @@ import React from 'react'; -import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; +import { useRouteMatch } from 'react-router-dom'; +import { + mountWithContexts, + waitForElement, +} from '../../../../../testUtils/enzymeHelpers'; +import mockAllOptions from '../../shared/data.allSettingOptions.json'; +import mockLDAP from '../../shared/data.ldapSettings.json'; +import mockLDAPDefault from '../../shared/data.ldapDefaultSettings.json'; +import { SettingsProvider } from '../../../../contexts/Settings'; +import { SettingsAPI } from '../../../../api'; import LDAPEdit from './LDAPEdit'; +jest.mock('../../../../api/models/Settings'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useRouteMatch: jest.fn(), +})); +SettingsAPI.readCategory.mockResolvedValue({ data: mockLDAP }); + describe('', () => { let wrapper; - beforeEach(() => { - wrapper = mountWithContexts(); + let history; + + beforeEach(async () => { + history = createMemoryHistory({ + initialEntries: ['/settings/ldap/default/edit'], + }); + useRouteMatch.mockImplementation(() => ({ + url: '/settings/ldap/default/edit', + path: '/settings/ldap/:category/edit', + params: { category: 'default' }, + })); + await act(async () => { + wrapper = mountWithContexts( + + + , + { + context: { router: { history } }, + } + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); }); + afterEach(() => { wrapper.unmount(); + jest.clearAllMocks(); }); + test('initially renders without crashing', () => { expect(wrapper.find('LDAPEdit').length).toBe(1); }); + + test('should display expected form fields', async () => { + expect(wrapper.find('FormGroup[label="LDAP Server URI"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Bind DN"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Bind Password"]').length).toBe( + 1 + ); + expect(wrapper.find('FormGroup[label="LDAP User Search"]').length).toBe(1); + expect( + wrapper.find('FormGroup[label="LDAP User DN Template"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="LDAP User Attribute Map"]').length + ).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Group Search"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Group Type"]').length).toBe(1); + expect( + wrapper.find('FormGroup[label="LDAP Group Type Parameters"]').length + ).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Require Group"]').length).toBe( + 1 + ); + expect(wrapper.find('FormGroup[label="LDAP Deny Group"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Start TLS"]').length).toBe(1); + expect( + wrapper.find('FormGroup[label="LDAP User Flags By Group"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="LDAP Organization Map"]').length + ).toBe(1); + expect(wrapper.find('FormGroup[label="LDAP Team Map"]').length).toBe(1); + expect( + wrapper.find('FormGroup[fieldId="AUTH_LDAP_SERVER_URI"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[fieldId="AUTH_LDAP_5_SERVER_URI"]').length + ).toBe(0); + }); + + test('should successfully send default values to api on form revert all', async () => { + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0); + expect(wrapper.find('RevertAllAlert')).toHaveLength(0); + await act(async () => { + wrapper + .find('button[aria-label="Revert all to default"]') + .invoke('onClick')(); + }); + wrapper.update(); + expect(wrapper.find('RevertAllAlert')).toHaveLength(1); + await act(async () => { + wrapper + .find('RevertAllAlert button[aria-label="Confirm revert all"]') + .invoke('onClick')(); + }); + wrapper.update(); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith(mockLDAPDefault); + }); + + test('should successfully send request to api on form submission', async () => { + act(() => { + wrapper + .find( + 'FormGroup[fieldId="AUTH_LDAP_BIND_PASSWORD"] button[aria-label="Revert"]' + ) + .invoke('onClick')(); + wrapper + .find( + 'FormGroup[fieldId="AUTH_LDAP_BIND_DN"] button[aria-label="Revert"]' + ) + .invoke('onClick')(); + wrapper.find('input#AUTH_LDAP_SERVER_URI').simulate('change', { + target: { + value: 'ldap://mock.example.com', + name: 'AUTH_LDAP_SERVER_URI', + }, + }); + wrapper.find('CodeMirrorInput#AUTH_LDAP_TEAM_MAP').invoke('onChange')( + '{\n"LDAP Sales":{\n"organization":\n"mock org"\n}\n}' + ); + }); + wrapper.update(); + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + AUTH_LDAP_BIND_DN: '', + AUTH_LDAP_BIND_PASSWORD: '', + AUTH_LDAP_DENY_GROUP: '', + AUTH_LDAP_GROUP_SEARCH: [], + AUTH_LDAP_GROUP_TYPE: 'MemberDNGroupType', + AUTH_LDAP_GROUP_TYPE_PARAMS: { name_attr: 'cn', member_attr: 'member' }, + AUTH_LDAP_ORGANIZATION_MAP: {}, + AUTH_LDAP_REQUIRE_GROUP: 'CN=Tower Users,OU=Users,DC=example,DC=com', + AUTH_LDAP_SERVER_URI: 'ldap://mock.example.com', + AUTH_LDAP_START_TLS: false, + AUTH_LDAP_USER_ATTR_MAP: {}, + AUTH_LDAP_USER_DN_TEMPLATE: 'uid=%(user)s,OU=Users,DC=example,DC=com', + AUTH_LDAP_USER_FLAGS_BY_GROUP: {}, + AUTH_LDAP_USER_SEARCH: [], + AUTH_LDAP_TEAM_MAP: { + 'LDAP Sales': { + organization: 'mock org', + }, + }, + }); + }); + test('should navigate to ldap default detail on successful submission', async () => { + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(history.location.pathname).toEqual('/settings/ldap/default/details'); + }); + + test('should navigate to ldap default detail when cancel is clicked', async () => { + await act(async () => { + wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); + }); + expect(history.location.pathname).toEqual('/settings/ldap/default/details'); + }); + + test('should display error message on unsuccessful submission', async () => { + const error = { + response: { + data: { detail: 'An error occurred' }, + }, + }; + SettingsAPI.updateAll.mockImplementation(() => Promise.reject(error)); + expect(wrapper.find('FormSubmitError').length).toBe(0); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0); + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + wrapper.update(); + expect(wrapper.find('FormSubmitError').length).toBe(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); + }); + + test('should display ContentError on throw', async () => { + SettingsAPI.readCategory.mockImplementationOnce(() => + Promise.reject(new Error()) + ); + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + expect(wrapper.find('ContentError').length).toBe(1); + }); + + test('should display ldap category 5 edit form', async () => { + history = createMemoryHistory({ + initialEntries: ['/settings/ldap/5/edit'], + }); + useRouteMatch.mockImplementation(() => ({ + url: '/settings/ldap/5/edit', + path: '/settings/ldap/:category/edit', + params: { category: '5' }, + })); + await act(async () => { + wrapper = mountWithContexts( + + + , + { + context: { router: { history } }, + } + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + expect( + wrapper.find('FormGroup[fieldId="AUTH_LDAP_SERVER_URI"]').length + ).toBe(0); + expect( + wrapper.find('FormGroup[fieldId="AUTH_LDAP_5_SERVER_URI"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[fieldId="AUTH_LDAP_5_SERVER_URI"] input').props() + .value + ).toEqual('ldap://ldap5.example.com'); + }); }); diff --git a/awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json b/awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json new file mode 100644 index 0000000000..29c09e52c4 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json @@ -0,0 +1,134 @@ +{ + "AUTH_LDAP_1_BIND_DN": "", + "AUTH_LDAP_1_BIND_PASSWORD": "", + "AUTH_LDAP_1_CONNECTION_OPTIONS": { + "OPT_NETWORK_TIMEOUT": 30, + "OPT_REFERRALS": 0 + }, + "AUTH_LDAP_1_DENY_GROUP": null, + "AUTH_LDAP_1_GROUP_SEARCH": [], + "AUTH_LDAP_1_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_1_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_1_ORGANIZATION_MAP": {}, + "AUTH_LDAP_1_REQUIRE_GROUP": null, + "AUTH_LDAP_1_SERVER_URI": "", + "AUTH_LDAP_1_START_TLS": false, + "AUTH_LDAP_1_TEAM_MAP": {}, + "AUTH_LDAP_1_USER_ATTR_MAP": {}, + "AUTH_LDAP_1_USER_DN_TEMPLATE": null, + "AUTH_LDAP_1_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_1_USER_SEARCH": [], + "AUTH_LDAP_2_BIND_DN": "", + "AUTH_LDAP_2_BIND_PASSWORD": "", + "AUTH_LDAP_2_CONNECTION_OPTIONS": { + "OPT_NETWORK_TIMEOUT": 30, + "OPT_REFERRALS": 0 + }, + "AUTH_LDAP_2_DENY_GROUP": null, + "AUTH_LDAP_2_GROUP_SEARCH": [], + "AUTH_LDAP_2_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_2_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_2_ORGANIZATION_MAP": {}, + "AUTH_LDAP_2_REQUIRE_GROUP": null, + "AUTH_LDAP_2_SERVER_URI": "", + "AUTH_LDAP_2_START_TLS": false, + "AUTH_LDAP_2_TEAM_MAP": {}, + "AUTH_LDAP_2_USER_ATTR_MAP": {}, + "AUTH_LDAP_2_USER_DN_TEMPLATE": null, + "AUTH_LDAP_2_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_2_USER_SEARCH": [], + "AUTH_LDAP_3_BIND_DN": "", + "AUTH_LDAP_3_BIND_PASSWORD": "", + "AUTH_LDAP_3_CONNECTION_OPTIONS": { + "OPT_NETWORK_TIMEOUT": 30, + "OPT_REFERRALS": 0 + }, + "AUTH_LDAP_3_DENY_GROUP": null, + "AUTH_LDAP_3_GROUP_SEARCH": [], + "AUTH_LDAP_3_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_3_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_3_ORGANIZATION_MAP": {}, + "AUTH_LDAP_3_REQUIRE_GROUP": null, + "AUTH_LDAP_3_SERVER_URI": "", + "AUTH_LDAP_3_START_TLS": false, + "AUTH_LDAP_3_TEAM_MAP": {}, + "AUTH_LDAP_3_USER_ATTR_MAP": {}, + "AUTH_LDAP_3_USER_DN_TEMPLATE": null, + "AUTH_LDAP_3_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_3_USER_SEARCH": [], + "AUTH_LDAP_4_BIND_DN": "", + "AUTH_LDAP_4_BIND_PASSWORD": "", + "AUTH_LDAP_4_CONNECTION_OPTIONS": { + "OPT_NETWORK_TIMEOUT": 30, + "OPT_REFERRALS": 0 + }, + "AUTH_LDAP_4_DENY_GROUP": null, + "AUTH_LDAP_4_GROUP_SEARCH": [], + "AUTH_LDAP_4_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_4_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_4_ORGANIZATION_MAP": {}, + "AUTH_LDAP_4_REQUIRE_GROUP": null, + "AUTH_LDAP_4_SERVER_URI": "", + "AUTH_LDAP_4_START_TLS": false, + "AUTH_LDAP_4_TEAM_MAP": {}, + "AUTH_LDAP_4_USER_ATTR_MAP": {}, + "AUTH_LDAP_4_USER_DN_TEMPLATE": null, + "AUTH_LDAP_4_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_4_USER_SEARCH": [], + "AUTH_LDAP_5_BIND_DN": "", + "AUTH_LDAP_5_BIND_PASSWORD": "", + "AUTH_LDAP_5_CONNECTION_OPTIONS": { + "OPT_NETWORK_TIMEOUT": 30, + "OPT_REFERRALS": 0 + }, + "AUTH_LDAP_5_DENY_GROUP": null, + "AUTH_LDAP_5_GROUP_SEARCH": [], + "AUTH_LDAP_5_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_5_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_5_ORGANIZATION_MAP": {}, + "AUTH_LDAP_5_REQUIRE_GROUP": null, + "AUTH_LDAP_5_SERVER_URI": "", + "AUTH_LDAP_5_START_TLS": false, + "AUTH_LDAP_5_TEAM_MAP": {}, + "AUTH_LDAP_5_USER_ATTR_MAP": {}, + "AUTH_LDAP_5_USER_DN_TEMPLATE": null, + "AUTH_LDAP_5_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_5_USER_SEARCH": [], + "AUTH_LDAP_BIND_DN": "", + "AUTH_LDAP_BIND_PASSWORD": "", + "AUTH_LDAP_CONNECTION_OPTIONS": { + "OPT_NETWORK_TIMEOUT": 30, + "OPT_REFERRALS": 0 + }, + "AUTH_LDAP_DENY_GROUP": null, + "AUTH_LDAP_GROUP_SEARCH": [], + "AUTH_LDAP_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_ORGANIZATION_MAP": {}, + "AUTH_LDAP_REQUIRE_GROUP": null, + "AUTH_LDAP_SERVER_URI": "", + "AUTH_LDAP_START_TLS": false, + "AUTH_LDAP_TEAM_MAP": {}, + "AUTH_LDAP_USER_ATTR_MAP": {}, + "AUTH_LDAP_USER_DN_TEMPLATE": null, + "AUTH_LDAP_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_USER_SEARCH": [] +} \ No newline at end of file diff --git a/awx/ui_next/src/screens/Setting/shared/data.ldapSettings.json b/awx/ui_next/src/screens/Setting/shared/data.ldapSettings.json index 161a96b8c5..dce6765491 100644 --- a/awx/ui_next/src/screens/Setting/shared/data.ldapSettings.json +++ b/awx/ui_next/src/screens/Setting/shared/data.ldapSettings.json @@ -109,7 +109,7 @@ "AUTH_LDAP_4_USER_FLAGS_BY_GROUP": {}, "AUTH_LDAP_4_ORGANIZATION_MAP": {}, "AUTH_LDAP_4_TEAM_MAP": {}, - "AUTH_LDAP_5_SERVER_URI": "", + "AUTH_LDAP_5_SERVER_URI": "ldap://ldap5.example.com", "AUTH_LDAP_5_BIND_DN": "", "AUTH_LDAP_5_BIND_PASSWORD": "", "AUTH_LDAP_5_START_TLS": false, From c90a45983781e1b90faa3c5665b628dc57c4d910 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Fri, 11 Dec 2020 15:48:51 -0500 Subject: [PATCH 2/3] Fix bug where revert all reverted all ldap categories --- .../Setting/LDAP/LDAPEdit/LDAPEdit.jsx | 31 +++- .../Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx | 27 +++- .../shared/data.ldapDefaultSettings.json | 134 ------------------ 3 files changed, 54 insertions(+), 138 deletions(-) delete mode 100644 awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx index 4e6dbe4e6e..f983907ec9 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx @@ -21,6 +21,15 @@ import useModal from '../../../../util/useModal'; import useRequest from '../../../../util/useRequest'; import { SettingsAPI } from '../../../../api'; +function filterByPrefix(data, prefix) { + return Object.keys(data) + .filter(key => key.includes(prefix)) + .reduce((obj, key) => { + obj[key] = data[key]; + return obj; + }, {}); +} + function LDAPEdit() { const history = useHistory(); const { isModalOpen, toggleModal, closeModal } = useModal(); @@ -44,8 +53,26 @@ function LDAPEdit() { mergedData[key].value = data[key]; }); - return mergedData; - }, [options]), + const allCategories = { + AUTH_LDAP_1_: filterByPrefix(mergedData, 'AUTH_LDAP_1_'), + AUTH_LDAP_2_: filterByPrefix(mergedData, 'AUTH_LDAP_2_'), + AUTH_LDAP_3_: filterByPrefix(mergedData, 'AUTH_LDAP_3_'), + AUTH_LDAP_4_: filterByPrefix(mergedData, 'AUTH_LDAP_4_'), + AUTH_LDAP_5_: filterByPrefix(mergedData, 'AUTH_LDAP_5_'), + AUTH_LDAP_: Object.assign({}, mergedData), + }; + Object.keys({ + ...allCategories.AUTH_LDAP_1_, + ...allCategories.AUTH_LDAP_2_, + ...allCategories.AUTH_LDAP_3_, + ...allCategories.AUTH_LDAP_4_, + ...allCategories.AUTH_LDAP_5_, + }).forEach(keyToOmit => { + delete allCategories.AUTH_LDAP_[keyToOmit]; + }); + + return allCategories[ldapCategory]; + }, [options, ldapCategory]), null ); diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx index c74fa67064..71f998e341 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.test.jsx @@ -8,7 +8,6 @@ import { } from '../../../../../testUtils/enzymeHelpers'; import mockAllOptions from '../../shared/data.allSettingOptions.json'; import mockLDAP from '../../shared/data.ldapSettings.json'; -import mockLDAPDefault from '../../shared/data.ldapDefaultSettings.json'; import { SettingsProvider } from '../../../../contexts/Settings'; import { SettingsAPI } from '../../../../api'; import LDAPEdit from './LDAPEdit'; @@ -110,7 +109,30 @@ describe('', () => { }); wrapper.update(); expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); - expect(SettingsAPI.updateAll).toHaveBeenCalledWith(mockLDAPDefault); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + AUTH_LDAP_BIND_DN: '', + AUTH_LDAP_BIND_PASSWORD: '', + AUTH_LDAP_CONNECTION_OPTIONS: { + OPT_NETWORK_TIMEOUT: 30, + OPT_REFERRALS: 0, + }, + AUTH_LDAP_DENY_GROUP: null, + AUTH_LDAP_GROUP_SEARCH: [], + AUTH_LDAP_GROUP_TYPE: 'MemberDNGroupType', + AUTH_LDAP_GROUP_TYPE_PARAMS: { + member_attr: 'member', + name_attr: 'cn', + }, + AUTH_LDAP_ORGANIZATION_MAP: {}, + AUTH_LDAP_REQUIRE_GROUP: null, + AUTH_LDAP_SERVER_URI: '', + AUTH_LDAP_START_TLS: false, + AUTH_LDAP_TEAM_MAP: {}, + AUTH_LDAP_USER_ATTR_MAP: {}, + AUTH_LDAP_USER_DN_TEMPLATE: null, + AUTH_LDAP_USER_FLAGS_BY_GROUP: {}, + AUTH_LDAP_USER_SEARCH: [], + }); }); test('should successfully send request to api on form submission', async () => { @@ -162,6 +184,7 @@ describe('', () => { }, }); }); + test('should navigate to ldap default detail on successful submission', async () => { await act(async () => { wrapper.find('Form').invoke('onSubmit')(); diff --git a/awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json b/awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json deleted file mode 100644 index 29c09e52c4..0000000000 --- a/awx/ui_next/src/screens/Setting/shared/data.ldapDefaultSettings.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "AUTH_LDAP_1_BIND_DN": "", - "AUTH_LDAP_1_BIND_PASSWORD": "", - "AUTH_LDAP_1_CONNECTION_OPTIONS": { - "OPT_NETWORK_TIMEOUT": 30, - "OPT_REFERRALS": 0 - }, - "AUTH_LDAP_1_DENY_GROUP": null, - "AUTH_LDAP_1_GROUP_SEARCH": [], - "AUTH_LDAP_1_GROUP_TYPE": "MemberDNGroupType", - "AUTH_LDAP_1_GROUP_TYPE_PARAMS": { - "member_attr": "member", - "name_attr": "cn" - }, - "AUTH_LDAP_1_ORGANIZATION_MAP": {}, - "AUTH_LDAP_1_REQUIRE_GROUP": null, - "AUTH_LDAP_1_SERVER_URI": "", - "AUTH_LDAP_1_START_TLS": false, - "AUTH_LDAP_1_TEAM_MAP": {}, - "AUTH_LDAP_1_USER_ATTR_MAP": {}, - "AUTH_LDAP_1_USER_DN_TEMPLATE": null, - "AUTH_LDAP_1_USER_FLAGS_BY_GROUP": {}, - "AUTH_LDAP_1_USER_SEARCH": [], - "AUTH_LDAP_2_BIND_DN": "", - "AUTH_LDAP_2_BIND_PASSWORD": "", - "AUTH_LDAP_2_CONNECTION_OPTIONS": { - "OPT_NETWORK_TIMEOUT": 30, - "OPT_REFERRALS": 0 - }, - "AUTH_LDAP_2_DENY_GROUP": null, - "AUTH_LDAP_2_GROUP_SEARCH": [], - "AUTH_LDAP_2_GROUP_TYPE": "MemberDNGroupType", - "AUTH_LDAP_2_GROUP_TYPE_PARAMS": { - "member_attr": "member", - "name_attr": "cn" - }, - "AUTH_LDAP_2_ORGANIZATION_MAP": {}, - "AUTH_LDAP_2_REQUIRE_GROUP": null, - "AUTH_LDAP_2_SERVER_URI": "", - "AUTH_LDAP_2_START_TLS": false, - "AUTH_LDAP_2_TEAM_MAP": {}, - "AUTH_LDAP_2_USER_ATTR_MAP": {}, - "AUTH_LDAP_2_USER_DN_TEMPLATE": null, - "AUTH_LDAP_2_USER_FLAGS_BY_GROUP": {}, - "AUTH_LDAP_2_USER_SEARCH": [], - "AUTH_LDAP_3_BIND_DN": "", - "AUTH_LDAP_3_BIND_PASSWORD": "", - "AUTH_LDAP_3_CONNECTION_OPTIONS": { - "OPT_NETWORK_TIMEOUT": 30, - "OPT_REFERRALS": 0 - }, - "AUTH_LDAP_3_DENY_GROUP": null, - "AUTH_LDAP_3_GROUP_SEARCH": [], - "AUTH_LDAP_3_GROUP_TYPE": "MemberDNGroupType", - "AUTH_LDAP_3_GROUP_TYPE_PARAMS": { - "member_attr": "member", - "name_attr": "cn" - }, - "AUTH_LDAP_3_ORGANIZATION_MAP": {}, - "AUTH_LDAP_3_REQUIRE_GROUP": null, - "AUTH_LDAP_3_SERVER_URI": "", - "AUTH_LDAP_3_START_TLS": false, - "AUTH_LDAP_3_TEAM_MAP": {}, - "AUTH_LDAP_3_USER_ATTR_MAP": {}, - "AUTH_LDAP_3_USER_DN_TEMPLATE": null, - "AUTH_LDAP_3_USER_FLAGS_BY_GROUP": {}, - "AUTH_LDAP_3_USER_SEARCH": [], - "AUTH_LDAP_4_BIND_DN": "", - "AUTH_LDAP_4_BIND_PASSWORD": "", - "AUTH_LDAP_4_CONNECTION_OPTIONS": { - "OPT_NETWORK_TIMEOUT": 30, - "OPT_REFERRALS": 0 - }, - "AUTH_LDAP_4_DENY_GROUP": null, - "AUTH_LDAP_4_GROUP_SEARCH": [], - "AUTH_LDAP_4_GROUP_TYPE": "MemberDNGroupType", - "AUTH_LDAP_4_GROUP_TYPE_PARAMS": { - "member_attr": "member", - "name_attr": "cn" - }, - "AUTH_LDAP_4_ORGANIZATION_MAP": {}, - "AUTH_LDAP_4_REQUIRE_GROUP": null, - "AUTH_LDAP_4_SERVER_URI": "", - "AUTH_LDAP_4_START_TLS": false, - "AUTH_LDAP_4_TEAM_MAP": {}, - "AUTH_LDAP_4_USER_ATTR_MAP": {}, - "AUTH_LDAP_4_USER_DN_TEMPLATE": null, - "AUTH_LDAP_4_USER_FLAGS_BY_GROUP": {}, - "AUTH_LDAP_4_USER_SEARCH": [], - "AUTH_LDAP_5_BIND_DN": "", - "AUTH_LDAP_5_BIND_PASSWORD": "", - "AUTH_LDAP_5_CONNECTION_OPTIONS": { - "OPT_NETWORK_TIMEOUT": 30, - "OPT_REFERRALS": 0 - }, - "AUTH_LDAP_5_DENY_GROUP": null, - "AUTH_LDAP_5_GROUP_SEARCH": [], - "AUTH_LDAP_5_GROUP_TYPE": "MemberDNGroupType", - "AUTH_LDAP_5_GROUP_TYPE_PARAMS": { - "member_attr": "member", - "name_attr": "cn" - }, - "AUTH_LDAP_5_ORGANIZATION_MAP": {}, - "AUTH_LDAP_5_REQUIRE_GROUP": null, - "AUTH_LDAP_5_SERVER_URI": "", - "AUTH_LDAP_5_START_TLS": false, - "AUTH_LDAP_5_TEAM_MAP": {}, - "AUTH_LDAP_5_USER_ATTR_MAP": {}, - "AUTH_LDAP_5_USER_DN_TEMPLATE": null, - "AUTH_LDAP_5_USER_FLAGS_BY_GROUP": {}, - "AUTH_LDAP_5_USER_SEARCH": [], - "AUTH_LDAP_BIND_DN": "", - "AUTH_LDAP_BIND_PASSWORD": "", - "AUTH_LDAP_CONNECTION_OPTIONS": { - "OPT_NETWORK_TIMEOUT": 30, - "OPT_REFERRALS": 0 - }, - "AUTH_LDAP_DENY_GROUP": null, - "AUTH_LDAP_GROUP_SEARCH": [], - "AUTH_LDAP_GROUP_TYPE": "MemberDNGroupType", - "AUTH_LDAP_GROUP_TYPE_PARAMS": { - "member_attr": "member", - "name_attr": "cn" - }, - "AUTH_LDAP_ORGANIZATION_MAP": {}, - "AUTH_LDAP_REQUIRE_GROUP": null, - "AUTH_LDAP_SERVER_URI": "", - "AUTH_LDAP_START_TLS": false, - "AUTH_LDAP_TEAM_MAP": {}, - "AUTH_LDAP_USER_ATTR_MAP": {}, - "AUTH_LDAP_USER_DN_TEMPLATE": null, - "AUTH_LDAP_USER_FLAGS_BY_GROUP": {}, - "AUTH_LDAP_USER_SEARCH": [] -} \ No newline at end of file From 28733beee8f5197febd8ba1bf6a7605810703a4e Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 13 Jan 2021 14:52:59 -0500 Subject: [PATCH 3/3] Show placeholder for codemirror inputs and expand width of "DN" ldap fields --- .../CodeMirrorInput/CodeMirrorInput.jsx | 14 +++++++ .../Setting/LDAP/LDAPEdit/LDAPEdit.jsx | 39 +++++++++++-------- .../screens/Setting/shared/SharedFields.jsx | 11 ++++-- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx index 92a9071332..a07b6feca5 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx @@ -6,6 +6,7 @@ import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/yaml/yaml'; import 'codemirror/mode/jinja2/jinja2'; import 'codemirror/lib/codemirror.css'; +import 'codemirror/addon/display/placeholder'; const LINE_HEIGHT = 24; const PADDING = 12; @@ -55,6 +56,17 @@ const CodeMirror = styled(ReactCodeMirror)` background-color: var(--pf-c-form-control--disabled--BackgroundColor); } `} + ${props => + props.options && + props.options.placeholder && + ` + .CodeMirror-empty { + pre.CodeMirror-placeholder { + color: var(--pf-c-form-control--placeholder--Color); + height: 100% !important; + } + } + `} `; function CodeMirrorInput({ @@ -66,6 +78,7 @@ function CodeMirrorInput({ rows, fullHeight, className, + placeholder, }) { // Workaround for CodeMirror bug: If CodeMirror renders in a modal on the // modal's initial render, it appears as an empty box due to mis-calculated @@ -92,6 +105,7 @@ function CodeMirrorInput({ smartIndent: false, lineNumbers: true, lineWrapping: true, + placeholder, readOnly, }} fullHeight={fullHeight} diff --git a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx index f983907ec9..af1764299a 100644 --- a/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx +++ b/awx/ui_next/src/screens/Setting/LDAP/LDAPEdit/LDAPEdit.jsx @@ -6,7 +6,10 @@ import { CardBody } from '../../../../components/Card'; import ContentError from '../../../../components/ContentError'; import ContentLoading from '../../../../components/ContentLoading'; import { FormSubmitError } from '../../../../components/FormField'; -import { FormColumnLayout } from '../../../../components/FormLayout'; +import { + FormColumnLayout, + FormFullWidthLayout, +} from '../../../../components/FormLayout'; import { useSettings } from '../../../../contexts/Settings'; import { RevertAllAlert, RevertFormActionGroup } from '../../shared'; import { @@ -165,26 +168,10 @@ function LDAPEdit() { name={`${ldapCategory}SERVER_URI`} config={ldap[`${ldapCategory}SERVER_URI`]} /> - - - - + + + + + + helpers.setValue(checked)} aria-label={ariaLabel || config.label} - ouiaId={ariaLabel || config.label} /> ) : null; @@ -242,11 +243,13 @@ const ObjectField = withI18n()(({ i18n, name, config, isRequired = false }) => { > { helpers.setValue(value); }} - mode="javascript" + placeholder={JSON.stringify(config?.placeholder, null, 2)} />