Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding cancel and remind actions to the budget assignment table #1070

Merged
merged 3 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Icon,
IconButton,
OverlayTrigger,
Stack,
Tooltip,
} from '@edx/paragon';
import { Mail, DoNotDisturbOn } from '@edx/paragon/icons';

const AssignmentRowActionTableCell = ({ row }) => {
const cancelButtonMarginLeft = row.original.state === 'allocated' ? 'ml-2.5' : 'ml-auto';
return (
<div className="d-flex">
{row.original.state === 'allocated' && (
<>
<OverlayTrigger
key="Remind"
placement="top"
overlay={<Tooltip variant="light" id="tooltip-remind">Remind learner</Tooltip>}
>
<IconButton
className="ml-auto mr-0"
src={Mail}
iconAs={Icon}
alt="Remind learner"
// eslint-disable-next-line no-console
onClick={() => console.log(`Reminding ${row.original.uuid}`)}

Check warning on line 29 in src/components/learner-credit-management/AssignmentRowActionTableCell.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/AssignmentRowActionTableCell.jsx#L29

Added line #L29 was not covered by tests
data-testid={`remind-assignment-${row.original.uuid}`}
/>
</OverlayTrigger>
<Stack direction="horizontal" gap={1} />
</>
)}
<OverlayTrigger
key="Cancel"
placement="top"
overlay={<Tooltip variant="light" id="tooltip-cancel">Cancel assignment</Tooltip>}
>
<IconButton
className={`${cancelButtonMarginLeft} mr-1 text-danger-500`}
src={DoNotDisturbOn}
iconAs={Icon}
alt="Cancel assignment"
// eslint-disable-next-line no-console
onClick={() => console.log(`Canceling ${row.original.uuid}`)}

Check warning on line 47 in src/components/learner-credit-management/AssignmentRowActionTableCell.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/AssignmentRowActionTableCell.jsx#L47

Added line #L47 was not covered by tests
data-testid={`cancel-assignment-${row.original.uuid}`}
/>
</OverlayTrigger>
</div>
);
};

AssignmentRowActionTableCell.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
uuid: PropTypes.string.isRequired,
state: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
};

export default AssignmentRowActionTableCell;
21 changes: 21 additions & 0 deletions src/components/learner-credit-management/AssignmentTableCancel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { DoNotDisturbOn } from '@edx/paragon/icons';

const AssignmentTableCancelAction = ({ selectedFlatRows, ...rest }) => (
// eslint-disable-next-line no-console
<Button variant="danger" iconBefore={DoNotDisturbOn} onClick={() => console.log('Cancel', selectedFlatRows, rest)}>
{`Cancel (${selectedFlatRows.length})`}
</Button>
);

AssignmentTableCancelAction.propTypes = {
selectedFlatRows: PropTypes.arrayOf(PropTypes.shape()),
};

AssignmentTableCancelAction.defaultProps = {
selectedFlatRows: [],
};

export default AssignmentTableCancelAction;
29 changes: 29 additions & 0 deletions src/components/learner-credit-management/AssignmentTableRemind.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { Mail } from '@edx/paragon/icons';

const AssignmentTableRemindAction = ({ selectedFlatRows, ...rest }) => {
const hideRemindAction = selectedFlatRows.some(
row => row.original.state !== 'allocated',
);
if (hideRemindAction) {
return null;
}
return (
// eslint-disable-next-line no-console
<Button iconBefore={Mail} onClick={() => console.log('Remind', selectedFlatRows, rest)}>

Check warning on line 15 in src/components/learner-credit-management/AssignmentTableRemind.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/AssignmentTableRemind.jsx#L15

Added line #L15 was not covered by tests
{`Remind (${selectedFlatRows.length})`}
</Button>
);
};

AssignmentTableRemindAction.propTypes = {
selectedFlatRows: PropTypes.arrayOf(PropTypes.shape()),
};

AssignmentTableRemindAction.defaultProps = {
selectedFlatRows: [],
};

export default AssignmentTableRemindAction;
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DataTable } from '@edx/paragon';

