Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into knguyen2/ent-9159
Browse files Browse the repository at this point in the history
  • Loading branch information
katrinan029 committed Aug 8, 2024
2 parents e433e78 + e541f84 commit 8177c21
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const OtherSubsidies = () => (
<div>
<FormattedMessage
id="configuration.customersPage.otherSubsidiesColumn.tooltip"
defaultMessage="Includes Offers and Codes"
defaultMessage="Includes offers and codes"
description="Tooltip for the Other Subsidies column header in the Customers table"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const CustomerDetailLink = ({ row }) => {
<Hyperlink
destination={`${HOME}/${row.original.uuid}/view`}
key={row.original.uuid}
rel="noopener noreferrer"
variant="muted"
target="_blank"
showLaunchIcon={false}
Expand All @@ -37,6 +38,7 @@ export const CustomerDetailLink = ({ row }) => {
<Hyperlink
destination={`${ADMIN_PORTAL_BASE_URL}/${row.original.slug}/admin/learners`}
key={row.original.uuid}
rel="noopener noreferrer"
variant="muted"
target="_blank"
showLaunchIcon
Expand All @@ -61,7 +63,7 @@ export const CustomerDetailLink = ({ row }) => {
show={showToast}
delay={2000}
>
Copied to clipboard!
Copied to clipboard
</Toast>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ const CustomersPage = () => {
}}
renderRowSubComponent={({ row }) => <CustomerDetailRowSubComponent row={row} />}
isPaginated
isSortable
isFilterable
defaultColumnValues={{ Filter: TextFilter }}
itemCount={enterpriseList?.length || 0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ describe('CustomerDetails', () => {
<CustomerDetailLink row={row} />
</IntlProvider>,
);
expect(screen.getByRole('link', { name: 'Ash Ketchum' })).toHaveAttribute('href', '/enterprise-configuration/customers/ash-ketchum/view');
expect(screen.getByRole('link', { name: 'Ash Ketchum' })).toHaveAttribute('href', '/enterprise-configuration/customers/123456789/view');
expect(screen.getByRole('link', { name: '/ash-ketchum/ in a new tab' })).toHaveAttribute('href', 'http://www.testportal.com/ash-ketchum/admin/learners');
expect(screen.getByText('123456789')).toBeInTheDocument();
const copy = screen.getByTestId('copy');
userEvent.click(copy);
await waitFor(() => expect(screen.getByText('Copied to clipboard!')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('Copied to clipboard')).toBeInTheDocument());
});
});
128 changes: 92 additions & 36 deletions src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,101 @@
import { ActionRow, Button, Card, Hyperlink } from '@openedx/paragon';
import { Launch } from '@openedx/paragon/icons';
import { useState } from 'react';
import PropTypes from 'prop-types';
import {
ActionRow,
Button,
Card,
Icon,
Hyperlink,
Toast,
} from '@openedx/paragon';
import { Launch, ContentCopy } from '@openedx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import { formatDate } from '../data/utils';

const CustomerCard = ({ enterpriseCustomer }) => {
const { ADMIN_PORTAL_BASE_URL } = getConfig();
const { ADMIN_PORTAL_BASE_URL, LMS_BASE_URL } = getConfig();

const [showToast, setShowToast] = useState(false);
const copyToClipboard = (id) => {
navigator.clipboard.writeText(id);
setShowToast(true);

Check warning on line 21 in src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx

View check run for this annotation

Codecov / codecov/patch

src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx#L20-L21

Added lines #L20 - L21 were not covered by tests
};

return (
<Card variant="dark mb-0">
<Card.Section
actions={
<ActionRow>
<Button>View Details</Button>
<Button variant="inverse-primary" iconAfter={Launch}>Open in Django</Button>
</ActionRow>
}
>
<p className="small font-weight-bold mb-0 mt-4">
CUSTOMER RECORD
</p>
<p className="lead font-weight-bold mb-0">
{enterpriseCustomer.name}
</p>
<Hyperlink
destination={`${ADMIN_PORTAL_BASE_URL}/${enterpriseCustomer.slug}/admin/learners`}
variant="muted"
target="_blank"
showLaunchIcon
className="small mb-1"
<div>
<Card variant="dark" className="mb-0">
<Card.Section
actions={(
<ActionRow>
<Button>View Details</Button>
<Button
className="text-dark-500"
as="a"
href={`${LMS_BASE_URL}/admin/enterprise/enterprisecustomer/${enterpriseCustomer.uuid}/change`}
variant="inverse-primary"
target="_blank"
rel="noopener noreferrer"
iconAfter={Launch}
>
Open in Django
</Button>
</ActionRow>
)}
>
/{enterpriseCustomer.slug}/
</Hyperlink>
<p className="small mb-1">
{enterpriseCustomer.uuid}
</p>
<p className="small mb-1">
Created {enterpriseCustomer.created} • Last modified {formatDate(enterpriseCustomer.modified)}
</p>
</Card.Section>
</Card>
)
<p className="small font-weight-bold mb-0 mt-2">
CUSTOMER RECORD
</p>
<p className="lead font-weight-bold mb-0">
{enterpriseCustomer.name}
</p>
<Hyperlink
destination={`${ADMIN_PORTAL_BASE_URL}/${enterpriseCustomer.slug}/admin/learners`}
variant="muted"
target="_blank"
showLaunchIcon
className="small mb-1"
>
/{enterpriseCustomer.slug}/
</Hyperlink>
<div
role="presentation"
className="pgn-doc__icons-table__preview-footer"
>
<p className="small mb-1">
{enterpriseCustomer.uuid}
</p>
<Icon
key="ContentCopy"
src={ContentCopy}
data-testid="copy"
onClick={() => copyToClipboard(enterpriseCustomer.uuid)}

Check warning on line 71 in src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx

View check run for this annotation

Codecov / codecov/patch

src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx#L71

Added line #L71 was not covered by tests
/>
</div>
<p className="small mb-1">
Created {formatDate(enterpriseCustomer.created)} • Last modified {formatDate(enterpriseCustomer.modified)}
</p>
</Card.Section>
</Card>
<Toast
onClose={() => setShowToast(false)}

Check warning on line 80 in src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx

View check run for this annotation

Codecov / codecov/patch

src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx#L80

Added line #L80 was not covered by tests
show={showToast}
delay={2000}
>
Copied to clipboard
</Toast>
</div>

);
};

