From aaffb9cb0743ac8600b1febc5f18791425df0ee5 Mon Sep 17 00:00:00 2001 From: kiranfin <4finjas@online.de> Date: Wed, 30 Sep 2026 15:28:10 +0200 Subject: [PATCH 1/9] Add Bitbucket service layer, input parser, icon and source type - Add bitbucketService and bitbucketRepositoryInput with tests, including defensive discover mapping and hasMore - Add BitbucketIcon and a wider IconComponent type so SOURCE_META and DrawerCard accept non-lucide icons - Add BITBUCKET to the source unions and Record maps, shown as "Soon" in the add-source grid --- src/components/icons/BitbucketIcon.tsx | 24 ++ src/components/icons/types.ts | 16 + src/features/admin/components/DrawerCard.tsx | 6 +- .../wizard/sources/AddSourceFlow.tsx | 1 + .../wizard/steps/WizardReviewStep.tsx | 3 + src/features/admin/projectSourcesDraft.ts | 2 +- .../components/AddSourceModal.tsx | 10 +- src/features/data-ingestion/data.ts | 16 +- src/features/data-ingestion/types.ts | 7 +- .../components/ArtifactFilters.tsx | 2 + src/features/knowledge-base/tabs.ts | 9 +- src/features/knowledge-base/types.ts | 2 +- src/services/projectService.ts | 3 +- .../sources/bitbucketRepositoryInput.ts | 89 ++++ src/services/sources/bitbucketService.ts | 292 +++++++++++++ .../a11y/SourceConnectModal.a11y.test.tsx | 6 + .../components/SourceDetailsPanel.test.tsx | 10 +- .../unit/features/data-ingestion/data.test.ts | 2 +- .../services/bitbucketRepositoryInput.test.ts | 87 ++++ tests/unit/services/bitbucketService.test.ts | 382 ++++++++++++++++++ 20 files changed, 951 insertions(+), 18 deletions(-) create mode 100644 src/components/icons/BitbucketIcon.tsx create mode 100644 src/components/icons/types.ts create mode 100644 src/services/sources/bitbucketRepositoryInput.ts create mode 100644 src/services/sources/bitbucketService.ts create mode 100644 tests/unit/services/bitbucketRepositoryInput.test.ts create mode 100644 tests/unit/services/bitbucketService.test.ts diff --git a/src/components/icons/BitbucketIcon.tsx b/src/components/icons/BitbucketIcon.tsx new file mode 100644 index 000000000..fefa5f4aa --- /dev/null +++ b/src/components/icons/BitbucketIcon.tsx @@ -0,0 +1,24 @@ +import type { IconProps } from "./types.ts"; + +/** + * The Bitbucket logo as an inline SVG. lucide has no Bitbucket icon, so this + * takes the same props as one and fills with `currentColor`, which lets it sit + * next to the other source icons. The logo is a filled shape, so `strokeWidth` + * is accepted and ignored. + */ +export function BitbucketIcon({ className, "aria-hidden": ariaHidden, size }: IconProps) { + return ( + + + + ); +} diff --git a/src/components/icons/types.ts b/src/components/icons/types.ts new file mode 100644 index 000000000..cdb976627 --- /dev/null +++ b/src/components/icons/types.ts @@ -0,0 +1,16 @@ +import type { ComponentType } from "react"; + +/** The props every icon in the app is drawn with, lucide or hand-made. */ +export type IconProps = { + className?: string; + "aria-hidden"?: boolean | "true" | "false"; + size?: number | string; + strokeWidth?: number | string; +}; + +/** + * An icon component. Wider than `LucideIcon` so a brand logo that lucide does + * not ship (Bitbucket) can be used wherever a lucide icon is; every lucide icon + * still satisfies it. + */ +export type IconComponent = ComponentType; diff --git a/src/features/admin/components/DrawerCard.tsx b/src/features/admin/components/DrawerCard.tsx index 7ed3463c3..56d34391c 100644 --- a/src/features/admin/components/DrawerCard.tsx +++ b/src/features/admin/components/DrawerCard.tsx @@ -1,12 +1,12 @@ import { motion, useReducedMotion } from "framer-motion"; -import type { LucideIcon } from "lucide-react"; import type { ReactNode } from "react"; +import type { IconComponent } from "../../../components/icons/types.ts"; type DrawerCardProps = { /** Uppercase section label shown in the card header. Omit for a headerless card. */ label?: string; - /** Optional lucide icon rendered in a soft tile beside the label. */ - icon?: LucideIcon; + /** Optional icon (lucide or a brand logo) rendered in a soft tile beside the label. */ + icon?: IconComponent; /** Position in the body stack, so cards reveal in sequence when the drawer opens. */ index?: number; /** Optional trailing content in the header row (counts, actions). */ diff --git a/src/features/admin/components/wizard/sources/AddSourceFlow.tsx b/src/features/admin/components/wizard/sources/AddSourceFlow.tsx index 44394294e..26b7e6eea 100644 --- a/src/features/admin/components/wizard/sources/AddSourceFlow.tsx +++ b/src/features/admin/components/wizard/sources/AddSourceFlow.tsx @@ -568,6 +568,7 @@ const DETAIL_SUBTITLE: Record = { JIRA: "Point to your Jira instance and pick a credential, then add it to the list.", UPLOAD: "Files are staged now and uploaded right after the project is created.", CONFLUENCE: "Point to your Confluence space and pick a credential, then add it to the list.", + BITBUCKET: "Pick a credential and a workspace, then choose the repositories to index.", }; /** diff --git a/src/features/admin/components/wizard/steps/WizardReviewStep.tsx b/src/features/admin/components/wizard/steps/WizardReviewStep.tsx index f8cbdf86f..b4ddba6c2 100644 --- a/src/features/admin/components/wizard/steps/WizardReviewStep.tsx +++ b/src/features/admin/components/wizard/steps/WizardReviewStep.tsx @@ -1,5 +1,6 @@ import { GitBranch, Ticket, FileText, BookOpen } from "lucide-react"; import type { ComponentType, ReactNode } from "react"; +import { BitbucketIcon } from "../../../../../components/icons/BitbucketIcon"; import { UserAvatar } from "../../../../../components/common/UserAvatar"; import { SourceTypeBadge } from "../../../../data-ingestion/components/SourceTypeBadge"; import type { DraftSource, DraftSourceType } from "../../../projectSourcesDraft"; @@ -30,6 +31,7 @@ const typeIcons: Record> JIRA: Ticket, UPLOAD: FileText, CONFLUENCE: BookOpen, + BITBUCKET: BitbucketIcon, }; const typeLabels: Record = { @@ -37,6 +39,7 @@ const typeLabels: Record = { JIRA: "Jira", UPLOAD: "Upload", CONFLUENCE: "Confluence", + BITBUCKET: "Bitbucket", }; function sourceTitle(source: DraftSource): string { diff --git a/src/features/admin/projectSourcesDraft.ts b/src/features/admin/projectSourcesDraft.ts index c95f739cf..86ff52f46 100644 --- a/src/features/admin/projectSourcesDraft.ts +++ b/src/features/admin/projectSourcesDraft.ts @@ -25,7 +25,7 @@ import type { DiscoverySelection } from "../data-ingestion/components/GithubRepo export type DraftSourceStatus = "pending" | "connecting" | "connected" | "failed"; -export type DraftSourceType = "GITHUB" | "JIRA" | "UPLOAD" | "CONFLUENCE"; +export type DraftSourceType = "GITHUB" | "JIRA" | "UPLOAD" | "CONFLUENCE" | "BITBUCKET"; /** Fields every staged source carries regardless of its type. */ type DraftSourceBase = { diff --git a/src/features/data-ingestion/components/AddSourceModal.tsx b/src/features/data-ingestion/components/AddSourceModal.tsx index 3541bf20e..fdffb90d3 100644 --- a/src/features/data-ingestion/components/AddSourceModal.tsx +++ b/src/features/data-ingestion/components/AddSourceModal.tsx @@ -36,6 +36,11 @@ import type { SourceSystem } from "../types.ts"; import type { DiscoverySelection } from "./GithubRepositoryDiscovery.tsx"; import type { AtlassianCredentialDto } from "../../../services/sources/atlassianService.ts"; +// Bitbucket shows up in the type grid as "Soon" until its add-source flow exists. +const AVAILABLE_SOURCE_TYPES: SourceSystem[] = SOURCE_SYSTEMS.filter( + (system) => system !== "BITBUCKET", +); + type AddSourceModalProps = { projectId: string | null; projectName?: string; @@ -230,6 +235,9 @@ export function AddSourceModal({ }; const handleSelectAddType = (type: SourceSystem) => { + // A "Soon" type (not yet wired) is inert until its phase lands. + if (!AVAILABLE_SOURCE_TYPES.includes(type)) return; + setAddType(type); setAddStep("detail"); }; @@ -558,7 +566,7 @@ export function AddSourceModal({ key={addFlowKey} step={addStep} selectedType={addType} - availableTypes={SOURCE_SYSTEMS} + availableTypes={AVAILABLE_SOURCE_TYPES} onSelectType={handleSelectAddType} onBack={backToTypeGrid} isBusy={isSubmitting} diff --git a/src/features/data-ingestion/data.ts b/src/features/data-ingestion/data.ts index d0d0b9578..3257d720d 100644 --- a/src/features/data-ingestion/data.ts +++ b/src/features/data-ingestion/data.ts @@ -21,10 +21,17 @@ import type { SourceStatusPresentation, SourceSystem, } from "./types.ts"; +import { BitbucketIcon } from "../../components/icons/BitbucketIcon.tsx"; import type { JiraInstanceDto } from "../../services/sources/jiraService.ts"; import type { ConfluenceConnectionDto } from "../../services/sources/confluenceService.ts"; -export const SOURCE_SYSTEMS: SourceSystem[] = ["GITHUB", "JIRA", "UPLOAD", "CONFLUENCE"]; +export const SOURCE_SYSTEMS: SourceSystem[] = [ + "GITHUB", + "JIRA", + "UPLOAD", + "CONFLUENCE", + "BITBUCKET", +]; export const SOURCE_META: Record = { GITHUB: { @@ -51,6 +58,13 @@ export const SOURCE_META: Record = { icon: BookOpen, description: "Indexes pages, hierarchical documents and spaces from Confluence Cloud.", }, + BITBUCKET: { + name: "Bitbucket Repository", + type: "Bitbucket", + icon: BitbucketIcon, + description: + "Indexes pull requests, README files and source files from Bitbucket Cloud repositories.", + }, }; export const INGESTION_RUN_LIMIT = 50; diff --git a/src/features/data-ingestion/types.ts b/src/features/data-ingestion/types.ts index 38c24bcab..fab9ae34e 100644 --- a/src/features/data-ingestion/types.ts +++ b/src/features/data-ingestion/types.ts @@ -1,6 +1,7 @@ import type { LucideIcon } from "lucide-react"; +import type { IconComponent } from "../../components/icons/types.ts"; -export type SourceSystem = "GITHUB" | "JIRA" | "UPLOAD" | "CONFLUENCE"; +export type SourceSystem = "GITHUB" | "JIRA" | "UPLOAD" | "CONFLUENCE" | "BITBUCKET"; export type BackendProjectSourceStatus = | "CONNECTED" @@ -255,7 +256,7 @@ export type SourceStatusPresentation = { export type SourceMeta = { name: string; type: string; - icon: LucideIcon; + icon: IconComponent; description: string; }; @@ -285,7 +286,7 @@ export type SourceDetailsSource = { }; export type DataSource = SourceDetailsSource & { - icon: LucideIcon; + icon: IconComponent; statusLabel: string; ingestionStatus: SourceStatus; ingestionStatusLabel: string; diff --git a/src/features/knowledge-base/components/ArtifactFilters.tsx b/src/features/knowledge-base/components/ArtifactFilters.tsx index eea194cf6..28d86e47e 100644 --- a/src/features/knowledge-base/components/ArtifactFilters.tsx +++ b/src/features/knowledge-base/components/ArtifactFilters.tsx @@ -15,6 +15,7 @@ import { Ticket, Upload, } from "lucide-react"; +import { BitbucketIcon } from "../../../components/icons/BitbucketIcon"; import { Button } from "../../../components/ui/Button"; import { Input } from "../../../components/ui/Input"; import { Select } from "../../../components/ui/Select.tsx"; @@ -124,6 +125,7 @@ const SOURCE_ICONS: Record = { GITHUB: