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',
+ });
+ });
+});