CustomerCard.propTypes = {
enterpriseCustomer: PropTypes.shape({
created: PropTypes.string,
modified: PropTypes.string,
slug: PropTypes.string,
name: PropTypes.string,
uuid: PropTypes.string,
}).isRequired,
};

export default CustomerCard;
export default CustomerCard;
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { Breadcrumb, Stack, Container } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import CustomerCard from "./CustomerCard";
import { useState, useEffect, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { getEnterpriseCustomer } from '../data/utils';
import { useParams } from 'react-router-dom';
import { logError } from '@edx/frontend-platform/logging';
import {
Breadcrumb,
Container,
Skeleton,
Stack,
} from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import CustomerCard from './CustomerCard';
import { getEnterpriseCustomer } from '../data/utils';

const CustomerViewContainer = () => {
const { id } = useParams();
const [enterpriseCustomer, setEnterpriseCustomer] = useState([])
const [enterpriseCustomer, setEnterpriseCustomer] = useState({});
const [isLoading, setIsLoading] = useState(true);
const intl = useIntl();

const fetchData = useCallback(
async () => {
try {
const enterpriseCustomer = await getEnterpriseCustomer(id);
setEnterpriseCustomer(enterpriseCustomer);
const response = await getEnterpriseCustomer({ uuid: id });
setEnterpriseCustomer(response[0]);
} catch (error) {
logError(error);

Check warning on line 26 in src/Configuration/Customers/CustomerDetailView/CustomerViewContainer.jsx

View check run for this annotation

Codecov / codecov/patch

src/Configuration/Customers/CustomerDetailView/CustomerViewContainer.jsx#L26

Added line #L26 was not covered by tests
} finally {
Expand All @@ -32,27 +37,31 @@ const CustomerViewContainer = () => {

return (
<div>
<Container className="mt-5">
<Breadcrumb arialLabel="customer detail"
links={[
{
label: intl.formatMessage({
id: 'lcm.budget.detail.page.breadcrumb.budgets',
defaultMessage: 'Budgets',
description: 'Breadcrumb label for the budgets page',
})
},
{ label: `${enterpriseCustomer.name}`, href: 'here' }
]}
/>
</Container>
{!isLoading ? (
<Container className="mt-5">
<Breadcrumb
arial-label="customer detail"
links={[
{
label: intl.formatMessage({
id: 'supportTool.customers.page.breadcrumb.customer',
defaultMessage: 'Customers',
description: 'Breadcrumb label for the customers page',
}),
href: '/enterprise-configuration/customers/',
},
{ label: enterpriseCustomer.name },
]}
/>
</Container>
) : <Skeleton />}
<Container className="mt-4">
<Stack gap={2}>
<CustomerCard enterpriseCustomer={enterpriseCustomer} />
{!isLoading ? <CustomerCard enterpriseCustomer={enterpriseCustomer} /> : <Skeleton height={230} />}
</Stack>
</Container>
</div>
);
}
};

export default CustomerViewContainer;
export default CustomerViewContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable react/prop-types */
import { screen, render } from '@testing-library/react';
import '@testing-library/jest-dom';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import { formatDate } from '../../data/utils';
import CustomerCard from '../CustomerCard';

jest.mock('../../data/utils', () => ({
getEnterpriseCustomer: jest.fn(),
formatDate: jest.fn(),
}));

const mockData = {
uuid: 'test-id',
name: 'Test Customer Name',
slug: 'customer-6',
created: '2024-07-23T20:02:57.651943Z',
modified: '2024-07-23T20:02:57.651943Z',
};

describe('CustomerCard', () => {
it('renders customer card data', () => {
formatDate.mockReturnValue('July 23, 2024');
render(
<IntlProvider locale="en">
<CustomerCard enterpriseCustomer={mockData} />
</IntlProvider>,
);
expect(screen.getByText('test-id')).toBeInTheDocument();
expect(screen.getByText('/customer-6/')).toBeInTheDocument();
expect(screen.getByText('Created July 23, 2024 • Last modified July 23, 2024')).toBeInTheDocument();
expect(screen.getByText('Test Customer Name'));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable react/prop-types */
import { screen, render, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import { getEnterpriseCustomer, formatDate } from '../../data/utils';
import CustomerViewContainer from '../CustomerViewContainer';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: () => ({ id: 'test-id' }),
}));

jest.mock('../../data/utils', () => ({
getEnterpriseCustomer: jest.fn(),
formatDate: jest.fn(),
}));

describe('CustomerViewContainer', () => {
it('renders data', async () => {
getEnterpriseCustomer.mockReturnValue([{
uuid: 'test-id',
name: 'Test Customer Name',
slug: 'customer-6',
created: '2024-07-23T20:02:57.651943Z',
modified: '2024-07-23T20:02:57.651943Z',
}]);
formatDate.mockReturnValue('July 23, 2024');
render(
<IntlProvider locale="en">
<CustomerViewContainer />
</IntlProvider>,
);
await waitFor(() => {
expect(screen.getByText('test-id')).toBeInTheDocument();
expect(screen.getByText('/customer-6/')).toBeInTheDocument();
expect(screen.getByText('Created July 23, 2024 • Last modified July 23, 2024')).toBeInTheDocument();
const customerNameText = screen.getAllByText('Test Customer Name');
customerNameText.forEach(customerName => {
expect(customerName).toBeInTheDocument();
});
});
});
});
9 changes: 3 additions & 6 deletions src/Configuration/Customers/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,10 @@ export const getSubsidyAccessPolicies = async (enterpriseId) => {
return subsidyAccessPolicies;
};

export const getEnterpriseCustomer = async (enterpriseId) => {
const response = await LmsApiService.fetchEnterpriseCustomer(enterpriseId)
export const getEnterpriseCustomer = async (options) => {
const response = await LmsApiService.fetchEnterpriseCustomerSupportTool(options);
const enterpriseCustomer = camelCaseObject(response.data);
console.log(enterpriseCustomer)
return enterpriseCustomer;
};

export const formatDate = (date) => {
return dayjs(date).utc().format('MMMM DD, YYYY');
};
export const formatDate = (date) => dayjs(date).utc().format('MMMM DD, YYYY');
Loading

0 comments on commit 8177c21

Please sign in to comment.