From aefc36b5969add77bbcbc1c3e9a586c1f439b8e5 Mon Sep 17 00:00:00 2001 From: sonika-shah <58761340+sonika-shah@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:43:30 +0530 Subject: [PATCH] GEN 16908 - Support pagination for children field (#19650) * GEN 16908 - Support pagination for children field * Fix tests - Support pagination for children field * move children pagination listing to separate api * added pagination support from UI * added playwright test for the pagination test --------- Co-authored-by: Ashish Gupta --- .../service/jdbi3/CollectionDAO.java | 20 ++++ .../service/jdbi3/ContainerRepository.java | 45 +++++++++ .../resources/storages/ContainerResource.java | 35 +++++++ .../storages/ContainerResourceTest.java | 23 +++++ .../ui/playwright/constant/contianer.ts | 22 +++++ .../playwright/e2e/Features/Container.spec.ts | 98 +++++++++++++++++++ .../support/entity/ContainerClass.ts | 49 +++++++--- .../ContainerChildren.test.tsx | 80 ++++++++++++++- .../ContainerChildren/ContainerChildren.tsx | 67 ++++++++++--- .../ui/src/interface/API.interface.ts | 4 + .../ContainerPage/ContainerPage.test.tsx | 11 +++ .../src/pages/ContainerPage/ContainerPage.tsx | 52 +++++++++- .../main/resources/ui/src/rest/storageAPI.ts | 18 +++- 13 files changed, 489 insertions(+), 35 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/constant/contianer.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Container.spec.ts diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java index 76d5c9e9e59..c40fcd4f34e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java @@ -1019,6 +1019,26 @@ public interface CollectionDAO { @Bind("relation") int relation, @Bind("toEntity") String toEntity); + @SqlQuery( + "SELECT COUNT(toId) FROM entity_relationship WHERE fromId = :fromId AND fromEntity = :fromEntity " + + "AND relation IN ()") + @RegisterRowMapper(ToRelationshipMapper.class) + int countFindTo( + @BindUUID("fromId") UUID fromId, + @Bind("fromEntity") String fromEntity, + @BindList("relation") List relation); + + @SqlQuery( + "SELECT toId, toEntity, json FROM entity_relationship WHERE fromId = :fromId AND fromEntity = :fromEntity " + + "AND relation IN () ORDER BY toId LIMIT :limit OFFSET :offset") + @RegisterRowMapper(ToRelationshipMapper.class) + List findToWithOffset( + @BindUUID("fromId") UUID fromId, + @Bind("fromEntity") String fromEntity, + @BindList("relation") List relation, + @Bind("offset") int offset, + @Bind("limit") int limit); + @ConnectionAwareSqlQuery( value = "SELECT toId, toEntity, json FROM entity_relationship " diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java index ded12ad9a03..09e77f0b621 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java @@ -8,6 +8,7 @@ import static org.openmetadata.service.Entity.FIELD_PARENT; import static org.openmetadata.service.Entity.FIELD_TAGS; import static org.openmetadata.service.Entity.STORAGE_SERVICE; import static org.openmetadata.service.Entity.populateEntityFieldTags; +import static org.openmetadata.service.util.EntityUtil.getEntityReferences; import com.google.common.collect.Lists; import java.util.ArrayList; @@ -33,6 +34,7 @@ import org.openmetadata.service.resources.storages.ContainerResource; import org.openmetadata.service.util.EntityUtil; import org.openmetadata.service.util.FullyQualifiedName; import org.openmetadata.service.util.JsonUtils; +import org.openmetadata.service.util.ResultList; public class ContainerRepository extends EntityRepository { private static final String CONTAINER_UPDATE_FIELDS = "dataModel"; @@ -223,6 +225,49 @@ public class ContainerRepository extends EntityRepository { return super.getTaskWorkflow(threadContext); } + public ResultList listChildren(String parentFQN, Integer limit, Integer offset) { + + Container parentContainer = dao.findEntityByName(parentFQN); + + try { + List relationshipRecords = + daoCollection + .relationshipDAO() + .findToWithOffset( + parentContainer.getId(), + CONTAINER, + List.of(Relationship.CONTAINS.ordinal()), + offset, + limit); + + int total = + daoCollection + .relationshipDAO() + .countFindTo( + parentContainer.getId(), CONTAINER, List.of(Relationship.CONTAINS.ordinal())); + + if (relationshipRecords.isEmpty()) { + return new ResultList<>(new ArrayList<>(), null, null, total); + } + + List refs = getEntityReferences(relationshipRecords); + List children = new ArrayList<>(); + + for (EntityReference ref : refs) { + Container container = + Entity.getEntity(ref, EntityUtil.Fields.EMPTY_FIELDS.toString(), Include.ALL); + children.add(container); + } + + return new ResultList<>(children, null, null, total); + } catch (Exception e) { + throw new RuntimeException( + String.format( + "Failed to fetch children for container [%s]: %s", parentFQN, e.getMessage()), + e); + } + } + static class DataModelDescriptionTaskWorkflow extends DescriptionTaskWorkflow { private final Column column; diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java index 806b18a893a..0da4d82084d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java @@ -546,4 +546,39 @@ public class ContainerResource extends EntityResource listChildren( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Fully qualified name of the container") @PathParam("fqn") + String fqn, + @Parameter( + description = "Limit the number of children returned. (1 to 1000000, default = 10)") + @DefaultValue("10") + @Min(0) + @Max(1000000) + @QueryParam("limit") + Integer limit, + @Parameter(description = "Returns list of children after the given offset") + @DefaultValue("0") + @QueryParam("offset") + @Min(0) + Integer offset) { + return repository.listChildren(fqn, limit, offset); + } } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/storages/ContainerResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/storages/ContainerResourceTest.java index f250a8de995..e90923378b8 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/storages/ContainerResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/storages/ContainerResourceTest.java @@ -40,6 +40,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import lombok.extern.slf4j.Slf4j; @@ -394,6 +395,7 @@ public class ContainerResourceTest extends EntityResourceTest rootContainerList = listEntities(queryParams, ADMIN_AUTH_HEADERS); assertEquals(1, rootContainerList.getData().size()); assertEquals("s3.0_root", rootContainerList.getData().get(0).getFullyQualifiedName()); + + // Test paginated child container list + ResultList children = getContainerChildren(rootContainerFQN, null, null); + assertEquals(2, children.getData().size()); + + ResultList childrenWithLimit = getContainerChildren(rootContainerFQN, 5, 0); + assertEquals(2, childrenWithLimit.getData().size()); + + ResultList childrenWithOffset = getContainerChildren(rootContainerFQN, 1, 1); + assertEquals(1, childrenWithOffset.getData().size()); + + ResultList childrenWithLargeOffset = getContainerChildren(rootContainerFQN, 1, 3); + assertTrue(childrenWithLargeOffset.getData().isEmpty()); } @Test @@ -696,6 +711,14 @@ public class ContainerResourceTest extends EntityResourceTest getContainerChildren(String fqn, Integer limit, Integer offset) + throws HttpResponseException { + WebTarget target = getResource(String.format("containers/name/%s/children", fqn)); + target = limit != null ? target.queryParam("limit", limit) : target; + target = offset != null ? target.queryParam("offset", offset) : target; + return TestUtils.get(target, ContainerList.class, ADMIN_AUTH_HEADERS); + } + @Test void testInheritedPermissionFromParent(TestInfo test) throws IOException { // Create a storage service with owner data consumer diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/contianer.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/contianer.ts new file mode 100644 index 00000000000..b295d645a88 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/contianer.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { uuid } from '../utils/common'; + +export const CONTAINER_CHILDREN = Array.from({ length: 25 }, (_, i) => { + const id = uuid(); + + return { + name: `pw-container-children${i + 1}-${id}`, + displayName: `pw-container-children-${i + 1}-${id}`, + }; +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Container.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Container.spec.ts new file mode 100644 index 00000000000..b116901ebbd --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Container.spec.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import test, { expect } from '@playwright/test'; +import { CONTAINER_CHILDREN } from '../../constant/contianer'; +import { ContainerClass } from '../../support/entity/ContainerClass'; +import { createNewPage, redirectToHomePage } from '../../utils/common'; + +// use the admin user to login +test.use({ storageState: 'playwright/.auth/admin.json' }); + +const container = new ContainerClass(); + +test.slow(true); + +test.describe('Container entity specific tests ', () => { + test.beforeAll('Setup pre-requests', async ({ browser }) => { + const { afterAction, apiContext } = await createNewPage(browser); + + await container.create(apiContext, CONTAINER_CHILDREN); + + await afterAction(); + }); + + test.afterAll('Clean up', async ({ browser }) => { + const { afterAction, apiContext } = await createNewPage(browser); + + await container.delete(apiContext); + + await afterAction(); + }); + + test.beforeEach('Visit home page', async ({ page }) => { + await redirectToHomePage(page); + }); + + test('Container page children pagination', async ({ page }) => { + await container.visitEntityPage(page); + + await page.getByText('Children').click(); + + await expect(page.getByTestId('pagination')).toBeVisible(); + await expect(page.getByTestId('previous')).toBeDisabled(); + await expect(page.getByTestId('page-indicator')).toContainText('1/2 Page'); + + // Check the second page pagination + const childrenResponse = page.waitForResponse( + '/api/v1/containers/name/*/children?limit=15&offset=15' + ); + await page.getByTestId('next').click(); + await childrenResponse; + + await expect(page.getByTestId('next')).toBeDisabled(); + await expect(page.getByTestId('page-indicator')).toContainText('2/2 Page'); + + // Check around the page sizing change + await page.getByTestId('page-size-selection-dropdown').click(); + + const childrenResponseSizeChange = page.waitForResponse( + '/api/v1/containers/name/*/children?limit=25&offset=0' + ); + await page.getByText('25 / Page').click(); + await childrenResponseSizeChange; + + await page.waitForSelector('.ant-spin', { + state: 'detached', + }); + + await expect(page.getByTestId('next')).toBeDisabled(); + await expect(page.getByTestId('previous')).toBeDisabled(); + await expect(page.getByTestId('page-indicator')).toContainText('1/1 Page'); + + // Back to the original page size + await page.getByTestId('page-size-selection-dropdown').click(); + + const childrenResponseSizeChange2 = page.waitForResponse( + '/api/v1/containers/name/*/children?limit=15&offset=0' + ); + await page.getByText('15 / Page').click(); + await childrenResponseSizeChange2; + + await page.waitForSelector('.ant-spin', { + state: 'detached', + }); + + await expect(page.getByTestId('previous')).toBeDisabled(); + await expect(page.getByTestId('page-indicator')).toContainText('1/2 Page'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts index 8f54d624f4b..53766442788 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts @@ -12,6 +12,7 @@ */ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; +import { isUndefined } from 'lodash'; import { SERVICE_TYPE } from '../../constant/service'; import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; @@ -89,6 +90,7 @@ export class ContainerClass extends EntityClass { entityResponseData: ResponseDataWithServiceType = {} as ResponseDataWithServiceType; childResponseData: ResponseDataType = {} as ResponseDataType; + childArrayResponseData: ResponseDataType[] = []; constructor(name?: string) { super(EntityTypeEndpoint.Container); @@ -98,7 +100,10 @@ export class ContainerClass extends EntityClass { this.serviceCategory = SERVICE_TYPE.Storage; } - async create(apiContext: APIRequestContext) { + async create( + apiContext: APIRequestContext, + customChildContainer?: { name: string; displayName: string }[] + ) { const serviceResponse = await apiContext.post( '/api/v1/services/storageServices', { @@ -112,19 +117,39 @@ export class ContainerClass extends EntityClass { this.serviceResponseData = await serviceResponse.json(); this.entityResponseData = await entityResponse.json(); - const childContainer = { - ...this.childContainer, - parent: { - id: this.entityResponseData.id, - type: 'container', - }, - }; + if (!isUndefined(customChildContainer)) { + const childArrayResponseData: ResponseDataType[] = []; + for (const child of customChildContainer) { + const childContainer = { + ...child, + service: this.service.name, + parent: { + id: this.entityResponseData.id, + type: 'container', + }, + }; + const childResponse = await apiContext.post('/api/v1/containers', { + data: childContainer, + }); - const childResponse = await apiContext.post('/api/v1/containers', { - data: childContainer, - }); + childArrayResponseData.push(await childResponse.json()); + } + this.childArrayResponseData = childArrayResponseData; + } else { + const childContainer = { + ...this.childContainer, + parent: { + id: this.entityResponseData.id, + type: 'container', + }, + }; - this.childResponseData = await childResponse.json(); + const childResponse = await apiContext.post('/api/v1/containers', { + data: childContainer, + }); + + this.childResponseData = await childResponse.json(); + } return { service: serviceResponse.body, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.test.tsx index 6a1914b02a7..252cc6e2bcc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.test.tsx @@ -10,12 +10,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; import { BrowserRouter } from 'react-router-dom'; +import { pagingObject } from '../../../constants/constants'; import ContainerChildren from './ContainerChildren'; +jest.mock('../../common/NextPrevious/NextPrevious', () => { + return jest.fn().mockImplementation(({ pagingHandler, onShowSizeChange }) => ( +
+

NextPreviousComponent

+ + +
+ )); +}); + const mockFetchChildren = jest.fn(); +const mockHandleChildrenPageChange = jest.fn(); +const mockHandlePageSizeChange = jest.fn(); + const mockChildrenList = [ { id: '1', @@ -36,6 +50,14 @@ const mockChildrenList = [ const mockDataProps = { childrenList: mockChildrenList, fetchChildren: mockFetchChildren, + pagingHookData: { + paging: pagingObject, + pageSize: 15, + currentPage: 1, + showPagination: true, + handleChildrenPageChange: mockHandleChildrenPageChange, + handlePageSizeChange: mockHandlePageSizeChange, + }, }; describe('ContainerChildren', () => { @@ -70,6 +92,22 @@ describe('ContainerChildren', () => { expect(screen.getByText('label.description')).toBeInTheDocument(); }); + it('Should not render pagination component when not visible', () => { + render( + + + + ); + + expect(screen.queryByText('NextPreviousComponent')).not.toBeInTheDocument(); + }); + it('Should render container names as links', () => { render( @@ -108,4 +146,44 @@ describe('ContainerChildren', () => { expect(previewer).toHaveTextContent(mockChildrenList[index].description); }); }); + + it('Should render pagination component when showPagination props is true', () => { + render( + + + + ); + + expect(screen.getByText('NextPreviousComponent')).toBeInTheDocument(); + }); + + it('Should trigger handleChildrenPageChange hook prop on button click', () => { + render( + + + + ); + + fireEvent.click(screen.getByText('childrenPageChangeButton')); + + expect(mockHandleChildrenPageChange).toHaveBeenCalled(); + }); + + it('Should trigger handlePageSizeChange hook prop on button click', () => { + render( + + + + ); + + fireEvent.click(screen.getByText('pageSizeChangeButton')); + + expect(mockHandlePageSizeChange).toHaveBeenCalled(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.tsx index 30892f78a47..442887f4989 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerChildren/ContainerChildren.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Typography } from 'antd'; +import { Col, Row, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import React, { FC, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -19,23 +19,43 @@ import { getEntityDetailsPath } from '../../../constants/constants'; import { EntityType } from '../../../enums/entity.enum'; import { Container } from '../../../generated/entity/data/container'; import { EntityReference } from '../../../generated/type/entityReference'; +import { Paging } from '../../../generated/type/paging'; import { getColumnSorter, getEntityName } from '../../../utils/EntityUtils'; import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; +import NextPrevious from '../../common/NextPrevious/NextPrevious'; +import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface'; import RichTextEditorPreviewerV1 from '../../common/RichTextEditor/RichTextEditorPreviewerV1'; import Table from '../../common/Table/Table'; interface ContainerChildrenProps { childrenList: Container['children']; isLoading?: boolean; + pagingHookData: { + paging: Paging; + pageSize: number; + currentPage: number; + showPagination: boolean; + handleChildrenPageChange: (data: PagingHandlerParams) => void; + handlePageSizeChange: (page: number) => void; + }; fetchChildren: () => void; } const ContainerChildren: FC = ({ childrenList, isLoading, + pagingHookData, fetchChildren, }) => { const { t } = useTranslation(); + const { + paging, + pageSize, + currentPage, + showPagination, + handleChildrenPageChange, + handlePageSizeChange, + } = pagingHookData; const columns: ColumnsType = useMemo( () => [ @@ -83,22 +103,39 @@ const ContainerChildren: FC = ({ useEffect(() => { fetchChildren(); - }, []); + }, [pageSize]); return ( - , - }} - pagination={false} - rowKey="id" - size="small" - /> + + +
, + }} + pagination={false} + rowKey="id" + size="small" + /> + + + {showPagination && ( + + )} + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/interface/API.interface.ts b/openmetadata-ui/src/main/resources/ui/src/interface/API.interface.ts index 51e59c3b7e8..fe9045d88f4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/interface/API.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/interface/API.interface.ts @@ -19,3 +19,7 @@ export type ListParams = { after?: string; include?: Include; }; + +export type ListParamsWithOffset = ListParams & { + offset?: number; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.test.tsx index 6c598f2d054..de592121deb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.test.tsx @@ -184,6 +184,17 @@ jest.mock('../../utils/CommonUtils', () => ({ sortTagsCaseInsensitive: jest.fn().mockImplementation((tags) => tags), })); +jest.mock('../../hooks/paging/usePaging', () => ({ + usePaging: jest.fn().mockReturnValue({ + currentPage: 1, + showPagination: true, + pageSize: 10, + handlePageChange: jest.fn(), + handlePagingChange: jest.fn(), + handlePageSizeChange: jest.fn(), + }), +})); + jest.mock('../../utils/EntityUtils', () => ({ getEntityName: jest .fn() diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx index 93338df8fbf..1270388cb9f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx @@ -27,6 +27,7 @@ import { CustomPropertyTable } from '../../components/common/CustomPropertyTable import DescriptionV1 from '../../components/common/EntityDescription/DescriptionV1'; import ErrorPlaceHolder from '../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder'; import Loader from '../../components/common/Loader/Loader'; +import { PagingHandlerParams } from '../../components/common/NextPrevious/NextPrevious.interface'; import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels'; import TabsLabel from '../../components/common/TabsLabel/TabsLabel.component'; import ContainerChildren from '../../components/Container/ContainerChildren/ContainerChildren'; @@ -63,8 +64,10 @@ import { Tag } from '../../generated/entity/classification/tag'; import { Container } from '../../generated/entity/data/container'; import { ThreadType } from '../../generated/entity/feed/thread'; import { Include } from '../../generated/type/include'; +import { Paging } from '../../generated/type/paging'; import { TagLabel } from '../../generated/type/tagLabel'; import LimitWrapper from '../../hoc/LimitWrapper'; +import { usePaging } from '../../hooks/paging/usePaging'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; import { FeedCounts } from '../../interface/feed.interface'; @@ -72,6 +75,7 @@ import { postThread } from '../../rest/feedsAPI'; import { addContainerFollower, getContainerByName, + getContainerChildrenByName, patchContainerDetails, removeContainerFollower, restoreContainer, @@ -121,6 +125,16 @@ const ContainerPage = () => { ThreadType.Conversation ); + const { + paging, + pageSize, + currentPage, + showPagination, + handlePagingChange, + handlePageChange, + handlePageSizeChange, + } = usePaging(); + const fetchContainerDetail = async (containerFQN: string) => { setIsLoading(true); try { @@ -161,13 +175,18 @@ const ContainerPage = () => { } }; - const fetchContainerChildren = async () => { + const fetchContainerChildren = async (pagingOffset?: Paging) => { setIsChildrenLoading(true); try { - const { children } = await getContainerByName(decodedContainerName, { - fields: TabSpecificField.CHILDREN, - }); - setContainerChildrenData(children); + const { data, paging } = await getContainerChildrenByName( + decodedContainerName, + { + limit: pageSize, + offset: pagingOffset?.offset ?? 0, + } + ); + setContainerChildrenData(data); + handlePagingChange(paging); } catch (error) { showErrorToast(error as AxiosError); } finally { @@ -562,6 +581,13 @@ const ContainerPage = () => { } }; + const handleChildrenPageChange = ({ currentPage }: PagingHandlerParams) => { + handlePageChange(currentPage); + fetchContainerChildren({ + offset: (currentPage - 1) * pageSize, + } as Paging); + }; + const handleTagSelection = async (selectedTags: EntityTags[]) => { const updatedTags: TagLabel[] | undefined = createTagObject(selectedTags); @@ -611,6 +637,14 @@ const ContainerPage = () => { childrenList={containerChildrenData} fetchChildren={fetchContainerChildren} isLoading={isChildrenLoading} + pagingHookData={{ + paging, + pageSize, + currentPage, + showPagination, + handleChildrenPageChange, + handlePageSizeChange, + }} /> ) : ( { childrenList={containerChildrenData} fetchChildren={fetchContainerChildren} isLoading={isChildrenLoading} + pagingHookData={{ + paging, + pageSize, + currentPage, + showPagination, + handleChildrenPageChange, + handlePageSizeChange, + }} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/storageAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/storageAPI.ts index c0e70da79b1..46d11423ca2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/storageAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/storageAPI.ts @@ -12,7 +12,7 @@ */ import { AxiosResponse } from 'axios'; import { Operation } from 'fast-json-patch'; -import { PagingWithoutTotal, RestoreRequestType } from 'Models'; +import { PagingResponse, PagingWithoutTotal, RestoreRequestType } from 'Models'; import { QueryVote } from '../components/Database/TableQueries/TableQueries.interface'; import { APPLICATION_JSON_CONTENT_TYPE_HEADER } from '../constants/constants'; import { Container } from '../generated/entity/data/container'; @@ -20,7 +20,7 @@ import { EntityHistory } from '../generated/type/entityHistory'; import { EntityReference } from '../generated/type/entityReference'; import { Include } from '../generated/type/include'; import { Paging } from '../generated/type/paging'; -import { ListParams } from '../interface/API.interface'; +import { ListParams, ListParamsWithOffset } from '../interface/API.interface'; import { ServicePageData } from '../pages/ServiceDetailsPage/ServiceDetailsPage'; import { getEncodedFqn } from '../utils/StringsUtils'; import APIClient from './index'; @@ -63,6 +63,20 @@ export const getContainerByName = async (name: string, params?: ListParams) => { return response.data; }; +export const getContainerChildrenByName = async ( + name: string, + params?: ListParamsWithOffset +) => { + const response = await APIClient.get>( + `${BASE_URL}/name/${getEncodedFqn(name)}/children`, + { + params, + } + ); + + return response.data; +}; + export const patchContainerDetails = async (id: string, data: Operation[]) => { const response = await APIClient.patch>( `${BASE_URL}/${id}`,