From 7084f65443a07062edbe874df96a1159bbae1367 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 22 Oct 2024 16:35:04 +0530
Subject: [PATCH] feat(CE): added new pagination format
---
.../EnhancedPagination/PageChangeButton.tsx | 56 ++++++++++
.../EnhancedPagination/PageNumberItem.tsx | 42 ++++++++
.../EnhancedPagination/Pagination.tsx | 101 ++++++++++++++++++
.../__tests__/Pagination.test.tsx | 86 +++++++++++++++
ui/src/components/EnhancedPagination/index.ts | 1 +
ui/src/components/ModelTable/ModelTable.tsx | 5 +-
ui/src/services/common.ts | 6 +-
.../Syncs/SyncRecords/SyncRecords.tsx | 32 ++----
8 files changed, 303 insertions(+), 26 deletions(-)
create mode 100644 ui/src/components/EnhancedPagination/PageChangeButton.tsx
create mode 100644 ui/src/components/EnhancedPagination/PageNumberItem.tsx
create mode 100644 ui/src/components/EnhancedPagination/Pagination.tsx
create mode 100644 ui/src/components/EnhancedPagination/__tests__/Pagination.test.tsx
create mode 100644 ui/src/components/EnhancedPagination/index.ts
diff --git a/ui/src/components/EnhancedPagination/PageChangeButton.tsx b/ui/src/components/EnhancedPagination/PageChangeButton.tsx
new file mode 100644
index 00000000..3a691edf
--- /dev/null
+++ b/ui/src/components/EnhancedPagination/PageChangeButton.tsx
@@ -0,0 +1,56 @@
+import { Button, Icon } from '@chakra-ui/react';
+import { FiChevronLeft, FiChevronRight, FiChevronsLeft, FiChevronsRight } from 'react-icons/fi';
+
+export enum PAGE_CHANGE_BUTTON_TYPE {
+ PREVIOUS = 'previous',
+ NEXT = 'next',
+ FIRST = 'first',
+ LAST = 'last',
+}
+
+type PageButtonProps = {
+ type: PAGE_CHANGE_BUTTON_TYPE;
+ onClick: () => void;
+ isEnabled?: boolean;
+};
+
+const PageChangeButton = ({ type, onClick, isEnabled = false }: PageButtonProps) => {
+ const iconMap = {
+ [PAGE_CHANGE_BUTTON_TYPE.PREVIOUS]: { icon: FiChevronLeft, value: 'previous' },
+ [PAGE_CHANGE_BUTTON_TYPE.FIRST]: { icon: FiChevronsLeft, value: 'first' },
+ [PAGE_CHANGE_BUTTON_TYPE.LAST]: { icon: FiChevronsRight, value: 'last' },
+ [PAGE_CHANGE_BUTTON_TYPE.NEXT]: { icon: FiChevronRight, value: 'next' },
+ };
+
+ const icon = iconMap[type] || iconMap[PAGE_CHANGE_BUTTON_TYPE.NEXT];
+
+ return (
+
+ );
+};
+
+export default PageChangeButton;
diff --git a/ui/src/components/EnhancedPagination/PageNumberItem.tsx b/ui/src/components/EnhancedPagination/PageNumberItem.tsx
new file mode 100644
index 00000000..c8ed9e58
--- /dev/null
+++ b/ui/src/components/EnhancedPagination/PageNumberItem.tsx
@@ -0,0 +1,42 @@
+import { Box, Text } from '@chakra-ui/react';
+
+const PageNumberItem = ({
+ isActive = false,
+ onClick,
+ value,
+ isEllipsis = false,
+}: {
+ isActive?: boolean;
+ onClick?: () => void;
+ value?: number;
+ isEllipsis?: boolean;
+}) => (
+ {} : onClick}
+ _hover={{ backgroundColor: 'gray.400', cursor: 'pointer' }}
+ _disabled={{
+ _hover: { cursor: 'not-allowed' },
+ backgroundColor: 'gray.400',
+ }}
+ data-testid={isEllipsis ? 'ellipsis' : `page-number-${value}`}
+ >
+
+ {isEllipsis ? '...' : value}
+
+
+);
+
+export default PageNumberItem;
diff --git a/ui/src/components/EnhancedPagination/Pagination.tsx b/ui/src/components/EnhancedPagination/Pagination.tsx
new file mode 100644
index 00000000..2aee2d87
--- /dev/null
+++ b/ui/src/components/EnhancedPagination/Pagination.tsx
@@ -0,0 +1,101 @@
+import { LinksType } from '@/services/common';
+import { Stack, Box } from '@chakra-ui/react';
+import PageNumberItem from './PageNumberItem';
+import PageChangeButton, { PAGE_CHANGE_BUTTON_TYPE } from './PageChangeButton';
+
+const getPage = (url: string): number => {
+ const parser = new URLSearchParams(new URL(url).search);
+ return parseInt(parser.get('page') || '0');
+};
+
+export type PaginationProps = {
+ links: LinksType;
+ currentPage: number;
+ handlePageChange: (pageNumber: number) => void;
+};
+
+const Pagination = ({ links, currentPage, handlePageChange }: PaginationProps) => {
+ const firstPage = getPage(links.first);
+ const lastPage = getPage(links.last);
+
+ const renderPageNumbers = () => {
+ if (currentPage === firstPage) {
+ return (
+ <>
+ handlePageChange(currentPage)}
+ />
+ {lastPage > firstPage + 1 && }
+ {lastPage !== firstPage && (
+ handlePageChange(lastPage)} />
+ )}
+ >
+ );
+ } else if (currentPage === lastPage) {
+ return (
+ <>
+ handlePageChange(firstPage)} />
+ {lastPage > firstPage + 1 && }
+ handlePageChange(currentPage)}
+ />
+ >
+ );
+ } else {
+ return (
+ <>
+ handlePageChange(firstPage)} />
+ {currentPage > firstPage + 1 && }
+ handlePageChange(currentPage)}
+ />
+ {currentPage < lastPage - 1 && }
+ handlePageChange(lastPage)} />
+ >
+ );
+ }
+ };
+
+ return (
+
+
+ handlePageChange(firstPage)}
+ />
+ handlePageChange(currentPage - 1)}
+ />
+ {renderPageNumbers()}
+ handlePageChange(currentPage + 1)}
+ />
+ handlePageChange(lastPage)}
+ />
+
+
+ );
+};
+
+export default Pagination;
diff --git a/ui/src/components/EnhancedPagination/__tests__/Pagination.test.tsx b/ui/src/components/EnhancedPagination/__tests__/Pagination.test.tsx
new file mode 100644
index 00000000..a41a037d
--- /dev/null
+++ b/ui/src/components/EnhancedPagination/__tests__/Pagination.test.tsx
@@ -0,0 +1,86 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import { expect } from '@jest/globals';
+import '@testing-library/jest-dom';
+
+import Pagination, { PaginationProps } from '../Pagination';
+
+describe('Pagination', () => {
+ const mockHandlePageChange = jest.fn();
+ const defaultProps: PaginationProps = {
+ links: {
+ self: 'http://localhost:3000/api/v1/connectors?',
+ first: 'http://localhost:3000/api/v1/connectors?page=1&per_page=10',
+ prev: null,
+ next: 'http://localhost:3000/api/v1/connectors?page=2&per_page=10',
+ last: 'http://localhost:3000/api/v1/connectors?page=10&per_page=10',
+ },
+ currentPage: 5,
+ handlePageChange: mockHandlePageChange,
+ };
+
+ beforeEach(() => {
+ mockHandlePageChange.mockClear();
+ });
+
+ it('should render correct page numbers for middle page', () => {
+ render();
+ expect(screen.getByTestId('page-number-1')).toBeTruthy();
+ expect(screen.getByTestId('page-number-5')).toBeTruthy();
+ expect(screen.getByTestId('page-number-10')).toBeTruthy();
+ });
+
+ it('should render correct page numbers for first page', () => {
+ render();
+ expect(screen.getByTestId('page-number-1')).toBeTruthy();
+ expect(screen.getByTestId('page-number-10')).toBeTruthy();
+ expect(screen.queryByTestId('page-number-5')).not.toBeTruthy();
+ });
+
+ it('should render correct page numbers for last page', () => {
+ render();
+ expect(screen.getByTestId('page-number-1')).toBeTruthy();
+ expect(screen.getByTestId('page-number-10')).toBeTruthy();
+ });
+
+ it('should call handlePageChange with correct page number when a page number is clicked', () => {
+ render();
+ const pageButton = screen.getByTestId('page-number-1');
+ fireEvent.click(pageButton);
+ expect(mockHandlePageChange).toHaveBeenCalledWith(1);
+ });
+
+ it('should disable first and previous buttons on first page', () => {
+ render();
+
+ expect(screen.getByTestId('page-change-first')).toHaveProperty('disabled', true);
+ expect(screen.getByTestId('page-change-previous')).toHaveProperty('disabled', true);
+ });
+
+ it('should disable next and last buttons on last page', () => {
+ render();
+
+ expect(screen.getByTestId('page-change-next')).toHaveProperty('disabled', true);
+ expect(screen.getByTestId('page-change-last')).toHaveProperty('disabled', true);
+ });
+
+ it('should call handlePageChange with correct page number when navigation buttons are clicked', () => {
+ render();
+ fireEvent.click(screen.getByTestId('page-change-first'));
+ expect(mockHandlePageChange).toHaveBeenCalledWith(1);
+
+ fireEvent.click(screen.getByTestId('page-change-previous'));
+ expect(mockHandlePageChange).toHaveBeenCalledWith(4);
+
+ fireEvent.click(screen.getByTestId('page-change-next'));
+ expect(mockHandlePageChange).toHaveBeenCalledWith(6);
+
+ fireEvent.click(screen.getByTestId('page-change-last'));
+ expect(mockHandlePageChange).toHaveBeenCalledWith(10);
+ });
+
+ it('should render ellipsis correctly', () => {
+ render();
+ const ellipses = screen.getAllByText('...');
+ expect(ellipses).toHaveLength(2);
+ });
+});
diff --git a/ui/src/components/EnhancedPagination/index.ts b/ui/src/components/EnhancedPagination/index.ts
new file mode 100644
index 00000000..eaef04eb
--- /dev/null
+++ b/ui/src/components/EnhancedPagination/index.ts
@@ -0,0 +1 @@
+export { default } from './Pagination';
diff --git a/ui/src/components/ModelTable/ModelTable.tsx b/ui/src/components/ModelTable/ModelTable.tsx
index a8984ff5..bc3920d1 100644
--- a/ui/src/components/ModelTable/ModelTable.tsx
+++ b/ui/src/components/ModelTable/ModelTable.tsx
@@ -1,10 +1,11 @@
import GenerateTable from '@/components/Table/Table';
-import { getAllModels, APIData } from '@/services/models';
+import { getAllModels, GetAllModelsResponse } from '@/services/models';
import { addIconDataToArray, ConvertToTableData } from '@/utils';
import NoModels from '@/views/Models/NoModels';
import Loader from '@/components/Loader';
import useQueryWrapper from '@/hooks/useQueryWrapper';
import { useStore } from '@/stores';
+import { ApiResponse } from '@/services/common';
type ModelTableProps = {
handleOnRowClick: (args: unknown) => void;
@@ -13,7 +14,7 @@ type ModelTableProps = {
const ModelTable = ({ handleOnRowClick }: ModelTableProps): JSX.Element => {
const activeWorkspaceId = useStore((state) => state.workspaceId);
- const { data } = useQueryWrapper(
+ const { data } = useQueryWrapper, Error>(
['models', activeWorkspaceId],
() => getAllModels({ type: 'data' }),
{
diff --git a/ui/src/services/common.ts b/ui/src/services/common.ts
index 68465e0d..b646148b 100644
--- a/ui/src/services/common.ts
+++ b/ui/src/services/common.ts
@@ -2,11 +2,11 @@ export * from '@/services/axios';
export type APIRequestMethod = 'get' | 'post' | 'put' | 'delete';
-type LinksType = {
+export type LinksType = {
first: string;
last: string;
- next: string;
- prev: string;
+ next: string | null;
+ prev: string | null;
self: string;
};
diff --git a/ui/src/views/Activate/Syncs/SyncRecords/SyncRecords.tsx b/ui/src/views/Activate/Syncs/SyncRecords/SyncRecords.tsx
index 5a228751..f87a9a97 100644
--- a/ui/src/views/Activate/Syncs/SyncRecords/SyncRecords.tsx
+++ b/ui/src/views/Activate/Syncs/SyncRecords/SyncRecords.tsx
@@ -7,7 +7,6 @@ import { getSyncRecords } from '@/services/syncs';
import Loader from '@/components/Loader';
import ContentContainer from '@/components/ContentContainer';
-import Pagination from '@/components/Pagination';
import SyncRunEmptyImage from '@/assets/images/empty-state-illustration.svg';
import { SyncRecordsTopBar } from './SyncRecordsTopBar';
@@ -21,6 +20,7 @@ import { SyncRecordResponse } from '@/views/Activate/Syncs/types';
import DataTable from '@/components/DataTable';
import { SyncRecordsColumns, useDynamicSyncColumns } from './SyncRecordsColumns';
+import Pagination from '@/components/EnhancedPagination';
const SyncRecords = (): JSX.Element => {
const [searchParams, setSearchParams] = useSearchParams();
@@ -81,18 +81,6 @@ const SyncRecords = (): JSX.Element => {
}
}, [isFilteredSyncRecordsError, toast]);
- const handleNextPage = () => {
- if (filteredSyncRunRecords?.links?.next) {
- setCurrentPage((prevPage) => prevPage + 1);
- }
- };
-
- const handlePrevPage = () => {
- if (filteredSyncRunRecords?.links?.prev) {
- setCurrentPage((prevPage) => Math.max(prevPage - 1, 1));
- }
- };
-
const handleStatusTabChange = (status: SyncRecordStatus) => {
setCurrentPage(1);
setCurrentStatusTab(status);
@@ -143,14 +131,16 @@ const SyncRecords = (): JSX.Element => {
-
-
+
+ {filteredSyncRunRecords.links ? (
+
+ ) : (
+ <>Pagination unavailable.>
+ )}
)}