diff --git a/awx/ui_next/src/api/models/Users.js b/awx/ui_next/src/api/models/Users.js
index b98cf45cae..12eb74c4a6 100644
--- a/awx/ui_next/src/api/models/Users.js
+++ b/awx/ui_next/src/api/models/Users.js
@@ -34,6 +34,16 @@ class Users extends Base {
readRoleOptions(userId) {
return this.http.options(`${this.baseUrl}${userId}/roles/`);
}
+
+ readTeams(userId, params) {
+ return this.http.get(`${this.baseUrl}${userId}/teams/`, {
+ params,
+ });
+ }
+
+ readTeamsOptions(userId) {
+ return this.http.options(`${this.baseUrl}${userId}/teams/`);
+ }
}
export default Users;
diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx
index f088dede27..47b2b4011c 100644
--- a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx
+++ b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx
@@ -58,7 +58,7 @@ class TeamListItem extends React.Component {
{team.summary_fields.organization && (
- {i18n._(t`Organization`)}
+ {i18n._(t`Organization`)}{' '}
diff --git a/awx/ui_next/src/screens/User/User.jsx b/awx/ui_next/src/screens/User/User.jsx
index 982ae0de5e..5e195da2f3 100644
--- a/awx/ui_next/src/screens/User/User.jsx
+++ b/awx/ui_next/src/screens/User/User.jsx
@@ -108,7 +108,7 @@ function User({ i18n, setBreadcrumb }) {
-
+
{user && (
diff --git a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.jsx b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.jsx
index f45c9a5b93..bc01af942d 100644
--- a/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.jsx
+++ b/awx/ui_next/src/screens/User/UserOrganizations/UserOrganizationListItem.jsx
@@ -1,7 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
-import { withI18n } from '@lingui/react';
-import { t } from '@lingui/macro';
import {
DataListItemCells,
DataListItemRow,
@@ -9,14 +7,18 @@ import {
} from '@patternfly/react-core';
import DataListCell from '../../../components/DataListCell';
-function UserOrganizationListItem({ organization, i18n }) {
+export default function UserOrganizationListItem({ organization }) {
+ const labelId = `organization-${organization.id}`;
return (
-
+
-
+
{organization.name}
,
@@ -29,5 +31,3 @@ function UserOrganizationListItem({ organization, i18n }) {
);
}
-
-export default withI18n()(UserOrganizationListItem);
diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx
new file mode 100644
index 0000000000..d7a902b7e3
--- /dev/null
+++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx
@@ -0,0 +1,81 @@
+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 UserTeamListItem from './UserTeamListItem';
+
+const QS_CONFIG = getQSConfig('teams', {
+ page: 1,
+ page_size: 20,
+ order_by: 'name',
+});
+
+function UserTeamList({ i18n }) {
+ const location = useLocation();
+ const { id: userId } = useParams();
+
+ const {
+ result: { teams, count },
+ error: contentError,
+ isLoading,
+ request: fetchOrgs,
+ } = useRequest(
+ useCallback(async () => {
+ const params = parseQueryString(QS_CONFIG, location.search);
+ const {
+ data: { results, count: teamCount },
+ } = await UsersAPI.readTeams(userId, params);
+ return {
+ teams: results,
+ count: teamCount,
+ };
+ }, [userId, location.search]),
+ {
+ teams: [],
+ count: 0,
+ }
+ );
+
+ useEffect(() => {
+ fetchOrgs();
+ }, [fetchOrgs]);
+
+ return (
+ (
+ {}}
+ isSelected={false}
+ />
+ )}
+ toolbarSearchColumns={[
+ {
+ name: i18n._(t`Name`),
+ key: 'name',
+ isDefault: true,
+ },
+ {
+ name: i18n._(t`Organization`),
+ key: 'organization__name',
+ },
+ ]}
+ />
+ );
+}
+
+export default withI18n()(UserTeamList);
diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamList.test.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.test.jsx
new file mode 100644
index 0000000000..caac6b0c5f
--- /dev/null
+++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.test.jsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { UsersAPI } from '../../../api';
+import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
+
+import UserTeamList from './UserTeamList';
+
+jest.mock('../../../api');
+
+const mockAPIUserTeamList = {
+ data: {
+ count: 3,
+ results: [
+ {
+ name: 'Team 0',
+ id: 1,
+ url: '/teams/1',
+ summary_fields: {
+ user_capabilities: {
+ delete: true,
+ edit: true,
+ },
+ },
+ },
+ {
+ name: 'Team 1',
+ id: 2,
+ url: '/teams/2',
+ summary_fields: {
+ user_capabilities: {
+ delete: true,
+ edit: true,
+ },
+ },
+ },
+ {
+ name: 'Team 2',
+ id: 3,
+ url: '/teams/3',
+ summary_fields: {
+ user_capabilities: {
+ delete: true,
+ edit: true,
+ },
+ },
+ },
+ ],
+ },
+ isModalOpen: false,
+ warningTitle: 'title',
+ warningMsg: 'message',
+};
+
+describe('', () => {
+ beforeEach(() => {
+ UsersAPI.readTeams = jest.fn(() =>
+ Promise.resolve({
+ data: mockAPIUserTeamList.data,
+ })
+ );
+ UsersAPI.readOptions = jest.fn(() =>
+ Promise.resolve({
+ data: {
+ actions: {
+ GET: {},
+ POST: {},
+ },
+ },
+ })
+ );
+ });
+
+ test('should load and render teams', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts();
+ });
+ wrapper.update();
+
+ expect(wrapper.find('UserTeamListItem')).toHaveLength(3);
+ });
+});
diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.jsx
new file mode 100644
index 0000000000..41f4429879
--- /dev/null
+++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import {
+ DataListItemCells,
+ DataListItemRow,
+ DataListItem,
+} from '@patternfly/react-core';
+import DataListCell from '../../../components/DataListCell';
+
+function UserTeamListItem({ team, i18n }) {
+ return (
+
+
+
+
+ {team.name}
+
+ ,
+
+ {team.summary_fields.organization && (
+ <>
+ {i18n._(t`Organization`)}{' '}
+
+ {team.summary_fields.organization.name}
+
+ >
+ )}
+ ,
+ {team.description},
+ ]}
+ />
+
+
+ );
+}
+
+export default withI18n()(UserTeamListItem);
diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.test.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.test.jsx
new file mode 100644
index 0000000000..5816622eb2
--- /dev/null
+++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.test.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { I18nProvider } from '@lingui/react';
+import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
+import UserTeamListItem from './UserTeamListItem';
+
+describe('', () => {
+ test('should render item', () => {
+ const wrapper = mountWithContexts(
+
+
+ {}}
+ />
+
+
+ );
+
+ const cells = wrapper.find('DataListCell');
+ expect(cells).toHaveLength(3);
+ expect(cells.at(0).text()).toEqual('Team 1');
+ expect(cells.at(1).text()).toEqual('Organization The Org');
+ expect(cells.at(2).text()).toEqual('something something team');
+ });
+});
diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx
index 5d342e00f2..65ddf670e5 100644
--- a/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx
+++ b/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx
@@ -1,10 +1,6 @@
-import React, { Component } from 'react';
-import { CardBody } from '../../../components/Card';
+import React from 'react';
+import UserTeamList from './UserTeamList';
-class UserAdd extends Component {
- render() {
- return Coming soon :);
- }
+export default function UserTeams() {
+ return ;
}
-
-export default UserAdd;