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/18 01:54:55 UTC
[incubator-devlake] branch main updated: feat(config-ui): github repo choose to support miller-columns component (#3750)
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 679c83dec feat(config-ui): github repo choose to support miller-columns component (#3750)
679c83dec is described below
commit 679c83deca8fbca8f35c3ebadada6d49925e61d5
Author: 青湛 <0x...@gmail.com>
AuthorDate: Fri Nov 18 09:54:51 2022 +0800
feat(config-ui): github repo choose to support miller-columns component (#3750)
* fix(config-ui): miss depends in miller-columns component
* feat(config-ui): support to show column count in miller-columns component
* refactor(config-ui): support to flat data in miller-columns component
* feat(config-ui): add test for miller-columns component
* feat(config-ui): support to scroll load data in gitlab miller-columns
* fix(config-ui): miss field in jira miller-columns component
* feat(config-ui): github repo choose to support miller-columns component
* fix(config-ui): github cannot choose repo on the miller-columns
* feat(config-ui): github miller-columns component to support scroll load
* fix(config-ui): gitlab miller-columns judgment condition error
* chore(config-ui): review opinion updated
* fix(config-ui): miss field in github miller-columns component
* fix(config-ui): github repo select all will an additional content
---
config-ui/package-lock.json | 13 +
config-ui/package.json | 1 +
.../blueprints/create-workflow/DataScopes.jsx | 34 ++-
.../{miller-columns/index.ts => github/config.ts} | 6 +-
.../components/{miller-columns => github}/index.ts | 3 -
.../{jira => github}/miller-columns/index.tsx | 37 ++-
.../miller-columns/use-github-miller-columns.ts | 234 ++++++++++++++++
.../src/components/gitlab/miller-columns/index.tsx | 21 +-
.../miller-columns/use-gitlab-miller-columns.ts | 295 ++++++++++++++++++---
.../src/components/jira/miller-columns/index.tsx | 1 +
.../jira/miller-columns/use-jira-miller-columns.ts | 5 +-
.../miller-columns/components/column/column.tsx | 17 +-
.../miller-columns/components/column/styled.ts | 8 +-
.../src/components/miller-columns/hooks/index.ts | 3 -
.../components/miller-columns/hooks/use-columns.ts | 33 ++-
.../styled.ts => hooks/use-convert-items.ts} | 44 +--
.../miller-columns/hooks/use-item-map.ts | 92 ++++---
.../miller-columns/hooks/use-load-items.ts | 100 -------
.../miller-columns/hooks/use-miller-columns.ts | 20 +-
config-ui/src/components/miller-columns/index.ts | 3 +-
.../components/miller-columns/miller-columns.tsx | 18 +-
.../column/styled.ts => test/index.tsx} | 47 ++--
.../styled.ts => test/mock/flat-data/first.ts} | 63 +++--
.../{ => test/mock/flat-data}/index.ts | 7 +-
.../{index.ts => test/mock/flat-data/second.ts} | 31 ++-
.../{index.ts => test/mock/flat-data/third.ts} | 26 +-
.../miller-columns/{ => test/mock}/index.ts | 5 +-
.../src/components/miller-columns/test/use-test.ts | 65 +++++
config-ui/src/components/miller-columns/types.ts | 35 +--
config-ui/src/models/GithubProject.js | 2 +
30 files changed, 906 insertions(+), 363 deletions(-)
diff --git a/config-ui/package-lock.json b/config-ui/package-lock.json
index 3e867995e..73ddcbb82 100644
--- a/config-ui/package-lock.json
+++ b/config-ui/package-lock.json
@@ -15852,6 +15852,14 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
+ "react-infinite-scroll-component": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
+ "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
+ "requires": {
+ "throttle-debounce": "^2.1.0"
+ }
+ },
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -18281,6 +18289,11 @@
"integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==",
"dev": true
},
+ "throttle-debounce": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
+ "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ=="
+ },
"throttleit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
diff --git a/config-ui/package.json b/config-ui/package.json
index 4d08bda69..f0425b405 100644
--- a/config-ui/package.json
+++ b/config-ui/package.json
@@ -32,6 +32,7 @@
"react": "17.0.2",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "17.0.2",
+ "react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^5.3.0",
"react-transition-group": "^2.9.0",
"typeface-montserrat": "^1.1.13"
diff --git a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
index 6ee5d3124..7fb6f901e 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
@@ -34,6 +34,7 @@ import { GitLabMillerColumns, GitLabProjectSelector } from '@/components/gitlab'
import GitlabProject from '@/models/GitlabProject'
import { JIRAMillerColumns } from '@/components/jira'
import JiraBoard from '@/models/JiraBoard'
+import { GitHubMillerColumns } from '@/components/github'
import GitHubProject from '@/models/GithubProject'
import JenkinsJobsSelector from '@/components/blueprints/JenkinsJobsSelector'
@@ -136,18 +137,45 @@ const DataScopes = (props) => {
configuredConnection.provider
) && (
<>
- <h4>Projects *</h4>
- <p>Enter the project names you would like to sync.</p>
+ <h4>Repositories *</h4>
+ {!!activeStep && (
+ <>
+ <p>Select the repositories you would like to sync.</p>
+ <GitHubMillerColumns
+ connectionId={configuredConnection.connectionId}
+ onChangeItems={(items) =>
+ setScopeEntities([
+ ...scopeEntitiesGroup[
+ configuredConnection.id
+ ].filter((it) => it.type !== 'miller-columns'),
+ ...items.map((it) => new GitHubProject(it))
+ ])
+ }
+ />
+ </>
+ )}
+ <div style={{ margin: '16px 0 8px' }}>
+ Add repositories outside of your organizations
+ </div>
+ <p>
+ Enter the repositories using the format “owner/repo” and
+ separate multiple repos with a comma.
+ </p>
<TagInput
id='project-id'
disabled={isRunning}
placeholder='username/repo, username/another-repo'
values={
- selectedScopeEntities?.map((p) => p.value) || []
+ selectedScopeEntities
+ ?.filter((it) => it.type !== 'miller-columns')
+ .map((p) => p.value) || []
}
fill={true}
onChange={(values) =>
setScopeEntities([
+ ...scopeEntitiesGroup[
+ configuredConnection.id
+ ].filter((it) => it.type === 'miller-columns'),
...values.map(
(v, vIdx) =>
new GitHubProject({
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/github/config.ts
similarity index 86%
copy from config-ui/src/components/miller-columns/index.ts
copy to config-ui/src/components/github/config.ts
index 3db209c1a..f5381cd4a 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/github/config.ts
@@ -16,7 +16,5 @@
*
*/
-export * from './miller-columns'
-export * from './components'
-export * from './hooks'
-export * from './types'
+export const getGitHubProxyApiPrefix = (connectionId: string) =>
+ `/plugins/github/connections/${connectionId}/proxy/rest`
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/github/index.ts
similarity index 91%
copy from config-ui/src/components/miller-columns/index.ts
copy to config-ui/src/components/github/index.ts
index 3db209c1a..1c06557ce 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/github/index.ts
@@ -17,6 +17,3 @@
*/
export * from './miller-columns'
-export * from './components'
-export * from './hooks'
-export * from './types'
diff --git a/config-ui/src/components/jira/miller-columns/index.tsx b/config-ui/src/components/github/miller-columns/index.tsx
similarity index 61%
copy from config-ui/src/components/jira/miller-columns/index.tsx
copy to config-ui/src/components/github/miller-columns/index.tsx
index dc40bc47e..9690ce643 100644
--- a/config-ui/src/components/jira/miller-columns/index.tsx
+++ b/config-ui/src/components/github/miller-columns/index.tsx
@@ -18,40 +18,53 @@
import React, { useState, useEffect } from 'react'
-import type { ItemType } from '@/components/miller-columns'
+import { ItemType, ItemTypeEnum } from '@/components/miller-columns'
import { MillerColumns } from '@/components/miller-columns'
import {
- useJIRAMillerColumns,
- UseJIRAMillerColumnsProps
-} from './use-jira-miller-columns'
+ useGitHubMillerColumns,
+ UseGitHubMillerColumnsProps
+} from './use-github-miller-columns'
-interface Props extends UseJIRAMillerColumnsProps {
+interface Props extends UseGitHubMillerColumnsProps {
onChangeItems: (items: Array<Pick<ItemType, 'id' | 'title'>>) => void
}
-export const JIRAMillerColumns = ({ connectionId, onChangeItems }: Props) => {
+export const GitHubMillerColumns = ({ connectionId, onChangeItems }: Props) => {
const [seletedIds, setSelectedIds] = useState<Array<ItemType['id']>>([])
- const { items, hasMore, onScroll } = useJIRAMillerColumns({ connectionId })
+ const { items, onExpandItem, hasMore, onScroll } = useGitHubMillerColumns({
+ connectionId
+ })
useEffect(() => {
onChangeItems(
items
- .filter((it) => seletedIds.includes(it.id))
- .map((it) => ({
- id: it.id,
- title: it.title
- }))
+ .filter(
+ (it) => seletedIds.includes(it.id) && it.type !== ItemTypeEnum.BRANCH
+ )
+ .map((it: any) => {
+ return {
+ id: it.id,
+ title: `${it.owner}/${it.repo}`,
+ owner: it.owner,
+ repo: it.repo,
+ value: `${it.owner}/${it.repo}`,
+ type: 'miller-columns'
+ }
+ })
)
}, [seletedIds])
return (
<MillerColumns
height={300}
+ columnCount={2}
+ firstColumnTitle='Organizations/Owners'
items={items}
selectedItemIds={seletedIds}
onSelectedItemIds={setSelectedIds}
+ onExpandItem={onExpandItem}
scrollProps={{
hasMore,
onScroll
diff --git a/config-ui/src/components/github/miller-columns/use-github-miller-columns.ts b/config-ui/src/components/github/miller-columns/use-github-miller-columns.ts
new file mode 100644
index 000000000..daf224cc9
--- /dev/null
+++ b/config-ui/src/components/github/miller-columns/use-github-miller-columns.ts
@@ -0,0 +1,234 @@
+/*
+ * 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, useCallback } from 'react'
+
+import type { MillerColumnsItem, ItemType } from '@/components/miller-columns'
+import { ItemTypeEnum, ItemStatusEnum } from '@/components/miller-columns'
+import request from '@/components/utils/request'
+
+import { getGitHubProxyApiPrefix } from '../config'
+
+type MapType = Record<
+ MillerColumnsItem['id'],
+ {
+ page: number
+ pageSize: number
+ loaded: boolean
+ }
+>
+
+export interface UseGitHubMillerColumnsProps {
+ connectionId: string
+}
+
+export const useGitHubMillerColumns = ({
+ connectionId
+}: UseGitHubMillerColumnsProps) => {
+ const [user, setUser] = useState<any>({})
+ const [items, setItems] = useState<Array<MillerColumnsItem>>([])
+ const [hasMore, setHasMore] = useState(true)
+ const [map, setMap] = useState<MapType>({
+ root: {
+ page: 1,
+ pageSize: 30,
+ loaded: false
+ }
+ })
+
+ const prefix = useMemo(
+ () => getGitHubProxyApiPrefix(connectionId),
+ [connectionId]
+ )
+
+ const getUserOrgs = (username: string, page: number, pageSize: number) => {
+ return request(`${prefix}/users/${username}/orgs`, {
+ data: { page, per_page: pageSize }
+ })
+ }
+
+ const getUserRepos = (username: string, page: number, pageSize: number) => {
+ return request(`${prefix}/users/${username}/repos`, {
+ data: { page, per_page: pageSize }
+ })
+ }
+
+ const getOrgRepos = (org: string, page: number, pageSize: number) => {
+ return request(`${prefix}/orgs/${org}/repos`, {
+ data: { page, per_page: pageSize }
+ })
+ }
+
+ useEffect(() => {
+ ;(async () => {
+ const params = map.root
+
+ const user = await request(`${prefix}/user`)
+ const orgs = await getUserOrgs(user.login, params.page, params.pageSize)
+
+ if (orgs.length < params.pageSize) {
+ setHasMore(false)
+ params.loaded = true
+ } else {
+ params.page += 1
+ }
+
+ setUser(user)
+ setMap({
+ ...map,
+ root: params
+ })
+ setItems([
+ {
+ parentId: null,
+ id: user.login,
+ title: user.login,
+ type: ItemTypeEnum.BRANCH,
+ status: ItemStatusEnum.PENDING
+ },
+ ...orgs.map((it: any) => ({
+ parentId: null,
+ id: it.id,
+ title: it.login,
+ type: ItemTypeEnum.BRANCH,
+ status: ItemStatusEnum.PENDING
+ }))
+ ])
+ })()
+ }, [prefix])
+
+ const onExpandItem = useCallback(
+ async (item: ItemType) => {
+ if (map[item.id]) {
+ return
+ }
+
+ let params = {
+ page: 1,
+ pageSize: 30,
+ loaded: false
+ }
+
+ const isUser = item.id === user.login
+ const repos = isUser
+ ? await getUserRepos(item.id as string, params.page, params.pageSize)
+ : await getOrgRepos(item.title, params.page, params.pageSize)
+
+ if (repos.length < params.pageSize) {
+ params.loaded = true
+ } else {
+ params.page += 1
+ }
+
+ setMap({
+ ...map,
+ [`${item.id}`]: params
+ })
+ setItems([
+ ...items.map((it) =>
+ it.id !== item.id
+ ? it
+ : !params.loaded
+ ? it
+ : {
+ ...it,
+ status: ItemStatusEnum.READY
+ }
+ ),
+ ...repos.map((it: any) => ({
+ parentId: item.id,
+ id: it.id,
+ title: it.name,
+ type: ItemTypeEnum.LEAF,
+ status: ItemStatusEnum.READY,
+ owner: it.owner?.login,
+ repo: it.name
+ }))
+ ])
+ },
+ [items, prefix, map]
+ )
+
+ const onScroll = async (parentId: MillerColumnsItem['parentId']) => {
+ const params = map[parentId ?? 'root']
+
+ if (params.loaded) {
+ setItems(
+ items.map((it) =>
+ it.id !== parentId
+ ? it
+ : {
+ ...it,
+ status: ItemStatusEnum.READY
+ }
+ )
+ )
+ } else {
+ const isUser = parentId === user.login
+ const org = items.find((it) => it.id === parentId)
+ const repos = !parentId
+ ? await getUserOrgs(user.login, params.page, params.pageSize)
+ : isUser
+ ? await getUserRepos(org?.title as string, params.page, params.pageSize)
+ : await getOrgRepos(org?.title as string, params.page, params.pageSize)
+
+ if (!repos.length || repos.length < params.pageSize) {
+ setHasMore(false)
+ params.loaded = true
+ } else {
+ params.page += 1
+ }
+
+ setMap({
+ ...map,
+ [`${parentId ?? 'root'}`]: params
+ })
+ setItems([
+ ...items.map((it) =>
+ it.id !== parentId
+ ? it
+ : !params.loaded
+ ? it
+ : {
+ ...it,
+ status: ItemStatusEnum.READY
+ }
+ ),
+ ...repos.map((it: any) => ({
+ parentId,
+ id: it.id,
+ title: it.name,
+ type: ItemTypeEnum.LEAF,
+ status: ItemStatusEnum.READY,
+ owner: it.owner.login,
+ repo: it.name
+ }))
+ ])
+ }
+ }
+
+ return useMemo(
+ () => ({
+ items,
+ onExpandItem,
+ hasMore,
+ onScroll
+ }),
+ [items, hasMore]
+ )
+}
diff --git a/config-ui/src/components/gitlab/miller-columns/index.tsx b/config-ui/src/components/gitlab/miller-columns/index.tsx
index 5db575c50..a3242ab39 100644
--- a/config-ui/src/components/gitlab/miller-columns/index.tsx
+++ b/config-ui/src/components/gitlab/miller-columns/index.tsx
@@ -28,9 +28,7 @@ import {
interface Props extends UseGitLabMillerColumnsProps {
disabledItemIds?: Array<number>
- onChangeItems: (
- items: Array<Pick<ItemType, 'id' | 'title'> & { shortTitle: string }>
- ) => void
+ onChangeItems: (items: Array<Pick<ItemType, 'id' | 'title'>>) => void
}
export const GitLabMillerColumns = ({
@@ -40,21 +38,12 @@ export const GitLabMillerColumns = ({
}: Props) => {
const [seletedIds, setSelectedIds] = useState<Array<ItemType['id']>>([])
- const { items, itemTree, onExpandItem } = useGitLabMillerColumns<{
- nameWithNameSpace?: string
- }>({
+ const { items, onExpandItem, hasMore, onScroll } = useGitLabMillerColumns({
connectionId
})
useEffect(() => {
- const curItems = seletedIds
- .filter((id) => itemTree[id].type === ItemTypeEnum.LEAF)
- .map((id) => ({
- id,
- title: itemTree[id].nameWithNameSpace ?? '',
- shortTitle: itemTree[id].title
- }))
-
+ const curItems = items.filter((it) => seletedIds.includes(it.id))
onChangeItems(curItems)
}, [seletedIds])
@@ -67,6 +56,10 @@ export const GitLabMillerColumns = ({
selectedItemIds={seletedIds}
onSelectedItemIds={setSelectedIds}
onExpandItem={onExpandItem}
+ scrollProps={{
+ hasMore,
+ onScroll
+ }}
/>
)
}
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 ea8c1dabd..bb4e982be 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
@@ -16,14 +16,30 @@
*
*/
-import { useMemo, useCallback } from 'react'
+import { useState, useEffect, useMemo } from 'react'
-import type { ItemType } from '@/components/miller-columns'
-import { useLoadItems, ItemTypeEnum } from '@/components/miller-columns'
+import {
+ MillerColumnsItem,
+ ItemType,
+ ItemStatusEnum
+} from '@/components/miller-columns'
+import { ItemTypeEnum } from '@/components/miller-columns'
import request from '@/components/utils/request'
import { getGitLabProxyApiPrefix } from '../config'
+type MapType = Record<
+ MillerColumnsItem['id'],
+ {
+ groupPage: number
+ groupPageSize: number
+ groupLoaded: boolean
+ projectPage: number
+ projectPageSize: number
+ projectLoaded: boolean
+ }
+>
+
export interface UseGitLabMillerColumnsProps {
connectionId: string
}
@@ -31,64 +47,259 @@ export interface UseGitLabMillerColumnsProps {
export const useGitLabMillerColumns = <T>({
connectionId
}: UseGitLabMillerColumnsProps) => {
+ const [userId, setUserId] = useState(0)
+ const [items, setItems] = useState<Array<MillerColumnsItem>>([])
+ const [hasMore, setHasMore] = useState(true)
+ const [map, setMap] = useState<MapType>({
+ root: {
+ groupPage: 1,
+ groupPageSize: 20,
+ groupLoaded: false,
+ projectPage: 1,
+ projectPageSize: 20,
+ projectLoaded: false
+ }
+ })
+
const prefix = useMemo(
() => getGitLabProxyApiPrefix(connectionId),
[connectionId]
)
- const upadateGroups = (arr: any): Array<ItemType> =>
+ const getRootGroups = (page: number, pageSize: number) => {
+ return request(`${prefix}/groups`, {
+ data: { top_level_only: 1, page, per_page: pageSize }
+ })
+ }
+
+ const getRootProjects = (id: number, page: number, pageSize: number) => {
+ return request(`${prefix}/users/${id}/projects`, {
+ data: { page, per_page: pageSize }
+ })
+ }
+
+ const getChildGroups = (
+ id: MillerColumnsItem['id'],
+ page: number,
+ pageSize: number
+ ) => {
+ return request(`${prefix}/groups/${id}/subgroups`, {
+ data: { page, per_page: pageSize }
+ })
+ }
+
+ const getChildProjects = (
+ id: MillerColumnsItem['id'],
+ page: number,
+ pageSize: number
+ ) => {
+ return request(`${prefix}/groups/${id}/projects`, {
+ data: { page, per_page: pageSize }
+ })
+ }
+
+ const updateGroups = (
+ arr: any,
+ parentId: MillerColumnsItem['parentId'] = null
+ ): Array<ItemType> =>
arr.map((it: any) => ({
+ parentId,
id: it.id,
title: it.name,
type: ItemTypeEnum.BRANCH,
- items: []
+ status: ItemStatusEnum.PENDING
}))
- const updateProjects = (arr: any): Array<ItemType> =>
+ const updateProjects = (
+ arr: any,
+ parentId: MillerColumnsItem['parentId'] = null
+ ): Array<ItemType> =>
arr.map((it: any) => ({
+ parentId,
id: it.id,
- title: it.name,
- type: ItemTypeEnum.LEAF,
- items: [],
- nameWithNameSpace: it.name_with_namespace
+ title: it.name
}))
- const getInitItems = useCallback(async () => {
- const user = await request(`${prefix}/user`)
- const [groups, projects] = await Promise.all([
- request(`${prefix}/groups`, {
- data: { top_level_only: 1, per_page: 100 }
- }),
- request(`${prefix}/users/${user.id}/projects`, {
- data: { per_page: 100 }
+ useEffect(() => {
+ ;(async () => {
+ const user = await request(`${prefix}/user`)
+ setUserId(user.id)
+
+ const target = map.root
+
+ let projects = []
+
+ const groups = await getRootGroups(target.groupPage, target.groupPageSize)
+
+ if (groups.length < target.groupPageSize) {
+ target.groupLoaded = true
+ projects = await getRootProjects(
+ user.id,
+ target.projectPage,
+ target.projectPageSize
+ )
+
+ if (projects.length < target.projectPageSize) {
+ target.projectLoaded = true
+ setHasMore(false)
+ } else {
+ target.projectPage += 1
+ }
+ } else {
+ target.groupPage += 1
+ }
+
+ setItems([...updateGroups(groups), ...updateProjects(projects)])
+ setMap({
+ ...map,
+ root: target
})
- ])
- return [...upadateGroups(groups), ...updateProjects(projects)]
+ })()
}, [prefix])
- const loadMoreItems = useCallback(
- async (item: ItemType) => {
- const [groups, projects] = await Promise.all([
- request(`${prefix}/groups/${item.id}/subgroups`, {
- data: { per_page: 100 }
- }),
- request(`${prefix}/groups/${item.id}/projects`, {
- data: { per_page: 100 }
- })
- ])
- return [...upadateGroups(groups), ...updateProjects(projects)]
- },
- [prefix]
- )
+ const onExpandItem = async (item: ItemType) => {
+ if (map[item.id]) {
+ return
+ }
- const { items, itemTree, loadItems } = useLoadItems<T>({
- getInitItems,
- loadMoreItems
- })
+ let target = {
+ groupPage: 1,
+ groupPageSize: 20,
+ groupLoaded: false,
+ projectPage: 1,
+ projectPageSize: 20,
+ projectLoaded: false
+ }
+
+ let groups = []
+ let projects = []
+
+ groups = await getChildGroups(
+ item.id,
+ target.groupPage,
+ target.groupPageSize
+ )
+
+ if (groups.length < target.groupPageSize) {
+ target.groupLoaded = true
- return {
- items,
- itemTree,
- onExpandItem: loadItems
+ projects = await getChildProjects(
+ item.id,
+ target.projectPage,
+ target.projectPageSize
+ )
+
+ if (projects.length < target.projectPageSize) {
+ target.projectLoaded = true
+ } else {
+ target.projectPage += 1
+ }
+ } else {
+ target.groupPage += 1
+ }
+
+ setItems([
+ ...items,
+ ...updateGroups(groups, item.id),
+ ...updateProjects(projects, item.id)
+ ])
+ setMap({
+ ...map,
+ [`${item.id}`]: target
+ })
}
+
+ const onScroll = async (parentId: ItemType['parentId']) => {
+ const target = map[parentId ?? 'root']
+
+ let groups = []
+ let projects = []
+
+ // All children ready
+ if (target.groupLoaded && target.projectLoaded) {
+ setItems(
+ items.map((it) =>
+ it.id !== parentId ? it : { ...it, status: ItemStatusEnum.READY }
+ )
+ )
+ // groups ready
+ } else if (target.groupLoaded) {
+ projects = parentId
+ ? await getChildProjects(
+ parentId,
+ target.projectPage,
+ target.projectPageSize
+ )
+ : await getRootProjects(
+ userId,
+ target.projectPage,
+ target.projectPageSize
+ )
+
+ if (projects.length < target.projectPageSize) {
+ target.projectLoaded = true
+ } else {
+ target.projectPage += 1
+ }
+ // no group ready
+ } else {
+ groups = parentId
+ ? await getChildGroups(parentId, target.groupPage, target.groupPageSize)
+ : await getRootGroups(target.groupPage, target.groupPageSize)
+
+ if (!groups.length) {
+ target.groupLoaded = true
+ projects = parentId
+ ? await getChildProjects(
+ parentId,
+ target.projectPage,
+ target.projectPageSize
+ )
+ : await getRootProjects(
+ userId,
+ target.projectPage,
+ target.projectPageSize
+ )
+
+ if (projects.length < target.projectPageSize) {
+ target.projectLoaded = true
+ } else {
+ target.projectPage += 1
+ }
+ } else if (groups.length < target.groupPageSize) {
+ target.groupLoaded = true
+ } else {
+ target.groupPage += 1
+ }
+ }
+
+ setItems([
+ ...items.map((it) =>
+ it.id !== parentId
+ ? it
+ : !(target.groupLoaded && target.projectLoaded)
+ ? it
+ : {
+ ...it,
+ status: ItemStatusEnum.READY
+ }
+ ),
+ ...updateGroups(groups, parentId),
+ ...updateProjects(projects, parentId)
+ ])
+ setMap({
+ ...map,
+ [`${parentId}`]: target
+ })
+ }
+
+ return useMemo(
+ () => ({
+ items,
+ onExpandItem,
+ hasMore,
+ onScroll
+ }),
+ [items, map, hasMore]
+ )
}
diff --git a/config-ui/src/components/jira/miller-columns/index.tsx b/config-ui/src/components/jira/miller-columns/index.tsx
index dc40bc47e..c2adb1d40 100644
--- a/config-ui/src/components/jira/miller-columns/index.tsx
+++ b/config-ui/src/components/jira/miller-columns/index.tsx
@@ -49,6 +49,7 @@ export const JIRAMillerColumns = ({ connectionId, onChangeItems }: Props) => {
return (
<MillerColumns
height={300}
+ columnCount={1}
items={items}
selectedItemIds={seletedIds}
onSelectedItemIds={setSelectedIds}
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
index 212f3d60d..4e6d3d682 100644
--- 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
@@ -18,7 +18,7 @@
import { useState, useEffect, useMemo } from 'react'
-import type { ItemType } from '@/components/miller-columns'
+import type { MillerColumnsItem } from '@/components/miller-columns'
import { ItemTypeEnum, ItemStatusEnum } from '@/components/miller-columns'
import request from '@/components/utils/request'
@@ -32,7 +32,7 @@ export interface UseJIRAMillerColumnsProps {
export const useJIRAMillerColumns = ({
connectionId
}: UseJIRAMillerColumnsProps) => {
- const [items, setItems] = useState<Array<ItemType>>([])
+ const [items, setItems] = useState<Array<MillerColumnsItem>>([])
const [hasMore, setHasMore] = useState(true)
const [page, setPage] = useState(1)
const [pageSize] = useState(50)
@@ -41,6 +41,7 @@ export const useJIRAMillerColumns = ({
const updateItems = (arr: Array<{ id: number; name: string }>) =>
arr.map((it) => ({
+ parentId: null,
id: it.id,
title: it.name,
type: ItemTypeEnum.LEAF,
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 269fc2328..400072c38 100644
--- a/config-ui/src/components/miller-columns/components/column/column.tsx
+++ b/config-ui/src/components/miller-columns/components/column/column.tsx
@@ -24,24 +24,27 @@ import type { ItemType } from '../../types'
import * as S from './styled'
export interface ColumnsProps {
+ parentId: ItemType['parentId']
items: Array<ItemType>
renderItem: (item: ItemType) => React.ReactNode
height?: number
title?: string | React.ReactNode
- bottom?: React.ReactNode
+ columnCount?: number
scrollProps?: {
hasMore: boolean
- onScroll: () => void
+ onScroll: (parentId: ItemType['parentId']) => void
renderLoader?: () => React.ReactNode
renderBottom?: () => React.ReactNode
}
}
export const Column = ({
+ parentId,
items,
renderItem,
height,
title,
+ columnCount = 3,
scrollProps
}: ColumnsProps) => {
const [hasMore, setHasMore] = useState(true)
@@ -54,7 +57,7 @@ export const Column = ({
const handleNext = useCallback(() => {
if (scrollProps) {
- scrollProps.onScroll()
+ scrollProps.onScroll(parentId)
} else {
setHasMore(false)
}
@@ -65,18 +68,20 @@ export const Column = ({
)
const bottom = scrollProps?.renderBottom?.() ?? (
- <S.StatusWrapper>All Data Loaded.</S.StatusWrapper>
+ <S.StatusWrapper>End.</S.StatusWrapper>
)
+ const id = `miller-columns-column-${parentId ?? 'root'}`
+
return (
- <S.Container id='miller-columns-column-container' height={height}>
+ <S.Container id={id} height={height} columnCount={columnCount}>
{title && <div className='title'>{title}</div>}
<InfiniteScroll
dataLength={items.length}
hasMore={hasMore}
next={handleNext}
loader={loader}
- scrollableTarget='miller-columns-column-container'
+ scrollableTarget={id}
endMessage={bottom}
>
{items.map((it) => renderItem(it))}
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 e522a0693..93ce796e7 100644
--- a/config-ui/src/components/miller-columns/components/column/styled.ts
+++ b/config-ui/src/components/miller-columns/components/column/styled.ts
@@ -18,11 +18,13 @@
import styled from '@emotion/styled'
-export const Container = styled.div<{ height?: number }>`
- flex: 0 0 33.33%;
+export const Container = styled.div<{ height?: number; columnCount: number }>`
margin: 0;
padding: 0;
- width: 33.33%;
+ ${({ columnCount }) => `
+ flex: 0 0 ${100 / columnCount}%;
+ width: ${100 / columnCount}%;
+ `}
${({ height }) => `height: ${height}px;`}
list-style: none;
border-left: 1px solid #dbe4fd;
diff --git a/config-ui/src/components/miller-columns/hooks/index.ts b/config-ui/src/components/miller-columns/hooks/index.ts
index 25195b787..e22a02a8c 100644
--- a/config-ui/src/components/miller-columns/hooks/index.ts
+++ b/config-ui/src/components/miller-columns/hooks/index.ts
@@ -16,7 +16,4 @@
*
*/
-export * from './use-columns'
-export * from './use-item-map'
-export * from './use-load-items'
export * from './use-miller-columns'
diff --git a/config-ui/src/components/miller-columns/hooks/use-columns.ts b/config-ui/src/components/miller-columns/hooks/use-columns.ts
index 9753e2f7e..5b3efa3fa 100644
--- a/config-ui/src/components/miller-columns/hooks/use-columns.ts
+++ b/config-ui/src/components/miller-columns/hooks/use-columns.ts
@@ -18,39 +18,48 @@
import { useMemo } from 'react'
-import { ItemType, ItemMapType, ColumnType } from '../types'
+import type { ItemType, ItemMapType, ColumnType } from '../types'
+import { ItemStatusEnum } from '../types'
interface Props {
- items: ItemType[]
itemMap: ItemMapType
activeItemId?: ItemType['id']
}
-export const useColumns = ({ items, itemMap, activeItemId }: Props) => {
+export const useColumns = ({ itemMap, activeItemId }: Props) => {
return useMemo(() => {
- const rootLeaf = { items, activeId: null, parentId: null }
+ const rootLeaf = {
+ parentId: null,
+ activeId: null,
+ items: Object.values(itemMap).filter((it) => it.parentId === null),
+ hasMore: false
+ }
if (!activeItemId) {
return [rootLeaf]
}
- const activeItem = itemMap.getItem(activeItemId)
+ const activeItem = itemMap[activeItemId]
const columns: ColumnType[] = [
{
parentId: activeItem.id,
- items: activeItem.items,
- activeId: null
+ items: activeItem.items ?? [],
+ activeId: null,
+ hasMore: activeItem.status !== ItemStatusEnum.READY
}
]
const collect = (item: ItemType) => {
- const parent = itemMap.getItemParent(item.id)
+ const parent = itemMap[item.parentId ?? '']
columns.unshift({
- parentId: parent?.id ?? null,
- items: parent?.items ?? items,
- activeId: item.id ?? null
+ parentId: item.parentId,
+ items: parent
+ ? parent.items
+ : Object.values(itemMap).filter((it) => it.parentId === null),
+ activeId: item.id ?? null,
+ hasMore: false
})
if (parent) {
@@ -61,5 +70,5 @@ export const useColumns = ({ items, itemMap, activeItemId }: Props) => {
collect(activeItem)
return columns
- }, [items, itemMap, activeItemId])
+ }, [itemMap, activeItemId])
}
diff --git a/config-ui/src/components/miller-columns/components/column/styled.ts b/config-ui/src/components/miller-columns/hooks/use-convert-items.ts
similarity index 53%
copy from config-ui/src/components/miller-columns/components/column/styled.ts
copy to config-ui/src/components/miller-columns/hooks/use-convert-items.ts
index e522a0693..acdbacc91 100644
--- a/config-ui/src/components/miller-columns/components/column/styled.ts
+++ b/config-ui/src/components/miller-columns/hooks/use-convert-items.ts
@@ -16,29 +16,31 @@
*
*/
-import styled from '@emotion/styled'
+import { useState, useEffect, useMemo } from 'react'
-export const Container = styled.div<{ height?: number }>`
- flex: 0 0 33.33%;
- margin: 0;
- padding: 0;
- width: 33.33%;
- ${({ height }) => `height: ${height}px;`}
- list-style: none;
- border-left: 1px solid #dbe4fd;
- overflow-y: auto;
+import type { MillerColumnsItem } from '../types'
- &:first-child {
- border-left: none;
- }
+export interface UseConvertItemsProps {
+ items: Array<MillerColumnsItem>
+}
+
+export const useConvertItems = ({ items }: UseConvertItemsProps) => {
+ const [convertItems, setConvertItems] = useState<Array<MillerColumnsItem>>([])
- & > .title {
- padding: 4px 12px;
- font-weight: 700;
- color: #292b3f;
+ const flatItems = (items: Array<MillerColumnsItem>) => {
+ let result: Array<MillerColumnsItem> = []
+ items.forEach((it) => {
+ result.push(it)
+ if (it.items) {
+ result.push(...flatItems(it.items))
+ }
+ })
+ return result
}
-`
-export const StatusWrapper = styled.div`
- padding: 4px 12px;
-`
+ useEffect(() => {
+ setConvertItems(flatItems(items))
+ }, [items])
+
+ return useMemo(() => convertItems, [convertItems])
+}
diff --git a/config-ui/src/components/miller-columns/hooks/use-item-map.ts b/config-ui/src/components/miller-columns/hooks/use-item-map.ts
index 89113d148..007490453 100644
--- a/config-ui/src/components/miller-columns/hooks/use-item-map.ts
+++ b/config-ui/src/components/miller-columns/hooks/use-item-map.ts
@@ -16,62 +16,72 @@
*
*/
-import { useMemo } from 'react'
+import { useState, useMemo, useEffect } from 'react'
-import type { ItemType, ItemInfoType } from '../types'
-import { ItemStatusEnum } from '../types'
+import type { MillerColumnsItem, ItemType, ItemMapType } from '../types'
+import { ItemTypeEnum, ItemStatusEnum } from '../types'
interface Props {
- items: ItemType[]
+ items: Array<MillerColumnsItem>
}
export const useItemMap = ({ items }: Props) => {
- const checkChildLoaded = (item: ItemType): boolean => {
+ const [itemMap, setItemMap] = useState<ItemMapType>({})
+
+ const checkChildLoaded = (item: MillerColumnsItem): boolean => {
if (item.status === ItemStatusEnum.PENDING) {
return false
}
- return item.items.every((it) => {
- return checkChildLoaded(it)
- })
+ return !items
+ .filter((it) => it.parentId === item.id)
+ .find((it) => it.status === ItemStatusEnum.PENDING)
}
- return useMemo(() => {
- const itemMap = new Map<ItemType['id'], ItemInfoType>()
+ const covertItem = (item: MillerColumnsItem): ItemType => {
+ const type = item.type
+ ? item.type
+ : (item.items ?? []).length
+ ? ItemTypeEnum.BRANCH
+ : ItemTypeEnum.LEAF
+ const status = item.status ? item.status : ItemStatusEnum.READY
+ return {
+ ...item,
+ type,
+ status,
+ childLoaded: checkChildLoaded(item)
+ } as ItemType
+ }
- const collect = ({
- item,
- parent
- }: {
- item: ItemType
- parent?: ItemType
- }) => {
- if (!itemMap.has(item.id)) {
- itemMap.set(item.id, {
- item,
- parentId: parent?.id,
- childLoaded: checkChildLoaded(item)
+ const collectChildItems = (
+ items: Array<MillerColumnsItem>,
+ item: MillerColumnsItem
+ ): Array<ItemType> => {
+ return items
+ .filter((it) => {
+ return it.parentId === item.id
+ })
+ .map((it) =>
+ covertItem({
+ ...it,
+ items: collectChildItems(items, it)
})
- }
-
- if (item.items) {
- item.items.forEach((it) => collect({ item: it, parent: item }))
- }
- }
+ )
+ }
- items.forEach((it) => collect({ item: it }))
+ const itemsToMap = (items: Array<MillerColumnsItem>): ItemMapType => {
+ return items.reduce((acc, cur) => {
+ acc[cur.id] = covertItem({
+ ...cur,
+ items: collectChildItems(items, cur)
+ })
+ return acc
+ }, {} as any)
+ }
- return {
- getItem(id: ItemType['id']) {
- return (itemMap.get(id) as ItemInfoType).item
- },
- getItemParent(id: ItemType['id']) {
- const parentId = itemMap.get(id)?.parentId
- return parentId ? (itemMap.get(parentId) as ItemInfoType).item : null
- },
- getItemChildLoaded(id: ItemType['id']) {
- return (itemMap.get(id) as ItemInfoType).childLoaded
- }
- }
+ useEffect(() => {
+ setItemMap(itemsToMap(items))
}, [items])
+
+ return useMemo(() => itemMap, [itemMap])
}
diff --git a/config-ui/src/components/miller-columns/hooks/use-load-items.ts b/config-ui/src/components/miller-columns/hooks/use-load-items.ts
deleted file mode 100644
index 1a75873b8..000000000
--- a/config-ui/src/components/miller-columns/hooks/use-load-items.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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 { ItemType, ItemTypeEnum, ItemStatusEnum } from '../types'
-
-interface Props {
- getInitItems: () => Promise<Array<ItemType>>
- loadMoreItems: (item: ItemType) => Promise<Array<ItemType>>
-}
-
-type TreeType<T> = Record<ItemType['id'], ItemType & T>
-
-export const useLoadItems = <T>({ getInitItems, loadMoreItems }: Props) => {
- const [tree, setTree] = useState<TreeType<T>>({})
-
- const itemsToTree = (items: Array<ItemType>) => {
- return items.reduce((acc, cur) => {
- acc[cur.id] = {
- ...cur,
- items: [],
- status:
- cur.type === ItemTypeEnum.BRANCH
- ? ItemStatusEnum.PENDING
- : ItemStatusEnum.READY
- }
- return acc
- }, {} as any)
- }
-
- const treeToItems = (t: TreeType<T>) => {
- if (!t.root) {
- return []
- }
-
- const transform = (arr: Array<ItemType>): Array<ItemType> => {
- return arr.map((it) => ({
- ...it,
- ...t[it.id],
- items: transform(t[it.id].items)
- }))
- }
-
- return transform(t.root.items)
- }
-
- useEffect(() => {
- ;(async () => {
- const initItems = await getInitItems()
- setTree({
- root: {
- id: 'root',
- title: 'root',
- type: ItemTypeEnum.BRANCH,
- status: ItemStatusEnum.READY,
- items: initItems
- },
- ...itemsToTree(initItems)
- })
- })()
- }, [])
-
- return useMemo(() => {
- return {
- items: treeToItems(tree),
- itemTree: tree,
- async loadItems(item: ItemType) {
- if (tree[item.id].status === ItemStatusEnum.READY) {
- return
- }
- const items = await loadMoreItems(item)
- setTree({
- ...tree,
- [`${item.id}`]: {
- ...item,
- items,
- status: ItemStatusEnum.READY
- },
- ...itemsToTree(items)
- })
- }
- }
- }, [tree])
-}
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 8f0f6b8fb..fa3e5c2bf 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
@@ -18,15 +18,16 @@
import { useState, useMemo, useEffect } from 'react'
-import type { ItemType, ColumnType } from '../types'
+import type { MillerColumnsItem, ItemType, ColumnType } from '../types'
import { ItemTypeEnum, RowStatus } from '../types'
import { CheckStatus } from '../components'
+import { useConvertItems } from './use-convert-items'
import { useItemMap } from './use-item-map'
import { useColumns } from './use-columns'
export interface UseMillerColumnsProps {
- items: ItemType[]
+ items: Array<MillerColumnsItem>
activeItemId?: ItemType['id']
onActiveItemId?: (id: ItemType['id']) => void
disabledItemIds?: Array<ItemType['id']>
@@ -48,8 +49,9 @@ export const useMillerColumns = ({
[]
)
- const itemMap = useItemMap({ items })
- const columns = useColumns({ items, itemMap, activeItemId })
+ const covertItems = useConvertItems({ items })
+ const itemMap = useItemMap({ items: covertItems })
+ const columns = useColumns({ itemMap, activeItemId })
useEffect(() => {
setActiveItemId(props.activeItemId)
@@ -62,7 +64,7 @@ export const useMillerColumns = ({
const collectAddParentIds = (item: ItemType) => {
let result: Array<ItemType['id']> = []
- const parentItem = itemMap.getItemParent(item.id)
+ const parentItem = itemMap[item.parentId ?? '']
if (parentItem) {
const childSelectedIds = parentItem.items
@@ -81,7 +83,7 @@ export const useMillerColumns = ({
const collectRemoveParentIds = (item: ItemType) => {
let result: Array<ItemType['id']> = []
- const parentItem = itemMap.getItemParent(item.id)
+ const parentItem = itemMap[item.parentId ?? '']
if (parentItem) {
result.push(parentItem.id)
@@ -98,18 +100,18 @@ export const useMillerColumns = ({
activeItemId,
selectedItemIds,
getStatus(item: ItemType, column: ColumnType) {
- if (column.activeId === item.id) {
+ if (item.id === column.activeId) {
return RowStatus.selected
}
return RowStatus.noselected
},
getChekecdStatus(item: ItemType) {
- const childSelectedIds = item.items
+ const childSelectedIds = (item.items ?? [])
.map((it) => it.id)
.filter((id) => selectedItemIds.includes(id))
switch (true) {
- case !itemMap.getItemChildLoaded(item.id):
+ case !itemMap[item.id].childLoaded:
case (disabledItemIds ?? []).includes(item.id):
return CheckStatus.disabled
case selectedItemIds.includes(item.id):
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/miller-columns/index.ts
index 3db209c1a..a3e6236e8 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/miller-columns/index.ts
@@ -17,6 +17,5 @@
*/
export * from './miller-columns'
-export * from './components'
-export * from './hooks'
export * from './types'
+export * from './test'
\ No newline at end of file
diff --git a/config-ui/src/components/miller-columns/miller-columns.tsx b/config-ui/src/components/miller-columns/miller-columns.tsx
index b07f2d621..f95efc403 100644
--- a/config-ui/src/components/miller-columns/miller-columns.tsx
+++ b/config-ui/src/components/miller-columns/miller-columns.tsx
@@ -18,20 +18,23 @@
import React from 'react'
-import { useMillerColumns, UseMillerColumnsProps } from './hooks'
+import type { UseMillerColumnsProps } from './hooks'
+import { useMillerColumns } from './hooks'
import { Column, ColumnsProps, Item } from './components'
import * as S from './styled'
interface Props extends UseMillerColumnsProps {
height?: number
+ columnCount?: number
firstColumnTitle?: React.ReactNode
scrollProps?: ColumnsProps['scrollProps']
}
export const MillerColumns = ({
- firstColumnTitle,
height,
+ columnCount,
+ firstColumnTitle,
scrollProps,
...props
}: Props) => {
@@ -43,6 +46,7 @@ export const MillerColumns = ({
{columns.map((col, i) => (
<Column
key={col.parentId}
+ parentId={col.parentId}
items={col.items}
renderItem={(item) => (
<Item
@@ -56,7 +60,15 @@ export const MillerColumns = ({
)}
height={height}
title={i === 0 && firstColumnTitle}
- scrollProps={scrollProps}
+ columnCount={columnCount}
+ scrollProps={{
+ ...scrollProps,
+ hasMore: col.parentId ? col.hasMore : scrollProps?.hasMore ?? true,
+ onScroll:
+ scrollProps?.onScroll ??
+ ((parentId) =>
+ console.log(`column: ${parentId ?? 'root'} scroll`))
+ }}
/>
))}
</S.Container>
diff --git a/config-ui/src/components/miller-columns/components/column/styled.ts b/config-ui/src/components/miller-columns/test/index.tsx
similarity index 55%
copy from config-ui/src/components/miller-columns/components/column/styled.ts
copy to config-ui/src/components/miller-columns/test/index.tsx
index e522a0693..412b972d9 100644
--- a/config-ui/src/components/miller-columns/components/column/styled.ts
+++ b/config-ui/src/components/miller-columns/test/index.tsx
@@ -16,29 +16,32 @@
*
*/
-import styled from '@emotion/styled'
+import React, { useState } from 'react'
-export const Container = styled.div<{ height?: number }>`
- flex: 0 0 33.33%;
- margin: 0;
- padding: 0;
- width: 33.33%;
- ${({ height }) => `height: ${height}px;`}
- list-style: none;
- border-left: 1px solid #dbe4fd;
- overflow-y: auto;
+import { MillerColumns, MillerColumnsItem } from '..'
- &:first-child {
- border-left: none;
- }
+import { useTest } from './use-test'
- & > .title {
- padding: 4px 12px;
- font-weight: 700;
- color: #292b3f;
- }
-`
+export const TestMillerColumns = () => {
+ const [selectedIds, setSelectedIds] = useState<
+ Array<MillerColumnsItem['id']>
+ >([])
-export const StatusWrapper = styled.div`
- padding: 4px 12px;
-`
+ const { items, onExpandItem, hasMore, onScroll } = useTest()
+
+ return (
+ <MillerColumns
+ height={100}
+ columnCount={4}
+ firstColumnTitle='TestMillerColumns'
+ items={items}
+ selectedItemIds={selectedIds}
+ onSelectedItemIds={setSelectedIds}
+ onExpandItem={onExpandItem}
+ scrollProps={{
+ hasMore,
+ onScroll
+ }}
+ />
+ )
+}
diff --git a/config-ui/src/components/miller-columns/components/column/styled.ts b/config-ui/src/components/miller-columns/test/mock/flat-data/first.ts
similarity index 58%
copy from config-ui/src/components/miller-columns/components/column/styled.ts
copy to config-ui/src/components/miller-columns/test/mock/flat-data/first.ts
index e522a0693..02107a4b6 100644
--- a/config-ui/src/components/miller-columns/components/column/styled.ts
+++ b/config-ui/src/components/miller-columns/test/mock/flat-data/first.ts
@@ -16,29 +16,44 @@
*
*/
-import styled from '@emotion/styled'
+import { ItemTypeEnum, ItemStatusEnum } from '../../..'
-export const Container = styled.div<{ height?: number }>`
- flex: 0 0 33.33%;
- margin: 0;
- padding: 0;
- width: 33.33%;
- ${({ height }) => `height: ${height}px;`}
- list-style: none;
- border-left: 1px solid #dbe4fd;
- overflow-y: auto;
-
- &:first-child {
- border-left: none;
- }
-
- & > .title {
- padding: 4px 12px;
- font-weight: 700;
- color: #292b3f;
+export const flatDataFirst = [
+ {
+ parentId: null,
+ id: '1',
+ title: '1'
+ },
+ {
+ parentId: null,
+ id: '2',
+ title: '2',
+ type: ItemTypeEnum.BRANCH,
+ status: ItemStatusEnum.PENDING
+ },
+ {
+ parentId: null,
+ id: '3',
+ title: '3'
+ },
+ {
+ parentId: '1',
+ id: '1-1',
+ title: '1-1'
+ },
+ {
+ parentId: '1',
+ id: '1-2',
+ title: '1-2'
+ },
+ {
+ parentId: '1-1',
+ id: '1-1-1',
+ title: '1-1-1'
+ },
+ {
+ parentId: '1-1-1',
+ id: '1-1-1-1',
+ title: '1-1-1-1'
}
-`
-
-export const StatusWrapper = styled.div`
- padding: 4px 12px;
-`
+]
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/miller-columns/test/mock/flat-data/index.ts
similarity index 87%
copy from config-ui/src/components/miller-columns/index.ts
copy to config-ui/src/components/miller-columns/test/mock/flat-data/index.ts
index 3db209c1a..cb9762c5f 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/miller-columns/test/mock/flat-data/index.ts
@@ -16,7 +16,6 @@
*
*/
-export * from './miller-columns'
-export * from './components'
-export * from './hooks'
-export * from './types'
+export * from './first'
+export * from './second'
+export * from './third'
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/miller-columns/test/mock/flat-data/second.ts
similarity index 70%
copy from config-ui/src/components/miller-columns/index.ts
copy to config-ui/src/components/miller-columns/test/mock/flat-data/second.ts
index 3db209c1a..5128c66ae 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/miller-columns/test/mock/flat-data/second.ts
@@ -16,7 +16,30 @@
*
*/
-export * from './miller-columns'
-export * from './components'
-export * from './hooks'
-export * from './types'
+export const flatDataSecond = [
+ {
+ parentId: '2',
+ id: '2-1',
+ title: '2-1'
+ },
+ {
+ parentId: '2',
+ id: '2-2',
+ title: '2-2'
+ },
+ {
+ parentId: '2',
+ id: '2-3',
+ title: '2-3'
+ },
+ {
+ parentId: '2',
+ id: '2-4',
+ title: '2-4'
+ },
+ {
+ parentId: '2',
+ id: '2-5',
+ title: '2-5'
+ }
+]
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/miller-columns/test/mock/flat-data/third.ts
similarity index 74%
copy from config-ui/src/components/miller-columns/index.ts
copy to config-ui/src/components/miller-columns/test/mock/flat-data/third.ts
index 3db209c1a..9b8f0cb7e 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/miller-columns/test/mock/flat-data/third.ts
@@ -16,7 +16,25 @@
*
*/
-export * from './miller-columns'
-export * from './components'
-export * from './hooks'
-export * from './types'
+export const flatDataThird = [
+ {
+ parentId: '2',
+ id: '2-6',
+ title: '2-6'
+ },
+ {
+ parentId: '2',
+ id: '2-7',
+ title: '2-7'
+ },
+ {
+ parentId: '2',
+ id: '2-8',
+ title: '2-8'
+ },
+ {
+ parentId: '2',
+ id: '2-9',
+ title: '2-9'
+ }
+]
diff --git a/config-ui/src/components/miller-columns/index.ts b/config-ui/src/components/miller-columns/test/mock/index.ts
similarity index 87%
copy from config-ui/src/components/miller-columns/index.ts
copy to config-ui/src/components/miller-columns/test/mock/index.ts
index 3db209c1a..1739541d6 100644
--- a/config-ui/src/components/miller-columns/index.ts
+++ b/config-ui/src/components/miller-columns/test/mock/index.ts
@@ -16,7 +16,4 @@
*
*/
-export * from './miller-columns'
-export * from './components'
-export * from './hooks'
-export * from './types'
+export * from './flat-data'
diff --git a/config-ui/src/components/miller-columns/test/use-test.ts b/config-ui/src/components/miller-columns/test/use-test.ts
new file mode 100644
index 000000000..36f805e34
--- /dev/null
+++ b/config-ui/src/components/miller-columns/test/use-test.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 { MillerColumnsItem, ItemType } from '..'
+import { ItemStatusEnum } from '..'
+
+import { flatDataFirst, flatDataSecond, flatDataThird } from './mock'
+
+export const useTest = () => {
+ const [items, setItems] = useState<Array<MillerColumnsItem>>([])
+ const [hasMore, setHasMore] = useState(true)
+
+ // Get the initial items data
+ // And know whether the first column has completed all data loading
+ useEffect(() => {
+ setItems(flatDataFirst)
+ setHasMore(false)
+ }, [])
+
+ // Load more data when expanding
+ // And judge whether the data is loaded
+ const onExpandItem = (item: ItemType) => {
+ if (item.id === '2') {
+ setItems([...items, ...flatDataSecond])
+ }
+ }
+
+ const onScroll = (parentId: ItemType['parentId']) => {
+ if (parentId === '2') {
+ setItems([
+ ...items.map((it) =>
+ it.id !== '2' ? it : { ...it, status: ItemStatusEnum.READY }
+ ),
+ ...flatDataThird
+ ])
+ }
+ }
+
+ return useMemo(
+ () => ({
+ items,
+ onExpandItem,
+ hasMore,
+ onScroll
+ }),
+ [items, hasMore]
+ )
+}
diff --git a/config-ui/src/components/miller-columns/types.ts b/config-ui/src/components/miller-columns/types.ts
index 4f830f354..f8ae8c9dd 100644
--- a/config-ui/src/components/miller-columns/types.ts
+++ b/config-ui/src/components/miller-columns/types.ts
@@ -16,6 +16,15 @@
*
*/
+export type MillerColumnsItem = {
+ parentId: string | number | null
+ id: string | number
+ title: string
+ items?: Array<MillerColumnsItem>
+ type?: ItemTypeEnum
+ status?: ItemStatusEnum
+}
+
export enum ItemTypeEnum {
LEAF = 'leaf',
BRANCH = 'branch'
@@ -26,39 +35,23 @@ export enum ItemStatusEnum {
READY = 'ready'
}
-export type ItemType = {
- id: string | number
- title: string
+export type ItemType = Pick<MillerColumnsItem, 'parentId' | 'id' | 'title'> & {
+ items: Array<ItemType>
type: ItemTypeEnum
status: ItemStatusEnum
- items: ItemType[]
-}
-
-export type ItemInfoType = {
- item: ItemType
- parentId?: ItemType['id']
childLoaded: boolean
}
-export type ItemMapType = {
- getItem: (id: ItemType['id']) => ItemType
- getItemParent: (id: ItemType['id']) => ItemType | null
- getItemChildLoaded: (id: ItemType['id']) => boolean
-}
+export type ItemMapType = Record<ItemType['id'], ItemType>
export type ColumnType = {
- parentId: ItemType['id'] | null
+ parentId: ItemType['parentId']
items: ItemType[]
activeId: ItemType['id'] | null
+ hasMore: boolean
}
export enum RowStatus {
selected = 'selected',
noselected = 'noselected'
}
-
-export enum CheckedStatus {
- selected = 'selected',
- noselected = 'noselected',
- indeterminate = 'indeterminate'
-}
diff --git a/config-ui/src/models/GithubProject.js b/config-ui/src/models/GithubProject.js
index 7e0f50445..52959fb42 100644
--- a/config-ui/src/models/GithubProject.js
+++ b/config-ui/src/models/GithubProject.js
@@ -49,6 +49,8 @@ class GitHubProject extends Entity {
this.useApi = data?.useApi || false
this.variant = data?.variant || 'project'
this.providerId = 'github'
+
+ this.type = data?.type
}
getConfiguredEntityId() {