From 1bb29ec5f78bba609a97183cfb74e5592d5935c5 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 21 May 2020 16:24:11 -0700 Subject: [PATCH 1/3] add user teams list --- awx/ui_next/src/api/models/Users.js | 6 ++ .../UserOrganizationListItem.jsx | 14 ++-- .../src/screens/User/UserTeams/UserTeams.jsx | 12 ++-- .../screens/User/UserTeams/UserTeamsList.jsx | 70 +++++++++++++++++++ .../User/UserTeams/UserTeamsListItem.jsx | 43 ++++++++++++ 5 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 awx/ui_next/src/screens/User/UserTeams/UserTeamsList.jsx create mode 100644 awx/ui_next/src/screens/User/UserTeams/UserTeamsListItem.jsx diff --git a/awx/ui_next/src/api/models/Users.js b/awx/ui_next/src/api/models/Users.js index b98cf45cae..3f5a177390 100644 --- a/awx/ui_next/src/api/models/Users.js +++ b/awx/ui_next/src/api/models/Users.js @@ -34,6 +34,12 @@ 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, + }); + } } export default Users; 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/UserTeams.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx index 5d342e00f2..1cfc018236 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 UserTeamsList from './UserTeamsList'; -class UserAdd extends Component { - render() { - return Coming soon :); - } +export default function UserTeams() { + return ; } - -export default UserAdd; diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamsList.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamsList.jsx new file mode 100644 index 0000000000..6bc06dba3e --- /dev/null +++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamsList.jsx @@ -0,0 +1,70 @@ +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 './UserTeamsListItem'; + +const QS_CONFIG = getQSConfig('teams', { + page: 1, + page_size: 20, + order_by: 'name', +}); + +function UserTeamsList({ 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} + /> + )} + /> + ); +} + +export default withI18n()(UserTeamsList); diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamsListItem.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamsListItem.jsx new file mode 100644 index 0000000000..44995f004c --- /dev/null +++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamsListItem.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 UserTeamsListItem({ team, i18n }) { + return ( + + + + + {team.name} + + , + + {team.summary_fields.organization && ( + <> + {i18n._(t`Organization`)} + + {team.summary_fields.organization.name} + + + )} + , + {team.description}, + ]} + /> + + + ); +} + +export default withI18n()(UserTeamsListItem); From 75fd703530112c61ddb4058bb872b80a6e96e56a Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Fri, 22 May 2020 11:05:32 -0700 Subject: [PATCH 2/3] add UserTeamList tests --- awx/ui_next/src/api/models/Users.js | 4 + .../screens/Team/TeamList/TeamListItem.jsx | 2 +- awx/ui_next/src/screens/User/User.jsx | 2 +- .../{UserTeamsList.jsx => UserTeamList.jsx} | 6 +- .../User/UserTeams/UserTeamList.test.jsx | 82 +++++++++++++++++++ ...TeamsListItem.jsx => UserTeamListItem.jsx} | 6 +- .../User/UserTeams/UserTeamListItem.test.jsx | 38 +++++++++ .../src/screens/User/UserTeams/UserTeams.jsx | 4 +- 8 files changed, 134 insertions(+), 10 deletions(-) rename awx/ui_next/src/screens/User/UserTeams/{UserTeamsList.jsx => UserTeamList.jsx} (92%) create mode 100644 awx/ui_next/src/screens/User/UserTeams/UserTeamList.test.jsx rename awx/ui_next/src/screens/User/UserTeams/{UserTeamsListItem.jsx => UserTeamListItem.jsx} (87%) create mode 100644 awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.test.jsx diff --git a/awx/ui_next/src/api/models/Users.js b/awx/ui_next/src/api/models/Users.js index 3f5a177390..12eb74c4a6 100644 --- a/awx/ui_next/src/api/models/Users.js +++ b/awx/ui_next/src/api/models/Users.js @@ -40,6 +40,10 @@ class Users extends Base { 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/UserTeams/UserTeamsList.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx similarity index 92% rename from awx/ui_next/src/screens/User/UserTeams/UserTeamsList.jsx rename to awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx index 6bc06dba3e..c9d0c051e8 100644 --- a/awx/ui_next/src/screens/User/UserTeams/UserTeamsList.jsx +++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx @@ -7,7 +7,7 @@ import PaginatedDataList from '../../../components/PaginatedDataList'; import useRequest from '../../../util/useRequest'; import { UsersAPI } from '../../../api'; import { getQSConfig, parseQueryString } from '../../../util/qs'; -import UserTeamListItem from './UserTeamsListItem'; +import UserTeamListItem from './UserTeamListItem'; const QS_CONFIG = getQSConfig('teams', { page: 1, @@ -15,7 +15,7 @@ const QS_CONFIG = getQSConfig('teams', { order_by: 'name', }); -function UserTeamsList({ i18n }) { +function UserTeamList({ i18n }) { const location = useLocation(); const { id: userId } = useParams(); @@ -67,4 +67,4 @@ function UserTeamsList({ i18n }) { ); } -export default withI18n()(UserTeamsList); +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/UserTeamsListItem.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.jsx similarity index 87% rename from awx/ui_next/src/screens/User/UserTeams/UserTeamsListItem.jsx rename to awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.jsx index 44995f004c..41f4429879 100644 --- a/awx/ui_next/src/screens/User/UserTeams/UserTeamsListItem.jsx +++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamListItem.jsx @@ -9,7 +9,7 @@ import { } from '@patternfly/react-core'; import DataListCell from '../../../components/DataListCell'; -function UserTeamsListItem({ team, i18n }) { +function UserTeamListItem({ team, i18n }) { return ( @@ -23,7 +23,7 @@ function UserTeamsListItem({ team, i18n }) { {team.summary_fields.organization && ( <> - {i18n._(t`Organization`)} + {i18n._(t`Organization`)}{' '} @@ -40,4 +40,4 @@ function UserTeamsListItem({ team, i18n }) { ); } -export default withI18n()(UserTeamsListItem); +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 1cfc018236..65ddf670e5 100644 --- a/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx +++ b/awx/ui_next/src/screens/User/UserTeams/UserTeams.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import UserTeamsList from './UserTeamsList'; +import UserTeamList from './UserTeamList'; export default function UserTeams() { - return ; + return ; } From 8cdb05e4a2a01f1ea58d4375e46b6c6cf3abe272 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Tue, 2 Jun 2020 09:13:42 -0700 Subject: [PATCH 3/3] add org name to user team search columns --- .../src/screens/User/UserTeams/UserTeamList.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx index c9d0c051e8..d7a902b7e3 100644 --- a/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx +++ b/awx/ui_next/src/screens/User/UserTeams/UserTeamList.jsx @@ -63,6 +63,17 @@ function UserTeamList({ i18n }) { isSelected={false} /> )} + toolbarSearchColumns={[ + { + name: i18n._(t`Name`), + key: 'name', + isDefault: true, + }, + { + name: i18n._(t`Organization`), + key: 'organization__name', + }, + ]} /> ); }