You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2022/11/16 06:53:05 UTC

[incubator-devlake] branch main updated: feat(config-ui): jira board select to support miller-columns component (#3744)

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

likyh 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 9909219ac feat(config-ui): jira board select to support miller-columns component (#3744)
9909219ac is described below

commit 9909219ac643536b420e8b0e65143ae9c1b52d32
Author: 青湛 <0x...@gmail.com>
AuthorDate: Wed Nov 16 14:53:02 2022 +0800

    feat(config-ui): jira board select to support miller-columns component (#3744)
    
    * refactor(config-ui): move file components/gitlab/request to components/utils/request
    
    * fix(config-ui): selectedItems not required in miller-columns component
    
    * feat(config-ui): support scroll load on miller-columns component
    
    * feat(config-ui): jira board select to support miller-columns component
---
 .../blueprints/create-workflow/DataScopes.jsx      | 35 +++++++----
 .../src/components/gitlab/miller-columns/index.tsx | 40 +-----------
 .../miller-columns/use-gitlab-miller-columns.ts    |  2 +-
 .../use-gitlab-project-selector.ts                 |  3 +-
 .../miller-columns/styled.ts => jira/config.ts}    |  7 +--
 .../miller-columns/styled.ts => jira/index.ts}     |  6 +-
 .../src/components/jira/miller-columns/index.tsx   | 61 +++++++++++++++++++
 .../jira/miller-columns/use-jira-miller-columns.ts | 71 ++++++++++++++++++++++
 .../miller-columns/components/column/column.tsx    | 57 +++++++++++++++--
 .../miller-columns/components/column/styled.ts     |  4 ++
 .../miller-columns/hooks/use-miller-columns.ts     |  2 +-
 .../components/miller-columns/miller-columns.tsx   | 48 +++++++--------
 .../src/components/{gitlab => utils}/request.ts    |  0
 13 files changed, 242 insertions(+), 94 deletions(-)

diff --git a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
index 9b3a3593c..6ee5d3124 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
@@ -32,6 +32,8 @@ import DataDomainsSelector from '@/components/blueprints/DataDomainsSelector'
 import NoData from '@/components/NoData'
 import { GitLabMillerColumns, GitLabProjectSelector } from '@/components/gitlab'
 import GitlabProject from '@/models/GitlabProject'
+import { JIRAMillerColumns } from '@/components/jira'
+import JiraBoard from '@/models/JiraBoard'
 import GitHubProject from '@/models/GithubProject'
 import JenkinsJobsSelector from '@/components/blueprints/JenkinsJobsSelector'
 
@@ -190,17 +192,28 @@ const DataScopes = (props) => {
                     <>
                       <h4>Boards *</h4>
                       <p>Select the boards you would like to sync.</p>
-                      <BoardsSelector
-                        items={jiraBoards}
-                        selectedItems={selectedScopeEntities}
-                        onQueryChange={setBoardSearch}
-                        onItemSelect={setScopeEntities}
-                        onClear={setScopeEntities}
-                        onRemove={setScopeEntities}
-                        disabled={isSaving}
-                        configuredConnection={configuredConnection}
-                        isLoading={isFetching}
-                      />
+                      {!activeStep ? (
+                        <BoardsSelector
+                          items={jiraBoards}
+                          selectedItems={selectedScopeEntities}
+                          onQueryChange={setBoardSearch}
+                          onItemSelect={setScopeEntities}
+                          onClear={setScopeEntities}
+                          onRemove={setScopeEntities}
+                          disabled={isSaving}
+                          configuredConnection={configuredConnection}
+                          isLoading={isFetching}
+                        />
+                      ) : (
+                        <JIRAMillerColumns
+                          connectionId={configuredConnection.connectionId}
+                          onChangeItems={(items) =>
+                            setScopeEntities(
+                              items.map((it) => new JiraBoard(it))
+                            )
+                          }
+                        />
+                      )}
                     </>
                   )}
 
