From d7cd1fcc1cbaf56765b87c1136df1613ac3ffb83 Mon Sep 17 00:00:00 2001
From: gaurav2733 <77378510+gaurav2733@users.noreply.github.com>
Date: Fri, 19 Apr 2024 20:01:19 +0530
Subject: [PATCH] feat(ui/tasks): add pagination on tasks listing page (#10293)

---
 .../app/entity/dataFlow/DataFlowEntity.tsx    |   3 +
 .../shared/tabs/Entity/DataFlowJobsTab.tsx    |  53 ++++++++-
 .../tabs/Entity/components/EntityList.tsx     | 108 ++++++++++++++++--
 .../src/graphql/dataFlow.graphql              |  30 +++--
 .../cypress/e2e/schema_blame/schema_blame.js  |   2 +-
 5 files changed, 168 insertions(+), 28 deletions(-)

diff --git a/datahub-web-react/src/app/entity/dataFlow/DataFlowEntity.tsx b/datahub-web-react/src/app/entity/dataFlow/DataFlowEntity.tsx
index 25c1af09e7e5c8..fb5ea280087412 100644
--- a/datahub-web-react/src/app/entity/dataFlow/DataFlowEntity.tsx
+++ b/datahub-web-react/src/app/entity/dataFlow/DataFlowEntity.tsx
@@ -81,6 +81,9 @@ export class DataFlowEntity implements Entity<DataFlow> {
                 {
                     name: 'Tasks',
                     component: DataFlowJobsTab,
+                    properties: {
+                        urn,
+                    },
                 },
                 {
                     name: 'Incidents',
diff --git a/datahub-web-react/src/app/entity/shared/tabs/Entity/DataFlowJobsTab.tsx b/datahub-web-react/src/app/entity/shared/tabs/Entity/DataFlowJobsTab.tsx
index fe43ee7dc518a9..36e8c035c0d236 100644
--- a/datahub-web-react/src/app/entity/shared/tabs/Entity/DataFlowJobsTab.tsx
+++ b/datahub-web-react/src/app/entity/shared/tabs/Entity/DataFlowJobsTab.tsx
@@ -1,19 +1,60 @@
-import React from 'react';
-import { useBaseEntity } from '../../EntityContext';
+import React, { useState } from 'react';
 import { EntityType } from '../../../../../types.generated';
 import { EntityList } from './components/EntityList';
 import { useEntityRegistry } from '../../../../useEntityRegistry';
+import { useGetDataFlowChildJobsQuery } from '../../../../../graphql/dataFlow.generated';
+import { SearchCfg } from '../../../../../conf';
 
-export const DataFlowJobsTab = () => {
-    const entity = useBaseEntity() as any;
-    const dataFlow = entity && entity.dataFlow;
+interface Props {
+    properties?: {
+        urn: string;
+    };
+}
+
+export const DataFlowJobsTab = ({ properties = { urn: '' } }: Props) => {
+    const [page, setPage] = useState(1);
+    const [numResultsPerPage, setNumResultsPerPage] = useState(SearchCfg.RESULTS_PER_PAGE);
+
+    const start: number = (page - 1) * numResultsPerPage;
+
+    const { data, loading, error } = useGetDataFlowChildJobsQuery({
+        variables: {
+            urn: properties.urn,
+            start,
+            count: numResultsPerPage,
+        },
+    });
+
+    const onChangePage = (newPage: number) => {
+        setPage(newPage);
+    };
+
+    const dataFlow = data && data?.dataFlow;
     const dataJobs = dataFlow?.childJobs?.relationships.map((relationship) => relationship.entity);
     const entityRegistry = useEntityRegistry();
     const totalJobs = dataFlow?.childJobs?.total || 0;
+    const pageSize = data?.dataFlow?.childJobs?.count || 0;
+    const pageStart = data?.dataFlow?.childJobs?.start || 0;
+    const lastResultIndex = pageStart + pageSize > totalJobs ? totalJobs : pageStart + pageSize;
     const title = `Contains ${totalJobs} ${
         totalJobs === 1
             ? entityRegistry.getEntityName(EntityType.DataJob)
             : entityRegistry.getCollectionName(EntityType.DataJob)
     }`;
-    return <EntityList title={title} type={EntityType.DataJob} entities={dataJobs || []} />;
+    return (
+        <EntityList
+            title={title}
+            type={EntityType.DataJob}
+            entities={dataJobs || []}
+            showPagination
+            loading={loading}
+            error={error}
+            totalAssets={totalJobs}
+            page={page}
+            pageSize={numResultsPerPage}
+            lastResultIndex={lastResultIndex}
+            onChangePage={onChangePage}
+            setNumResultsPerPage={setNumResultsPerPage}
+        />
+    );
 };
diff --git a/datahub-web-react/src/app/entity/shared/tabs/Entity/components/EntityList.tsx b/datahub-web-react/src/app/entity/shared/tabs/Entity/components/EntityList.tsx
index 3a9061fd97d6e7..9e07e928be55e4 100644
--- a/datahub-web-react/src/app/entity/shared/tabs/Entity/components/EntityList.tsx
+++ b/datahub-web-react/src/app/entity/shared/tabs/Entity/components/EntityList.tsx
@@ -1,9 +1,27 @@
 import React from 'react';
-import { List } from 'antd';
+import { List, Pagination, Typography } from 'antd';
 import styled from 'styled-components';
 import { useEntityRegistry } from '../../../../../useEntityRegistry';
 import { PreviewType } from '../../../../Entity';
 import { EntityType } from '../../../../../../types.generated';
+import { SearchCfg } from '../../../../../../conf';
+import { Message } from '../../../../../shared/Message';
+
+const ScrollWrapper = styled.div`
+    overflow: auto;
+    height: 100%;
+
+    &::-webkit-scrollbar {
+        height: 12px;
+        width: 5px;
+        background: #f2f2f2;
+    }
+    &::-webkit-scrollbar-thumb {
+        background: #cccccc;
+        -webkit-border-radius: 1ex;
+        -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.75);
+    }
+`;
 
 const StyledList = styled(List)`
     padding-left: 40px;
@@ -28,22 +46,94 @@ const StyledListItem = styled(List.Item)`
     padding-top: 20px;
 `;
 
+const PaginationInfoContainer = styled.span`
+    padding: 8px;
+    padding-left: 16px;
+    border-top: 1px solid;
+    border-color: ${(props) => props.theme.styles['border-color-base']};
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+`;
+
+const StyledPagination = styled(Pagination)`
+    padding: 12px 12px 12px 12px;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+`;
+
+const PaginationInfo = styled(Typography.Text)`
+    padding: 0px;
+    width: 20%;
+`;
+
 type EntityListProps = {
     type: EntityType;
     entities: Array<any>;
     title?: string;
+    totalAssets?: number;
+    pageSize?: any;
+    page?: number;
+    lastResultIndex?: any;
+    showPagination?: boolean;
+    loading?: boolean;
+    error?: any;
+    onChangePage?: (number: any) => void;
+    setNumResultsPerPage?: (number: any) => void;
 };
 
-export const EntityList = ({ type, entities, title }: EntityListProps) => {
+export const EntityList = ({
+    type,
+    entities,
+    title,
+    totalAssets,
+    pageSize,
+    page,
+    lastResultIndex,
+    showPagination = false,
+    loading = false,
+    error = undefined,
+    onChangePage,
+    setNumResultsPerPage,
+}: EntityListProps) => {
     const entityRegistry = useEntityRegistry();
+
     return (
-        <StyledList
-            bordered
-            dataSource={entities}
-            header={title || `${entities.length || 0} ${entityRegistry.getCollectionName(type)}`}
-            renderItem={(item) => (
-                <StyledListItem>{entityRegistry.renderPreview(type, PreviewType.PREVIEW, item)}</StyledListItem>
+        <>
+            <ScrollWrapper>
+                <StyledList
+                    bordered
+                    dataSource={entities}
+                    header={title || `${entities.length || 0} ${entityRegistry.getCollectionName(type)}`}
+                    renderItem={(item) => (
+                        <StyledListItem>{entityRegistry.renderPreview(type, PreviewType.PREVIEW, item)}</StyledListItem>
+                    )}
+                />
+            </ScrollWrapper>
+            {loading && <Message type="loading" content="Loading..." style={{ marginTop: '10%' }} />}
+            {error && <Message type="error" content="Failed to load results! An unexpected error occurred." />}
+            {showPagination && (
+                <PaginationInfoContainer>
+                    <PaginationInfo>
+                        <b>
+                            {lastResultIndex > 0 ? ((page as number) - 1) * pageSize + 1 : 0} - {lastResultIndex}
+                        </b>{' '}
+                        of <b>{totalAssets}</b>
+                    </PaginationInfo>
+                    <StyledPagination
+                        current={page}
+                        pageSize={pageSize}
+                        total={totalAssets}
+                        showLessItems
+                        onChange={onChangePage}
+                        showSizeChanger={(totalAssets as any) > SearchCfg.RESULTS_PER_PAGE}
+                        onShowSizeChange={(_currNum, newNum) => setNumResultsPerPage?.(newNum)}
+                        pageSizeOptions={['10', '20', '50', '100']}
+                    />
+                </PaginationInfoContainer>
             )}
-        />
+        </>
     );
 };
diff --git a/datahub-web-react/src/graphql/dataFlow.graphql b/datahub-web-react/src/graphql/dataFlow.graphql
index e4284225e55b9a..2441ce600c3c55 100644
--- a/datahub-web-react/src/graphql/dataFlow.graphql
+++ b/datahub-web-react/src/graphql/dataFlow.graphql
@@ -70,7 +70,24 @@ query getDataFlow($urn: String!) {
         downstream: lineage(input: { direction: DOWNSTREAM, start: 0, count: 100 }) {
             ...partialLineageResults
         }
-        childJobs: relationships(input: { types: ["IsPartOf"], direction: INCOMING, start: 0, count: 100 }) {
+        autoRenderAspects: aspects(input: { autoRenderOnly: true }) {
+            ...autoRenderAspectFields
+        }
+        structuredProperties {
+            properties {
+                ...structuredPropertiesFields
+            }
+        }
+        forms {
+            ...formsFields
+        }
+    }
+}
+
+query getDataFlowChildJobs($urn: String!, $start: Int, $count: Int) {
+    dataFlow(urn: $urn) {
+        ...dataFlowFields
+        childJobs: relationships(input: { types: ["IsPartOf"], direction: INCOMING, start: $start, count: $count }) {
             start
             count
             total
@@ -111,17 +128,6 @@ query getDataFlow($urn: String!) {
                 }
             }
         }
-        autoRenderAspects: aspects(input: { autoRenderOnly: true }) {
-            ...autoRenderAspectFields
-        }
-        structuredProperties {
-            properties {
-                ...structuredPropertiesFields
-            }
-        }
-        forms {
-            ...formsFields
-        }
     }
 }
 
diff --git a/smoke-test/tests/cypress/cypress/e2e/schema_blame/schema_blame.js b/smoke-test/tests/cypress/cypress/e2e/schema_blame/schema_blame.js
index 1ce1fbe900172a..2218cbd95cf9dd 100644
--- a/smoke-test/tests/cypress/cypress/e2e/schema_blame/schema_blame.js
+++ b/smoke-test/tests/cypress/cypress/e2e/schema_blame/schema_blame.js
@@ -12,9 +12,9 @@ describe('schema blame', () => {
       cy.contains('field_foo');
       cy.contains('field_baz');
       cy.contains('field_bar').should('not.exist');
+      cy.clickOptionWithText("field_foo");
       cy.contains('Foo field description has changed');
       cy.contains('Baz field description');
-      cy.clickOptionWithText("field_foo");
       cy.get('[data-testid="schema-field-field_foo-tags"]').contains('Legacy');
 
       // Make sure the schema blame is accurate