Skip to content

Commit

Permalink
feat: add paginators on reporting configurations page
Browse files Browse the repository at this point in the history
  • Loading branch information
jajjibhai008 committed Dec 3, 2024
1 parent 349cae2 commit e6fcf2a
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 25 deletions.
99 changes: 77 additions & 22 deletions src/components/ReportingConfig/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Collapsible, Icon } from '@openedx/paragon';
import { Collapsible, Icon, Pagination } from '@openedx/paragon';
import { Check, Close } from '@openedx/paragon/icons';
import { camelCaseObject } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
Expand All @@ -12,6 +12,7 @@ import LoadingMessage from '../LoadingMessage';
import ErrorPage from '../ErrorPage';

const STATUS_FULFILLED = 'fulfilled';
const DEFAULT_PAGE_SIZE = 10;

class ReportingConfig extends React.Component {
// eslint-disable-next-line react/state-in-constructor
Expand All @@ -33,7 +34,15 @@ class ReportingConfig extends React.Component {
LMSApiService.fetchReportingConfigTypes(this.props.enterpriseId),
])
.then((responses) => {
let totalPages = responses[0].status === STATUS_FULFILLED ? responses[0].value.data.num_pages : 1;
if (!totalPages) {
totalPages = 1;
}

this.setState({
totalPages,
currentPage: 1,
totalRecords: responses[0].status === STATUS_FULFILLED ? responses[0].value.data.count : 0,
reportingConfigs: responses[0].status === STATUS_FULFILLED ? responses[0].value.data.results : undefined,
availableCatalogs: responses[1].status === STATUS_FULFILLED ? responses[1].value.data.results : undefined,
reportingConfigTypes: responses[2].status === STATUS_FULFILLED ? responses[2].value.data : undefined,
Expand All @@ -52,17 +61,25 @@ class ReportingConfig extends React.Component {
* @param {FormData} formData
*/
createConfig = async (formData) => {
// snake_case the data before sending it to the backend
const transformedData = snakeCaseFormData(formData);
try {
const response = await LMSApiService.postNewReportingConfig(transformedData);
this.setState(prevState => ({
reportingConfigs: [
...prevState.reportingConfigs,
response.data,
],
}));
// Transform data to snake_case format
const transformedData = snakeCaseFormData(formData);

Check warning on line 66 in src/components/ReportingConfig/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/ReportingConfig/index.jsx#L66

Added line #L66 was not covered by tests

// Post the new configuration to the backend
await LMSApiService.postNewReportingConfig(transformedData);

Check warning on line 69 in src/components/ReportingConfig/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/ReportingConfig/index.jsx#L69

Added line #L69 was not covered by tests

const { totalRecords, totalPages } = this.state;

Check warning on line 71 in src/components/ReportingConfig/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/ReportingConfig/index.jsx#L71

Added line #L71 was not covered by tests

// Determine the target page to navigate to
const shouldAddNewPage = totalRecords % DEFAULT_PAGE_SIZE === 0 && totalRecords !== 0;
const targetPage = shouldAddNewPage ? totalPages + 1 : totalPages;

// Navigate to the appropriate page
this.handlePageSelect(targetPage);

Check warning on line 78 in src/components/ReportingConfig/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/ReportingConfig/index.jsx#L78

Added line #L78 was not covered by tests

// Close the new config form
this.newConfigFormRef.current.close();

return undefined;
} catch (error) {
return error;
Expand All @@ -72,18 +89,17 @@ class ReportingConfig extends React.Component {
deleteConfig = async (uuid) => {
try {
await LMSApiService.deleteReportingConfig(uuid);
const deletedIndex = this.state.reportingConfigs
.findIndex(config => config.uuid === uuid);

this.setState((state) => {
// Copy the existing, needs to be updated, list of reporting configs
const newReportingConfig = [...state.reportingConfigs];
// Splice out the one that's being deleted
newReportingConfig.splice(deletedIndex, 1);
return {
reportingConfigs: newReportingConfig,
};
});

const isLastPage = this.state.currentPage === this.state.totalPages;
const hasOneRecord = this.state.reportingConfigs.length === 1;
const isOnlyRecordOnLastPage = hasOneRecord && isLastPage;

if (isOnlyRecordOnLastPage && this.state.currentPage > 1) {
this.handlePageSelect(this.state.totalPages - 1);

Check warning on line 98 in src/components/ReportingConfig/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/ReportingConfig/index.jsx#L98

Added line #L98 was not covered by tests
} else {
this.handlePageSelect(this.state.currentPage);
}

return undefined;
} catch (error) {
return error;
Expand Down Expand Up @@ -111,13 +127,41 @@ class ReportingConfig extends React.Component {
}
};

/**
* Handles page select event and fetches the data for the selected page
* @param {number} page - The page number to fetch data for
*/
handlePageSelect = async (page) => {
this.setState({
loading: true,
});

try {
const response = await LMSApiService.fetchReportingConfigs(this.props.enterpriseId, page);
this.setState({
totalPages: response.data.num_pages || 1,
totalRecords: response.data.count,
currentPage: page,
reportingConfigs: response.data.results,
loading: false,
});
} catch (error) {
this.setState({

Check warning on line 149 in src/components/ReportingConfig/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/ReportingConfig/index.jsx#L149

Added line #L149 was not covered by tests
loading: false,
error,
});
}
};

render() {
const {
reportingConfigs,
loading,
error,
availableCatalogs,
reportingConfigTypes,
currentPage,
totalPages,
} = this.state;
const { intl } = this.props;
if (loading) {
Expand Down Expand Up @@ -200,6 +244,17 @@ class ReportingConfig extends React.Component {
</Collapsible>
</div>
))}

{reportingConfigs && reportingConfigs.length > 0 && (
<Pagination
variant="reduced"
onPageSelect={this.handlePageSelect}
pageCount={totalPages}
currentPage={currentPage}
paginationLabel="reporting configurations pagination"
/>
)}

<Collapsible
styling="basic"
title={intl.formatMessage({
Expand Down
59 changes: 58 additions & 1 deletion src/components/ReportingConfig/index.test.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { act } from 'react-dom/test-utils';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import { Pagination } from '@openedx/paragon';

import ReportingConfig from './index';
import LmsApiService from '../../data/services/LmsApiService';

Expand Down Expand Up @@ -170,4 +173,58 @@ describe('<ReportingConfig />', () => {
const afterClickingDeleteButton = wrapper.find('button[data-testid="deleteConfigButton"]');
expect(afterClickingDeleteButton.exists()).toBe(false);
});
it('should not render Pagination when reportingConfigs is empty', async () => {
LmsApiService.fetchReportingConfigs.mockResolvedValue({
data: {
results: [],
count: 0,
num_pages: 0,
},
});

let wrapper;
await act(async () => {
wrapper = mount(
<IntlProvider locale="en">
<ReportingConfig {...defaultProps} intl={mockIntl} />
</IntlProvider>,
);
});

wrapper.update();

// Check that Pagination component is not rendered when no configs
const paginationComponent = wrapper.find(Pagination);
expect(paginationComponent.exists()).toBe(false);
});
it('should render Pagination when reportingConfigs has items', async () => {
let wrapper;

LmsApiService.fetchReportingConfigs.mockResolvedValue({
data: {
results: [{
enterpriseCustomerId: 'test-customer-uuid',
active: true,
delivery_method: 'email',
uuid: 'test-config-uuid',
}],
count: 1,
num_pages: 1,
},
});

await act(async () => {
wrapper = mount(
<IntlProvider locale="en">
<ReportingConfig {...defaultProps} intl={mockIntl} />
</IntlProvider>,
);
});

wrapper.update();

// Check that Pagination component is rendered when configs exist
const paginationComponent = wrapper.find(Pagination);
expect(paginationComponent.exists()).toBe(true);
});
});
9 changes: 7 additions & 2 deletions src/data/services/LmsApiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,13 @@ class LmsApiService {
return LmsApiService.apiClient().post(requestCodesUrl, postParams);
}

static fetchReportingConfigs(uuid) {
return LmsApiService.apiClient().get(`${LmsApiService.reportingConfigUrl}?enterprise_customer=${uuid}&page_size=100`);
static fetchReportingConfigs(uuid, pageNumber) {
let url = `${LmsApiService.reportingConfigUrl}?enterprise_customer=${uuid}`;
if (pageNumber) {
url += `&page=${pageNumber}`;

Check warning on line 125 in src/data/services/LmsApiService.js

View check run for this annotation

Codecov / codecov/patch

src/data/services/LmsApiService.js#L125

Added line #L125 was not covered by tests
}

return LmsApiService.apiClient().get(url);
}

static fetchReportingConfigTypes(uuid) {
Expand Down

0 comments on commit e6fcf2a

Please sign in to comment.