diff --git a/packages/web/src/app/[domain]/connections/[id]/components/connectionError.tsx b/packages/web/src/app/[domain]/connections/[id]/components/connectionError.tsx
deleted file mode 100644
index 928cdca7..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/connectionError.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-"use client"
-
-import { BackendError } from "@sourcebot/error";
-import { Prisma } from "@sourcebot/db";
-
-export function DisplayConnectionError({ syncStatusMetadata, onSecretsClick }: { syncStatusMetadata: Prisma.JsonValue, onSecretsClick: () => void }) {
- const errorCode = syncStatusMetadata && typeof syncStatusMetadata === 'object' && 'error' in syncStatusMetadata
- ? (syncStatusMetadata.error as string)
- : undefined;
-
- switch (errorCode) {
- case BackendError.CONNECTION_SYNC_INVALID_TOKEN:
- return
- case BackendError.CONNECTION_SYNC_SECRET_DNE:
- return
- case BackendError.CONNECTION_SYNC_SYSTEM_ERROR:
- return
- case BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS:
- return
- default:
- return
- }
-}
-
-function SecretNotFoundError({ syncStatusMetadata, onSecretsClick }: { syncStatusMetadata: Prisma.JsonValue, onSecretsClick: () => void }) {
- const secretKey = syncStatusMetadata && typeof syncStatusMetadata === 'object' && 'secretKey' in syncStatusMetadata
- ? (syncStatusMetadata.secretKey as string)
- : undefined;
-
- return (
-
-
Secret Not Found
-
- The secret key provided for this connection was not found. Please ensure your config is referencing a secret
- that exists in your{" "}
-
- organization's secrets
-
- , and try again.
-
- {secretKey && (
-
- Secret Key: {secretKey}
-
- )}
-
- );
-}
-
-function InvalidTokenError({ syncStatusMetadata, onSecretsClick }: { syncStatusMetadata: Prisma.JsonValue, onSecretsClick: () => void }) {
- const secretKey = syncStatusMetadata && typeof syncStatusMetadata === 'object' && 'secretKey' in syncStatusMetadata
- ? (syncStatusMetadata.secretKey as string)
- : undefined;
-
- return (
-
-
Invalid Authentication Token
-
- The authentication token provided for this connection is invalid. Please update your config with a valid token and try again.
-
- {secretKey && (
-
- Secret Key: {secretKey}
-
- )}
-
- );
-}
-
-function SystemError() {
- return (
-
-
System Error
-
- An error occurred while syncing this connection. Please try again later.
-
-
- )
-}
-
-function FailedToFetchGerritProjects({ syncStatusMetadata }: { syncStatusMetadata: Prisma.JsonValue}) {
- const status = syncStatusMetadata && typeof syncStatusMetadata === 'object' && 'status' in syncStatusMetadata
- ? (syncStatusMetadata.status as number)
- : undefined;
-
- return (
-
-
Failed to Fetch Gerrit Projects
-
- An error occurred while syncing this connection. Please try again later.
-
- {status && (
-
- Status: {status}
-
- )}
-
- )
-}
-
-function UnknownError() {
- return (
-
-
Unknown Error
-
- An unknown error occurred while syncing this connection. Please try again later.
-
-
- )
-}
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/deleteConnectionSetting.tsx b/packages/web/src/app/[domain]/connections/[id]/components/deleteConnectionSetting.tsx
deleted file mode 100644
index 1eb2488d..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/deleteConnectionSetting.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-'use client';
-
-import { Button } from "@/components/ui/button";
-import { useCallback, useState } from "react";
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
- AlertDialogTrigger,
- } from "@/components/ui/alert-dialog";
-import { deleteConnection } from "@/actions";
-import { Loader2 } from "lucide-react";
-import { isServiceError } from "@/lib/utils";
-import { useToast } from "@/components/hooks/use-toast";
-import { useRouter } from "next/navigation";
-import { useDomain } from "@/hooks/useDomain";
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-
-interface DeleteConnectionSettingProps {
- connectionId: number;
- disabled?: boolean;
-}
-
-export const DeleteConnectionSetting = ({
- connectionId,
- disabled,
-}: DeleteConnectionSettingProps) => {
- const [isDialogOpen, setIsDialogOpen] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const domain = useDomain();
- const { toast } = useToast();
- const router = useRouter();
- const captureEvent = useCaptureEvent();
-
- const handleDelete = useCallback(() => {
- setIsDialogOpen(false);
- setIsLoading(true);
- deleteConnection(connectionId, domain)
- .then((response) => {
- if (isServiceError(response)) {
- toast({
- description: `❌ Failed to delete connection. Reason: ${response.message}`
- });
- captureEvent('wa_connection_delete_fail', {
- error: response.errorCode,
- });
- } else {
- toast({
- description: `✅ Connection deleted successfully.`
- });
- captureEvent('wa_connection_delete_success', {});
- router.replace(`/${domain}/connections`);
- router.refresh();
- }
- })
- .finally(() => {
- setIsLoading(false);
- });
- }, [connectionId, domain, router, toast, captureEvent]);
-
- return (
-
-
Delete Connection
-
- Permanently delete this connection from Sourcebot. All linked repositories that are not linked to any other connection will also be deleted.
-
-
-
-
-
- {isLoading && }
- Delete
-
-
-
-
- Are you sure?
-
- This action cannot be undone.
-
-
-
- Cancel
- Yes, delete connection
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/displayNameSetting.tsx b/packages/web/src/app/[domain]/connections/[id]/components/displayNameSetting.tsx
deleted file mode 100644
index e7f795e8..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/displayNameSetting.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-'use client';
-
-import { updateConnectionDisplayName } from "@/actions";
-import { useToast } from "@/components/hooks/use-toast";
-import { Button } from "@/components/ui/button";
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { useDomain } from "@/hooks/useDomain";
-import { isServiceError } from "@/lib/utils";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { Loader2 } from "lucide-react";
-import { useRouter } from "next/navigation";
-import { useCallback, useState } from "react";
-import { useForm } from "react-hook-form";
-import { z } from "zod";
-
-const formSchema = z.object({
- name: z.string().min(1),
-});
-
-interface DisplayNameSettingProps {
- connectionId: number;
- name: string;
- disabled?: boolean;
-}
-
-export const DisplayNameSetting = ({
- connectionId,
- name,
- disabled,
-}: DisplayNameSettingProps) => {
- const { toast } = useToast();
- const router = useRouter();
- const domain = useDomain();
- const form = useForm>({
- resolver: zodResolver(formSchema),
- defaultValues: {
- name,
- },
- });
-
- const [isLoading, setIsLoading] = useState(false);
- const onSubmit = useCallback((data: z.infer) => {
- setIsLoading(true);
- updateConnectionDisplayName(connectionId, data.name, domain)
- .then((response) => {
- if (isServiceError(response)) {
- toast({
- description: `❌ Failed to rename connection. Reason: ${response.message}`
- });
- } else {
- toast({
- description: `✅ Connection renamed successfully.`
- });
- router.refresh();
- }
- }).finally(() => {
- setIsLoading(false);
- });
- }, [connectionId, domain, router, toast]);
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/notFoundWarning.tsx b/packages/web/src/app/[domain]/connections/[id]/components/notFoundWarning.tsx
deleted file mode 100644
index c65d4f10..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/notFoundWarning.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-'use client';
-
-import { AlertTriangle } from "lucide-react"
-import { Prisma, ConnectionSyncStatus } from "@sourcebot/db"
-import { SyncStatusMetadataSchema } from "@/lib/syncStatusMetadataSchema"
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-import { ReloadIcon } from "@radix-ui/react-icons";
-import { Button } from "@/components/ui/button";
-
-interface NotFoundWarningProps {
- syncStatus: ConnectionSyncStatus
- syncStatusMetadata: Prisma.JsonValue
- onSecretsClick: () => void
- connectionType: string
- onRetrySync: () => void
-}
-
-export const NotFoundWarning = ({ syncStatus, syncStatusMetadata, onSecretsClick, connectionType, onRetrySync }: NotFoundWarningProps) => {
- const captureEvent = useCaptureEvent();
-
- const parseResult = SyncStatusMetadataSchema.safeParse(syncStatusMetadata);
- if (syncStatus !== ConnectionSyncStatus.SYNCED_WITH_WARNINGS || !parseResult.success || !parseResult.data.notFound) {
- return null;
- }
-
- const { notFound } = parseResult.data;
-
- if (notFound.users.length === 0 && notFound.orgs.length === 0 && notFound.repos.length === 0) {
- return null;
- } else {
- captureEvent('wa_connection_not_found_warning_displayed', {});
- }
-
- return (
-
-
-
-
Unable to fetch all references
-
-
- Some requested references couldn't be found. Please ensure you've provided the information listed below correctly, and that you've provided a{" "}
-
- valid token
- {" "}
- to access them if they're private.
-
-
- {notFound.users.length > 0 && (
-
- Users:
- {notFound.users.join(', ')}
-
- )}
- {notFound.orgs.length > 0 && (
-
- {connectionType === "gitlab" ? "Groups" : "Organizations"}:
- {notFound.orgs.join(', ')}
-
- )}
- {notFound.repos.length > 0 && (
-
- {connectionType === "gitlab" ? "Projects" : "Repositories"}:
- {notFound.repos.join(', ')}
-
- )}
-
-
-
-
- Retry Sync
-
-
-
- )
-}
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/overview.tsx b/packages/web/src/app/[domain]/connections/[id]/components/overview.tsx
deleted file mode 100644
index 6b7dbbaf..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/overview.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-'use client';
-
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
-import { DisplayConnectionError } from "./connectionError"
-import { NotFoundWarning } from "./notFoundWarning"
-import { useDomain } from "@/hooks/useDomain";
-import { useRouter } from "next/navigation";
-import { useCallback } from "react";
-import { useQuery } from "@tanstack/react-query";
-import { flagConnectionForSync, getConnectionInfo } from "@/actions";
-import { isServiceError, unwrapServiceError } from "@/lib/utils";
-import { env } from "@/env.mjs";
-import { ConnectionSyncStatus } from "@sourcebot/db";
-import { FiLoader } from "react-icons/fi";
-import { CircleCheckIcon, AlertTriangle, CircleXIcon } from "lucide-react";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { ReloadIcon } from "@radix-ui/react-icons";
-import { toast } from "@/components/hooks/use-toast";
-
-interface OverviewProps {
- connectionId: number;
-}
-
-export const Overview = ({ connectionId }: OverviewProps) => {
- const captureEvent = useCaptureEvent();
- const domain = useDomain();
- const router = useRouter();
-
- const { data: connection, isPending, error, refetch } = useQuery({
- queryKey: ['connection', domain, connectionId],
- queryFn: () => unwrapServiceError(getConnectionInfo(connectionId, domain)),
- refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
- });
-
- const handleSecretsNavigation = useCallback(() => {
- captureEvent('wa_connection_secrets_navigation_pressed', {});
- router.push(`/${domain}/secrets`);
- }, [captureEvent, domain, router]);
-
- const onRetrySync = useCallback(async () => {
- const result = await flagConnectionForSync(connectionId, domain);
- if (isServiceError(result)) {
- toast({
- description: `❌ Failed to flag connection for sync.`,
- });
- captureEvent('wa_connection_retry_sync_fail', {
- error: result.errorCode,
- });
- } else {
- toast({
- description: "✅ Connection flagged for sync.",
- });
- captureEvent('wa_connection_retry_sync_success', {});
- refetch();
- }
- }, [connectionId, domain, captureEvent, refetch]);
-
-
- if (error) {
- return
- {`Error loading connection. Reason: ${error.message}`}
-
- }
-
- if (isPending) {
- return (
-
- {Array.from({ length: 4 }).map((_, i) => (
-
- ))}
-
- )
- }
-
- return (
-
-
-
-
Connection Type
-
{connection.connectionType}
-
-
-
Last Synced At
-
- {connection.syncedAt ? new Date(connection.syncedAt).toLocaleDateString() : "never"}
-
-
-
-
Linked Repositories
-
{connection.numLinkedRepos}
-
-
-
Status
-
- {connection.syncStatus === "FAILED" ? (
-
- captureEvent('wa_connection_failed_status_hover', {})}>
-
-
-
-
-
-
- ) : (
-
- )}
- {connection.syncStatus === "FAILED" && (
-
-
- Retry Sync
-
- )}
-
-
-
-
-
- )
-}
-
-const SyncStatusBadge = ({ status }: { status: ConnectionSyncStatus }) => {
- return (
-
- {status === ConnectionSyncStatus.SYNC_NEEDED || status === ConnectionSyncStatus.IN_SYNC_QUEUE ? (
- <> Sync queued>
- ) : status === ConnectionSyncStatus.SYNCING ? (
- <> Syncing>
- ) : status === ConnectionSyncStatus.SYNCED ? (
- Synced
- ) : status === ConnectionSyncStatus.SYNCED_WITH_WARNINGS ? (
- Synced with warnings
- ) : status === ConnectionSyncStatus.FAILED ? (
- <> Sync failed>
- ) : null}
-
- )
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/repoList.tsx b/packages/web/src/app/[domain]/connections/[id]/components/repoList.tsx
deleted file mode 100644
index cab5c4b1..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/repoList.tsx
+++ /dev/null
@@ -1,229 +0,0 @@
-'use client';
-
-import { useDomain } from "@/hooks/useDomain";
-import { useQuery } from "@tanstack/react-query";
-import { flagReposForIndex, getConnectionInfo, getRepos } from "@/actions";
-import { RepoListItem } from "./repoListItem";
-import { isServiceError, unwrapServiceError } from "@/lib/utils";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { ConnectionSyncStatus, RepoIndexingStatus } from "@sourcebot/db";
-import { Search, Loader2 } from "lucide-react";
-import { Input } from "@/components/ui/input";
-import { useCallback, useMemo, useState } from "react";
-import { RepoListItemSkeleton } from "./repoListItemSkeleton";
-import { env } from "@/env.mjs";
-import { Button } from "@/components/ui/button";
-import { useRouter } from "next/navigation";
-import { MultiSelect } from "@/components/ui/multi-select";
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-import { useToast } from "@/components/hooks/use-toast";
-
-interface RepoListProps {
- connectionId: number;
-}
-
-const getPriority = (status: RepoIndexingStatus) => {
- switch (status) {
- case RepoIndexingStatus.FAILED:
- return 0
- case RepoIndexingStatus.IN_INDEX_QUEUE:
- case RepoIndexingStatus.INDEXING:
- return 1
- case RepoIndexingStatus.INDEXED:
- return 2
- default:
- return 3
- }
-}
-
-const convertIndexingStatus = (status: RepoIndexingStatus) => {
- switch (status) {
- case RepoIndexingStatus.FAILED:
- return 'failed';
- case RepoIndexingStatus.NEW:
- return 'waiting';
- case RepoIndexingStatus.IN_INDEX_QUEUE:
- case RepoIndexingStatus.INDEXING:
- return 'running';
- case RepoIndexingStatus.INDEXED:
- return 'succeeded';
- default:
- return 'unknown';
- }
-}
-
-export const RepoList = ({ connectionId }: RepoListProps) => {
- const domain = useDomain();
- const router = useRouter();
- const { toast } = useToast();
- const [searchQuery, setSearchQuery] = useState("");
- const [selectedStatuses, setSelectedStatuses] = useState([]);
- const captureEvent = useCaptureEvent();
- const [isRetryAllFailedReposLoading, setIsRetryAllFailedReposLoading] = useState(false);
-
- const { data: unfilteredRepos, isPending: isReposPending, error: reposError, refetch: refetchRepos } = useQuery({
- queryKey: ['repos', domain, connectionId],
- queryFn: async () => {
- const repos = await unwrapServiceError(getRepos());
- return repos.sort((a, b) => {
- const priorityA = getPriority(a.repoIndexingStatus);
- const priorityB = getPriority(b.repoIndexingStatus);
-
- // First sort by priority
- if (priorityA !== priorityB) {
- return priorityA - priorityB;
- }
-
- // If same priority, sort by indexedAt
- return new Date(a.indexedAt ?? new Date()).getTime() - new Date(b.indexedAt ?? new Date()).getTime();
- });
- },
- refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
- });
-
- const { data: connection, isPending: isConnectionPending, error: isConnectionError } = useQuery({
- queryKey: ['connection', domain, connectionId],
- queryFn: () => unwrapServiceError(getConnectionInfo(connectionId, domain)),
- })
-
-
- const failedRepos = useMemo(() => {
- return unfilteredRepos?.filter((repo) => repo.repoIndexingStatus === RepoIndexingStatus.FAILED) ?? [];
- }, [unfilteredRepos]);
-
-
- const onRetryAllFailedRepos = useCallback(() => {
- if (failedRepos.length === 0) {
- return;
- }
-
- setIsRetryAllFailedReposLoading(true);
- flagReposForIndex(failedRepos.map((repo) => repo.repoId))
- .then((response) => {
- if (isServiceError(response)) {
- captureEvent('wa_connection_retry_all_failed_repos_fail', {});
- toast({
- description: `❌ Failed to flag repositories for indexing. Reason: ${response.message}`,
- });
- } else {
- captureEvent('wa_connection_retry_all_failed_repos_success', {});
- toast({
- description: `✅ ${failedRepos.length} repositories flagged for indexing.`,
- });
- }
- })
- .then(() => { refetchRepos() })
- .finally(() => {
- setIsRetryAllFailedReposLoading(false);
- });
- }, [captureEvent, failedRepos, refetchRepos, toast]);
-
- const filteredRepos = useMemo(() => {
- if (isServiceError(unfilteredRepos)) {
- return unfilteredRepos;
- }
-
- const searchLower = searchQuery.toLowerCase();
- return unfilteredRepos?.filter((repo) => {
- return repo.repoName.toLowerCase().includes(searchLower);
- }).filter((repo) => {
- if (selectedStatuses.length === 0) {
- return true;
- }
-
- return selectedStatuses.includes(convertIndexingStatus(repo.repoIndexingStatus));
- });
- }, [unfilteredRepos, searchQuery, selectedStatuses]);
-
- if (reposError) {
- return
- {`Error loading repositories. Reason: ${reposError.message}`}
-
- }
-
- return (
-
-
-
-
- setSearchQuery(e.target.value)}
- />
-
-
-
setSelectedStatuses(value)}
- defaultValue={[]}
- placeholder="Filter by status"
- maxCount={2}
- animation={0}
- />
-
- {failedRepos.length > 0 && (
-
- {isRetryAllFailedReposLoading && }
- Retry All Failed
-
- )}
-
-
- {isReposPending ? (
-
- {Array.from({ length: 3 }).map((_, i) => (
-
- ))}
-
- ) : (!filteredRepos || filteredRepos.length === 0) ? (
-
-
No Repositories Found
-
- {
- searchQuery.length > 0 ? (
- No repositories found matching your filters.
- ) : (!isConnectionError && !isConnectionPending && (connection.syncStatus === ConnectionSyncStatus.IN_SYNC_QUEUE || connection.syncStatus === ConnectionSyncStatus.SYNCING || connection.syncStatus === ConnectionSyncStatus.SYNC_NEEDED)) ? (
- Repositories are being synced. Please check back soon.
- ) : (
- {
- router.push(`?tab=settings`)
- }}
- variant="outline"
- >
- Configure connection
-
- )}
-
-
- ) : (
-
- {filteredRepos?.map((repo) => (
-
- ))}
-
- )}
-
-
- )
-}
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx b/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx
deleted file mode 100644
index fd491376..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-'use client';
-
-import { getDisplayTime, getRepoImageSrc } from "@/lib/utils";
-import Image from "next/image";
-import { StatusIcon } from "../../components/statusIcon";
-import { RepoIndexingStatus } from "@sourcebot/db";
-import { useMemo } from "react";
-import { RetryRepoIndexButton } from "./repoRetryIndexButton";
-
-
-interface RepoListItemProps {
- name: string;
- status: RepoIndexingStatus;
- imageUrl?: string;
- indexedAt?: Date;
- repoId: number;
- domain: string;
-}
-
-export const RepoListItem = ({
- imageUrl,
- name,
- indexedAt,
- status,
- repoId,
- domain,
-}: RepoListItemProps) => {
- const statusDisplayName = useMemo(() => {
- switch (status) {
- case RepoIndexingStatus.NEW:
- return 'Waiting...';
- case RepoIndexingStatus.IN_INDEX_QUEUE:
- return 'In index queue...';
- case RepoIndexingStatus.INDEXING:
- return 'Indexing...';
- case RepoIndexingStatus.INDEXED:
- return 'Indexed';
- case RepoIndexingStatus.FAILED:
- return 'Index failed';
- case RepoIndexingStatus.IN_GC_QUEUE:
- return 'In garbage collection queue...';
- case RepoIndexingStatus.GARBAGE_COLLECTING:
- return 'Garbage collecting...';
- case RepoIndexingStatus.GARBAGE_COLLECTION_FAILED:
- return 'Garbage collection failed';
- }
- }, [status]);
-
- const imageSrc = getRepoImageSrc(imageUrl, repoId, domain);
-
- return (
-
-
- {imageSrc ? (
-
- ) : (
-
- {name.charAt(0)}
-
- )}
-
{name}
-
-
- {status === RepoIndexingStatus.FAILED && (
-
- )}
-
-
-
- {statusDisplayName}
- {
- (
- status === RepoIndexingStatus.INDEXED ||
- status === RepoIndexingStatus.FAILED
- ) && indexedAt && (
- {` ${getDisplayTime(indexedAt)}`}
- )
- }
-
-
-
-
- )
-}
-
-const convertIndexingStatus = (status: RepoIndexingStatus) => {
- switch (status) {
- case RepoIndexingStatus.NEW:
- return 'waiting';
- case RepoIndexingStatus.IN_INDEX_QUEUE:
- case RepoIndexingStatus.INDEXING:
- return 'running';
- case RepoIndexingStatus.IN_GC_QUEUE:
- case RepoIndexingStatus.GARBAGE_COLLECTING:
- return "garbage-collecting"
- case RepoIndexingStatus.INDEXED:
- return 'succeeded';
- case RepoIndexingStatus.GARBAGE_COLLECTION_FAILED:
- case RepoIndexingStatus.FAILED:
- return 'failed';
- }
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/repoListItemSkeleton.tsx b/packages/web/src/app/[domain]/connections/[id]/components/repoListItemSkeleton.tsx
deleted file mode 100644
index 2eab9a38..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/repoListItemSkeleton.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Skeleton } from "@/components/ui/skeleton"
-
-export const RepoListItemSkeleton = () => {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/repoRetryIndexButton.tsx b/packages/web/src/app/[domain]/connections/[id]/components/repoRetryIndexButton.tsx
deleted file mode 100644
index ad44c60e..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/components/repoRetryIndexButton.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-"use client";
-
-import { Button } from "@/components/ui/button";
-import { ReloadIcon } from "@radix-ui/react-icons"
-import { toast } from "@/components/hooks/use-toast";
-import { flagReposForIndex } from "@/actions";
-import { isServiceError } from "@/lib/utils";
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-
-interface RetryRepoIndexButtonProps {
- repoId: number;
-}
-
-export const RetryRepoIndexButton = ({ repoId }: RetryRepoIndexButtonProps) => {
- const captureEvent = useCaptureEvent();
-
- return (
- {
- const result = await flagReposForIndex([repoId]);
- if (isServiceError(result)) {
- toast({
- description: `❌ Failed to flag repository for indexing.`,
- });
- captureEvent('wa_repo_retry_index_fail', {
- error: result.errorCode,
- });
- } else {
- toast({
- description: "✅ Repository flagged for indexing.",
- });
- captureEvent('wa_repo_retry_index_success', {});
- }
- }}
- >
-
- Retry Index
-
- );
-};
diff --git a/packages/web/src/app/[domain]/connections/[id]/page.tsx b/packages/web/src/app/[domain]/connections/[id]/page.tsx
deleted file mode 100644
index 0e0a91d2..00000000
--- a/packages/web/src/app/[domain]/connections/[id]/page.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { NotFound } from "@/app/[domain]/components/notFound"
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb"
-import { ConnectionIcon } from "../components/connectionIcon"
-import { Header } from "../../components/header"
-import { RepoList } from "./components/repoList"
-import { getConnectionByDomain } from "@/data/connection"
-import { Overview } from "./components/overview"
-
-interface ConnectionManagementPageProps {
- params: Promise<{
- domain: string
- id: string
- }>,
-}
-
-export default async function ConnectionManagementPage(props: ConnectionManagementPageProps) {
- const params = await props.params;
- const connection = await getConnectionByDomain(Number(params.id), params.domain);
- if (!connection) {
- return
- }
-
- return (
-
-
-
-
-
- Connections
-
-
-
- {connection.name}
-
-
-
-
-
-
{connection.name}
-
-
-
-
-
-
Overview
-
-
-
-
-
Linked Repositories
-
-
-
-
- )
-}
diff --git a/packages/web/src/app/[domain]/connections/components/connectionIcon.tsx b/packages/web/src/app/[domain]/connections/components/connectionIcon.tsx
deleted file mode 100644
index 2c08121a..00000000
--- a/packages/web/src/app/[domain]/connections/components/connectionIcon.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-'use client';
-
-import { cn, CodeHostType, getCodeHostIcon } from "@/lib/utils";
-import { useMemo } from "react";
-import Image from "next/image";
-import placeholderLogo from "@/public/placeholder_avatar.png";
-
-interface ConnectionIconProps {
- type: string;
- className?: string;
-}
-
-export const ConnectionIcon = ({
- type,
- className,
-}: ConnectionIconProps) => {
- const Icon = useMemo(() => {
- const iconInfo = getCodeHostIcon(type as CodeHostType);
- if (iconInfo) {
- return (
-
- )
- }
-
- return
-
- }, [className, type]);
-
- return Icon;
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItem.tsx b/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItem.tsx
deleted file mode 100644
index 02c41139..00000000
--- a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItem.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { getDisplayTime } from "@/lib/utils";
-import { useMemo } from "react";
-import { ConnectionIcon } from "../connectionIcon";
-import { ConnectionSyncStatus, Prisma } from "@sourcebot/db";
-import { StatusIcon } from "../statusIcon";
-import { ConnectionListItemErrorIndicator } from "./connectionListItemErrorIndicator";
-import { ConnectionListItemWarningIndicator } from "./connectionListItemWarningIndicator";
-import { ConnectionListItemManageButton } from "./connectionListItemManageButton";
-
-const convertSyncStatus = (status: ConnectionSyncStatus) => {
- switch (status) {
- case ConnectionSyncStatus.SYNC_NEEDED:
- return 'waiting';
- case ConnectionSyncStatus.IN_SYNC_QUEUE:
- case ConnectionSyncStatus.SYNCING:
- return 'running';
- case ConnectionSyncStatus.SYNCED:
- return 'succeeded';
- case ConnectionSyncStatus.SYNCED_WITH_WARNINGS:
- return 'succeeded-with-warnings';
- case ConnectionSyncStatus.FAILED:
- return 'failed';
- }
-}
-
-interface ConnectionListItemProps {
- id: string;
- name: string;
- type: string;
- status: ConnectionSyncStatus;
- syncStatusMetadata: Prisma.JsonValue;
- editedAt: Date;
- syncedAt?: Date;
- failedRepos?: { repoId: number, repoName: string }[];
- disabled: boolean;
-}
-
-export const ConnectionListItem = ({
- id,
- name,
- type,
- status,
- syncStatusMetadata,
- editedAt,
- syncedAt,
- failedRepos,
- disabled,
-}: ConnectionListItemProps) => {
- const statusDisplayName = useMemo(() => {
- switch (status) {
- case ConnectionSyncStatus.SYNC_NEEDED:
- return 'Waiting...';
- case ConnectionSyncStatus.IN_SYNC_QUEUE:
- case ConnectionSyncStatus.SYNCING:
- return 'Syncing...';
- case ConnectionSyncStatus.SYNCED:
- return 'Synced';
- case ConnectionSyncStatus.FAILED:
- return 'Sync failed';
- case ConnectionSyncStatus.SYNCED_WITH_WARNINGS:
- return null;
- }
- }, [status]);
-
- const { notFoundData, displayNotFoundWarning } = useMemo(() => {
- if (!syncStatusMetadata || typeof syncStatusMetadata !== 'object' || !('notFound' in syncStatusMetadata)) {
- return { notFoundData: null, displayNotFoundWarning: false };
- }
-
- const notFoundData = syncStatusMetadata.notFound as {
- users: string[],
- orgs: string[],
- repos: string[],
- }
-
- return { notFoundData, displayNotFoundWarning: notFoundData.users.length > 0 || notFoundData.orgs.length > 0 || notFoundData.repos.length > 0 };
- }, [syncStatusMetadata]);
-
- return (
-
-
-
-
-
{name}
-
{`Edited ${getDisplayTime(editedAt)}`}
-
-
-
-
-
-
-
- {statusDisplayName}
- {
- (
- status === ConnectionSyncStatus.SYNCED ||
- status === ConnectionSyncStatus.FAILED
- ) && syncedAt && (
- {` ${getDisplayTime(syncedAt)}`}
- )
- }
-
-
-
-
- )
-}
diff --git a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemErrorIndicator.tsx b/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemErrorIndicator.tsx
deleted file mode 100644
index 26c9ee21..00000000
--- a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemErrorIndicator.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-'use client'
-
-import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
-import { CircleX } from "lucide-react";
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-
-interface ConnectionListItemErrorIndicatorProps {
- failedRepos: { repoId: number; repoName: string; }[] | undefined;
- connectionId: string;
-}
-
-export const ConnectionListItemErrorIndicator = ({
- failedRepos,
- connectionId
-}: ConnectionListItemErrorIndicatorProps) => {
- const captureEvent = useCaptureEvent()
-
- if (!failedRepos || failedRepos.length === 0) return null;
-
- return (
-
-
- {
- captureEvent('wa_connection_list_item_error_pressed', {})
- window.location.href = `connections/${connectionId}`
- }}
- onMouseEnter={() => captureEvent('wa_connection_list_item_error_hover', {})}
- />
-
-
-
-
-
-
Failed to Index Repositories
-
-
-
- {failedRepos.length} {failedRepos.length === 1 ? 'repository' : 'repositories'} failed to index. This is likely due to temporary server load.
-
-
-
- {failedRepos.slice(0, 10).map(repo => (
- {repo.repoName}
- ))}
- {failedRepos.length > 10 && (
-
- And {failedRepos.length - 10} more...
-
- )}
-
-
-
- Navigate to the connection for more details and to retry indexing.
-
-
-
-
-
- );
-};
diff --git a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemManageButton.tsx b/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemManageButton.tsx
deleted file mode 100644
index 00b7db27..00000000
--- a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemManageButton.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-'use client'
-
-import { Button } from "@/components/ui/button";
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-import { useRouter } from "next/navigation";
-import { useDomain } from "@/hooks/useDomain";
-
-interface ConnectionListItemManageButtonProps {
- id: string;
- disabled: boolean;
-}
-
-export const ConnectionListItemManageButton = ({
- id,
- disabled,
-}: ConnectionListItemManageButtonProps) => {
- const captureEvent = useCaptureEvent()
- const router = useRouter();
- const domain = useDomain();
-
- return (
- {
- if (!disabled) {
- captureEvent('wa_connection_list_item_manage_pressed', {})
- router.push(`/${domain}/connections/${id}`)
- }
- }}
- >
- Manage
-
- );
-};
diff --git a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemWarningIndicator.tsx b/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemWarningIndicator.tsx
deleted file mode 100644
index 690c9b18..00000000
--- a/packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemWarningIndicator.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-'use client'
-
-import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
-import { AlertTriangle } from "lucide-react";
-import { NotFoundData } from "@/lib/syncStatusMetadataSchema";
-import useCaptureEvent from "@/hooks/useCaptureEvent";
-
-
-interface ConnectionListItemWarningIndicatorProps {
- notFoundData: NotFoundData | null;
- connectionId: string;
- type: string;
- displayWarning: boolean;
-}
-
-export const ConnectionListItemWarningIndicator = ({
- notFoundData,
- connectionId,
- type,
- displayWarning
-}: ConnectionListItemWarningIndicatorProps) => {
- const captureEvent = useCaptureEvent()
-
- if (!notFoundData || !displayWarning) return null;
-
- return (
-
-
- {
- captureEvent('wa_connection_list_item_warning_pressed', {})
- window.location.href = `connections/${connectionId}`
- }}
- onMouseEnter={() => captureEvent('wa_connection_list_item_warning_hover', {})}
- />
-
-
-
-
-
-
Unable to fetch all references
-
-
- Some requested references couldn't be found. Verify the details below and ensure your connection is using a {" "}
- window.location.href = `secrets`}
- className="font-medium text-yellow-700 dark:text-yellow-400 hover:text-yellow-600 dark:hover:text-yellow-300 transition-colors"
- >
- valid access token
- {" "}
- that has access to any private references.
-
-
- {notFoundData.users.length > 0 && (
-
- Users:
- {notFoundData.users.join(', ')}
-
- )}
- {notFoundData.orgs.length > 0 && (
-
- {type === "gitlab" ? "Groups" : "Organizations"}:
- {notFoundData.orgs.join(', ')}
-
- )}
- {notFoundData.repos.length > 0 && (
-
- {type === "gitlab" ? "Projects" : "Repositories"}:
- {notFoundData.repos.join(', ')}
-
- )}
-
-
-
-
- );
-};
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/components/connectionList/index.tsx b/packages/web/src/app/[domain]/connections/components/connectionList/index.tsx
deleted file mode 100644
index 94b4f8be..00000000
--- a/packages/web/src/app/[domain]/connections/components/connectionList/index.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-"use client";
-import { useDomain } from "@/hooks/useDomain";
-import { ConnectionListItem } from "./connectionListItem";
-import { cn, unwrapServiceError } from "@/lib/utils";
-import { InfoCircledIcon } from "@radix-ui/react-icons";
-import { getConnections } from "@/actions";
-import { Skeleton } from "@/components/ui/skeleton";
-import { useQuery } from "@tanstack/react-query";
-import { env } from "@/env.mjs";
-import { RepoIndexingStatus, ConnectionSyncStatus } from "@sourcebot/db";
-import { Search } from "lucide-react";
-import { Input } from "@/components/ui/input";
-import { useMemo, useState } from "react";
-import { MultiSelect } from "@/components/ui/multi-select";
-
-interface ConnectionListProps {
- className?: string;
- isDisabled: boolean;
-}
-
-const convertSyncStatus = (status: ConnectionSyncStatus) => {
- switch (status) {
- case ConnectionSyncStatus.SYNC_NEEDED:
- return 'waiting';
- case ConnectionSyncStatus.SYNCING:
- return 'running';
- case ConnectionSyncStatus.SYNCED:
- return 'succeeded';
- case ConnectionSyncStatus.SYNCED_WITH_WARNINGS:
- return 'synced-with-warnings';
- case ConnectionSyncStatus.FAILED:
- return 'failed';
- default:
- return 'unknown';
- }
-}
-
-export const ConnectionList = ({
- className,
- isDisabled,
-}: ConnectionListProps) => {
- const domain = useDomain();
- const [searchQuery, setSearchQuery] = useState("");
- const [selectedStatuses, setSelectedStatuses] = useState([]);
-
- const { data: unfilteredConnections, isPending, error } = useQuery({
- queryKey: ['connections', domain],
- queryFn: () => unwrapServiceError(getConnections(domain)),
- refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
- });
-
- const connections = useMemo(() => {
- return unfilteredConnections
- ?.filter((connection) => connection.name.toLowerCase().includes(searchQuery.toLowerCase()))
- .filter((connection) => {
- if (selectedStatuses.length === 0) {
- return true;
- }
-
- return selectedStatuses.includes(convertSyncStatus(connection.syncStatus));
- })
- .sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()) ?? [];
- }, [unfilteredConnections, searchQuery, selectedStatuses]);
-
- if (error) {
- return
-
Error loading connections: {error.message}
-
- }
-
- return (
-
-
-
-
- setSearchQuery(e.target.value)}
- />
-
-
-
setSelectedStatuses(value)}
- defaultValue={[]}
- placeholder="Filter by status"
- maxCount={2}
- animation={0}
- />
-
-
-
- {isPending ? (
- // Skeleton for loading state
-
- {Array.from({ length: 3 }).map((_, i) => (
-
- ))}
-
- ) : connections.length > 0 ? (
- connections
- .sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
- .map((connection) => (
-
repo.repoIndexingStatus === RepoIndexingStatus.FAILED).map((repo) => ({
- repoId: repo.id,
- repoName: repo.name,
- }))}
- disabled={isDisabled}
- />
- ))
- ) : (
-
-
-
No connections
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/components/statusIcon.tsx b/packages/web/src/app/[domain]/connections/components/statusIcon.tsx
deleted file mode 100644
index 4aad8eb6..00000000
--- a/packages/web/src/app/[domain]/connections/components/statusIcon.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { cn } from "@/lib/utils";
-import { CircleCheckIcon, CircleXIcon } from "lucide-react";
-import { useMemo } from "react";
-import { FiLoader } from "react-icons/fi";
-
-export type Status = 'waiting' | 'running' | 'succeeded' | 'succeeded-with-warnings' | 'garbage-collecting' | 'failed';
-
-export const StatusIcon = ({
- status,
- className,
-}: { status: Status, className?: string }) => {
- const Icon = useMemo(() => {
- switch (status) {
- case 'waiting':
- case 'garbage-collecting':
- case 'running':
- return ;
- case 'succeeded':
- return ;
- case 'failed':
- return ;
- case 'succeeded-with-warnings':
- default:
- return null;
- }
- }, [className, status]);
-
- return Icon;
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/layout.tsx b/packages/web/src/app/[domain]/connections/layout.tsx
deleted file mode 100644
index 9aeb3541..00000000
--- a/packages/web/src/app/[domain]/connections/layout.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { auth } from "@/auth";
-import { NavigationMenu } from "../components/navigationMenu";
-import { redirect } from "next/navigation";
-
-interface LayoutProps {
- children: React.ReactNode;
- params: Promise<{ domain: string }>;
-}
-
-export default async function Layout(
- props: LayoutProps
-) {
- const params = await props.params;
-
- const {
- domain
- } = params;
-
- const {
- children
- } = props;
-
- const session = await auth();
- if (!session) {
- return redirect(`/${domain}`);
- }
-
- return (
-
- )
-}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/connections/page.tsx b/packages/web/src/app/[domain]/connections/page.tsx
deleted file mode 100644
index fdf3f32c..00000000
--- a/packages/web/src/app/[domain]/connections/page.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { ConnectionList } from "./components/connectionList";
-import { Header } from "../components/header";
-import { getConnections, getOrgMembership } from "@/actions";
-import { isServiceError } from "@/lib/utils";
-import { notFound, ServiceErrorException } from "@/lib/serviceError";
-import { OrgRole } from "@sourcebot/db";
-
-export default async function ConnectionsPage(props: { params: Promise<{ domain: string }> }) {
- const params = await props.params;
-
- const {
- domain
- } = params;
-
- const connections = await getConnections(domain);
- if (isServiceError(connections)) {
- throw new ServiceErrorException(connections);
- }
-
- const membership = await getOrgMembership(domain);
- if (isServiceError(membership)) {
- throw new ServiceErrorException(notFound());
- }
-
- return (
-
-
-
-
- );
-}
diff --git a/packages/web/src/app/[domain]/connections/quickActions.tsx b/packages/web/src/app/[domain]/connections/quickActions.tsx
deleted file mode 100644
index 5f8008b0..00000000
--- a/packages/web/src/app/[domain]/connections/quickActions.tsx
+++ /dev/null
@@ -1,575 +0,0 @@
-import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"
-import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
-import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type";
-import { QuickAction } from "../components/configEditor";
-import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
-import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
-import { CodeSnippet } from "@/app/components/codeSnippet";
-
-export const githubQuickActions: QuickAction[] = [
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- repos: [
- ...(previous.repos ?? []),
- "/"
- ]
- }),
- name: "Add a single repo",
- selectionText: "/",
- description: (
-
-
Add a individual repository to sync with. Ensure the repository is visible to the provided token (if any).
-
Examples:
-
- {[
- "sourcebot/sourcebot",
- "vercel/next.js",
- "torvalds/linux"
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- orgs: [
- ...(previous.orgs ?? []),
- ""
- ]
- }),
- name: "Add an organization",
- selectionText: "",
- description: (
-
-
Add an organization to sync with. All repositories in the organization visible to the provided token (if any) will be synced.
-
Examples:
-
- {[
- "commaai",
- "sourcebot",
- "vercel"
- ].map((org) => (
- {org}
- ))}
-
-
- )
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- users: [
- ...(previous.users ?? []),
- ""
- ]
- }),
- name: "Add a user",
- selectionText: "",
- description: (
-
-
Add a user to sync with. All repositories that the user owns visible to the provided token (if any) will be synced.
-
Examples:
-
- {[
- "jane-doe",
- "torvalds",
- "octocat"
- ].map((org) => (
- {org}
- ))}
-
-
- )
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- url: previous.url ?? "https://github.example.com",
- }),
- name: "Set url to GitHub instance",
- selectionText: "https://github.example.com",
- description: Set a custom GitHub host. Defaults to https://github.com .
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- repos: [
- ...(previous.exclude?.repos ?? []),
- ""
- ]
- }
- }),
- name: "Exclude by repo name",
- selectionText: "",
- description: (
-
-
Exclude repositories from syncing by name. Glob patterns are supported.
-
Examples:
-
- {[
- "my-org/docs*",
- "my-org/test*"
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- topics: [
- ...(previous.exclude?.topics ?? []),
- ""
- ]
- }
- }),
- name: "Exclude by topic",
- selectionText: "",
- description: (
-
-
Exclude topics from syncing. Only repos that do not match any of the provided topics will be synced. Glob patterns are supported.
-
Examples:
-
- {[
- "docs",
- "ci"
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- topics: [
- ...(previous.topics ?? []),
- ""
- ]
- }),
- name: "Include by topic",
- selectionText: "",
- description: (
-
-
Include repositories by topic. Only repos that match at least one of the provided topics will be synced. Glob patterns are supported.
-
Examples:
-
- {[
- "docs",
- "ci"
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- archived: true,
- }
- }),
- name: "Exclude archived repos",
- description: Exclude archived repositories from syncing.
- },
- {
- fn: (previous: GithubConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- forks: true,
- }
- }),
- name: "Exclude forked repos",
- description: Exclude forked repositories from syncing.
- }
-];
-
-export const gitlabQuickActions: QuickAction[] = [
- {
- fn: (previous: GitlabConnectionConfig) => ({
- ...previous,
- projects: [
- ...previous.projects ?? [],
- ""
- ]
- }),
- name: "Add a project",
- selectionText: "",
- description: (
-
-
Add a individual project to sync with. Ensure the project is visible to the provided token (if any).
-
Examples:
-
- {[
- "gitlab-org/gitlab",
- "corp/team-project",
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- {
- fn: (previous: GitlabConnectionConfig) => ({
- ...previous,
- users: [
- ...previous.users ?? [],
- ""
- ]
- }),
- name: "Add a user",
- selectionText: "",
- description: (
-
-
Add a user to sync with. All projects that the user owns visible to the provided token (if any) will be synced.
-
Examples:
-
- {[
- "jane-doe",
- "torvalds"
- ].map((org) => (
- {org}
- ))}
-
-
- )
- },
- {
- fn: (previous: GitlabConnectionConfig) => ({
- ...previous,
- groups: [
- ...previous.groups ?? [],
- ""
- ]
- }),
- name: "Add a group",
- selectionText: "",
- description: (
-
-
Add a group to sync with. All projects in the group (and recursive subgroups) visible to the provided token (if any) will be synced.
-
Examples:
-
- {[
- "my-group",
- "path/to/subgroup"
- ].map((org) => (
- {org}
- ))}
-
-
- )
- },
- {
- fn: (previous: GitlabConnectionConfig) => ({
- ...previous,
- url: previous.url ?? "https://gitlab.example.com",
- }),
- name: "Set url to GitLab instance",
- selectionText: "https://gitlab.example.com",
- description: Set a custom GitLab host. Defaults to https://gitlab.com .
- },
- {
- fn: (previous: GitlabConnectionConfig) => ({
- ...previous,
- all: true,
- }),
- name: "Sync all projects",
- description: Sync all projects visible to the provided token (if any). Only available when using a self-hosted GitLab instance.
- },
- {
- fn: (previous: GitlabConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- projects: [
- ...(previous.exclude?.projects ?? []),
- ""
- ]
- }
- }),
- name: "Exclude a project",
- selectionText: "",
- description: (
-
-
List of projects to exclude from syncing. Glob patterns are supported.
-
Examples:
-
- {[
- "docs/**",
- "**/tests/**",
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- }
-]
-
-export const giteaQuickActions: QuickAction[] = [
- {
- fn: (previous: GiteaConnectionConfig) => ({
- ...previous,
- orgs: [
- ...(previous.orgs ?? []),
- ""
- ]
- }),
- name: "Add an organization",
- selectionText: "",
- },
- {
- fn: (previous: GiteaConnectionConfig) => ({
- ...previous,
- repos: [
- ...(previous.repos ?? []),
- "/"
- ]
- }),
- name: "Add a repo",
- selectionText: "/",
- },
- {
- fn: (previous: GiteaConnectionConfig) => ({
- ...previous,
- url: previous.url ?? "https://gitea.example.com",
- }),
- name: "Set url to Gitea instance",
- selectionText: "https://gitea.example.com",
- }
-]
-
-export const gerritQuickActions: QuickAction[] = [
- {
- fn: (previous: GerritConnectionConfig) => ({
- ...previous,
- projects: [
- ...(previous.projects ?? []),
- ""
- ]
- }),
- name: "Add a project",
- },
- {
- fn: (previous: GerritConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- projects: [
- ...(previous.exclude?.projects ?? []),
- ""
- ]
- }
- }),
- name: "Exclude a project",
- }
-]
-
-export const bitbucketCloudQuickActions: QuickAction[] = [
- {
- // add user
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- user: previous.user ?? "username"
- }),
- name: "Add username",
- selectionText: "username",
- description: (
-
- Username to use for authentication. This is only required if you're using an App Password (stored in token ) for authentication.
-
- )
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- workspaces: [
- ...(previous.workspaces ?? []),
- "myWorkspace"
- ]
- }),
- name: "Add a workspace",
- selectionText: "myWorkspace",
- description: (
-
- Add a workspace to sync with. Ensure the workspace is visible to the provided token (if any).
-
- )
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- repos: [
- ...(previous.repos ?? []),
- "myWorkspace/myRepo"
- ]
- }),
- name: "Add a repo",
- selectionText: "myWorkspace/myRepo",
- description: (
-
- Add an individual repository to sync with. Ensure the repository is visible to the provided token (if any).
-
- )
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- projects: [
- ...(previous.projects ?? []),
- "myProject"
- ]
- }),
- name: "Add a project",
- selectionText: "myProject",
- description: (
-
- Add a project to sync with. Ensure the project is visible to the provided token (if any).
-
- )
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- repos: [...(previous.exclude?.repos ?? []), "myWorkspace/myExcludedRepo"]
- }
- }),
- name: "Exclude a repo",
- selectionText: "myWorkspace/myExcludedRepo",
- description: (
-
- Exclude a repository from syncing. Glob patterns are supported.
-
- )
- },
- // exclude forked
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- forks: true
- }
- }),
- name: "Exclude forked repos",
- description: Exclude forked repositories from syncing.
- }
-]
-
-export const bitbucketDataCenterQuickActions: QuickAction[] = [
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- url: previous.url ?? "https://bitbucket.example.com",
- }),
- name: "Set url to Bitbucket DC instance",
- selectionText: "https://bitbucket.example.com",
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- repos: [
- ...(previous.repos ?? []),
- "myProject/myRepo"
- ]
- }),
- name: "Add a repo",
- selectionText: "myProject/myRepo",
- description: (
-
-
Add a individual repository to sync with. Ensure the repository is visible to the provided token (if any).
-
Examples:
-
- {[
- "PROJ/repo-name",
- "MYPROJ/api"
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- projects: [
- ...(previous.projects ?? []),
- "myProject"
- ]
- }),
- name: "Add a project",
- selectionText: "myProject",
- description: (
-
- Add a project to sync with. Ensure the project is visible to the provided token (if any).
-
- )
- },
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- repos: [...(previous.exclude?.repos ?? []), "myProject/myExcludedRepo"]
- }
- }),
- name: "Exclude a repo",
- selectionText: "myProject/myExcludedRepo",
- description: (
-
-
Exclude a repository from syncing. Glob patterns are supported.
-
Examples:
-
- {[
- "myProject/myExcludedRepo",
- "myProject2/*"
- ].map((repo) => (
- {repo}
- ))}
-
-
- )
- },
- // exclude archived
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- archived: true
- }
- }),
- name: "Exclude archived repos",
- },
- // exclude forked
- {
- fn: (previous: BitbucketConnectionConfig) => ({
- ...previous,
- exclude: {
- ...previous.exclude,
- forks: true
- }
- }),
- name: "Exclude forked repos",
- }
-]
-
diff --git a/packages/web/src/app/[domain]/connections/utils.ts b/packages/web/src/app/[domain]/connections/utils.ts
deleted file mode 100644
index cd728092..00000000
--- a/packages/web/src/app/[domain]/connections/utils.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import Ajv, { Schema } from "ajv";
-import { z } from "zod";
-
-export const createZodConnectionConfigValidator = (jsonSchema: Schema, additionalConfigValidation?: (config: T) => { message: string, isValid: boolean }) => {
- const ajv = new Ajv({
- validateFormats: false,
- });
- const validate = ajv.compile(jsonSchema);
-
- return z
- .string()
- .superRefine((data, ctx) => {
- const addIssue = (message: string) => {
- return ctx.addIssue({
- code: "custom",
- message: `Schema validation error: ${message}`
- });
- }
-
- let parsed;
- try {
- parsed = JSON.parse(data);
- } catch {
- addIssue("Invalid JSON");
- return;
- }
-
- const valid = validate(parsed);
- if (!valid) {
- addIssue(ajv.errorsText(validate.errors));
- }
-
- if (additionalConfigValidation) {
- const result = additionalConfigValidation(parsed as T);
- if (!result.isValid) {
- addIssue(result.message);
- }
- }
- });
-}
\ No newline at end of file