import TableTextFilter from './TableTextFilter';
import CustomDataTableEmptyState from './CustomDataTableEmptyState';
import AssignmentDetailsTableCell from './AssignmentDetailsTableCell';
import AssignmentStatusTableCell from './AssignmentStatusTableCell';
import AssignmentRowActionTableCell from './AssignmentRowActionTableCell';
import AssignmentTableRemindAction from './AssignmentTableRemind';
import AssignmentTableCancelAction from './AssignmentTableCancel';
import { DEFAULT_PAGE, PAGE_SIZE, formatPrice } from './data';
import AssignmentRecentActionTableCell from './AssignmentRecentActionTableCell';
import AssignmentsTableRefreshAction from './AssignmentsTableRefreshAction';
Expand All @@ -19,6 +21,7 @@ const BudgetAssignmentsTable = ({
}) => (
<DataTable
isSortable
isSelectable
manualSortBy
isPaginated
manualPagination
Expand Down Expand Up @@ -51,6 +54,13 @@ const BudgetAssignmentsTable = ({
disableFilters: true,
},
]}
additionalColumns={[
{
Header: '',
Cell: AssignmentRowActionTableCell,
id: 'action',
},
]}
tableActions={[
<AssignmentsTableRefreshAction refresh={fetchTableData} />,
]}
Expand All @@ -68,6 +78,10 @@ const BudgetAssignmentsTable = ({
itemCount={tableData?.count || 0}
pageCount={tableData?.numPages || 1}
EmptyTableComponent={CustomDataTableEmptyState}
bulkActions={[
<AssignmentTableRemindAction />,
<AssignmentTableCancelAction />,
]}
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,4 +723,117 @@ describe('<BudgetDetailPage />', () => {

expect(screen.getByText('loading budget activity overview')).toBeInTheDocument();
});

it('displays remind row and bulk actions when allocated', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it also asserts that the row-level Cancel CTA is there as well.

useParams.mockReturnValue({
budgetId: mockSubsidyAccessPolicyUUID,
activeTabKey: 'activity',
});
useOfferRedemptions.mockReturnValue({
isLoading: false,
offerRedemptions: mockEmptyOfferRedemptions,
fetchOfferRedemptions: jest.fn(),
});
useSubsidyAccessPolicy.mockReturnValue({
isInitialLoading: false,
data: mockAssignableSubsidyAccessPolicy,
});
useBudgetDetailActivityOverview.mockReturnValue({
isLoading: false,
data: {
contentAssignments: { count: 1 },
spentTransactions: { count: 0 },
},
});
useBudgetContentAssignments.mockReturnValue({
isLoading: false,
contentAssignments: {
count: 1,
results: [
{
uuid: 'test-uuid',
contentKey: mockCourseKey,
contentQuantity: -19900,
learnerState: 'active',
recentAction: { actionType: 'assigned', timestamp: '2023-10-27' },
actions: [],
state: 'allocated',
},
],
numPages: 1,
currentPage: 1,
},
fetchContentAssignments: jest.fn(),
});
renderWithRouter(<BudgetDetailPageWrapper />);
const cancelRowAction = screen.getByTestId('cancel-assignment-test-uuid');
const remindRowAction = screen.getByTestId('remind-assignment-test-uuid');
expect(cancelRowAction).toBeInTheDocument();
expect(remindRowAction).toBeInTheDocument();

const checkBox = screen.getByTestId('datatable-select-column-checkbox-cell');
expect(checkBox).toBeInTheDocument();
userEvent.click(checkBox);
await waitFor(() => {
expect(screen.getByText('Remind (1)')).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByText('Cancel (1)')).toBeInTheDocument();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: likely could group both of these assertions under the single waitFor. Alternatively, could also rely on await screen.findByText('Cancel (1)') as well:

expect(await screen.findByText('Remind (1)')).toBeInTheDocument();
expect(await screen.findByText('Cancel (1)')).toBeInTheDocument();

});
});

it('hides remind row and bulk actions when allocated', () => {
useOfferRedemptions.mockReturnValue({
isLoading: false,
offerRedemptions: mockEmptyOfferRedemptions,
fetchOfferRedemptions: jest.fn(),
});
useBudgetDetailActivityOverview.mockReturnValue({
isLoading: false,
data: {
contentAssignments: { count: 1 },
spentTransactions: { count: 0 },
},
});
useParams.mockReturnValue({
budgetId: mockSubsidyAccessPolicyUUID,
activeTabKey: 'activity',
});
useSubsidyAccessPolicy.mockReturnValue({
isInitialLoading: false,
data: mockAssignableSubsidyAccessPolicy,
});
useBudgetDetailActivityOverview.mockReturnValue({
isLoading: false,
data: {
contentAssignments: { count: 1 },
spentTransactions: { count: 0 },
},
});
useBudgetContentAssignments.mockReturnValue({
isLoading: false,
contentAssignments: {
count: 1,
results: [
{
uuid: 'test-uuid',
contentKey: mockCourseKey,
contentQuantity: -19900,
learnerState: 'accepted',
recentAction: { actionType: 'assigned', timestamp: '2023-10-27' },
actions: [],
state: 'accepted',
},
],
numPages: 1,
currentPage: 1,
},
fetchContentAssignments: jest.fn(),
});
renderWithRouter(<BudgetDetailPageWrapper />);
expect(screen.queryByTestId('remind-assignment-test-uuid')).not.toBeInTheDocument();
const checkBox = screen.getByTestId('datatable-select-column-checkbox-cell');
userEvent.click(checkBox);
expect(screen.queryByText('Remind (1)')).not.toBeInTheDocument();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clarification] Will the "Cancel" CTA also not show in this test case? Might be good to be explicit about whether the "Cancel" CTA is present or not here as well.

});
});