Merge pull request #7129 from keithjgrant/6594-user-teams-list

User teams list

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot] 2020-06-02 17:32:37 +00:00 committed by GitHub
commit 5e5026aae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 267 additions and 17 deletions

View File

@ -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;

View File

@ -58,7 +58,7 @@ class TeamListItem extends React.Component {
<DataListCell key="organization">
{team.summary_fields.organization && (
<Fragment>
<b css="margin-right: 24px">{i18n._(t`Organization`)}</b>
<b>{i18n._(t`Organization`)}</b>{' '}
<Link
to={`/organizations/${team.summary_fields.organization.id}/details`}
>

View File

@ -108,7 +108,7 @@ function User({ i18n, setBreadcrumb }) {
<UserOrganizations id={Number(match.params.id)} />
</Route>
<Route path="/users/:id/teams">
<UserTeams id={Number(match.params.id)} />
<UserTeams userId={Number(match.params.id)} />
</Route>
{user && (
<Route path="/users/:id/access">

View File

@ -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 (
<DataListItem aria-labelledby={i18n._(t`User Organization List Item`)}>
<DataListItem aria-labelledby={labelId}>
<DataListItemRow>
<DataListItemCells
dataListCells={[
<DataListCell key={organization.id}>
<Link to={`/organizations/${organization.id}/details`}>
<Link
to={`/organizations/${organization.id}/details`}
id={labelId}
>
{organization.name}
</Link>
</DataListCell>,
@ -29,5 +31,3 @@ function UserOrganizationListItem({ organization, i18n }) {
</DataListItem>
);
}
export default withI18n()(UserOrganizationListItem);

View File

@ -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 (
<PaginatedDataList
items={teams}
contentError={contentError}
hasContentLoading={isLoading}
itemCount={count}
pluralizedItemName={i18n._(t`Teams`)}
qsConfig={QS_CONFIG}
renderItem={team => (
<UserTeamListItem
key={team.id}
value={team.name}
team={team}
detailUrl={`/teams/${team.id}/details`}
onSelect={() => {}}
isSelected={false}
/>
)}
toolbarSearchColumns={[
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true,
},
{
name: i18n._(t`Organization`),
key: 'organization__name',
},
]}
/>
);
}
export default withI18n()(UserTeamList);

View File

@ -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('<UserTeamList />', () => {
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(<UserTeamList />);
});
wrapper.update();
expect(wrapper.find('UserTeamListItem')).toHaveLength(3);
});
});

View File

@ -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 (
<DataListItem aria-labelledby={`team-${team.id}`}>
<DataListItemRow>
<DataListItemCells
dataListCells={[
<DataListCell key="name">
<Link to={`/teams/${team.id}/details`} id={`team-${team.id}`}>
{team.name}
</Link>
</DataListCell>,
<DataListCell key="organization">
{team.summary_fields.organization && (
<>
<b>{i18n._(t`Organization`)}</b>{' '}
<Link
to={`/organizations/${team.summary_fields.organization.id}/details`}
>
<b>{team.summary_fields.organization.name}</b>
</Link>
</>
)}
</DataListCell>,
<DataListCell key="description">{team.description}</DataListCell>,
]}
/>
</DataListItemRow>
</DataListItem>
);
}
export default withI18n()(UserTeamListItem);

View File

@ -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('<UserTeamListItem />', () => {
test('should render item', () => {
const wrapper = mountWithContexts(
<I18nProvider>
<MemoryRouter initialEntries={['/teams']} initialIndex={0}>
<UserTeamListItem
team={{
id: 1,
name: 'Team 1',
description: 'something something team',
summary_fields: {
organization: {
id: 2,
name: 'The Org',
},
},
}}
detailUrl="/team/1"
isSelected
onSelect={() => {}}
/>
</MemoryRouter>
</I18nProvider>
);
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');
});
});

View File

@ -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 <CardBody>Coming soon :)</CardBody>;
}
export default function UserTeams() {
return <UserTeamList />;
}
export default UserAdd;