Skip to content

Commit

Permalink
Merge pull request #406 from openedx/knguyen2/ent-9166
Browse files Browse the repository at this point in the history
feat: add users table
  • Loading branch information
katrinan029 authored Sep 5, 2024
2 parents c6efe80 + 5456ed3 commit 89a3569
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useCopyToClipboard } from '../data/utils';
const { HOME } = ROUTES.CONFIGURATION.SUB_DIRECTORY.CUSTOMERS;

export const CustomerDetailLink = ({ row }) => {
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard();
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard(row.original.uuid);
const { ADMIN_PORTAL_BASE_URL } = getConfig();

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import {
import { Launch, ContentCopy } from '@openedx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import { formatDate, useCopyToClipboard } from '../data/utils';
import DJANGO_ADMIN_BASE_URL from '../data/constants';
import CustomerDetailModal from './CustomerDetailModal';

const CustomerCard = ({ enterpriseCustomer }) => {
const { ADMIN_PORTAL_BASE_URL } = getConfig();
const { ADMIN_PORTAL_BASE_URL, DJANGO_ADMIN_LMS_BASE_URL } = getConfig();
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard();
const [isDetailsOpen, openDetails, closeDetails] = useToggle(false);

Expand All @@ -28,7 +27,7 @@ const CustomerCard = ({ enterpriseCustomer }) => {
<Button
className="text-dark-500"
as="a"
href={`${DJANGO_ADMIN_BASE_URL}/admin/enterprise/enterprisecustomer/${enterpriseCustomer.uuid}/change`}
href={`${DJANGO_ADMIN_LMS_BASE_URL}/admin/enterprise/enterprisecustomer/${enterpriseCustomer.uuid}/change`}
variant="inverse-primary"
target="_blank"
rel="noopener noreferrer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const CustomerIntegrations = ({
<div>
{(activeSSO || activeIntegrations || apiCredentialsEnabled) && (
<div>
<h2>Associated Integrations</h2>
<h2>Associated integrations</h2>
<hr />
{activeSSO && activeSSO.map((sso) => (
<CustomerViewCard
slug={slug}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const CustomerPlanContainer = ({ slug }) => {
Show inactive
</Form.Switch>
</div>
<hr />
{renderActivePoliciesCard}
{renderActiveSubscriptions}
{showInactive ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import CustomerCard from './CustomerCard';
import { getEnterpriseCustomer } from '../data/utils';
import CustomerIntegrations from './CustomerIntegrations';
import EnterpriseCustomerUsersTable from './EnterpriseCustomerUsersTable';
import CustomerPlanContainer from './CustomerPlanContainer';

const CustomerViewContainer = () => {
Expand Down Expand Up @@ -75,6 +76,7 @@ const CustomerViewContainer = () => {
activeSSO={enterpriseCustomer.activeSsoConfigurations}
apiCredentialsEnabled={enterpriseCustomer.enableGenerationOfApiCredentials}
/>
<EnterpriseCustomerUsersTable />
</Stack>
</Container>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Icon, IconButton, Stack, Chip,
} from '@openedx/paragon';
import { Person, Check, Timelapse } from '@openedx/paragon/icons';

export const EnterpriseCustomerUserDetail = ({
row,
}) => {
let memberDetails;
const memberDetailIcon = (
<IconButton
isActive
invertColors
src={Person}
iconAs={Icon}
className="border rounded-circle mr-3"
alt="members detail column icon"
style={{ opacity: 1, flexShrink: 0 }}
/>
);

if (row.original.enterpriseCustomerUser?.username) {
memberDetails = (
<div className="mb-n3">
<p className="font-weight-bold mb-0">
{row.original.enterpriseCustomerUser?.username}
</p>
<p>{row.original.enterpriseCustomerUser?.email}</p>
</div>
);
} else {
memberDetails = (
<p className="align-middle mb-0">
{row.original.pendingEnterpriseCustomerUser?.userEmail}
</p>
);
}
return (
<Stack gap={0} direction="horizontal">
{memberDetailIcon}
{memberDetails}
</Stack>
);
};

export const AdministratorCell = ({ row }) => {
if (row.original?.pendingEnterpriseCustomerUser?.isPendingAdmin) {
return (
<Chip
iconBefore={Timelapse}
>
Pending
</Chip>
);
}
return (
<div>
{row.original?.roleAssignments?.includes('enterprise_admin') ? <Check data-testid="admin check" aria-label="admin check" /> : null}
</div>
);
};

export const LearnerCell = ({ row }) => {
if (!row.original?.pendingEnterpriseCustomerUser?.isPendingLearner) {
return (
<div>
{row.original?.roleAssignments?.includes('enterprise_learner') ? <Check data-testid="learner check" aria-label="learner check" /> : null}
</div>
);
}

return (
<Chip
iconBefore={Timelapse}
>
Pending
</Chip>
);
};

EnterpriseCustomerUserDetail.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
enterpriseCustomerUser: PropTypes.shape({
email: PropTypes.string.isRequired,
username: PropTypes.string,
}),
pendingEnterpriseCustomerUser: PropTypes.shape({
isPendingAdmin: PropTypes.bool,
userEmail: PropTypes.string,
}),
roleAssignments: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
}).isRequired,
};

AdministratorCell.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
pendingEnterpriseCustomerUser: PropTypes.shape({
isPendingAdmin: PropTypes.bool,
}),
roleAssignments: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
}).isRequired,
};

LearnerCell.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
pendingEnterpriseCustomerUser: PropTypes.shape({
isPendingLearner: PropTypes.bool,
}),
roleAssignments: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
}).isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useParams } from 'react-router-dom';
import { DataTable, TextFilter } from '@openedx/paragon';
import { EnterpriseCustomerUserDetail, LearnerCell, AdministratorCell } from './EnterpriseCustomerUserDetail';
import useCustomerUsersTableData from '../data/hooks/useCustomerUsersTableData';

const EnterpriseCustomerUsersTable = () => {
const { id } = useParams();
const {
isLoading,
enterpriseUsersTableData,
fetchEnterpriseUsersData,
} = useCustomerUsersTableData(id);
return (
<div>
<h2>Associated users ({enterpriseUsersTableData.itemCount})</h2>
<hr />
<DataTable
isLoading={isLoading}
isExpandable
isPaginated
manualPagination
isFilterable
manualFilters
initialState={{
pageSize: 8,
pageIndex: 0,
sortBy: [],
filters: [],
}}
defaultColumnValues={{ Filter: TextFilter }}
fetchData={fetchEnterpriseUsersData}
data={enterpriseUsersTableData.results}
itemCount={enterpriseUsersTableData.itemCount}
pageCount={enterpriseUsersTableData.pageCount}
columns={[
{
id: 'details',
Header: 'User details',
accessor: 'details',
Cell: EnterpriseCustomerUserDetail,
},
{
id: 'administrator',
Header: 'Administrator',
accessor: 'administrator',
disableFilters: true,
Cell: AdministratorCell,
},
{
id: 'learner',
Header: 'Learner',
accessor: 'learner',
disableFilters: true,
Cell: LearnerCell,
},
]}
/>
</div>
);
};

export default EnterpriseCustomerUsersTable;
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('CustomerViewIntegrations', () => {
</IntlProvider>,
);
await waitFor(() => {
expect(screen.getByText('Associated Integrations')).toBeInTheDocument();
expect(screen.getByText('Associated integrations')).toBeInTheDocument();

expect(screen.getByText('SSO')).toBeInTheDocument();
expect(screen.getByText('Orange cats rule')).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable react/prop-types */
import {
screen,
render,
} from '@testing-library/react';
import '@testing-library/jest-dom';
import {
EnterpriseCustomerUserDetail,
AdministratorCell,
LearnerCell,
} from '../EnterpriseCustomerUserDetail';

describe('EnterpriseCustomerUserDetail', () => {
it('renders enterprise customer detail', () => {
const enterpriseCustomerUser = {
original: {
enterpriseCustomerUser: {
username: 'ash ketchum',
email: 'ash@ketchum.org',
},
},
};
render(<EnterpriseCustomerUserDetail row={enterpriseCustomerUser} />);
expect(screen.getByText('ash ketchum')).toBeInTheDocument();
expect(screen.getByText('ash@ketchum.org')).toBeInTheDocument();
});

it('renders pending enterprise customer detail', () => {
const pendingEnterpriseCustomerUser = {
original: {
pendingEnterpriseCustomerUser: {
userEmail: 'pending@customer.org',
},
},
};
render(<EnterpriseCustomerUserDetail row={pendingEnterpriseCustomerUser} />);
expect(screen.getByText('pending@customer.org')).toBeInTheDocument();
});

it('renders AdministratorCell there is a pending admin', () => {
const pendingAdmin = {
original: {
pendingEnterpriseCustomerUser: {
isPendingAdmin: true,
},
roleAssignments: ['enterprise_learner'],
},
};
render(<AdministratorCell row={pendingAdmin} />);
expect(screen.getByText('Pending')).toBeInTheDocument();
});

it('renders AdministratorCell there is a registered admin', () => {
const adminRow = {
original: {
pendingEnterpriseCustomerUser: {
isPendingAdmin: false,
},
roleAssignments: ['enterprise_admin'],
},
};
render(<AdministratorCell row={adminRow} />);
expect(screen.queryByText('Pending')).not.toBeInTheDocument();
});

it('renders LearnerCell when there is a registered learner and not pending', () => {
const learnerRow = {
original: {
pendingEnterpriseCustomerUser: null,
enterpriseCustomerUser: {
username: 'ash ketchum',
email: 'ash@ketchum.org',
},
roleAssignments: ['enterprise_learner'],
},
};
render(<LearnerCell row={learnerRow} />);
expect(screen.queryByText('Pending')).not.toBeInTheDocument();
});

it('renders LearnerCell for pending user', () => {
const pendingLearnerRow = {
original: {
pendingEnterpriseCustomerUser: {
isPendingLearner: true,
userEmail: 'pending@customer.org',
},
enterpriseCustomerUser: null,
},
};
render(<LearnerCell row={pendingLearnerRow} />);
expect(screen.queryByText('Pending')).toBeInTheDocument();
});
});
Loading

0 comments on commit 89a3569

Please sign in to comment.