You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2022/08/29 15:53:20 UTC

[incubator-devlake] branch main updated: feat: create paginator hook and paginate blueprints grid (#2780)

This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new e90db240 feat: create paginator hook and paginate blueprints grid (#2780)
e90db240 is described below

commit e90db240ab73178dc4dd564064b22d434913963e
Author: Julien Chinapen <ju...@merico.dev>
AuthorDate: Mon Aug 29 11:53:15 2022 -0400

    feat: create paginator hook and paginate blueprints grid (#2780)
    
    * feat: create paginator hook
    
    * feat: finish paginator
    
    * feat: add filter for pageComponent
    
    * fix: fix for review
    
    * fix: rename setData in page components
    
    * fix: fix for lint
    
    Co-authored-by: linyh <ya...@meri.co>
---
 .../src/components/blueprints/BlueprintsGrid.jsx   |   5 +-
 .../create-workflow/DataTransformations.jsx        |   5 +-
 config-ui/src/hooks/useConnectionManager.jsx       |   2 +-
 config-ui/src/hooks/usePaginator.jsx               | 209 +++++++++++++++++++++
 config-ui/src/pages/blueprints/index.jsx           |  52 ++---
 5 files changed, 242 insertions(+), 31 deletions(-)

diff --git a/config-ui/src/components/blueprints/BlueprintsGrid.jsx b/config-ui/src/components/blueprints/BlueprintsGrid.jsx
index 5c1bee38..2b5d302b 100644
--- a/config-ui/src/components/blueprints/BlueprintsGrid.jsx
+++ b/config-ui/src/components/blueprints/BlueprintsGrid.jsx
@@ -37,7 +37,6 @@ import DeletePopover from '@/components/blueprints/DeletePopover'
 const BlueprintsGrid = (props) => {
   const {
     blueprints = [],
-    filteredBlueprints = [],
     pipelines = [],
     activeBlueprint,
     blueprintSchedule,
@@ -152,7 +151,7 @@ const BlueprintsGrid = (props) => {
             minWidth: '830px'
           }}
         >
-          {(activeFilterStatus ? filteredBlueprints : blueprints).map((b, bIdx) => (
+          {(blueprints).map((b, bIdx) => (
             <div key={`blueprint-row-key-${bIdx}`}>
               <div
                 style={{
@@ -481,7 +480,7 @@ const BlueprintsGrid = (props) => {
               </Collapse>
             </div>
           ))}
-          {(activeFilterStatus ? filteredBlueprints.length === 0 : blueprints.length === 0) && (
+          {(blueprints.length === 0) && (
             <div style={{ padding: '12px' }}>
               <h3 style={{
                 fontWeight: 800,
diff --git a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
index 197b2050..bba7e3cc 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
@@ -387,8 +387,9 @@ const DataTransformations = (props) => {
                               <Tooltip
                                 position={Position.TOP}
                                 intent={Intent.PRIMARY}
-                                content={'Close Editor to Continue'}>
-                                  <Spinner size={12} />
+                                content='Close Editor to Continue'
+                              >
+                                <Spinner size={12} />
                               </Tooltip>
                             )}
                           />
diff --git a/config-ui/src/hooks/useConnectionManager.jsx b/config-ui/src/hooks/useConnectionManager.jsx
index 8ed2f3bd..cfeea122 100644
--- a/config-ui/src/hooks/useConnectionManager.jsx
+++ b/config-ui/src/hooks/useConnectionManager.jsx
@@ -588,7 +588,7 @@ function useConnectionManager (
           setPassword(activeConnection.password)
           setProxy(activeConnection.Proxy || activeConnection.proxy)
           setRateLimit(activeConnection.rateLimitPerHour)
-        break
+          break
       }
       ToastNotification.clear()
       // ToastNotification.show({ message: `Fetched settings for ${activeConnection.name}.`, intent: 'success', icon: 'small-tick' })
diff --git a/config-ui/src/hooks/usePaginator.jsx b/config-ui/src/hooks/usePaginator.jsx
new file mode 100644
index 00000000..19a97e0d
--- /dev/null
+++ b/config-ui/src/hooks/usePaginator.jsx
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
+import {
+  Popover,
+  Menu,
+  MenuItem,
+  Button
+} from '@blueprintjs/core'
+
+import { integrationsData } from '@/data/integrations'
+
+const PagingOptionsMenu = (props) => {
+  const { pageOptions = [10, 25, 50, 75, 100], perPage = 10, setPerPage = (page) => undefined } = props
+  return (
+    <Menu>
+      {pageOptions && pageOptions.map(pageOption => (
+        <MenuItem
+          key={pageOption}
+          active={perPage === pageOption}
+          icon='key-option'
+          text={`${pageOption} Records`}
+          onClick={() => setPerPage(pageOption)}
+        />))}
+    </Menu>
+  )
+}
+
+const Controls = (props) => {
+  const {
+    enabled = true,
+    pagingOptionsMenu,
+    currentPage,
+    perPage = 10,
+    maxPage,
+    onPrevPage = () => {},
+    onNextPage = () => {},
+    isLoading = false,
+  } = props
+
+  return (
+    <div
+      className='pagination-controls'
+      style={{ display: 'flex', whiteSpace: 'nowrap' }}
+    >
+      <Popover placement='bottom'>
+        <Button
+          className='btn-select-page-size'
+          style={{ whiteSpace: 'nowrap' }}
+          icon='numbered-list'
+          text={`Rows: ${perPage}`}
+          disabled={isLoading}
+          outlined
+          minimal
+        />
+        <>{pagingOptionsMenu}</>
+      </Popover>
+      <Button
+        onClick={onPrevPage}
+        className='pagination-btn btn-prev-page'
+        icon='step-backward'
+        small
+        text='PREV'
+        style={{ marginLeft: '5px', marginRight: '5px', whiteSpace: 'nowrap' }}
+        disabled={currentPage === 1 || isLoading}
+      />
+      <Button
+        style={{ whiteSpace: 'nowrap' }}
+        disabled={currentPage === maxPage || isLoading}
+        onClick={onNextPage}
+        className='pagination-btn btn-next-page'
+        rightIcon='step-forward'
+        text='NEXT'
+        small
+      />
+    </div>
+  )
+}
+
+function usePaginator (initialLoadingState = false) {
+  // const [integrations, setIntegrations] = useState(integrationsData)
+
+  const [data, setData] = useState([])
+
+  // filter related
+  const [filterParams, setFilterParams] = useState({})
+  const [filterFunc, setFilterFunc] = useState(() => (params, item) => true)
+  const filteredData = useMemo(() => {
+    console.log('>> SET FILTER DATA BY', filterParams, data)
+    const filteredData = []
+    for (const item of data) {
+      if (filterFunc(filterParams, item)) {
+        filteredData.push(item)
+      }
+    }
+    return filteredData
+  }, [data, filterParams, filterFunc])
+
+  // page related
+  const [pagedData, setPagedData] = useState([])
+  const [pageOptions, setPageOptions] = useState([5, 25, 50, 75, 100])
+  const [currentPage, setCurrentPage] = useState(1)
+  const [perPage, setPerPage] = useState(pageOptions[1])
+  const maxPage = useMemo(() => Math.max(1, Math.ceil(filteredData.length / perPage)), [filteredData, perPage])
+
+  // others
+  const [isLoading, setIsLoading] = useState(initialLoadingState || false)
+  const [isProcessing, setIsProcessing] = useState(false)
+  const [refresh, setRefresh] = useState(false)
+
+  const setDataWithDefault = useCallback((data) => {
+    console.log('>> SET ALL DATA...', data)
+    setData(data || [])
+  }, [setData])
+
+  const goNextPage = useCallback(() => {
+    console.log('>>> PAGINATOR: GO NEXT PAGE ...')
+    setCurrentPage(currentPage => Math.min(maxPage, currentPage + 1))
+    // setRefresh((r) => !r)
+    console.log('>>>> NEXT PAGE', currentPage)
+  }, [maxPage, currentPage])
+
+  const goPrevPage = useCallback(() => {
+    console.log('>>> PAGINATOR: GO PREV PAGE ...')
+    setCurrentPage(currentPage => Math.max(1, currentPage - 1))
+    // setRefresh((r) => !r)
+    console.log('>>>> PREV PAGE', currentPage)
+  }, [currentPage])
+
+  const changePerPage = useCallback((perPage) => {
+    setPerPage(perPage)
+    setCurrentPage(1)
+  }, [setPerPage])
+
+  const resetPage = useCallback(() => {
+    setCurrentPage(1)
+  }, [setCurrentPage])
+
+  const paginateData = useCallback(() => {
+    console.log('>> FILTERED DATA...', filteredData)
+    const sliceBegin = (currentPage - 1) * perPage
+    const sliceEnd = currentPage * perPage
+    setPagedData(filteredData.slice(sliceBegin, sliceEnd))
+    console.log('>> PAGED DATA = ', filteredData.slice(sliceBegin, sliceEnd))
+  }, [filteredData, perPage, currentPage, setPagedData])
+
+  useEffect(() => {
+    paginateData()
+  }, [/* refresh, */perPage, filteredData, currentPage, paginateData])
+
+  useEffect(() => {
+    setCurrentPage(currentPage => Math.min(maxPage, currentPage))
+  }, [maxPage, setCurrentPage])
+
+  const renderControlsComponent = useCallback(() => {
+    return (
+      <Controls
+        currentPage={currentPage}
+        onNextPage={goNextPage}
+        onPrevPage={goPrevPage}
+        maxPage={maxPage}
+        perPage={perPage}
+        isLoading={isLoading}
+        pagingOptionsMenu={
+          <PagingOptionsMenu pageOptions={pageOptions} perPage={perPage} setPerPage={changePerPage} />
+        }
+      />
+    )
+  }, [currentPage, maxPage, perPage, pageOptions, isLoading, goNextPage, goPrevPage, changePerPage])
+
+  return {
+    goNextPage,
+    goPrevPage,
+    resetPage,
+    setPageOptions,
+    renderControlsComponent,
+    isLoading,
+    isProcessing,
+    data,
+    filteredData,
+    pagedData,
+    perPage,
+    maxPage,
+    setData: setDataWithDefault,
+    setPagedData,
+    setFilterParams,
+    setFilterFunc,
+    // setMaxPage,
+    setIsLoading,
+    setIsProcessing,
+  }
+}
+
+export default usePaginator
diff --git a/config-ui/src/pages/blueprints/index.jsx b/config-ui/src/pages/blueprints/index.jsx
index 95d7c463..288450ba 100644
--- a/config-ui/src/pages/blueprints/index.jsx
+++ b/config-ui/src/pages/blueprints/index.jsx
@@ -32,6 +32,7 @@ import {
 import usePipelineManager from '@/hooks/usePipelineManager'
 import useBlueprintManager from '@/hooks/useBlueprintManager'
 import useBlueprintValidation from '@/hooks/useBlueprintValidation'
+import usePaginator from '@/hooks/usePaginator'
 import Nav from '@/components/Nav'
 import Sidebar from '@/components/Sidebar'
 import AppCrumbs from '@/components/Breadcrumbs'
@@ -89,6 +90,14 @@ const Blueprints = (props) => {
     detectPipelineProviders
   } = usePipelineManager()
 
+  const {
+    pagedData,
+    setFilterParams,
+    setFilterFunc,
+    setData: setPaginatorData,
+    renderControlsComponent: renderPagnationControls
+  } = usePaginator()
+
   const [expandDetails, setExpandDetails] = useState(false)
   const [activeBlueprint, setActiveBlueprint] = useState(null)
   const [draftBlueprint, setDraftBlueprint] = useState(null)
@@ -98,7 +107,6 @@ const Blueprints = (props) => {
   const [pipelineTemplates, setPipelineTemplates] = useState([])
   const [selectedPipelineTemplate, setSelectedPipelineTemplate] = useState()
 
-  const [filteredBlueprints, setFilteredBlueprints] = useState([])
   const [activeFilterStatus, setActiveFilterStatus] = useState()
 
   const [relatedPipelines, setRelatedPipelines] = useState([])
@@ -239,37 +247,27 @@ const Blueprints = (props) => {
   }, [fetchAllPipelines])
 
   useEffect(() => {
-    if (activeFilterStatus) {
+    setFilterFunc(() => (activeFilterStatus, blueprint) => {
       switch (activeFilterStatus) {
         case 'hourly':
-          setFilteredBlueprints(blueprints.filter(b => b.cronConfig === getCronPreset(activeFilterStatus).cronConfig))
-          break
         case 'daily':
-          setFilteredBlueprints(blueprints.filter(b => b.cronConfig === getCronPreset(activeFilterStatus).cronConfig))
-          break
         case 'weekly':
-          setFilteredBlueprints(blueprints.filter(b => b.cronConfig === getCronPreset(activeFilterStatus).cronConfig))
-          break
         case 'monthly':
-          setFilteredBlueprints(blueprints.filter(b => b.cronConfig === getCronPreset(activeFilterStatus).cronConfig))
-          break
+          console.log(blueprint.cronConfig === getCronPreset(activeFilterStatus).cronConfig)
+          return blueprint.cronConfig === getCronPreset(activeFilterStatus).cronConfig
         case 'manual':
-          setFilteredBlueprints(blueprints.filter(b => b.isManual))
-          break
+          return blueprint.isManual
         case 'custom':
-          setFilteredBlueprints(blueprints.filter(
-            b => b.cronConfig !== getCronPreset('hourly').cronConfig &&
-            b.cronConfig !== getCronPreset('daily').cronConfig &&
-            b.cronConfig !== getCronPreset('weekly').cronConfig &&
-            b.cronConfig !== getCronPreset('monthly').cronConfig
-          ))
-          break
+          return blueprint.cronConfig !== getCronPreset('hourly').cronConfig &&
+            blueprint.cronConfig !== getCronPreset('daily').cronConfig &&
+            blueprint.cronConfig !== getCronPreset('weekly').cronConfig &&
+            blueprint.cronConfig !== getCronPreset('monthly').cronConfig
         default:
-        // NO FILTERS
-          break
+          return true
       }
-    }
-  }, [activeFilterStatus, blueprints, getCronPreset])
+    })
+    setFilterParams(activeFilterStatus)
+  }, [activeFilterStatus, setFilterParams, getCronPreset])
 
   // useEffect(() => {
   //   if (Array.isArray(tasks)) {
@@ -282,6 +280,10 @@ const Blueprints = (props) => {
     console.log('>>>> DETECTED PROVIDERS TASKS....', detectedProviderTasks)
   }, [detectedProviderTasks])
 
+  useEffect(() => {
+    setPaginatorData(blueprints)
+  }, [blueprints])
+
   return (
     <>
       <div className='container'>
@@ -316,9 +318,8 @@ const Blueprints = (props) => {
             {(!isFetchingBlueprints) && blueprints.length > 0 && (
               <>
                 <BlueprintsGrid
-                  blueprints={blueprints}
+                  blueprints={pagedData}
                   pipelines={relatedPipelines}
-                  filteredBlueprints={filteredBlueprints}
                   activeFilterStatus={activeFilterStatus}
                   onFilter={setActiveFilterStatus}
                   activeBlueprint={activeBlueprint}
@@ -381,6 +382,7 @@ const Blueprints = (props) => {
                 />
               </Card>
             )}
+            <div style={{ alignSelf: 'flex-end', padding: '10px' }}>{renderPagnationControls()}</div>
           </main>
         </Content>
       </div>