-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add enterprise users datatable (#1341)
* feat: add enterprise users datatable
- Loading branch information
1 parent
0f957c5
commit ba8fc56
Showing
7 changed files
with
450 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,12 +13,21 @@ import { queryClient } from '../../test/testUtils'; | |
import LmsApiService from '../../../data/services/LmsApiService'; | ||
import { EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY } from '../../learner-credit-management/cards/data'; | ||
import CreateGroupModal from '../CreateGroupModal'; | ||
import { | ||
useEnterpriseLearnersTableData, | ||
useGetAllEnterpriseLearnerEmails, | ||
} from '../../learner-credit-management/data/hooks/useEnterpriseLearnersTableData'; | ||
|
||
jest.mock('@tanstack/react-query', () => ({ | ||
...jest.requireActual('@tanstack/react-query'), | ||
useQueryClient: jest.fn(), | ||
})); | ||
jest.mock('../../../data/services/LmsApiService'); | ||
jest.mock('../../learner-credit-management/data/hooks/useEnterpriseLearnersTableData', () => ({ | ||
...jest.requireActual('../../learner-credit-management/data/hooks/useEnterpriseLearnersTableData'), | ||
useEnterpriseLearnersTableData: jest.fn(), | ||
useGetAllEnterpriseLearnerEmails: jest.fn(), | ||
})); | ||
|
||
const mockStore = configureMockStore([thunk]); | ||
const getMockStore = store => mockStore(store); | ||
|
@@ -43,6 +52,45 @@ const defaultProps = { | |
enterpriseUUID: 'test-uuid', | ||
}; | ||
|
||
const mockTabledata = { | ||
itemCount: 3, | ||
pageCount: 1, | ||
results: [ | ||
{ | ||
id: 1, | ||
user: { | ||
id: 1, | ||
username: 'testuser-1', | ||
firstName: '', | ||
lastName: '', | ||
email: '[email protected]', | ||
dateJoined: '2023-05-09T16:18:22Z', | ||
}, | ||
}, | ||
{ | ||
id: 2, | ||
user: { | ||
id: 2, | ||
username: 'testuser-2', | ||
firstName: '', | ||
lastName: '', | ||
email: '[email protected]', | ||
dateJoined: '2023-05-09T16:18:22Z', | ||
}, | ||
}, | ||
{ | ||
id: 3, | ||
user: { | ||
id: 3, | ||
username: 'testuser-3', | ||
firstName: '', | ||
lastName: '', | ||
email: '[email protected]', | ||
dateJoined: '2023-05-09T16:18:22Z', | ||
}, | ||
}, | ||
], | ||
}; | ||
const CreateGroupModalWrapper = ({ | ||
initialState = initialStoreState, | ||
}) => { | ||
|
@@ -59,6 +107,18 @@ const CreateGroupModalWrapper = ({ | |
}; | ||
|
||
describe('<CreateGroupModal />', () => { | ||
beforeEach(() => { | ||
useEnterpriseLearnersTableData.mockReturnValue({ | ||
isLoading: false, | ||
enterpriseCustomerUserTableData: mockTabledata, | ||
fetchEnterpriseLearnersData: jest.fn(), | ||
}); | ||
useGetAllEnterpriseLearnerEmails.mockReturnValue({ | ||
isLoading: false, | ||
fetchLearnerEmails: jest.fn(), | ||
addButtonState: 'complete', | ||
}); | ||
}); | ||
it('Modal renders as expected', async () => { | ||
render(<CreateGroupModalWrapper />); | ||
expect(screen.getByText('Create a custom group of members')).toBeInTheDocument(); | ||
|
@@ -69,6 +129,16 @@ describe('<CreateGroupModal />', () => { | |
expect(screen.getByText('Upload a CSV file or select members to get started.')).toBeInTheDocument(); | ||
expect(screen.getByText('Create')).toBeInTheDocument(); | ||
expect(screen.getByText('Cancel')).toBeInTheDocument(); | ||
|
||
// renders datatable | ||
expect(screen.getByText('Member details')).toBeInTheDocument(); | ||
expect(screen.getByText('Joined organization')).toBeInTheDocument(); | ||
expect(screen.getByText('testuser-1')).toBeInTheDocument(); | ||
expect(screen.getByText('[email protected]')).toBeInTheDocument(); | ||
expect(screen.getByText('testuser-2')).toBeInTheDocument(); | ||
expect(screen.getByText('[email protected]')).toBeInTheDocument(); | ||
expect(screen.getByText('testuser-3')).toBeInTheDocument(); | ||
expect(screen.getByText('[email protected]')).toBeInTheDocument(); | ||
}); | ||
it('creates groups and assigns learners', async () => { | ||
const mockCreateGroup = jest.spyOn(LmsApiService, 'createEnterpriseGroup'); | ||
|
@@ -93,10 +163,36 @@ describe('<CreateGroupModal />', () => { | |
userEvent.type(groupNameInput, 'test group name'); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('emails.csv')).toBeInTheDocument(); | ||
expect(screen.getByText('Summary (1)')).toBeInTheDocument(); | ||
expect(screen.getByText('[email protected]')).toBeInTheDocument(); | ||
}, { timeout: EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY + 1000 }); | ||
|
||
// testing interaction with adding members from the datatable | ||
const membersCheckbox = screen.getAllByTitle('Toggle Row Selected'); | ||
userEvent.click(membersCheckbox[0]); | ||
userEvent.click(membersCheckbox[1]); | ||
const addMembersButton = screen.getByText('Add'); | ||
userEvent.click(addMembersButton); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('Summary (3)')).toBeInTheDocument(); | ||
// checking that each user appears twice, once in the datatable and once in the summary section | ||
expect(screen.getAllByText('[email protected]')).toHaveLength(2); | ||
expect(screen.getAllByText('[email protected]')).toHaveLength(2); | ||
}); | ||
|
||
// testing interaction with removing members from the datatable | ||
const removeMembersButton = screen.getByText('Remove'); | ||
userEvent.click(removeMembersButton); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('Summary (1)')).toBeInTheDocument(); | ||
expect(screen.getByText('emails.csv')).toBeInTheDocument(); | ||
expect(screen.getByText('Total members to add')).toBeInTheDocument(); | ||
expect(screen.getByText('[email protected]')).toBeInTheDocument(); | ||
expect(screen.getAllByText('[email protected]')).toHaveLength(1); | ||
expect(screen.getAllByText('[email protected]')).toHaveLength(1); | ||
expect(screen.getAllByText('[email protected]')).toHaveLength(1); | ||
const formFeedbackText = 'Maximum members at a time: 1000'; | ||
expect(screen.queryByText(formFeedbackText)).not.toBeInTheDocument(); | ||
}, { timeout: EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY + 1000 }); | ||
|
@@ -137,7 +233,9 @@ describe('<CreateGroupModal />', () => { | |
const createButton = screen.getByRole('button', { name: 'Create' }); | ||
userEvent.click(createButton); | ||
await waitFor(() => { | ||
expect(screen.getByText('We\'re sorry. Something went wrong behind the scenes. Please try again, or reach out to customer support for help.')).toBeInTheDocument(); | ||
expect(screen.getByText( | ||
'We\'re sorry. Something went wrong behind the scenes. Please try again, or reach out to customer support for help.', | ||
)).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
79 changes: 79 additions & 0 deletions
79
src/components/learner-credit-management/data/hooks/useEnterpriseLearnersTableData.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { | ||
useCallback, useMemo, useState, | ||
} from 'react'; | ||
import { camelCaseObject } from '@edx/frontend-platform/utils'; | ||
import { logError } from '@edx/frontend-platform/logging'; | ||
import debounce from 'lodash.debounce'; | ||
|
||
import LmsApiService from '../../../../data/services/LmsApiService'; | ||
import { fetchPaginatedData } from '../../../../data/services/apiServiceUtils'; | ||
|
||
export const useGetAllEnterpriseLearnerEmails = ({ | ||
enterpriseId, | ||
onHandleAddMembersBulkAction, | ||
}) => { | ||
const [isLoading, setIsLoading] = useState(true); | ||
const [addButtonState, setAddButtonState] = useState('default'); | ||
|
||
const fetchLearnerEmails = useCallback(async () => { | ||
setAddButtonState('pending'); | ||
try { | ||
const url = `${LmsApiService.enterpriseLearnerUrl}?enterprise_customer=${enterpriseId}`; | ||
const { results } = await fetchPaginatedData(url); | ||
const learnerEmails = results.map(result => result?.user?.email).filter(email => email !== undefined); | ||
onHandleAddMembersBulkAction(learnerEmails); | ||
} catch (error) { | ||
logError(error); | ||
setAddButtonState('error'); | ||
} finally { | ||
setIsLoading(false); | ||
setAddButtonState('complete'); | ||
} | ||
}, [enterpriseId, onHandleAddMembersBulkAction]); | ||
|
||
return { | ||
isLoading, | ||
fetchLearnerEmails, | ||
addButtonState, | ||
}; | ||
}; | ||
|
||
export const useEnterpriseLearnersTableData = (enterpriseId) => { | ||
const [isLoading, setIsLoading] = useState(true); | ||
const [enterpriseCustomerUserTableData, setEnterpriseCustomerUserTableData] = useState({ | ||
itemCount: 0, | ||
pageCount: 0, | ||
results: [], | ||
}); | ||
const fetchEnterpriseLearnersData = useCallback(async (args) => { | ||
try { | ||
setIsLoading(true); | ||
const options = { | ||
enterprise_customer: enterpriseId, | ||
}; | ||
options.page = args.pageIndex + 1; | ||
const response = await LmsApiService.fetchEnterpriseLearners(options); | ||
const { data } = camelCaseObject(response); | ||
setEnterpriseCustomerUserTableData({ | ||
itemCount: data.count, | ||
pageCount: data.numPages ?? Math.floor(data.count / options.pageSize), | ||
results: data.results, | ||
}); | ||
} catch (error) { | ||
logError(error); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}, [enterpriseId, setEnterpriseCustomerUserTableData]); | ||
|
||
const debouncedFetchEnterpriseLearnersData = useMemo( | ||
() => debounce(fetchEnterpriseLearnersData, 300), | ||
[fetchEnterpriseLearnersData], | ||
); | ||
|
||
return { | ||
isLoading, | ||
enterpriseCustomerUserTableData, | ||
fetchEnterpriseLearnersData: debouncedFetchEnterpriseLearnersData, | ||
}; | ||
}; |
Oops, something went wrong.