diff --git a/awx/ui_next/src/api/models/Users.js b/awx/ui_next/src/api/models/Users.js index a76c4e49b7..e15908270b 100644 --- a/awx/ui_next/src/api/models/Users.js +++ b/awx/ui_next/src/api/models/Users.js @@ -7,7 +7,9 @@ class Users extends Base { } associateRole(userId, roleId) { - return this.http.post(`${this.baseUrl}${userId}/roles/`, { id: roleId }); + return this.http.post(`${this.baseUrl}${userId}/roles/`, { + id: roleId, + }); } disassociateRole(userId, roleId) { @@ -16,6 +18,12 @@ class Users extends Base { disassociate: true, }); } + + readOrganizations(userId, params) { + return this.http.get(`${this.baseUrl}${userId}/organizations/`, { + params, + }); + } } export default Users; diff --git a/awx/ui_next/src/screens/User/User.jsx b/awx/ui_next/src/screens/User/User.jsx index 5eb0e633a4..d2398846f9 100644 --- a/awx/ui_next/src/screens/User/User.jsx +++ b/awx/ui_next/src/screens/User/User.jsx @@ -130,10 +130,9 @@ class User extends Component { render={() => } /> )} - } - /> + + + } diff --git a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.test.jsx b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.test.jsx new file mode 100644 index 0000000000..23622d6124 --- /dev/null +++ b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.test.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithContexts } from '@testUtils/enzymeHelpers'; + +import UserOrganizationListItem from './UserOrganizationListItem'; + +describe('', () => { + test('mounts correctly', () => { + let wrapper; + act(() => { + wrapper = mountWithContexts( + + ); + }); + expect(wrapper.find('UserOrganizationListItem').length).toBe(1); + }); + test('render correct information', () => { + let wrapper; + act(() => { + wrapper = mountWithContexts( + + ); + }); + expect( + wrapper + .find('DataListCell') + .at(0) + .text() + ).toBe('foo'); + expect( + wrapper + .find('DataListCell') + .at(1) + .text() + ).toBe('Bar'); + expect(wrapper.find('Link').prop('to')).toBe('/organizations/1/details'); + }); +}); diff --git a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.jsx b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.jsx index aa97e30d80..1bd83ad2fa 100644 --- a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.jsx +++ b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.jsx @@ -1,10 +1,7 @@ -import React, { Component } from 'react'; -import { CardBody } from '@components/Card'; +import React from 'react'; +import UserOrganizationsList from './UserOrganizationsList'; -class UserAdd extends Component { - render() { - return Coming soon :); - } +function UserOrganizations() { + return ; } - -export default UserAdd; +export default UserOrganizations; diff --git a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.test.jsx b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.test.jsx new file mode 100644 index 0000000000..e5093b07d0 --- /dev/null +++ b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizations.test.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; +import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; + +import UserOrganizations from './UserOrganizations'; + +describe('', () => { + test('userOrganizations mounts successfully', () => { + const history = createMemoryHistory({ + initialEntries: ['/users/1/organizations'], + }); + let wrapper; + act(() => { + wrapper = mountWithContexts( + } + />, + { + context: { + router: { + history, + route: { + location: history.location, + match: { params: { id: 1 } }, + }, + }, + }, + } + ); + }); + waitForElement(wrapper, 'UserOrganizationList'); + }); +}); diff --git a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationsList.jsx b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationsList.jsx new file mode 100644 index 0000000000..ce9a2042ad --- /dev/null +++ b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationsList.jsx @@ -0,0 +1,71 @@ +import React, { useCallback, useEffect } from 'react'; +import { withI18n } from '@lingui/react'; +import { useLocation, useParams } from 'react-router-dom'; +import { t } from '@lingui/macro'; + +import PaginatedDataList from '@components/PaginatedDataList'; +import useRequest from '@util/useRequest'; +import { UsersAPI } from '@api'; +import { getQSConfig, parseQueryString } from '@util/qs'; +import UserOrganizationListItem from './UserOrganizationListItem'; + +const QS_CONFIG = getQSConfig('organizations', { + page: 1, + page_size: 20, + order_by: 'name', + type: 'organization', +}); + +function UserOrganizationsList({ i18n }) { + const location = useLocation(); + const { id: userId } = useParams(); + + const { + result: { organizations, count }, + error: contentError, + isLoading, + request: fetchOrgs, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, location.search); + const { + data: { results, count: orgCount }, + } = await UsersAPI.readOrganizations(userId, params); + return { + organizations: results, + count: orgCount, + }; + }, [userId, location.search]), + { + organizations: [], + count: 0, + } + ); + + useEffect(() => { + fetchOrgs(); + }, [fetchOrgs]); + + return ( + ( + {}} + isSelected={false} + /> + )} + /> + ); +} + +export default withI18n()(UserOrganizationsList); diff --git a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationsList.test.jsx b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationsList.test.jsx new file mode 100644 index 0000000000..11bdf96eac --- /dev/null +++ b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationsList.test.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { act } from 'react-dom/test-utils'; +import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; +import { createMemoryHistory } from 'history'; + +import UserOrganizationsList from './UserOrganizationsList'; +import { UsersAPI } from '@api'; + +jest.mock('@api/models/Users'); + +describe('', () => { + let history; + let wrapper; + beforeEach(async () => { + history = createMemoryHistory({ + initialEntries: ['/users/1/organizations'], + }); + UsersAPI.readOrganizations.mockResolvedValue({ + data: { + results: [ + { + name: 'Foo', + id: 1, + description: 'Bar', + url: '/api/v2/organizations/1/', + }, + ], + count: 1, + }, + }); + await act(async () => { + wrapper = mountWithContexts( + } + />, + { + context: { + router: { + history, + route: { + location: history.location, + match: { params: { id: 1 } }, + }, + }, + }, + } + ); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + test('successfully mounts', async () => { + await waitForElement(wrapper, 'UserOrganizationListItem'); + }); + test('calls api to get organizations', () => { + expect(UsersAPI.readOrganizations).toBeCalledWith('1', { + order_by: 'name', + page: 1, + page_size: 20, + type: 'organization', + }); + }); +});