Skip to content

Commit 5456ed3

Browse files
author
katrinan029
committed
feat: add user table with filtering
1 parent 1fca718 commit 5456ed3

9 files changed

+287
-81
lines changed

src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import {
55
import { Launch, ContentCopy } from '@openedx/paragon/icons';
66
import { getConfig } from '@edx/frontend-platform';
77
import { formatDate, useCopyToClipboard } from '../data/utils';
8-
import DJANGO_ADMIN_BASE_URL from '../data/constants';
98
import CustomerDetailModal from './CustomerDetailModal';
109

1110
const CustomerCard = ({ enterpriseCustomer }) => {
12-
const { ADMIN_PORTAL_BASE_URL } = getConfig();
11+
const { ADMIN_PORTAL_BASE_URL, DJANGO_ADMIN_LMS_BASE_URL } = getConfig();
1312
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard();
1413
const [isDetailsOpen, openDetails, closeDetails] = useToggle(false);
1514

@@ -28,7 +27,7 @@ const CustomerCard = ({ enterpriseCustomer }) => {
2827
<Button
2928
className="text-dark-500"
3029
as="a"
31-
href={`${DJANGO_ADMIN_BASE_URL}/admin/enterprise/enterprisecustomer/${enterpriseCustomer.uuid}/change`}
30+
href={`${DJANGO_ADMIN_LMS_BASE_URL}/admin/enterprise/enterprisecustomer/${enterpriseCustomer.uuid}/change`}
3231
variant="inverse-primary"
3332
target="_blank"
3433
rel="noopener noreferrer"

src/Configuration/Customers/CustomerDetailView/CustomerIntegrations.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const CustomerIntegrations = ({
1616
<div>
1717
{(activeSSO || activeIntegrations || apiCredentialsEnabled) && (
1818
<div>
19-
<h2>Associated Integrations</h2>
19+
<h2>Associated integrations</h2>
20+
<hr />
2021
{activeSSO && activeSSO.map((sso) => (
2122
<CustomerViewCard
2223
slug={slug}

src/Configuration/Customers/CustomerDetailView/CustomerPlanContainer.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const CustomerPlanContainer = ({ slug }) => {
4848
Show inactive
4949
</Form.Switch>
5050
</div>
51+
<hr />
5152
{renderActivePoliciesCard}
5253
{renderActiveSubscriptions}
5354
{showInactive ? (

src/Configuration/Customers/CustomerDetailView/EnterpriseCustomerUserDetail.jsx

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import {
4-
Icon, IconButton, Stack, Chip
4+
Icon, IconButton, Stack, Chip,
55
} from '@openedx/paragon';
66
import { Person, Check, Timelapse } from '@openedx/paragon/icons';
77

88
export const EnterpriseCustomerUserDetail = ({
99
row,
1010
}) => {
1111
let memberDetails;
12-
let memberDetailIcon = (
12+
const memberDetailIcon = (
1313
<IconButton
1414
isActive
1515
invertColors
@@ -31,7 +31,6 @@ export const EnterpriseCustomerUserDetail = ({
3131
</div>
3232
);
3333
} else {
34-
console.log(row.original)
3534
memberDetails = (
3635
<p className="align-middle mb-0">
3736
{row.original.pendingEnterpriseCustomerUser?.userEmail}
@@ -47,20 +46,29 @@ export const EnterpriseCustomerUserDetail = ({
4746
};
4847

4948
export const AdministratorCell = ({ row }) => {
49+
if (row.original?.pendingEnterpriseCustomerUser?.isPendingAdmin) {
50+
return (
51+
<Chip
52+
iconBefore={Timelapse}
53+
>
54+
Pending
55+
</Chip>
56+
);
57+
}
5058
return (
5159
<div>
52-
{row.original?.roleAssignments?.includes("enterprise_admin") ? <Check /> : null}
60+
{row.original?.roleAssignments?.includes('enterprise_admin') ? <Check data-testid="admin check" aria-label="admin check" /> : null}
5361
</div>
54-
)
55-
}
62+
);
63+
};
5664

5765
export const LearnerCell = ({ row }) => {
58-
if (!row.original?.pendingEnterpriseCustomerUser) {
66+
if (!row.original?.pendingEnterpriseCustomerUser?.isPendingLearner) {
5967
return (
6068
<div>
61-
{row.original?.roleAssignments?.includes("enterprise_learner") ? <Check /> : null}
69+
{row.original?.roleAssignments?.includes('enterprise_learner') ? <Check data-testid="learner check" aria-label="learner check" /> : null}
6270
</div>
63-
)
71+
);
6472
}
6573

6674
return (
@@ -69,20 +77,43 @@ export const LearnerCell = ({ row }) => {
6977
>
7078
Pending
7179
</Chip>
72-
)
80+
);
7381
};
7482

75-
7683
EnterpriseCustomerUserDetail.propTypes = {
7784
row: PropTypes.shape({
7885
original: PropTypes.shape({
79-
memberDetails: PropTypes.shape({
80-
userEmail: PropTypes.string.isRequired,
81-
userName: PropTypes.string,
86+
enterpriseCustomerUser: PropTypes.shape({
87+
email: PropTypes.string.isRequired,
88+
username: PropTypes.string,
89+
}),
90+
pendingEnterpriseCustomerUser: PropTypes.shape({
91+
isPendingAdmin: PropTypes.bool,
92+
userEmail: PropTypes.string,
93+
}),
94+
roleAssignments: PropTypes.arrayOf(PropTypes.string),
95+
}).isRequired,
96+
}).isRequired,
97+
};
98+
99+
AdministratorCell.propTypes = {
100+
row: PropTypes.shape({
101+
original: PropTypes.shape({
102+
pendingEnterpriseCustomerUser: PropTypes.shape({
103+
isPendingAdmin: PropTypes.bool,
104+
}),
105+
roleAssignments: PropTypes.arrayOf(PropTypes.string),
106+
}).isRequired,
107+
}).isRequired,
108+
};
109+
110+
LearnerCell.propTypes = {
111+
row: PropTypes.shape({
112+
original: PropTypes.shape({
113+
pendingEnterpriseCustomerUser: PropTypes.shape({
114+
isPendingLearner: PropTypes.bool,
82115
}),
83-
status: PropTypes.string,
84-
recentAction: PropTypes.string.isRequired,
85-
memberEnrollments: PropTypes.string,
116+
roleAssignments: PropTypes.arrayOf(PropTypes.string),
86117
}).isRequired,
87118
}).isRequired,
88119
};
Lines changed: 48 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,62 @@
11
import { useParams } from 'react-router-dom';
2-
import { Container, DataTable, TextFilter } from '@openedx/paragon';
2+
import { DataTable, TextFilter } from '@openedx/paragon';
33
import { EnterpriseCustomerUserDetail, LearnerCell, AdministratorCell } from './EnterpriseCustomerUserDetail';
4-
import useEnterpriseUsersTableData from '../data/hooks/useCustomerUsersTableData';
4+
import useCustomerUsersTableData from '../data/hooks/useCustomerUsersTableData';
55

66
const EnterpriseCustomerUsersTable = () => {
77
const { id } = useParams();
88
const {
99
isLoading,
1010
enterpriseUsersTableData,
1111
fetchEnterpriseUsersData,
12-
} = useEnterpriseUsersTableData(id);
12+
} = useCustomerUsersTableData(id);
1313
return (
14-
<Container>
15-
<h2>Associated users ({ enterpriseUsersTableData.itemCount })</h2>
14+
<div>
15+
<h2>Associated users ({enterpriseUsersTableData.itemCount})</h2>
1616
<hr />
17-
<DataTable
18-
isLoading={isLoading}
19-
isExpandable
20-
isSortable
21-
manualSortBy
22-
isPaginated
23-
manualPagination
24-
isFilterable
25-
manualFilters
26-
initialState={{
27-
pageSize: 8,
28-
pageIndex: 0,
29-
sortBy: [],
30-
filters: [],
31-
}}
32-
defaultColumnValues={{ Filter: TextFilter }}
33-
fetchData={fetchEnterpriseUsersData}
34-
data={enterpriseUsersTableData.results}
35-
itemCount={enterpriseUsersTableData.itemCount}
36-
pageCount={enterpriseUsersTableData.pageCount}
37-
columns={[
38-
{
39-
id: 'userDetails',
40-
Header: 'User details',
41-
accessor: 'userDetails',
42-
Cell: EnterpriseCustomerUserDetail,
43-
},
44-
{
45-
id: 'administrator',
46-
Header: 'Administrator',
47-
accessor: 'administrator',
48-
disableFilters: true,
49-
Cell: AdministratorCell,
50-
},
51-
{
52-
id: 'learner',
53-
Header: 'Learner',
54-
accessor: 'learner',
55-
disableFilters: true,
56-
Cell: LearnerCell,
57-
},
58-
]}
59-
/>
60-
</Container>
17+
<DataTable
18+
isLoading={isLoading}
19+
isExpandable
20+
isPaginated
21+
manualPagination
22+
isFilterable
23+
manualFilters
24+
initialState={{
25+
pageSize: 8,
26+
pageIndex: 0,
27+
sortBy: [],
28+
filters: [],
29+
}}
30+
defaultColumnValues={{ Filter: TextFilter }}
31+
fetchData={fetchEnterpriseUsersData}
32+
data={enterpriseUsersTableData.results}
33+
itemCount={enterpriseUsersTableData.itemCount}
34+
pageCount={enterpriseUsersTableData.pageCount}
35+
columns={[
36+
{
37+
id: 'details',
38+
Header: 'User details',
39+
accessor: 'details',
40+
Cell: EnterpriseCustomerUserDetail,
41+
},
42+
{
43+
id: 'administrator',
44+
Header: 'Administrator',
45+
accessor: 'administrator',
46+
disableFilters: true,
47+
Cell: AdministratorCell,
48+
},
49+
{
50+
id: 'learner',
51+
Header: 'Learner',
52+
accessor: 'learner',
53+
disableFilters: true,
54+
Cell: LearnerCell,
55+
},
56+
]}
57+
/>
58+
</div>
6159
);
6260
};
6361

64-
export default EnterpriseCustomerUsersTable;
62+
export default EnterpriseCustomerUsersTable;

src/Configuration/Customers/CustomerDetailView/tests/CustomerViewIntegrations.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('CustomerViewIntegrations', () => {
4949
</IntlProvider>,
5050
);
5151
await waitFor(() => {
52-
expect(screen.getByText('Associated Integrations')).toBeInTheDocument();
52+
expect(screen.getByText('Associated integrations')).toBeInTheDocument();
5353

5454
expect(screen.getByText('SSO')).toBeInTheDocument();
5555
expect(screen.getByText('Orange cats rule')).toBeInTheDocument();
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* eslint-disable react/prop-types */
2+
import {
3+
screen,
4+
render,
5+
} from '@testing-library/react';
6+
import '@testing-library/jest-dom';
7+
import {
8+
EnterpriseCustomerUserDetail,
9+
AdministratorCell,
10+
LearnerCell,
11+
} from '../EnterpriseCustomerUserDetail';
12+
13+
describe('EnterpriseCustomerUserDetail', () => {
14+
it('renders enterprise customer detail', () => {
15+
const enterpriseCustomerUser = {
16+
original: {
17+
enterpriseCustomerUser: {
18+
username: 'ash ketchum',
19+
email: 'ash@ketchum.org',
20+
},
21+
},
22+
};
23+
render(<EnterpriseCustomerUserDetail row={enterpriseCustomerUser} />);
24+
expect(screen.getByText('ash ketchum')).toBeInTheDocument();
25+
expect(screen.getByText('ash@ketchum.org')).toBeInTheDocument();
26+
});
27+
28+
it('renders pending enterprise customer detail', () => {
29+
const pendingEnterpriseCustomerUser = {
30+
original: {
31+
pendingEnterpriseCustomerUser: {
32+
userEmail: 'pending@customer.org',
33+
},
34+
},
35+
};
36+
render(<EnterpriseCustomerUserDetail row={pendingEnterpriseCustomerUser} />);
37+
expect(screen.getByText('pending@customer.org')).toBeInTheDocument();
38+
});
39+
40+
it('renders AdministratorCell there is a pending admin', () => {
41+
const pendingAdmin = {
42+
original: {
43+
pendingEnterpriseCustomerUser: {
44+
isPendingAdmin: true,
45+
},
46+
roleAssignments: ['enterprise_learner'],
47+
},
48+
};
49+
render(<AdministratorCell row={pendingAdmin} />);
50+
expect(screen.getByText('Pending')).toBeInTheDocument();
51+
});
52+
53+
it('renders AdministratorCell there is a registered admin', () => {
54+
const adminRow = {
55+
original: {
56+
pendingEnterpriseCustomerUser: {
57+
isPendingAdmin: false,
58+
},
59+
roleAssignments: ['enterprise_admin'],
60+
},
61+
};
62+
render(<AdministratorCell row={adminRow} />);
63+
expect(screen.queryByText('Pending')).not.toBeInTheDocument();
64+
});
65+
66+
it('renders LearnerCell when there is a registered learner and not pending', () => {
67+
const learnerRow = {
68+
original: {
69+
pendingEnterpriseCustomerUser: null,
70+
enterpriseCustomerUser: {
71+
username: 'ash ketchum',
72+
email: 'ash@ketchum.org',
73+
},
74+
roleAssignments: ['enterprise_learner'],
75+
},
76+
};
77+
render(<LearnerCell row={learnerRow} />);
78+
expect(screen.queryByText('Pending')).not.toBeInTheDocument();
79+
});
80+
81+
it('renders LearnerCell for pending user', () => {
82+
const pendingLearnerRow = {
83+
original: {
84+
pendingEnterpriseCustomerUser: {
85+
isPendingLearner: true,
86+
userEmail: 'pending@customer.org',
87+
},
88+
enterpriseCustomerUser: null,
89+
},
90+
};
91+
render(<LearnerCell row={pendingLearnerRow} />);
92+
expect(screen.queryByText('Pending')).toBeInTheDocument();
93+
});
94+
});

0 commit comments

Comments
 (0)