diff --git a/config-ui/src/components/gitlab/miller-columns/index.tsx b/config-ui/src/components/gitlab/miller-columns/index.tsx
index 648d4602a..5db575c50 100644
--- a/config-ui/src/components/gitlab/miller-columns/index.tsx
+++ b/config-ui/src/components/gitlab/miller-columns/index.tsx
@@ -18,18 +18,13 @@
 
 import React, { useEffect, useState } from 'react'
 
-import type { ColumnType, ItemType } from '@/components/miller-columns'
-import {
-  MillerColumns,
-  ItemStatusEnum,
-  ItemTypeEnum
-} from '@/components/miller-columns'
+import type { ItemType } from '@/components/miller-columns'
+import { MillerColumns, ItemTypeEnum } from '@/components/miller-columns'
 
 import {
   useGitLabMillerColumns,
   UseGitLabMillerColumnsProps
 } from './use-gitlab-miller-columns'
-import * as S from './styled'
 
 interface Props extends UseGitLabMillerColumnsProps {
   disabledItemIds?: Array<number>
@@ -63,21 +58,6 @@ export const GitLabMillerColumns = ({
     onChangeItems(curItems)
   }, [seletedIds])
 
-  const renderColumnBottom = ({
-    isLoading,
-    isEmpty
-  }: {
-    isLoading: boolean
-    isEmpty: boolean
-  }) => {
-    switch (true) {
-      case isLoading:
-        return <S.Placeholder>Loading...</S.Placeholder>
-      case isEmpty:
-        return <S.Placeholder>No Data.</S.Placeholder>
-    }
-  }
-
   return (
     <MillerColumns
       height={300}
@@ -87,22 +67,6 @@ export const GitLabMillerColumns = ({
       selectedItemIds={seletedIds}
       onSelectedItemIds={setSelectedIds}
       onExpandItem={onExpandItem}
-      renderColumnBottom={(col: ColumnType) => {
-        if (!col.parentId) {
-          return renderColumnBottom({
-            isLoading: !itemTree.root,
-            isEmpty: !itemTree.root || !itemTree.root.items.length
-          })
-        } else {
-          return renderColumnBottom({
-            isLoading:
-              !itemTree[col.parentId] ||
-              itemTree[col.parentId].status === ItemStatusEnum.PENDING,
-            isEmpty:
-              !itemTree[col.parentId] || !itemTree[col.parentId].items.length
-          })
-        }
-      }}
     />
   )
 }
diff --git a/config-ui/src/components/gitlab/miller-columns/use-gitlab-miller-columns.ts b/config-ui/src/components/gitlab/miller-columns/use-gitlab-miller-columns.ts
index 1874f73c1..ea8c1dabd 100644
--- a/config-ui/src/components/gitlab/miller-columns/use-gitlab-miller-columns.ts
+++ b/config-ui/src/components/gitlab/miller-columns/use-gitlab-miller-columns.ts
@@ -20,8 +20,8 @@ import { useMemo, useCallback } from 'react'
 
 import type { ItemType } from '@/components/miller-columns'
 import { useLoadItems, ItemTypeEnum } from '@/components/miller-columns'
+import request from '@/components/utils/request'
 
-import request from '../request'
 import { getGitLabProxyApiPrefix } from '../config'
 
 export interface UseGitLabMillerColumnsProps {
diff --git a/config-ui/src/components/gitlab/project-selector/use-gitlab-project-selector.ts b/config-ui/src/components/gitlab/project-selector/use-gitlab-project-selector.ts
index 66491d534..527ccd367 100644
--- a/config-ui/src/components/gitlab/project-selector/use-gitlab-project-selector.ts
+++ b/config-ui/src/components/gitlab/project-selector/use-gitlab-project-selector.ts
@@ -18,7 +18,8 @@
 
 import { useState, useEffect, useMemo } from 'react'
 
-import request from '../request'
+import request from '@/components/utils/request'
+
 import { getGitLabProxyApiPrefix } from '../config'
 
 export type ItemType = {
diff --git a/config-ui/src/components/gitlab/miller-columns/styled.ts b/config-ui/src/components/jira/config.ts
similarity index 87%
copy from config-ui/src/components/gitlab/miller-columns/styled.ts
copy to config-ui/src/components/jira/config.ts
index 6b7cbc9e7..efca6cf86 100644
--- a/config-ui/src/components/gitlab/miller-columns/styled.ts
+++ b/config-ui/src/components/jira/config.ts
@@ -16,8 +16,5 @@
  *
  */
 
-import styled from '@emotion/styled'
-
-export const Placeholder = styled.div`
-  padding: 4px 12px;
-`
+export const getJIRAApiPrefix = (connectionId: string) =>
+  `/plugins/jira/connections/${connectionId}/proxy/rest`
diff --git a/config-ui/src/components/gitlab/miller-columns/styled.ts b/config-ui/src/components/jira/index.ts
similarity index 88%
rename from config-ui/src/components/gitlab/miller-columns/styled.ts
rename to config-ui/src/components/jira/index.ts
index 6b7cbc9e7..1c06557ce 100644
--- a/config-ui/src/components/gitlab/miller-columns/styled.ts
+++ b/config-ui/src/components/jira/index.ts
@@ -16,8 +16,4 @@
  *
  */
 
-import styled from '@emotion/styled'
-
-export const Placeholder = styled.div`
-  padding: 4px 12px;
-`
+export * from './miller-columns'
diff --git a/config-ui/src/components/jira/miller-columns/index.tsx b/config-ui/src/components/jira/miller-columns/index.tsx
new file mode 100644
index 000000000..dc40bc47e
--- /dev/null
+++ b/config-ui/src/components/jira/miller-columns/index.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 } from 'react'
+
+import type { ItemType } from '@/components/miller-columns'
+import { MillerColumns } from '@/components/miller-columns'
+
+import {
+  useJIRAMillerColumns,
+  UseJIRAMillerColumnsProps
+} from './use-jira-miller-columns'
+
+interface Props extends UseJIRAMillerColumnsProps {
+  onChangeItems: (items: Array<Pick<ItemType, 'id' | 'title'>>) => void
+}
+
+export const JIRAMillerColumns = ({ connectionId, onChangeItems }: Props) => {
+  const [seletedIds, setSelectedIds] = useState<Array<ItemType['id']>>([])
+
+  const { items, hasMore, onScroll } = useJIRAMillerColumns({ connectionId })
+
+  useEffect(() => {
+    onChangeItems(
+      items
+        .filter((it) => seletedIds.includes(it.id))
+        .map((it) => ({
+          id: it.id,
+          title: it.title
+        }))
+    )
+  }, [seletedIds])
+
+  return (
+    <MillerColumns
+      height={300}
+      items={items}
+      selectedItemIds={seletedIds}
+      onSelectedItemIds={setSelectedIds}
+      scrollProps={{
+        hasMore,
+        onScroll
+      }}
+    />
+  )
+}
diff --git a/config-ui/src/components/jira/miller-columns/use-jira-miller-columns.ts b/config-ui/src/components/jira/miller-columns/use-jira-miller-columns.ts
new file mode 100644
index 000000000..212f3d60d
--- /dev/null
+++ b/config-ui/src/components/jira/miller-columns/use-jira-miller-columns.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 { useState, useEffect, useMemo } from 'react'
+
+import type { ItemType } from '@/components/miller-columns'
+import { ItemTypeEnum, ItemStatusEnum } from '@/components/miller-columns'
+
+import request from '@/components/utils/request'
+
+import { getJIRAApiPrefix } from '../config'
+
+export interface UseJIRAMillerColumnsProps {
+  connectionId: string
+}
+
+export const useJIRAMillerColumns = ({
+  connectionId
+}: UseJIRAMillerColumnsProps) => {
+  const [items, setItems] = useState<Array<ItemType>>([])
+  const [hasMore, setHasMore] = useState(true)
+  const [page, setPage] = useState(1)
+  const [pageSize] = useState(50)
+
+  const prefix = useMemo(() => getJIRAApiPrefix(connectionId), [connectionId])
+
+  const updateItems = (arr: Array<{ id: number; name: string }>) =>
+    arr.map((it) => ({
+      id: it.id,
+      title: it.name,
+      type: ItemTypeEnum.LEAF,
+      status: ItemStatusEnum.READY,
+      items: []
+    }))
+
+  useEffect(() => {
+    ;(async () => {
+      const res = await request(`${prefix}/agile/1.0/board`, {
+        data: { startAt: (page - 1) * pageSize, maxResults: pageSize }
+      })
+      setHasMore(!res.isLast)
+      setItems([...items, ...updateItems(res.values)])
+    })()
+  }, [prefix, page, pageSize])
+
+  return useMemo(
+    () => ({
+      items,
+      hasMore,
+      onScroll() {
+        setPage(page + 1)
+      }
+    }),
+    [items, hasMore]
+  )
+}
diff --git a/config-ui/src/components/miller-columns/components/column/column.tsx b/config-ui/src/components/miller-columns/components/column/column.tsx
index 967cf005d..269fc2328 100644
--- a/config-ui/src/components/miller-columns/components/column/column.tsx
+++ b/config-ui/src/components/miller-columns/components/column/column.tsx
@@ -16,26 +16,71 @@
  *
  */
 
-import React from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
+import InfiniteScroll from 'react-infinite-scroll-component'
 
 import type { ItemType } from '../../types'
 
 import * as S from './styled'
 
-interface Props {
+export interface ColumnsProps {
   items: Array<ItemType>
   renderItem: (item: ItemType) => React.ReactNode
   height?: number
   title?: string | React.ReactNode
   bottom?: React.ReactNode
+  scrollProps?: {
+    hasMore: boolean
+    onScroll: () => void
+    renderLoader?: () => React.ReactNode
+    renderBottom?: () => React.ReactNode
+  }
 }
 
-export const Column = ({ items, renderItem, height, title, bottom }: Props) => {
+export const Column = ({
+  items,
+  renderItem,
+  height,
+  title,
+  scrollProps
+}: ColumnsProps) => {
+  const [hasMore, setHasMore] = useState(true)
+
+  useEffect(() => {
+    if (scrollProps) {
+      setHasMore(scrollProps.hasMore)
+    }
+  }, [scrollProps])
+
+  const handleNext = useCallback(() => {
+    if (scrollProps) {
+      scrollProps.onScroll()
+    } else {
+      setHasMore(false)
+    }
+  }, [scrollProps])
+
+  const loader = scrollProps?.renderLoader?.() ?? (
+    <S.StatusWrapper>Loading...</S.StatusWrapper>
+  )
+
+  const bottom = scrollProps?.renderBottom?.() ?? (
+    <S.StatusWrapper>All Data Loaded.</S.StatusWrapper>
+  )
+
   return (
-    <S.Container height={height}>
+    <S.Container id='miller-columns-column-container' height={height}>
       {title && <div className='title'>{title}</div>}
-      {items.map((it) => renderItem(it))}
-      {bottom}
+      <InfiniteScroll
+        dataLength={items.length}
+        hasMore={hasMore}
+        next={handleNext}
+        loader={loader}
+        scrollableTarget='miller-columns-column-container'
+        endMessage={bottom}
+      >
+        {items.map((it) => renderItem(it))}
+      </InfiniteScroll>
     </S.Container>
   )
 }
diff --git a/config-ui/src/components/miller-columns/components/column/styled.ts b/config-ui/src/components/miller-columns/components/column/styled.ts
index b91d76491..e522a0693 100644
--- a/config-ui/src/components/miller-columns/components/column/styled.ts
+++ b/config-ui/src/components/miller-columns/components/column/styled.ts
@@ -38,3 +38,7 @@ export const Container = styled.div<{ height?: number }>`
     color: #292b3f;
   }
 `
+
+export const StatusWrapper = styled.div`
+  padding: 4px 12px;
+`
diff --git a/config-ui/src/components/miller-columns/hooks/use-miller-columns.ts b/config-ui/src/components/miller-columns/hooks/use-miller-columns.ts
index bc57cf79c..8f0f6b8fb 100644
--- a/config-ui/src/components/miller-columns/hooks/use-miller-columns.ts
+++ b/config-ui/src/components/miller-columns/hooks/use-miller-columns.ts
@@ -30,7 +30,7 @@ export interface UseMillerColumnsProps {
   activeItemId?: ItemType['id']
   onActiveItemId?: (id: ItemType['id']) => void
   disabledItemIds?: Array<ItemType['id']>
-  selectedItemIds: Array<ItemType['id']>
+  selectedItemIds?: Array<ItemType['id']>
   onSelectedItemIds?: (ids: Array<ItemType['id']>) => void
   onExpandItem?: (item: ItemType) => void
 }
diff --git a/config-ui/src/components/miller-columns/miller-columns.tsx b/config-ui/src/components/miller-columns/miller-columns.tsx
index 7aadb9a95..b07f2d621 100644
--- a/config-ui/src/components/miller-columns/miller-columns.tsx
+++ b/config-ui/src/components/miller-columns/miller-columns.tsx
@@ -19,21 +19,20 @@
 import React from 'react'
 
 import { useMillerColumns, UseMillerColumnsProps } from './hooks'
-import { Column, Item } from './components'
+import { Column, ColumnsProps, Item } from './components'
 
-import { ColumnType } from './types'
 import * as S from './styled'
 
 interface Props extends UseMillerColumnsProps {
   height?: number
   firstColumnTitle?: React.ReactNode
-  renderColumnBottom?: (col: ColumnType) => React.ReactNode
+  scrollProps?: ColumnsProps['scrollProps']
 }
 
 export const MillerColumns = ({
   firstColumnTitle,
   height,
-  renderColumnBottom,
+  scrollProps,
   ...props
 }: Props) => {
   const { columns, getStatus, getChekecdStatus, onExpandItem, onSelectItem } =
@@ -41,28 +40,25 @@ export const MillerColumns = ({
 
   return (
     <S.Container>
-      {columns.map((col, i) => {
-        const bottom = renderColumnBottom?.(col)
-        return (
-          <Column
-            key={col.parentId}
-            items={col.items}
-            renderItem={(item) => (
-              <Item
-                key={item.id}
-                item={item}
-                status={getStatus(item, col)}
-                checkStatus={getChekecdStatus(item)}
-                onExpandItem={onExpandItem}
-                onSelectItem={onSelectItem}
-              />
-            )}
-            height={height}
-            title={i === 0 && firstColumnTitle}
-            bottom={bottom}
-          />
-        )
-      })}
+      {columns.map((col, i) => (
+        <Column
+          key={col.parentId}
+          items={col.items}
+          renderItem={(item) => (
+            <Item
+              key={item.id}
+              item={item}
+              status={getStatus(item, col)}
+              checkStatus={getChekecdStatus(item)}
+              onExpandItem={onExpandItem}
+              onSelectItem={onSelectItem}
+            />
+          )}
+          height={height}
+          title={i === 0 && firstColumnTitle}
+          scrollProps={scrollProps}
+        />
+      ))}
     </S.Container>
   )
 }
diff --git a/config-ui/src/components/gitlab/request.ts b/config-ui/src/components/utils/request.ts
similarity index 100%
rename from config-ui/src/components/gitlab/request.ts
rename to config-ui/src/components/utils/request.ts