From 39339d771d80b4bd31ec5c476e147e8c5786704f Mon Sep 17 00:00:00 2001 From: liangfung <1098486429@qq.com> Date: Sat, 27 Apr 2024 18:32:38 +0800 Subject: [PATCH] feat(ui): integrate gitlab repositories --- .../components/common-provider-form.tsx} | 86 ++++-- .../components/provider-list.tsx} | 105 +++++-- .../repository/[kind]/constants.tsx | 27 ++ .../components/add-repository-form.tsx} | 128 ++++++--- .../[kind]/detail/components/detail-page.tsx | 25 ++ .../components/github-provider-detail.tsx} | 27 +- .../components/gitlab-provider-detail.tsx | 266 ++++++++++++++++++ .../update-github-provider-form.tsx} | 8 +- .../update-gitlab-provider-form.tsx | 91 ++++++ .../repository/[kind]/detail/page.tsx | 10 + .../repository/[kind]/detail/query.ts | 15 + .../[kind]/hooks/use-repository-kind.ts | 17 ++ .../[kind]/new/components/new-page.tsx | 81 ++++++ .../repository/[kind]/new/page.tsx | 10 + .../(integrations)/repository/[kind]/page.tsx | 28 ++ .../repository/components/tabs-header.tsx | 24 +- .../repository/github/detail/page.tsx | 5 - .../repository/github/detail/query.ts | 9 - .../github/new/components/new-page.tsx | 49 ---- .../repository/github/new/page.tsx | 5 - .../(integrations)/repository/github/page.tsx | 5 - ee/tabby-ui/components/ui/icons.tsx | 27 ++ ee/tabby-ui/lib/tabby/query.ts | 69 +++++ 23 files changed, 947 insertions(+), 170 deletions(-) rename ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/{github/components/github-form.tsx => [kind]/components/common-provider-form.tsx} (73%) rename ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/{github/components/github.tsx => [kind]/components/provider-list.tsx} (55%) create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/constants.tsx rename ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/{github/detail/components/new-repository-form.tsx => [kind]/detail/components/add-repository-form.tsx} (58%) create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/detail-page.tsx rename ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/{github/detail/components/detail.tsx => [kind]/detail/components/github-provider-detail.tsx} (91%) create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/gitlab-provider-detail.tsx rename ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/{github/detail/components/provider-detail-form.tsx => [kind]/detail/components/update-github-provider-form.tsx} (93%) create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/update-gitlab-provider-form.tsx create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/page.tsx create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/query.ts create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/hooks/use-repository-kind.ts create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/components/new-page.tsx create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/page.tsx create mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/page.tsx delete mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/page.tsx delete mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/query.ts delete mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/components/new-page.tsx delete mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/page.tsx delete mode 100644 ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/page.tsx diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/components/github-form.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/components/common-provider-form.tsx similarity index 73% rename from ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/components/github-form.tsx rename to ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/components/common-provider-form.tsx index 41d3f7c1bcd4..fb514a7676c4 100644 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/components/github-form.tsx +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/components/common-provider-form.tsx @@ -9,6 +9,7 @@ import { useForm, UseFormReturn } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' +import { RepositoryKind } from '@/lib/gql/generates/graphql' import { AlertDialog, AlertDialogAction, @@ -33,6 +34,8 @@ import { import { IconExternalLink, IconSpinner } from '@/components/ui/icons' import { Input } from '@/components/ui/input' +import { useRepositoryKind } from '../hooks/use-repository-kind' + export const repositoryProviderFormSchema = z.object({ displayName: z.string(), accessToken: z.string() @@ -51,7 +54,7 @@ interface GithubProviderFormProps { deletable?: boolean } -export const GithubProviderForm: React.FC = ({ +export const ProviderForm: React.FC = ({ isNew, form, onSubmit, @@ -59,6 +62,7 @@ export const GithubProviderForm: React.FC = ({ cancleable = true, deletable }) => { + const kind = useRepositoryKind() const router = useRouter() const [deleteAlertVisible, setDeleteAlertVisible] = React.useState(false) @@ -83,6 +87,29 @@ export const GithubProviderForm: React.FC = ({ } } + const displayNamePlaceholder = React.useMemo(() => { + switch (kind) { + case RepositoryKind.Github: + return 'e.g. GitHub' + case RepositoryKind.Gitlab: + return 'e.g. GitLab' + default: + return '' + } + }, [kind]) + + const accessTokenPlaceholder = React.useMemo(() => { + if (!isNew) return '*****' + switch (kind) { + case RepositoryKind.Github: + return 'e.g. github_pat_1ABCD1234ABCD1234ABCD1234ABCD1234ABCD1234' + case RepositoryKind.Gitlab: + return 'e.g. glpat_1ABCD1234ABCD1234ABCD1234ABCD1234' + default: + return '' + } + }, [kind, isNew]) + return (
@@ -98,7 +125,7 @@ export const GithubProviderForm: React.FC = ({ = ({ Personal Access Token -
- Create a dedicated service user and generate a{' '} - - fine-grained personal access - {' '} - token with the member role for the organization or all - projects to be managed. -
-
• Contents (Read-only)
+
+
+ Create a dedicated service user and generate a{' '} + + fine-grained personal access + {' '} + token with the member role for the organization or all projects to be + managed. +
+
• Contents (Read-only)
+ + ) + } + + if (kind === RepositoryKind.Gitlab) { + return ( + <> +
+ Create a dedicated service user and generate a{' '} + + personal access token + {' '} + with the maintainer role and at least following permissions for the + group or projects to be managed. You can generate a project access + token for managing a single project, or generate a group access token + to manage all projects within the group. +
+
• read repository
+ + ) + } + + return null +} + function ExternalLink({ href, children diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/components/github.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/components/provider-list.tsx similarity index 55% rename from ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/components/github.tsx rename to ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/components/provider-list.tsx index 39352adcf59b..0c626098f37c 100644 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/components/github.tsx +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/components/provider-list.tsx @@ -4,44 +4,88 @@ import Link from 'next/link' import { useQuery } from 'urql' import { - ListGithubRepositoryProvidersQuery, + RepositoryKind, RepositoryProviderStatus } from '@/lib/gql/generates/graphql' -import { listGithubRepositoryProviders } from '@/lib/tabby/query' +import { + listGithubRepositoryProviders, + listGitlabRepositoryProviders +} from '@/lib/tabby/query' import { buttonVariants } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import LoadingWrapper from '@/components/loading-wrapper' -export default function GitProvidersPage() { +import { useRepositoryKind } from '../hooks/use-repository-kind' + +interface GitProvidersListProps { + kind: RepositoryKind +} + +export default function RepositoryProvidersPage({ + kind +}: GitProvidersListProps) { + if (kind === RepositoryKind.Github) { + return + } + + if (kind === RepositoryKind.Gitlab) { + return + } + + return
404
+} + +function GithubProviders() { const [{ data, fetching }] = useQuery({ query: listGithubRepositoryProviders }) - const githubRepositoryProviders = data?.githubRepositoryProviders?.edges + const providers = data?.githubRepositoryProviders?.edges + + return +} + +function GitlabProviders() { + const [{ data, fetching }] = useQuery({ + query: listGitlabRepositoryProviders + }) + const providers = data?.gitlabRepositoryProviders?.edges + return +} + +interface RepositoryProvidersViewProps { + fetching: boolean + providers: + | Array<{ + node: { + id: string + displayName: string + status: RepositoryProviderStatus + } + }> + | undefined +} +function RepositoryProvidersView({ + fetching, + providers +}: RepositoryProvidersViewProps) { return ( - <> - - {githubRepositoryProviders?.length ? ( -
- -
- - Create - -
-
- ) : ( - - )} -
- + + {providers?.length ? ( + <> + + · + + ) : ( + + )} + ) } interface GitProvidersTableProps { - data: ListGithubRepositoryProvidersQuery['githubRepositoryProviders']['edges'] + data: RepositoryProvidersViewProps['providers'] } - const GitProvidersList: React.FC = ({ data }) => { return (
@@ -78,6 +122,20 @@ const GitProvidersList: React.FC = ({ data }) => { ) } +const CreateRepositoryProvider = () => { + const kind = useRepositoryKind() + return ( +
+ + Create + +
+ ) +} + function toStatusMessage(status: RepositoryProviderStatus) { switch (status) { case RepositoryProviderStatus.Ready: @@ -90,12 +148,13 @@ function toStatusMessage(status: RepositoryProviderStatus) { } const GitProvidersPlaceholder = () => { + const kind = useRepositoryKind() return (
No Data
Create diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/constants.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/constants.tsx new file mode 100644 index 000000000000..e518f3eaf1cc --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/constants.tsx @@ -0,0 +1,27 @@ +import { RepositoryKind } from '@/lib/gql/generates/graphql' + +export const REPOSITORY_KIND_METAS: Array<{ + name: string + enum: RepositoryKind + meta: { + domain: string + displayName: string + } +}> = [ + { + name: 'github', + enum: RepositoryKind.Github, + meta: { + domain: 'github.com', + displayName: 'GitHub' + } + }, + { + name: 'gitlab', + enum: RepositoryKind.Gitlab, + meta: { + domain: 'gitlab.com', + displayName: 'GitLab' + } + } +] diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/new-repository-form.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/add-repository-form.tsx similarity index 58% rename from ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/new-repository-form.tsx rename to ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/add-repository-form.tsx index f1d76bca2be2..99f2d3f86b09 100644 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/new-repository-form.tsx +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/add-repository-form.tsx @@ -5,8 +5,11 @@ import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import * as z from 'zod' -import { QueryResponseData, useMutation } from '@/lib/tabby/gql' -import { listGithubRepositories } from '@/lib/tabby/query' +import { + RepositoryKind, + RepositoryProviderStatus +} from '@/lib/gql/generates/graphql' +import { useMutation } from '@/lib/tabby/gql' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' import { @@ -31,7 +34,10 @@ import { PopoverTrigger } from '@/components/ui/popover' -import { updateGithubProvidedRepositoryActiveMutation } from '../query' +import { + updateGithubProvidedRepositoryActiveMutation, + updateGitlabProvidedRepositoryActiveMutation +} from '../query' const formSchema = z.object({ id: z.string() @@ -39,25 +45,50 @@ const formSchema = z.object({ type LinkRepositoryFormValues = z.infer -export default function LinkRepositoryForm({ - onCreated, - onCancel, - repositories -}: { +interface LinkRepositoryFormProps { + kind: RepositoryKind onCreated?: () => void onCancel: () => void + providerStatus: RepositoryProviderStatus | undefined repositories: - | QueryResponseData< - typeof listGithubRepositories - >['githubRepositories']['edges'] + | Array<{ + cursor: string + node: { + id: string + vendorId: string + name: string + gitUrl: string + active: boolean + } + }> | undefined -}) { +} + +export default function LinkRepositoryForm({ + kind, + onCreated, + onCancel, + repositories, + providerStatus +}: LinkRepositoryFormProps) { const [open, setOpen] = React.useState(false) const form = useForm({ resolver: zodResolver(formSchema) }) const { isSubmitting } = form.formState + + const emptyText = React.useMemo(() => { + switch (providerStatus) { + case RepositoryProviderStatus.Pending: + return 'Synchronizing Repositories...' + case RepositoryProviderStatus.Error: + return 'Synchronization Error with Repositories' + default: + return 'No repository found.' + } + }, [providerStatus]) + const updateGithubProvidedRepositoryActive = useMutation( updateGithubProvidedRepositoryActiveMutation, { @@ -71,11 +102,33 @@ export default function LinkRepositoryForm({ } ) + const updateGitlabProvidedRepositoryActive = useMutation( + updateGitlabProvidedRepositoryActiveMutation, + { + onCompleted(data) { + if (data?.updateGitlabProvidedRepositoryActive) { + form.reset({ id: undefined }) + onCreated?.() + } + }, + form + } + ) + const onSubmit = (values: LinkRepositoryFormValues) => { - return updateGithubProvidedRepositoryActive({ - id: values.id, - active: true - }) + if (kind === RepositoryKind.Github) { + return updateGithubProvidedRepositoryActive({ + id: values.id, + active: true + }) + } + + if (kind === RepositoryKind.Gitlab) { + return updateGitlabProvidedRepositoryActive({ + id: values.id, + active: true + }) + } } return ( @@ -115,28 +168,29 @@ export default function LinkRepositoryForm({ - No repository found. + {emptyText} - {repositories?.map(repo => ( - { - form.setValue('id', repo.node.id) - setOpen(false) - }} - > - - {repo.node.gitUrl} - - ))} + {providerStatus !== + RepositoryProviderStatus.Pending && + repositories?.map(repo => ( + { + form.setValue('id', repo.node.id) + setOpen(false) + }} + > + + {repo.node.gitUrl} + + ))} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/detail-page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/detail-page.tsx new file mode 100644 index 000000000000..c5c244cb1449 --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/detail-page.tsx @@ -0,0 +1,25 @@ +'use client' + +import React from 'react' + +import { RepositoryKind } from '@/lib/gql/generates/graphql' + +import { useRepositoryKind } from '../../hooks/use-repository-kind' +import GithubProviderDetail from './github-provider-detail' +import GitlabProviderDetail from './gitlab-provider-detail' + +const DetailPage: React.FC = () => { + const kind = useRepositoryKind() + + if (kind === RepositoryKind.Github) { + return + } + + if (kind === RepositoryKind.Gitlab) { + return + } + + return
404
+} + +export default DetailPage diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/detail.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/github-provider-detail.tsx similarity index 91% rename from ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/detail.tsx rename to ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/github-provider-detail.tsx index deaa6b6e8d2f..3766cc6d8f14 100644 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/detail.tsx +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/github-provider-detail.tsx @@ -6,7 +6,10 @@ import { toast } from 'sonner' import { useQuery } from 'urql' import { DEFAULT_PAGE_SIZE } from '@/lib/constants' -import { RepositoryProviderStatus } from '@/lib/gql/generates/graphql' +import { + RepositoryKind, + RepositoryProviderStatus +} from '@/lib/gql/generates/graphql' import { QueryResponseData, QueryVariables, useMutation } from '@/lib/tabby/gql' import { listGithubRepositories, @@ -36,14 +39,14 @@ import LoadingWrapper from '@/components/loading-wrapper' import { ListSkeleton } from '@/components/skeleton' import { updateGithubProvidedRepositoryActiveMutation } from '../query' -import LinkRepositoryForm from './new-repository-form' -import { UpdateProviderForm } from './provider-detail-form' +import LinkRepositoryForm from './add-repository-form' +import { UpdateProviderForm } from './update-github-provider-form' type GithubRepositories = QueryResponseData< typeof listGithubRepositories >['githubRepositories']['edges'] -const DetailPage: React.FC = () => { +const GithubProviderDetail: React.FC = () => { const searchParams = useSearchParams() const router = useRouter() const id = searchParams.get('id')?.toString() ?? '' @@ -99,7 +102,10 @@ const DetailPage: React.FC = () => {
- +
@@ -119,8 +125,9 @@ function toStatusBadge(status: RepositoryProviderStatus) { const LinkedRepoTable: React.FC<{ data: GithubRepositories | undefined + providerStatus: RepositoryProviderStatus | undefined onDelete?: () => void -}> = ({ data, onDelete }) => { +}> = ({ data, onDelete, providerStatus }) => { const updateGithubProvidedRepositoryActive = useMutation( updateGithubProvidedRepositoryActiveMutation, { @@ -175,6 +182,8 @@ const LinkedRepoTable: React.FC<{ onCancel={() => setOpen(false)} onCreated={onCreated} repositories={inactiveRepos} + kind={RepositoryKind.Github} + providerStatus={providerStatus} /> @@ -221,9 +230,7 @@ const LinkedRepoTable: React.FC<{ ) } -export function useAllProvidedRepositories( - id: string -): [GithubRepositories, boolean] { +function useAllProvidedRepositories(id: string): [GithubRepositories, boolean] { const [queryVariables, setQueryVariables] = useState< QueryVariables >({ providerIds: [id], first: DEFAULT_PAGE_SIZE }) @@ -256,4 +263,4 @@ export function useAllProvidedRepositories( return [data?.githubRepositories?.edges ?? [], !isAllLoaded] } -export default DetailPage +export default GithubProviderDetail diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/gitlab-provider-detail.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/gitlab-provider-detail.tsx new file mode 100644 index 000000000000..790254f3b994 --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/gitlab-provider-detail.tsx @@ -0,0 +1,266 @@ +'use client' + +import React, { useEffect, useMemo, useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' +import { toast } from 'sonner' +import { useQuery } from 'urql' + +import { DEFAULT_PAGE_SIZE } from '@/lib/constants' +import { + RepositoryKind, + RepositoryProviderStatus +} from '@/lib/gql/generates/graphql' +import { QueryResponseData, QueryVariables, useMutation } from '@/lib/tabby/gql' +import { + listGitlabRepositories, + listGitlabRepositoryProviders +} from '@/lib/tabby/query' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { CardContent, CardTitle } from '@/components/ui/card' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger +} from '@/components/ui/dialog' +import { IconChevronLeft, IconPlus, IconTrash } from '@/components/ui/icons' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/ui/table' +import LoadingWrapper from '@/components/loading-wrapper' +import { ListSkeleton } from '@/components/skeleton' + +import { updateGitlabProvidedRepositoryActiveMutation } from '../query' +import LinkRepositoryForm from './add-repository-form' +import { UpdateProviderForm } from './update-gitlab-provider-form' + +type GitlabRepositories = QueryResponseData< + typeof listGitlabRepositories +>['gitlabRepositories']['edges'] + +const GitlabProviderDetail: React.FC = () => { + const searchParams = useSearchParams() + const router = useRouter() + const id = searchParams.get('id')?.toString() ?? '' + const [{ data, fetching }] = useQuery({ + query: listGitlabRepositoryProviders, + variables: { ids: [id] }, + pause: !id + }) + const provider = data?.gitlabRepositoryProviders?.edges?.[0]?.node + const [gitlabRepositories, isGitlabRepositoriesLoading] = + useAllProvidedRepositories(id) + + const onDeleteProvider = () => { + router.back() + } + + if (!id || (!!id && !fetching && !provider)) { + return ( +
+ Provider not found +
+ ) + } + + return ( + + +
+ + {provider?.displayName} +
+
+
+ {provider && toStatusBadge(provider.status)} +
+
+
+ + }> + + + + +
+ + + +
+
+ ) +} + +function toStatusBadge(status: RepositoryProviderStatus) { + switch (status) { + case RepositoryProviderStatus.Ready: + return Ready + case RepositoryProviderStatus.Error: + return Error + case RepositoryProviderStatus.Error: + return Pending + } +} + +const LinkedRepoTable: React.FC<{ + data: GitlabRepositories | undefined + providerStatus: RepositoryProviderStatus | undefined + onDelete?: () => void +}> = ({ data, onDelete, providerStatus }) => { + const updateGitlabProvidedRepositoryActive = useMutation( + updateGitlabProvidedRepositoryActiveMutation, + { + onCompleted(data) { + if (data?.updateGitlabProvidedRepositoryActive) { + onDelete?.() + } + }, + onError(error) { + toast.error(error.message || 'Failed to delete') + } + } + ) + + const handleDelete = async (id: string) => { + updateGitlabProvidedRepositoryActive({ + id, + active: false + }) + } + + const [open, setOpen] = React.useState(false) + + const onCreated = () => { + setOpen(false) + } + + const activeRepos = useMemo(() => { + return data?.filter(item => item.node.active) + }, [data]) + + const inactiveRepos = useMemo(() => { + return data?.filter(item => !item.node.active) + }, [data]) + + return ( + + + + Name + URL + + + + + Add new repository + + Add new GitLab repository from this provider + + + setOpen(false)} + onCreated={onCreated} + repositories={inactiveRepos} + kind={RepositoryKind.Gitlab} + providerStatus={providerStatus} + /> + + + + + + + + + + {activeRepos?.length ? ( + <> + {activeRepos?.map(x => { + return ( + + {x.node.name} + {x.node.gitUrl} + +
+ +
+
+
+ ) + })} + + ) : ( + + + No repositories yet. + + + )} +
+
+ ) +} + +function useAllProvidedRepositories(id: string): [GitlabRepositories, boolean] { + const [queryVariables, setQueryVariables] = useState< + QueryVariables + >({ providerIds: [id], first: DEFAULT_PAGE_SIZE }) + + const [isAllLoaded, setIsAllLoaded] = useState(!id) + + const [{ data, fetching }] = useQuery({ + query: listGitlabRepositories, + variables: queryVariables, + pause: !id + }) + + useEffect(() => { + if (isAllLoaded) return + if (!fetching && data) { + const pageInfo = data?.gitlabRepositories?.pageInfo + + if (pageInfo?.hasNextPage) { + setQueryVariables({ + providerIds: [id], + first: DEFAULT_PAGE_SIZE, + after: pageInfo.endCursor + }) + } else { + setIsAllLoaded(true) + } + } + }, [fetching, data]) + + return [data?.gitlabRepositories?.edges ?? [], !isAllLoaded] +} + +export default GitlabProviderDetail diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/provider-detail-form.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/update-github-provider-form.tsx similarity index 93% rename from ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/provider-detail-form.tsx rename to ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/update-github-provider-form.tsx index 24a4b02158a6..cea739a793d1 100644 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/components/provider-detail-form.tsx +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/components/update-github-provider-form.tsx @@ -7,13 +7,13 @@ import { graphql } from '@/lib/gql/generates' import { useMutation } from '@/lib/tabby/gql' import { - GithubProviderForm, + ProviderForm, RepositoryProviderFormValues, useRepositoryProviderForm -} from '../../components/github-form' +} from '../../components/common-provider-form' const deleteGithubRepositoryProviderMutation = graphql(/* GraphQL */ ` - mutation DeleteRepositoryProvider($id: ID!) { + mutation DeleteGithubRepositoryProvider($id: ID!) { deleteGithubRepositoryProvider(id: $id) } `) @@ -80,7 +80,7 @@ export const UpdateProviderForm: React.FC = ({ } return ( - + onSuccess?: () => void + onDelete: () => void +} + +export const UpdateProviderForm: React.FC = ({ + defaultValues, + onSuccess, + onDelete, + id +}) => { + const form = useRepositoryProviderForm(defaultValues) + + const deleteGitlabRepositoryProvider = useMutation( + deleteGitlabRepositoryProviderMutation + ) + + const updateGitlabRepositoryProvider = useMutation( + updateGitlabRepositoryProviderMutation, + { + form, + onCompleted(values) { + if (values?.updateGitlabRepositoryProvider) { + toast.success('Updated GitLab repository provider successfully') + form?.reset(form?.getValues()) + onSuccess?.() + } + } + } + ) + + const onSubmit = async (values: RepositoryProviderFormValues) => { + await updateGitlabRepositoryProvider({ + input: { + id, + ...values + } + }) + } + + const handleDeleteRepositoryProvider = async () => { + const res = await deleteGitlabRepositoryProvider({ id }) + if (res?.data?.deleteGitlabRepositoryProvider) { + onDelete?.() + } else { + toast.error( + res?.error?.message || 'Failed to delete GitHub repository provider' + ) + } + } + + return ( + + ) +} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/page.tsx new file mode 100644 index 000000000000..edc55eb54282 --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/page.tsx @@ -0,0 +1,10 @@ +import { REPOSITORY_KIND_METAS } from '../constants' +import ProviderDetail from './components/detail-page' + +export function generateStaticParams() { + return REPOSITORY_KIND_METAS.map(item => ({ kind: item.enum.toLowerCase() })) +} + +export default function IndexPage() { + return +} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/query.ts b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/query.ts new file mode 100644 index 000000000000..4d430a70d7ee --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/detail/query.ts @@ -0,0 +1,15 @@ +import { graphql } from '@/lib/gql/generates' + +export const updateGithubProvidedRepositoryActiveMutation = + graphql(/* GraphQL */ ` + mutation UpdateGithubProvidedRepositoryActive($id: ID!, $active: Boolean!) { + updateGithubProvidedRepositoryActive(id: $id, active: $active) + } + `) + +export const updateGitlabProvidedRepositoryActiveMutation = + graphql(/* GraphQL */ ` + mutation UpdateGitlabProvidedRepositoryActive($id: ID!, $active: Boolean!) { + updateGitlabProvidedRepositoryActive(id: $id, active: $active) + } + `) diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/hooks/use-repository-kind.ts b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/hooks/use-repository-kind.ts new file mode 100644 index 000000000000..0f5dd8b1a8a6 --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/hooks/use-repository-kind.ts @@ -0,0 +1,17 @@ +import { useParams } from 'next/navigation' +import { findIndex } from 'lodash-es' + +import { REPOSITORY_KIND_METAS } from '../constants' + +export function useRepositoryKind() { + const params = useParams<{ kind?: string }>() + const kindIndex = findIndex( + REPOSITORY_KIND_METAS, + item => item.enum.toLocaleLowerCase() === params.kind?.toLocaleLowerCase() + ) + const kind = + kindIndex > -1 + ? REPOSITORY_KIND_METAS[kindIndex].enum + : REPOSITORY_KIND_METAS[0].enum + return kind +} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/components/new-page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/components/new-page.tsx new file mode 100644 index 000000000000..eb92bdd831dd --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/components/new-page.tsx @@ -0,0 +1,81 @@ +'use client' + +import React from 'react' +import { useRouter } from 'next/navigation' + +import { graphql } from '@/lib/gql/generates' +import { RepositoryKind } from '@/lib/gql/generates/graphql' +import { useMutation } from '@/lib/tabby/gql' + +import { + ProviderForm, + RepositoryProviderFormValues, + useRepositoryProviderForm +} from '../../components/common-provider-form' +import { useRepositoryKind } from '../../hooks/use-repository-kind' + +const createGithubRepositoryProvider = graphql(/* GraphQL */ ` + mutation CreateGithubRepositoryProvider( + $input: CreateRepositoryProviderInput! + ) { + createGithubRepositoryProvider(input: $input) + } +`) + +const createGitlabRepositoryProvider = graphql(/* GraphQL */ ` + mutation CreateGitlabRepositoryProvider( + $input: CreateRepositoryProviderInput! + ) { + createGitlabRepositoryProvider(input: $input) + } +`) + +export const NewProvider = () => { + const kind = useRepositoryKind() + const router = useRouter() + const form = useRepositoryProviderForm() + // for github + const createGithubRepositoryProviderMutation = useMutation( + createGithubRepositoryProvider, + { + onCompleted(data) { + if (data?.createGithubRepositoryProvider) { + router.back() + } + }, + form + } + ) + + // for gitlab + const createGitlabRepositoryProviderMutation = useMutation( + createGitlabRepositoryProvider, + { + onCompleted(data) { + if (data?.createGitlabRepositoryProvider) { + router.back() + } + }, + form + } + ) + + const handleSubmit = async (values: RepositoryProviderFormValues) => { + if (kind === RepositoryKind.Github) { + return createGithubRepositoryProviderMutation({ + input: values + }) + } + if (kind === RepositoryKind.Gitlab) { + return createGitlabRepositoryProviderMutation({ + input: values + }) + } + } + + return ( +
+ +
+ ) +} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/page.tsx new file mode 100644 index 000000000000..b439814811c7 --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/new/page.tsx @@ -0,0 +1,10 @@ +import { REPOSITORY_KIND_METAS } from '../constants' +import { NewProvider } from './components/new-page' + +export function generateStaticParams() { + return REPOSITORY_KIND_METAS.map(item => ({ kind: item.enum.toLowerCase() })) +} + +export default function IndexPage() { + return +} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/page.tsx new file mode 100644 index 000000000000..ee72127cb0e2 --- /dev/null +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/[kind]/page.tsx @@ -0,0 +1,28 @@ +import { NextPage } from 'next' +import { findIndex } from 'lodash-es' + +import GithubPage from './components/provider-list' +import { REPOSITORY_KIND_METAS } from './constants' + +type Params = { + kind: string +} + +export function generateStaticParams() { + return REPOSITORY_KIND_METAS.map(item => ({ kind: item.enum.toLowerCase() })) +} + +const IntegrateGitPage: NextPage<{ params: Params }> = ({ params }) => { + const kindIndex = findIndex( + REPOSITORY_KIND_METAS, + item => item.enum.toLocaleLowerCase() === params.kind?.toLocaleLowerCase() + ) + const kind = + kindIndex > -1 + ? REPOSITORY_KIND_METAS[kindIndex].enum + : REPOSITORY_KIND_METAS[0].enum + + return +} + +export default IntegrateGitPage diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/components/tabs-header.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/components/tabs-header.tsx index 18585b8e41c7..5c78ee5b3834 100644 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/components/tabs-header.tsx +++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/components/tabs-header.tsx @@ -1,21 +1,25 @@ 'use client' import Link from 'next/link' -import { usePathname } from 'next/navigation' +import { useParams } from 'next/navigation' -import { IconGitHub } from '@/components/ui/icons' +import { RepositoryKind } from '@/lib/gql/generates/graphql' +import { IconGit, IconGitHub, IconGitLab } from '@/components/ui/icons' import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' export default function GitTabsHeader() { - const pathname = usePathname() - const defualtValue = pathname.indexOf('github') >= 0 ? 'github' : 'git' + const params = useParams<{ kind?: RepositoryKind }>() + const defaultValue = params?.kind ? params.kind?.toLowerCase() : 'git' return ( - +
- + - Git + + + Git + @@ -23,6 +27,12 @@ export default function GitTabsHeader() { GitHub + + + + GitLab + +
diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/page.tsx deleted file mode 100644 index a631dd12d106..000000000000 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import ProviderDetail from './components/detail' - -export default function IndexPage() { - return -} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/query.ts b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/query.ts deleted file mode 100644 index 20a3bd0cb23d..000000000000 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/detail/query.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { graphql } from '@/lib/gql/generates' - -const updateGithubProvidedRepositoryActiveMutation = graphql(/* GraphQL */ ` - mutation UpdateGithubProvidedRepositoryActive($id: ID!, $active: Boolean!) { - updateGithubProvidedRepositoryActive(id: $id, active: $active) - } -`) - -export { updateGithubProvidedRepositoryActiveMutation } diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/components/new-page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/components/new-page.tsx deleted file mode 100644 index cabe7e28d05a..000000000000 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/components/new-page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' - -import React from 'react' -import { useRouter } from 'next/navigation' - -import { graphql } from '@/lib/gql/generates' -import { useMutation } from '@/lib/tabby/gql' - -import { - GithubProviderForm, - RepositoryProviderFormValues, - useRepositoryProviderForm -} from '../../components/github-form' - -const createGithubRepositoryProvider = graphql(/* GraphQL */ ` - mutation CreateGithubRepositoryProvider( - $input: CreateRepositoryProviderInput! - ) { - createGithubRepositoryProvider(input: $input) - } -`) - -export const NewProvider = () => { - const router = useRouter() - const form = useRepositoryProviderForm() - const createGithubRepositoryProviderMutation = useMutation( - createGithubRepositoryProvider, - { - onCompleted(data) { - if (data?.createGithubRepositoryProvider) { - router.back() - } - }, - form - } - ) - - const handleSubmit = async (values: RepositoryProviderFormValues) => { - return createGithubRepositoryProviderMutation({ - input: values - }) - } - - return ( -
- -
- ) -} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/page.tsx deleted file mode 100644 index 22c998b7c6d7..000000000000 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/new/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { NewProvider } from './components/new-page' - -export default function IndexPage() { - return -} diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/page.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/page.tsx deleted file mode 100644 index cea9a7d5bbf4..000000000000 --- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/repository/github/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import GithubPage from './components/github' - -export default function Github() { - return -} diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx index a636692de970..815189c08547 100644 --- a/ee/tabby-ui/components/ui/icons.tsx +++ b/ee/tabby-ui/components/ui/icons.tsx @@ -1008,6 +1008,32 @@ function IconGitLab({ className, ...props }: React.ComponentProps<'svg'>) { ) } +function IconGit({ className, ...props }: React.ComponentProps<'svg'>) { + return ( + + + + + + + + + + + ) +} + function IconLightingBolt({ className, ...props @@ -1380,6 +1406,7 @@ export { IconOpenAI, IconVercel, IconGitHub, + IconGit, IconSeparator, IconArrowDown, IconArrowRight, diff --git a/ee/tabby-ui/lib/tabby/query.ts b/ee/tabby-ui/lib/tabby/query.ts index dfb099da152b..89fcde740fff 100644 --- a/ee/tabby-ui/lib/tabby/query.ts +++ b/ee/tabby-ui/lib/tabby/query.ts @@ -235,3 +235,72 @@ export const listGithubRepositories = graphql(/* GraphQL */ ` } } `) + +export const listGitlabRepositoryProviders = graphql(/* GraphQL */ ` + query ListGitlabRepositoryProviders( + $ids: [ID!] + $after: String + $before: String + $first: Int + $last: Int + ) { + gitlabRepositoryProviders( + ids: $ids + after: $after + before: $before + first: $first + last: $last + ) { + edges { + node { + id + displayName + status + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + } +`) + +export const listGitlabRepositories = graphql(/* GraphQL */ ` + query ListGitlabRepositories( + $providerIds: [ID!]! + $after: String + $before: String + $first: Int + $last: Int + ) { + gitlabRepositories( + providerIds: $providerIds + after: $after + before: $before + first: $first + last: $last + ) { + edges { + node { + id + vendorId + gitlabRepositoryProviderId + name + gitUrl + active + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + } +`)