Skip to content

Commit

Permalink
add pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
balanza committed Aug 29, 2024
1 parent 10bffa1 commit cf9b180
Show file tree
Hide file tree
Showing 6 changed files with 415 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function ActivityLogOverview({
const [selectedEntry, setEntry] = useState({});

const activityLogTableConfig = {
pagination: true,
pagination: false,
usePadding: false,
columns: [
{
Expand Down
11 changes: 11 additions & 0 deletions assets/js/lib/test-utils/factories/pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { faker } from '@faker-js/faker';
import { Factory } from 'fishery';

export const paginationFirstPageFactory = Factory.define(() => ({
first: faker.number.int({ min: 1, max: 20 }),
last: null,
start_cursor: faker.string.binary(),
end_cursor: faker.string.binary(),
has_next_page: true,
has_previous_page: false,
}));
72 changes: 67 additions & 5 deletions assets/js/pages/ActivityLogPage/ActivityLogPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import ComposedFilter from '@common/ComposedFilter';

import { getActivityLog } from '@lib/api/activityLogs';
import { ACTIVITY_TYPES_CONFIG } from '@lib/model/activityLog';
import { PaginationPrevNext } from '@common/Pagination/Pagination';
import { pipe } from 'lodash/fp';
import {
filterValueToSearchParams,
getItemsPerPageFromSearchParams,
resetPaginationToSearchParams,
searchParamsToAPIParams,
searchParamsToFilterValue,
setFilterValueToSearchParams,
setPaginationToSearchParams,
} from './searchParams';

const filters = [
Expand All @@ -37,19 +42,47 @@ const filters = [
},
];

const defaultItemsPerPage = 20;
const itemsPerPageOptions = [10, 20, 50, 75, 100];

const changeItemsPerPage = (searchParams) => (items) => {
if (searchParams.has('after')) {
return {
first: items,
after: searchParams.get('after'),
};
}
if (searchParams.has('before')) {
return {
last: items,
before: searchParams.get('before'),
};
}
return { first: items };
};

function ActivityLogPage() {
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams, setSearchParams] = useSearchParams(
resetPaginationToSearchParams(defaultItemsPerPage)()
);
const [activityLog, setActivityLog] = useState([]);
const [activityLogMetadata, setActivityLogMetadata] = useState({});

const [isLoading, setLoading] = useState(true);
const [activityLogDetailModalOpen, setActivityLogDetailModalOpen] =
useState(false);

const itemsPerPage = pipe(getItemsPerPageFromSearchParams, (n) =>
itemsPerPageOptions.includes(n) ? n : itemsPerPageOptions[0]
)(searchParams);

const fetchActivityLog = () => {
setLoading(true);
const params = searchParamsToAPIParams(searchParams);
getActivityLog(params)
.then((response) => {
setActivityLog(response.data?.data ?? []);
.then(({ data: { data = [], ...metadata } = {} }) => {
setActivityLog(data);
setActivityLogMetadata(metadata);
})
.catch(() => setActivityLog([]))
.finally(() => {
Expand All @@ -71,7 +104,11 @@ function ActivityLogPage() {
filters={filters}
autoApply={false}
value={searchParamsToFilterValue(searchParams)}
onChange={(p) => setSearchParams(filterValueToSearchParams(p))}
onChange={pipe(
setFilterValueToSearchParams,
resetPaginationToSearchParams(itemsPerPage),
setSearchParams
)}
/>
</div>
<ActivityLogOverview
Expand All @@ -83,6 +120,31 @@ function ActivityLogPage() {
setActivityLogDetailModalOpen(false)
}
/>
<PaginationPrevNext
hasPrev={activityLogMetadata.pagination?.has_previous_page}
hasNext={activityLogMetadata.pagination?.has_next_page}
currentItemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onSelect={pipe(
(selection) =>
selection === 'prev'
? {
last: itemsPerPage,
before: activityLogMetadata.pagination.start_cursor,
}
: {
first: itemsPerPage,
after: activityLogMetadata.pagination.end_cursor,
},
setPaginationToSearchParams,
setSearchParams
)}
onChangeItemsPerPage={pipe(
changeItemsPerPage(searchParams),
setPaginationToSearchParams,
setSearchParams
)}
/>
</div>
</>
);
Expand Down
15 changes: 15 additions & 0 deletions assets/js/pages/ActivityLogPage/ActivityLogPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { renderWithRouter } from '@lib/test-utils';

import { networkClient } from '@lib/network';
import { activityLogEntryFactory } from '@lib/test-utils/factories/activityLog';
import { paginationFirstPageFactory } from '@lib/test-utils/factories/pagination';

import ActivityLogPage from './ActivityLogPage';

Expand Down Expand Up @@ -56,4 +57,18 @@ describe('ActivityLogPage', () => {

expect(container.querySelectorAll('tbody > tr')).toHaveLength(5);
});

it('should render pagination', async () => {
const response = {
data: activityLogEntryFactory.buildList(20),
pagination: paginationFirstPageFactory.build(),
};

axiosMock.onGet('/api/v1/activity_log').reply(200, response);

await act(() => renderWithRouter(<ActivityLogPage />));

expect(screen.getByText('<')).toBeInTheDocument();
expect(screen.getByText('>')).toBeInTheDocument();
});
});
79 changes: 76 additions & 3 deletions assets/js/pages/ActivityLogPage/searchParams.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@
*/

import { uniq } from 'lodash';
import { pipe, map, reduce, defaultTo } from 'lodash/fp';
import { pipe, map, reduce, defaultTo, omit } from 'lodash/fp';
import { fromZonedTime, toZonedTime } from 'date-fns-tz';

const paginationFields = ['after', 'before', 'first', 'last'];
const scalarKeys = [...paginationFields];

const omitUndefined = (obj) =>
Object.fromEntries(
Object.entries(obj).filter(([_, v]) => typeof v !== 'undefined')
);

const searchParamsToEntries = (searchParams) =>
pipe(Array.from, uniq, (keys) =>
keys.map((key) => [key, searchParams.getAll(key)])
keys.map((key) => [
key,
scalarKeys.includes(key)
? searchParams.get(key)
: searchParams.getAll(key),
])
)(searchParams.keys());

/**
Expand All @@ -39,6 +47,12 @@ export const searchParamsToAPIParams = pipe(
* Make the necessary transformations to the values before setting them in the search params
*/
export const searchParamsToFilterValue = pipe(
/* (sp) => {
if (!sp.has('first') && !sp.has('last')) {
sp.set('first', '20');
}
return sp;
}, */
searchParamsToEntries,
map(([k, v]) => {
switch (k) {
Expand Down Expand Up @@ -70,8 +84,67 @@ export const filterValueToSearchParams = pipe(
}),
reduce((acc, [k, v]) => {
const sp = acc || new URLSearchParams();
Array.from(v).forEach((value) => sp.append(k, value));
if (scalarKeys.includes(k)) {
sp.set(k, v);
} else {
Array.from(v).forEach((value) => sp.append(k, value));
}
return sp;
}, null),
defaultTo(new URLSearchParams())
);

export const paginatorToFilterValues =
(paginationMetadata) =>
(selection, itemsPerPage = paginationMetadata.first) => ({
...(selection === 'prev'
? { before: paginationMetadata.start_cursor }
: {}),
...(selection === 'next' ? { after: paginationMetadata.end_cursor } : {}),
first: itemsPerPage,
});

export const setPaginationToSearchParams = (
pagination,
searchParams = new URLSearchParams()
) => {
// eslint-disable-next-line no-unused-vars
const filters = pipe(
searchParamsToEntries,
Object.fromEntries,
omit(paginationFields)
)(searchParams);

return filterValueToSearchParams({ ...pagination, ...filters });
};

export const setFilterValueToSearchParams = (
filterValue,
searchParams = new URLSearchParams()
) =>
pipe(
searchParamsToEntries,
reduce(
(acc, [k, v]) =>
paginationFields.includes(k) ? { ...acc, [k]: v } : acc,
{}
),
(x) => ({ ...x, ...filterValue }),
filterValueToSearchParams
)(searchParams);

export const getItemsPerPageFromSearchParams = (searchParams) =>
Number(searchParams.get('first') || searchParams.get('last'));

export const resetPaginationToSearchParams =
(itemsPerPage) =>
(searchParams = new URLSearchParams()) => {
// eslint-disable-next-line no-unused-vars
const filters = pipe(
searchParamsToEntries,
Object.fromEntries,
omit(paginationFields)
)(searchParams);

return filterValueToSearchParams({ first: itemsPerPage, ...filters });
};
Loading

0 comments on commit cf9b180

Please sign in to comment.