From f96e899d9fe06a7c613ef0ac2afc4177d4e7c5b6 Mon Sep 17 00:00:00 2001 From: johnvente Date: Fri, 5 Jan 2024 11:30:08 -0500 Subject: [PATCH] feat: adding pagination for teams --- .../ScheduleSection/index.jsx | 3 ++ .../ScheduleSection/messages.js | 4 ++ plugins/communications-app/TeamEmails/api.js | 4 +- .../communications-app/TeamEmails/api.test.js | 14 +++--- .../communications-app/TeamEmails/index.jsx | 45 +++++++++++++------ .../TeamEmails/index.test.jsx | 14 +++--- 6 files changed, 55 insertions(+), 29 deletions(-) diff --git a/plugins/communications-app/ScheduleSection/index.jsx b/plugins/communications-app/ScheduleSection/index.jsx index 05f87883..588b25ae 100644 --- a/plugins/communications-app/ScheduleSection/index.jsx +++ b/plugins/communications-app/ScheduleSection/index.jsx @@ -89,6 +89,7 @@ const ScheduleSection = ({ openTaskAlert }) => { complete: , completeSchedule: , error: , + loadingTeams: , }), []); const statefulButtonLabels = useMemo(() => ({ @@ -99,12 +100,14 @@ const ScheduleSection = ({ openTaskAlert }) => { complete: intl.formatMessage(messages.ScheduleSectionSubmitButtonComplete), completeSchedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonCompleteSchedule), error: intl.formatMessage(messages.ScheduleSectionSubmitButtonError), + loadingTeams: intl.formatMessage(messages.ScheduleSectionSubmitButtonLoadingTeams), }), [intl]); const statefulButtonDisableStates = useMemo(() => [ 'pending', 'complete', 'completeSchedule', + 'loadingTeams', ], []); return ( diff --git a/plugins/communications-app/ScheduleSection/messages.js b/plugins/communications-app/ScheduleSection/messages.js index e795715d..c3f8ff07 100644 --- a/plugins/communications-app/ScheduleSection/messages.js +++ b/plugins/communications-app/ScheduleSection/messages.js @@ -48,6 +48,10 @@ const messages = defineMessages({ id: 'schedule.section.submit.button.schedule.complete', defaultMessage: 'Scheduling Done', }, + ScheduleSectionSubmitButtonLoadingTeams: { + id: 'schedule.section.submit.button.loading.teams', + defaultMessage: 'Loading teams', + }, }); export default messages; diff --git a/plugins/communications-app/TeamEmails/api.js b/plugins/communications-app/TeamEmails/api.js index b4dafbd0..625abf3d 100644 --- a/plugins/communications-app/TeamEmails/api.js +++ b/plugins/communications-app/TeamEmails/api.js @@ -1,10 +1,10 @@ import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -export async function getTeamsList(courseId, page = 1, pageSize = 100) { +export async function getTopicsList(courseId, page = 1, pageSize = 100) { const endpointUrl = `${ getConfig().LMS_BASE_URL - }/platform-plugin-teams/${courseId}/api/topics/?page=${page}&pageSize=${pageSize}`; + }/platform-plugin-teams/${courseId}/api/topics/?page=${page}&page_size=${pageSize}`; try { const response = await getAuthenticatedHttpClient().get(endpointUrl); return response; diff --git a/plugins/communications-app/TeamEmails/api.test.js b/plugins/communications-app/TeamEmails/api.test.js index b326508c..477784bc 100644 --- a/plugins/communications-app/TeamEmails/api.test.js +++ b/plugins/communications-app/TeamEmails/api.test.js @@ -1,7 +1,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getConfig } from '@edx/frontend-platform'; -import { getTeamsList } from './api'; +import { getTopicsList } from './api'; jest.mock('@edx/frontend-platform/auth', () => ({ getAuthenticatedHttpClient: jest.fn(), @@ -10,7 +10,7 @@ jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn(), })); -describe('getTeamsList function', () => { +describe('getTopicsList function', () => { const mockCourseId = 'course123'; const mockResponseData = { data: 'someData' }; const mockConfig = { LMS_BASE_URL: 'http://localhost' }; @@ -23,11 +23,11 @@ describe('getTeamsList function', () => { }); test('successfully fetches teams list with default parameters', async () => { - const response = await getTeamsList(mockCourseId); + const response = await getTopicsList(mockCourseId); expect(response).toEqual(mockResponseData); expect(getAuthenticatedHttpClient().get).toHaveBeenCalledWith( - `http://localhost/platform-plugin-teams/${mockCourseId}/api/topics/?page=1&pageSize=100`, + `http://localhost/platform-plugin-teams/${mockCourseId}/api/topics/?page=1&page_size=100`, ); }); @@ -35,11 +35,11 @@ describe('getTeamsList function', () => { const customPage = 2; const customPageSize = 50; - const response = await getTeamsList(mockCourseId, customPage, customPageSize); + const response = await getTopicsList(mockCourseId, customPage, customPageSize); expect(response).toEqual(mockResponseData); expect(getAuthenticatedHttpClient().get).toHaveBeenCalledWith( - `http://localhost/platform-plugin-teams/${mockCourseId}/api/topics/?page=${customPage}&pageSize=${customPageSize}`, + `http://localhost/platform-plugin-teams/${mockCourseId}/api/topics/?page=${customPage}&page_size=${customPageSize}`, ); }); @@ -47,6 +47,6 @@ describe('getTeamsList function', () => { const errorMessage = 'Network error'; getAuthenticatedHttpClient().get.mockRejectedValue(new Error(errorMessage)); - await expect(getTeamsList(mockCourseId)).rejects.toThrow(errorMessage); + await expect(getTopicsList(mockCourseId)).rejects.toThrow(errorMessage); }); }); diff --git a/plugins/communications-app/TeamEmails/index.jsx b/plugins/communications-app/TeamEmails/index.jsx index acc22158..53abbfbc 100644 --- a/plugins/communications-app/TeamEmails/index.jsx +++ b/plugins/communications-app/TeamEmails/index.jsx @@ -9,7 +9,7 @@ import { actionCreators as formActions } from '@communications-app/src/component import ListTeams from './ListTeams'; import messages from './messages'; -import { getTeamsList } from './api'; +import { getTopicsList } from './api'; import { getTeamsFromTopics, convertSnakeCaseToCamelCase } from './utils'; const TeamEmails = ({ courseId }) => { @@ -24,25 +24,44 @@ const TeamEmails = ({ courseId }) => { } = formData; const [teams, setTeams] = useState([]); const [checkedTeams, setCheckedTeams] = useState([]); + const [loadingTeams, setLoadingTeams] = useState(false); const previousFormStatusRef = useRef(null); - useEffect(() => { - const getTeamsFromApi = async () => { - try { - const responseTeams = await getTeamsList(courseId); - const { results } = responseTeams.data; - const camelCaseResult = convertSnakeCaseToCamelCase(results); - const formatResult = getTeamsFromTopics(camelCaseResult); - setTeams(formatResult); - } catch (error) { - console.error('there was an error while getting teams', error.messages); + const fetchTeams = async (page = 1) => { + try { + setLoadingTeams(true); + const responseTopics = await getTopicsList(courseId, page); + const { results, next } = responseTopics.data; + + const camelCaseResult = convertSnakeCaseToCamelCase(results); + const formatResult = getTeamsFromTopics(camelCaseResult); + + setTeams((prevTeams) => [...prevTeams, ...formatResult]); + + if (next) { + fetchTeams(page + 1); + } else { + dispatch(formActions.updateForm({ formStatus: 'default' })); } - }; + } catch (error) { + console.error('There was an error while getting teams:', error.message); + } finally { + setLoadingTeams(false); + } + }; - getTeamsFromApi(); + useEffect(() => { + fetchTeams(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [courseId]); + useEffect(() => { + if (loadingTeams) { + dispatch(formActions.updateForm({ formStatus: 'loadingTeams' })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formStatus, loadingTeams]); + useEffect(() => { if (teams.length) { dispatch(formActions.updateForm({ teamsListFullData: teams })); diff --git a/plugins/communications-app/TeamEmails/index.test.jsx b/plugins/communications-app/TeamEmails/index.test.jsx index dcf1b2e9..c1256f50 100644 --- a/plugins/communications-app/TeamEmails/index.test.jsx +++ b/plugins/communications-app/TeamEmails/index.test.jsx @@ -148,7 +148,7 @@ describe('TeamEmails Component', () => { beforeEach(() => { jest.clearAllMocks(); - api.getTeamsList.mockResolvedValue({ data: mockData }); + api.getTopicsList.mockResolvedValue({ data: mockData }); useSelector.mockImplementation((selectorFn) => selectorFn({ form: { teamsList: [], @@ -169,7 +169,7 @@ describe('TeamEmails Component', () => { ); test('renders the component without errors', async () => { - api.getTeamsList.mockResolvedValue({ data: mockData }); + api.getTopicsList.mockResolvedValue({ data: mockData }); render( @@ -189,7 +189,7 @@ describe('TeamEmails Component', () => { }); test('renders null when teams are empty', async () => { - api.getTeamsList.mockResolvedValue({ data: { results: [] } }); + api.getTopicsList.mockResolvedValue({ data: { results: [] } }); render( @@ -205,7 +205,7 @@ describe('TeamEmails Component', () => { test('handles checkbox change', async () => { const mockDispatch = jest.fn(); useDispatch.mockReturnValue(mockDispatch); - api.getTeamsList.mockResolvedValue({ data: mockData }); + api.getTopicsList.mockResolvedValue({ data: mockData }); render( @@ -220,9 +220,9 @@ describe('TeamEmails Component', () => { }); }); - test('handles error when api.getTeamsList fails', async () => { + test('handles error when api.getTopicsList fails', async () => { const mockedError = new Error('API Failed'); - api.getTeamsList.mockRejectedValue(mockedError); + api.getTopicsList.mockRejectedValue(mockedError); const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); @@ -233,7 +233,7 @@ describe('TeamEmails Component', () => { ); await waitFor(() => { - expect(consoleSpy).toHaveBeenCalledWith('there was an error while getting teams', mockedError.messages); + expect(consoleSpy).toHaveBeenCalledWith('There was an error while getting teams:', mockedError.message); }); consoleSpy.mockRestore();