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: add pagination for reporting configs list #1361

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
89 changes: 69 additions & 20 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 @@ -55,13 +64,15 @@ class ReportingConfig extends React.Component {
// 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,
],
}));
await LMSApiService.postNewReportingConfig(transformedData);

const lastPageHaveSpace = this.state.totalRecords % this.state.totalPages > 0;

if (lastPageHaveSpace || this.state.reportingConfigs.length < DEFAULT_PAGE_SIZE) {
this.handlePageSelect(this.state.totalPages);
} else {
this.handlePageSelect(this.state.totalPages + 1);
}
this.newConfigFormRef.current.close();
return undefined;
} catch (error) {
Expand All @@ -72,18 +83,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);
} else {
this.handlePageSelect(this.state.currentPage);
}

return undefined;
} catch (error) {
return error;
Expand Down Expand Up @@ -111,13 +121,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({
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 +238,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}`);
static fetchReportingConfigs(uuid, pageNumber) {
let url = `${LmsApiService.reportingConfigUrl}?enterprise_customer=${uuid}`;
if (pageNumber) {
url += `&page=${pageNumber}`;
}

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

static fetchReportingConfigTypes(uuid) {
Expand Down