From 67332c772f3a534dedf3984c7b2c30b485ccf0b8 Mon Sep 17 00:00:00 2001 From: Alexander J Sheehan Date: Tue, 28 May 2024 20:09:53 +0000 Subject: [PATCH] fix: allowing members table to select all --- .../data/tests/constants.js | 1 + .../GroupMembersCsvDownloadTableAction.jsx | 14 +- .../LearnerCreditGroupMembersTable.jsx | 11 +- .../bulk-actions/MemberRemoveModal.jsx | 15 +- .../members-tab/tests/MembersTab.test.jsx | 137 ++++++++++++++++-- 5 files changed, 160 insertions(+), 18 deletions(-) diff --git a/src/components/learner-credit-management/data/tests/constants.js b/src/components/learner-credit-management/data/tests/constants.js index 5f4e6f87df..0e01421716 100644 --- a/src/components/learner-credit-management/data/tests/constants.js +++ b/src/components/learner-credit-management/data/tests/constants.js @@ -23,6 +23,7 @@ export const mockAssignableSubsidyAccessPolicy = { }, isAssignable: true, subsidyUuid: 'mock-subsidy-uuid', + catalogUuid: 'mock-catalog-uuid', }; export const mockAssignableSubsidyAccessPolicyWithNoUtilization = { diff --git a/src/components/learner-credit-management/members-tab/GroupMembersCsvDownloadTableAction.jsx b/src/components/learner-credit-management/members-tab/GroupMembersCsvDownloadTableAction.jsx index 453d5c0941..103e82c9bc 100644 --- a/src/components/learner-credit-management/members-tab/GroupMembersCsvDownloadTableAction.jsx +++ b/src/components/learner-credit-management/members-tab/GroupMembersCsvDownloadTableAction.jsx @@ -9,6 +9,7 @@ import EnterpriseAccessApiService from '../../../data/services/EnterpriseAccessA import { useBudgetId, useSubsidyAccessPolicy } from '../data'; const GroupMembersCsvDownloadTableAction = ({ + isEntireTableSelected, tableInstance, }) => { const selectedEmails = Object.keys(tableInstance.state.selectedRowIds); @@ -55,7 +56,7 @@ const GroupMembersCsvDownloadTableAction = ({ EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData( subsidyAccessPolicyId, options, - selectedEmails, + isEntireTableSelected ? null : selectedEmails, ).then(response => { // download CSV const blob = new Blob([response.data], { @@ -69,6 +70,13 @@ const GroupMembersCsvDownloadTableAction = ({ }); }; + let buttonSelectedNumber = 0; + if (selectedEmailCount > 0) { + buttonSelectedNumber = isEntireTableSelected ? `(${tableInstance.itemCount})` : `(${selectedEmailCount})`; + } else { + buttonSelectedNumber = `all (${tableInstance.itemCount})`; + } + return ( <> - Download {selectedEmailCount > 0 ? `(${selectedEmailCount})` : `all (${tableInstance.itemCount})`} + Download {buttonSelectedNumber} ); }; GroupMembersCsvDownloadTableAction.propTypes = { + isEntireTableSelected: PropTypes.bool, tableInstance: PropTypes.shape({ itemCount: PropTypes.number, state: PropTypes.shape({ @@ -131,6 +140,7 @@ GroupMembersCsvDownloadTableAction.defaultProps = { itemCount: 0, state: {}, }, + isEntireTableSelected: false, }; export default GroupMembersCsvDownloadTableAction; diff --git a/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx b/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx index 03b89510ff..9b4c6cc15f 100644 --- a/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx +++ b/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx @@ -59,6 +59,13 @@ const KabobMenu = ({ ); }; +const selectColumn = { + id: 'selection', + Header: DataTable.ControlledSelectHeader, + Cell: DataTable.ControlledSelect, + disableSortBy: true, +}; + const LearnerCreditGroupMembersTable = ({ isLoading, tableData, @@ -70,8 +77,10 @@ const LearnerCreditGroupMembersTable = ({ }) => ( { setRequestState({ ...initialRequestState, loading: true }); const makeRequest = () => { - const userEmailsToRemove = usersToRemove.map((user) => user.original.memberDetails.userEmail); - const requestBody = snakeCaseObject({ - learnerEmails: userEmailsToRemove, - catalogUuid: subsidyAccessPolicy.catalogUuid, - }); + const baseRequestBody = { catalogUuid: subsidyAccessPolicy.catalogUuid }; + let requestBody; + if (removeAllUsers) { + requestBody = snakeCaseObject({ remove_all: true, ...baseRequestBody }); + } else { + const userEmailsToRemove = usersToRemove.map((user) => user.original.memberDetails.userEmail); + requestBody = snakeCaseObject({ learnerEmails: userEmailsToRemove, ...baseRequestBody }); + } return LmsApiService.removeEnterpriseLearnersFromGroup(groupUuid, requestBody); }; - try { const response = await makeRequest(); setRequestState({ ...initialRequestState, success: true }); @@ -77,6 +79,7 @@ const MemberRemoveModal = ({ setRequestState, groupUuid, subsidyAccessPolicy, + removeAllUsers, ]); const handleClose = () => { diff --git a/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx b/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx index 91e3fab2f7..913ca7ab58 100644 --- a/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx +++ b/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx @@ -361,14 +361,16 @@ describe('', () => { sortBy: [{ desc: false, id: 'status' }], })); - // TODO Sorting by enrollment count is currently not supported by the backend - // userEvent.click(screen.getByTestId('members-table-enrollments-column-header')); - // await waitFor(() => expect(mockFetchEnterpriseGroupMembersTableData).toHaveBeenCalledWith({ - // filters: [], - // pageIndex: 0, - // pageSize: 10, - // sortBy: [{ desc: false, id: 'enrollmentCount' }], - // })); + userEvent.click(screen.getByTestId('members-table-enrollments-column-header')); + await waitFor(() => expect(mockFetchEnterpriseGroupMembersTableData).toHaveBeenCalledWith({ + filters: [ + { id: 'memberDetails', value: 'foobar' }, + { id: 'status', value: true }, + ], + pageIndex: 0, + pageSize: 10, + sortBy: [{ desc: false, id: 'enrollmentCount' }], + })); }); it('remove learner flow', async () => { const initialState = { @@ -459,6 +461,7 @@ describe('', () => { enterpriseSlug: 'test-enterprise-slug', enterpriseAppPage: 'test-enterprise-page', activeTabKey: 'members', + budgetId: mockAssignableSubsidyAccessPolicy.uuid, }); useSubsidyAccessPolicy.mockReturnValue({ isInitialLoading: false, @@ -525,6 +528,13 @@ describe('', () => { expect(mockRemoveSpy).toHaveBeenCalled(); await waitForElementToBeRemoved(() => screen.queryByText('Removing (2)')); await waitFor(() => expect(screen.queryByText('2 members successfully removed')).toBeInTheDocument()); + + // Because there is only one page of data, and the whole page is selected, + // the request should be to remove the entire org + expect(LmsApiService.removeEnterpriseLearnersFromGroup).toHaveBeenCalledWith( + mockAssignableSubsidyAccessPolicy.groupAssociations[0], + { remove_all: true, catalog_uuid: mockAssignableSubsidyAccessPolicy.catalogUuid }, + ); }); it('remove learner flow with the kabob menu', async () => { const initialState = { @@ -716,7 +726,7 @@ describe('', () => { const mockGroupData = { isLoading: false, enterpriseGroupMembersTableData: { - itemCount: 1, + itemCount: 100, pageCount: 1, results: [{ memberDetails: { userEmail: 'foobar@test.com', userName: 'ayy lmao' }, @@ -844,4 +854,113 @@ describe('', () => { screen.getByText('This member has been successfully removed and can not browse this budget\'s ' + 'catalog and enroll using their member permissions.'); }); + it('download learner flow for multiple selected pages of users', async () => { + // Setup + const initialState = { + portalConfiguration: { + ...initialStoreState.portalConfiguration, + enterpriseFeatures: { + enterpriseGroupsV1: true, + }, + }, + }; + useParams.mockReturnValue({ + enterpriseSlug: 'test-enterprise-slug', + enterpriseAppPage: 'test-enterprise-page', + activeTabKey: 'members', + budgetId: mockAssignableSubsidyAccessPolicy.uuid, + }); + useSubsidyAccessPolicy.mockReturnValue({ + isInitialLoading: false, + data: mockAssignableSubsidyAccessPolicy, + }); + useBudgetDetailActivityOverview.mockReturnValue({ + isLoading: false, + data: mockEmptyStateBudgetDetailActivityOverview, + }); + useBudgetRedemptions.mockReturnValue({ + isLoading: false, + budgetRedemptions: mockEmptyBudgetRedemptions, + fetchBudgetRedemptions: jest.fn(), + }); + useEnterpriseGroupLearners.mockReturnValue({ + data: { + count: 100, + currentPage: 1, + next: null, + numPages: 4, + results: { + enterpriseGroupMembershipUuid: 'cde2e374-032f-4c08-8c0d-bf3205fa7c7e', + learnerId: 4382, + memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' }, + }, + }, + }); + useEnterpriseGroupMembersTableData.mockReturnValue({ + isLoading: false, + enterpriseGroupMembersTableData: { + // Item count tells the table whether or not there are more records that are not displayed by results + itemCount: 100, + pageCount: 4, + results: [{ + memberDetails: { userEmail: 'dukesilver@test.com', userName: 'duke silver' }, + status: 'pending', + recentAction: 'Pending: April 02, 2024', + enrollmentCount: 0, + }, + { + memberDetails: { userEmail: 'tammy2@test.com', userName: 'tammy 2' }, + status: 'pending', + recentAction: 'Pending: April 02, 2024', + enrollmentCount: 0, + }], + }, + fetchEnterpriseGroupMembersTableData: jest.fn(), + }); + EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData.mockResolvedValue({ status: 200 }); + const mockDownloadSpy = jest.spyOn(EnterpriseAccessApiService, 'fetchSubsidyHydratedGroupMembersData'); + + renderWithRouter(); + await waitFor(() => expect(screen.queryByText('dukesilver@test.com')).toBeInTheDocument()); + + // Select all the records on the current page + const selectAllCheckbox = screen.queryAllByRole('checkbox')[0]; + userEvent.click(selectAllCheckbox); + + // Download the results + const downloadButton = screen.queryByText('Download (2)'); + userEvent.click(downloadButton); + // Expect the mock to have been called once + expect(mockDownloadSpy).toHaveBeenCalledTimes(1); + // Expect the fetch members call to have been made with the emails of the records selected on the current page + expect(EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData).toHaveBeenCalledWith( + mockAssignableSubsidyAccessPolicy.uuid, + { + format_csv: true, + traverse_pagination: true, + group_uuid: mockAssignableSubsidyAccessPolicy.groupAssociations[0], + sort_by: 'member_details', + }, + ['dukesilver@test.com', 'tammy2@test.com'], + ); + + // Value correlates to the itemCount coming from ``useEnterpriseGroupMembersTableData`` + // Click the select all to apply the selection to all records passed the currently selected page + const selectAllButton = screen.queryByText('Select all 100'); + userEvent.click(selectAllButton); + userEvent.click(downloadButton); + // Expect an additional call to the mock + expect(mockDownloadSpy).toHaveBeenCalledTimes(2); + // Expect the call to fetch member records to be made without any email specification, indicating a fetch of all + expect(EnterpriseAccessApiService.fetchSubsidyHydratedGroupMembersData).toHaveBeenCalledWith( + mockAssignableSubsidyAccessPolicy.uuid, + { + format_csv: true, + traverse_pagination: true, + group_uuid: mockAssignableSubsidyAccessPolicy.groupAssociations[0], + sort_by: 'member_details', + }, + null, + ); + }); });