diff --git a/frontend/module-federation.config.ts b/frontend/module-federation.config.ts index 940cade796..9b156feb1a 100644 --- a/frontend/module-federation.config.ts +++ b/frontend/module-federation.config.ts @@ -36,7 +36,6 @@ export const moduleFederationConfig: ModuleFederationPluginOptions = { // Legacy: Keep for backward compat with old Cloud UI './EmbeddedApp': './src/embedded-app.tsx', './injectApp': './src/inject-app.tsx', - './connect-tiles': './src/components/pages/rp-connect/onboarding/connect-tiles.tsx', './config': './src/config.ts', }, diff --git a/frontend/src/components/constants.ts b/frontend/src/components/constants.ts index c83424780a..662fbf2c7a 100644 --- a/frontend/src/components/constants.ts +++ b/frontend/src/components/constants.ts @@ -6,10 +6,8 @@ export const BUILDER_API_KEY = '4abd0efa0759420b88149ada5c1eb216'; // By default, most feature flags will be false when there's no embedded mode on. export const FEATURE_FLAGS = { - enableRpcnTiles: false, enableRpcnTemplateGallery: false, enableRpcnVisualEditor: false, - enableServerlessOnboardingWizard: false, enableDataplaneObservabilityServerless: false, enableDataplaneObservability: false, enableNewPipelineLogs: false, diff --git a/frontend/src/components/layout/header.tsx b/frontend/src/components/layout/header.tsx index 3485ae59e4..75b5e63895 100644 --- a/frontend/src/components/layout/header.tsx +++ b/frontend/src/components/layout/header.tsx @@ -198,8 +198,6 @@ function useShouldShowRefresh() { const schemaCreateMatch = matchRoute({ to: '/schema-registry/create' }); const topicProduceRecordMatch = matchRoute({ to: '/topics/$topicName/produce-record' }); const secretsMatch = matchRoute({ to: '/secrets', fuzzy: false }); - const connectWizardPagesMatch = matchRoute({ to: '/rp-connect/wizard' }); - const getStartedApiMatch = matchRoute({ to: '/get-started/api' }); // matches acls const aclDetailMatch = matchRoute({ to: '/security/acls/$aclName/details' }); @@ -235,13 +233,6 @@ function useShouldShowRefresh() { if (userDetailMatch) { return false; } - if (connectWizardPagesMatch) { - return false; - } - if (getStartedApiMatch) { - return false; - } - return true; } function useShouldHideHeader() { @@ -254,14 +245,11 @@ function useShouldHideHeader() { matchRoute({ to: '/rp-connect/$pipelineId/edit' }) || matchRoute({ to: '/rp-connect/create' }); - // Both flags are cloud-only (schema requires embedded mode). - // enablePipelineDiagrams: full new pipeline layout with diagrams. - // enableRpcnTiles: new tiles-based create flow embedded in legacy layout. - if ( - isPipelineRoute && - isEmbedded() && - (isFeatureFlagEnabled('enablePipelineDiagrams') || isFeatureFlagEnabled('enableRpcnTiles')) - ) { + /** + * Flag is cloud-only (schema requires embedded mode). + * enablePipelineDiagrams: full new pipeline layout with diagrams. + */ + if (isPipelineRoute && isEmbedded() && isFeatureFlagEnabled('enablePipelineDiagrams')) { return true; } diff --git a/frontend/src/components/pages/connect/overview.tsx b/frontend/src/components/pages/connect/overview.tsx index d777b5e0a1..af42c009e9 100644 --- a/frontend/src/components/pages/connect/overview.tsx +++ b/frontend/src/components/pages/connect/overview.tsx @@ -29,7 +29,7 @@ import { TaskState, TasksColumn, } from './helper'; -import { isEmbedded, isFeatureFlagEnabled, isServerless } from '../../../config'; +import { isServerless } from '../../../config'; import { ListSecretScopesRequestSchema } from '../../../protogen/redpanda/api/dataplane/v1/secret_pb'; import { appGlobal } from '../../../state/app-global'; import { api, rpcnSecretManagerApi } from '../../../state/backend-api'; @@ -42,7 +42,6 @@ import SearchBar from '../../misc/search-bar'; import Section from '../../misc/section'; import Tabs, { type Tab } from '../../misc/tabs/tabs'; import { PageComponent, type PageInitHelper } from '../page'; -import { PipelineListPage } from '../rp-connect/pipeline/list'; import RpConnectPipelinesList from '../rp-connect/pipelines-list'; import { RedpandaConnectIntro } from '../rp-connect/redpanda-connect-intro'; @@ -141,9 +140,6 @@ class KafkaConnectOverview extends PageComponent<{ } render() { - if (isFeatureFlagEnabled('enableRpcnTiles') && isEmbedded()) { - return ; - } if (this.props.isLoadingKafkaConnectors) { return ; } diff --git a/frontend/src/components/pages/overview/api-connect-wizard.tsx b/frontend/src/components/pages/overview/api-connect-wizard.tsx deleted file mode 100644 index 0757bb5308..0000000000 --- a/frontend/src/components/pages/overview/api-connect-wizard.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import { TransportProvider } from '@connectrpc/connect-query'; -import { Markdown } from '@redpanda-data/ui'; -import { useNavigate, useRouter } from '@tanstack/react-router'; -import PageContent from 'components/misc/page-content'; -import { Button } from 'components/redpanda-ui/components/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'components/redpanda-ui/components/card'; -import { Spinner } from 'components/redpanda-ui/components/spinner'; -import { defineStepper } from 'components/redpanda-ui/components/stepper'; -import { config } from 'config'; -import { useControlplaneTransport } from 'hooks/use-controlplane-transport'; -import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useGetOnboardingCodeSnippetQuery } from 'react-query/api/onboarding'; -import { useGetServerlessClusterQuery } from 'react-query/api/serverless'; -import { useAPIWizardStore } from 'state/api-wizard-store'; -import { uiState } from 'state/ui-state'; -import { capitalizeFirst } from 'utils/utils'; -import { useShallow } from 'zustand/react/shallow'; - -import { AddTopicStep } from '../rp-connect/onboarding/add-topic-step'; -import { AddUserStep } from '../rp-connect/onboarding/add-user-step'; -import type { AddTopicFormData, BaseStepRef, UserStepRef } from '../rp-connect/types/wizard'; -import { handleStepResult } from '../rp-connect/utils/wizard'; - -const APIWizardStep = { - ADD_DATA: 'add-data-step', - ADD_TOPIC: 'add-topic-step', - ADD_USER: 'add-user-step', - CONNECT_CLUSTER: 'connect-cluster-step', -}; - -const apiWizardStepDefinitions = [ - { id: APIWizardStep.ADD_DATA, title: 'Add data' }, - { id: APIWizardStep.ADD_TOPIC, title: 'Add a topic' }, - { id: APIWizardStep.ADD_USER, title: 'Add a user' }, - { id: APIWizardStep.CONNECT_CLUSTER, title: 'Connect cluster' }, -]; - -const APIStepper = defineStepper(...apiWizardStepDefinitions); - -const APIWizardStepper = APIStepper.Stepper; -type APIWizardStepperSteps = typeof APIStepper.Steps; - -type HowToConnectProps = { - topicName?: string; - username?: string; - saslMechanism?: string; -}; - -const HowToConnectComponent = ({ topicName, username, saslMechanism }: HowToConnectProps) => { - const connectionName = useAPIWizardStore(useShallow((state) => state.connectionName)); - const { data: codeSnippet, isLoading: isLoadingCodeSnippet } = useGetOnboardingCodeSnippetQuery({ - language: connectionName, - }); - const { data: cluster } = useGetServerlessClusterQuery({ - id: config.clusterId, - }); - - const bootstrapServerUrl = cluster?.serverlessCluster?.kafkaApi?.seedBrokers.join(',') as string; - - const formattedCodeSnippet = useMemo(() => { - let codeSnippetWithVariables = codeSnippet; - if (bootstrapServerUrl) { - codeSnippetWithVariables = codeSnippetWithVariables?.replaceAll('', bootstrapServerUrl); - } - if (topicName) { - codeSnippetWithVariables = codeSnippetWithVariables?.replaceAll('demo-topic', topicName); - } - if (username) { - codeSnippetWithVariables = codeSnippetWithVariables?.replaceAll('', username); - } - if (saslMechanism) { - codeSnippetWithVariables = codeSnippetWithVariables - ?.replaceAll('', saslMechanism) - ?.replaceAll('', saslMechanism); - } - return codeSnippetWithVariables; - }, [codeSnippet, bootstrapServerUrl, topicName, username, saslMechanism]); - - const content = useMemo(() => { - if (isLoadingCodeSnippet) { - return ( -
-
Loading code snippet...
-
- ); - } - if (formattedCodeSnippet) { - return {formattedCodeSnippet}; - } - return ( -
-
No code snippet found
-
- ); - }, [isLoadingCodeSnippet, formattedCodeSnippet]); - - return ( - - - -

Connect to your cluster

-
- - Follow the instructions below to connect to your cluster using the {capitalizeFirst(connectionName ?? '')}{' '} - API. - -
- -
{content}
-
-
- ); -}; - -const HowToConnectStep = ({ topicName, username, saslMechanism }: HowToConnectProps) => { - const controlplaneTransport = useControlplaneTransport(); - return ( - - - - ); -}; - -export const APIConnectWizard = () => { - const navigate = useNavigate(); - const router = useRouter(); - const { reset: resetApiWizardStore } = useAPIWizardStore(); - const [topicName, setTopicName] = useState(undefined); - const [username, setUsername] = useState(undefined); - const [saslMechanism, setSaslMechanism] = useState(undefined); - - const addTopicStepRef = useRef>(null); - const addUserStepRef = useRef(null); - - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleNext = async (methods: APIWizardStepperSteps) => { - switch (methods.current.id) { - case APIWizardStep.ADD_TOPIC: { - setIsSubmitting(true); - const topicResult = await addTopicStepRef.current?.triggerSubmit().finally(() => setIsSubmitting(false)); - if (topicResult?.success) { - setTopicName(topicResult.data?.topicName); - } - handleStepResult(topicResult, methods.next); - break; - } - case APIWizardStep.ADD_USER: { - setIsSubmitting(true); - const userResult = await addUserStepRef.current?.triggerSubmit().finally(() => setIsSubmitting(false)); - if (userResult?.success && userResult.data && 'username' in userResult.data) { - // SASL user data - setUsername(userResult.data.username); - setSaslMechanism(userResult.data.saslMechanism); - } - // Service account data doesn't set username/saslMechanism - handleStepResult(userResult, methods.next); - break; - } - default: - methods.next(); - } - }; - - const handleSkip = (methods: APIWizardStepperSteps) => { - methods.next(); - }; - - const handleCancel = useCallback(() => { - resetApiWizardStore(); - router.history.back(); - }, [router, resetApiWizardStore]); - - useEffect(() => { - uiState.pageTitle = 'Connect to your cluster'; - uiState.pageBreadcrumbs = [ - { title: 'Cluster Overview', linkTo: '/overview' }, - { title: 'Connect to your cluster', linkTo: '' }, - ]; - }, []); - - useEffect(() => { - useAPIWizardStore.persist.rehydrate(); - return () => { - // Only clear if we're not on the get-started/api route (user navigated away) - const currentPath = window.location.pathname; - if (!currentPath.includes('/get-started/api')) { - resetApiWizardStore(); - } - }; - }, [resetApiWizardStore]); - - const handleCreate = useCallback(() => { - resetApiWizardStore(); - navigate({ to: '/overview' }); - window.location.reload(); // Required because we want to load Cloud UI's overview, not Console UI. - }, [navigate, resetApiWizardStore]); - - return ( - - - {({ methods }) => ( -
-
-
- - {apiWizardStepDefinitions.map((step) => ( - { - if (step.id === APIWizardStep.ADD_DATA) { - router.history.back(); - } else { - methods.goTo(step.id); - } - }} - > - {step.title} - - ))} - -
- {methods.switch({ - [APIWizardStep.ADD_TOPIC]: () => , - [APIWizardStep.ADD_USER]: () => ( - - ), - [APIWizardStep.CONNECT_CLUSTER]: () => ( - - ), - })} -
- -
- {!(methods.isFirst || methods.isLast) && ( - - )} - -
-
- {!methods.isLast && ( - - )} - - {methods.isLast ? ( - - ) : ( - - )} -
-
-
- )} -
-
- ); -}; diff --git a/frontend/src/components/pages/rp-connect/onboarding/connect-tile.tsx b/frontend/src/components/pages/rp-connect/onboarding/connect-tile.tsx deleted file mode 100644 index f1f48beef6..0000000000 --- a/frontend/src/components/pages/rp-connect/onboarding/connect-tile.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { type ComponentName, componentLogoMap } from 'assets/connectors/component-logo-map'; -import { Badge } from 'components/redpanda-ui/components/badge'; -import { ChoiceboxItem } from 'components/redpanda-ui/components/choicebox'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'components/redpanda-ui/components/tooltip'; -import { InlineCode } from 'components/redpanda-ui/components/typography'; -import { cn } from 'components/redpanda-ui/lib/utils'; -import { CheckIcon, Waypoints } from 'lucide-react'; -import { AnimatePresence, type MotionProps, motion } from 'motion/react'; -import { ComponentStatus } from 'protogen/redpanda/api/dataplane/v1/pipeline_pb'; - -import { ConnectorLogo } from './connector-logo'; -import type { ConnectComponentSpec } from '../types/schema'; -import { componentStatusToString } from '../utils/schema'; - -const getLogoForComponent = (component: ConnectComponentSpec) => { - if (component?.logoUrl) { - return {component.name}; - } - if (componentLogoMap[component.name as ComponentName]) { - return ; - } - return ; -}; - -const logoMotionProps: MotionProps = { - initial: { - opacity: 0, - transform: 'scale(0.8)', - }, - animate: { - opacity: 1, - transform: 'scale(1)', - }, - exit: { - opacity: 0, - transform: 'scale(0.8)', - }, - transition: { - duration: 0.2, - ease: 'easeInOut', - }, -}; - -export const ConnectTile = ({ - checked, - uniqueKey, - component, - onChange, -}: { - checked: boolean; - uniqueKey: string; - component: ConnectComponentSpec; - onChange: () => void; -}) => { - const content = ( - - {/* padding right to compensate for the absolute position of the logo */} -
-
- - {component.name} - - - {(component.status === ComponentStatus.BETA || - component.status === ComponentStatus.EXPERIMENTAL || - component.status === ComponentStatus.DEPRECATED) && - component.name !== 'redpanda' && ( - - {componentStatusToString(component.status)} - - )} - -
-
- - {checked ? ( - -
- -
-
- ) : ( - - {getLogoForComponent(component)} - - )} -
-
-
-
- ); - return component.summary ? ( - - - - -
{component.summary}
-
-
-
- ) : ( - content - ); -}; diff --git a/frontend/src/components/pages/rp-connect/onboarding/connect-tiles.tsx b/frontend/src/components/pages/rp-connect/onboarding/connect-tiles.tsx deleted file mode 100644 index 702e19915f..0000000000 --- a/frontend/src/components/pages/rp-connect/onboarding/connect-tiles.tsx +++ /dev/null @@ -1,462 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - type CardSize, - CardTitle, - type CardVariant, -} from 'components/redpanda-ui/components/card'; -import { Choicebox } from 'components/redpanda-ui/components/choicebox'; -import { DataTableFilter, type FilterColumnConfig } from 'components/redpanda-ui/components/data-table-filter'; -import { Form, FormControl, FormField, FormItem, FormMessage } from 'components/redpanda-ui/components/form'; -import { Input, InputStart } from 'components/redpanda-ui/components/input'; -import { Skeleton, SkeletonGroup } from 'components/redpanda-ui/components/skeleton'; -import { Link } from 'components/redpanda-ui/components/typography'; -import type { FiltersState } from 'components/redpanda-ui/lib/filter-utils'; -import { useDataTableFilter } from 'components/redpanda-ui/lib/use-data-table-filter'; -import { cn } from 'components/redpanda-ui/lib/utils'; -import { Search } from 'lucide-react'; -import type { ComponentList } from 'protogen/redpanda/api/dataplane/v1/pipeline_pb'; -import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { docsLinks } from 'utils/docs-links'; - -import { ConnectTile } from './connect-tile'; -import type { ConnectComponentSpec, ConnectComponentType, ExtendedConnectComponentSpec } from '../types/schema'; -import type { BaseStepRef } from '../types/wizard'; -import { type ConnectTilesListFormData, connectTilesListFormSchema } from '../types/wizard'; -import { getAllCategories } from '../utils/categories'; -import { parseSchema } from '../utils/schema'; - -const PRIORITY_COMPONENTS = [ - 'redpanda', - 'aws_s3', - 'gcp_cloud_storage', - 'azure_blob_storage', - 'gcp_spanner_cdc', - 'postgres_cdc', - 'mysql_cdc', - 'mongodb_cdc', - 'snowflake_streaming', - 'redpanda_migrator', - 'snowflake_put', - 'slack', - 'sftp', - 'nats', -]; - -const searchComponents = ( - allComponents: ConnectComponentSpec[], - query: string, - filters?: { - types?: ConnectComponentType[]; - categories?: string[]; - }, - additionalComponents?: ExtendedConnectComponentSpec[] -): ConnectComponentSpec[] => { - const matchesFilters = (component: ConnectComponentSpec) => { - if (filters?.types?.length && !filters.types.includes(component.type)) { - return false; - } - - if (query.trim()) { - const q = query.toLowerCase().trim(); - const matchesText = - component.name.toLowerCase().includes(q) || - (component.summary ?? '').toLowerCase().includes(q) || - (component.description ?? '').toLowerCase().includes(q) || - (component.categories ?? []).some((c) => c.toLowerCase().includes(q)); - if (!matchesText) { - return false; - } - } - - if (filters?.categories?.length) { - const hasMatchingCategory = component.categories?.some((cat: string) => filters.categories?.includes(cat)); - if (!hasMatchingCategory) { - return false; - } - } - - return true; - }; - - const result: ConnectComponentSpec[] = []; - - // 1. Additional components that match filters (in the order provided) - if (additionalComponents?.length) { - const filteredAdditional = additionalComponents.filter((comp) => matchesFilters(comp)); - result.push(...filteredAdditional); - } - - // 2. Priority components that match filters (excluding additional components) - const additionalNames = new Set(additionalComponents?.map((c) => c.name) || []); - const priorityComponents = allComponents - .filter( - (comp) => !additionalNames.has(comp.name) && PRIORITY_COMPONENTS.includes(comp.name) && matchesFilters(comp) - ) - .sort((a, b) => { - const aIndex = PRIORITY_COMPONENTS.indexOf(a.name); - const bIndex = PRIORITY_COMPONENTS.indexOf(b.name); - return aIndex - bIndex; - }); - result.push(...priorityComponents); - - // 3. Remaining components that match filters (alphabetically sorted) - const remainingComponents = allComponents - .filter((comp) => { - const isAdditional = additionalNames.has(comp.name); - const isPriority = PRIORITY_COMPONENTS.includes(comp.name); - if (isAdditional || isPriority) { - return false; - } - return matchesFilters(comp); - }) - .sort((a, b) => a.name.localeCompare(b.name)); - result.push(...remainingComponents); - - return result; -}; - -const ConnectTilesSkeleton = memo( - ({ - children, - gridCols = 4, - tileCount = 12, - }: { - children?: React.ReactNode; - gridCols?: number; - tileCount?: number; - }) => { - const skeletonIds = Array.from({ length: tileCount }, (_, i) => `skeleton-${i}`); - const skeletonTiles = skeletonIds.map((id) => ( -
- - - - - - - -
- )); - - return ( - -
- {children} - {skeletonTiles} -
-
- ); - } -); - -ConnectTilesSkeleton.displayName = 'ConnectTilesSkeleton'; - -export type ConnectTilesProps = { - components?: ComponentList; - isLoading?: boolean; - additionalComponents?: ExtendedConnectComponentSpec[]; - componentTypeFilter?: ConnectComponentType[]; - onChange?: (connectionName: string, connectionType: ConnectComponentType) => void; - onValidityChange?: (isValid: boolean) => void; - hideHeader?: boolean; - hideFilters?: boolean; - defaultConnectionName?: string; - defaultConnectionType?: ConnectComponentType; - gridCols?: number; - variant?: CardVariant; - size?: CardSize; - className?: string; - tileWrapperClassName?: string; - title?: React.ReactNode; - description?: React.ReactNode; - searchPlaceholder?: string; -}; - -export const ConnectTiles = memo( - forwardRef, ConnectTilesProps>( - ( - { - components, - isLoading, - additionalComponents, - componentTypeFilter, - onChange, - onValidityChange, - hideHeader, - hideFilters, - defaultConnectionName, - defaultConnectionType, - gridCols = 4, - variant, - size = 'full', - className, - tileWrapperClassName, - title, - description, - searchPlaceholder, - }, - ref - ) => { - const [showScrollGradient, setShowScrollGradient] = useState(false); - const scrollContainerRef = useRef(null); - - const checkScrollable = useCallback(() => { - const container = scrollContainerRef.current; - if (!container) { - return; - } - - const { scrollTop, scrollHeight, clientHeight } = container; - const isScrollable = scrollHeight > clientHeight; - const isNearBottom = scrollTop + clientHeight >= scrollHeight - 40; - - // Show gradient if scrollable AND not near bottom - setShowScrollGradient(isScrollable && !isNearBottom); - }, []); - - const defaultValues = useMemo( - () => ({ - connectionName: defaultConnectionName, - connectionType: defaultConnectionType, - }), - [defaultConnectionName, defaultConnectionType] - ); - - const form = useForm({ - resolver: zodResolver(connectTilesListFormSchema), - mode: 'onChange', - defaultValues, - }); - - const builtInComponents = useMemo(() => (components ? parseSchema(components) : []), [components]); - const allComponents = useMemo( - () => [...builtInComponents, ...(additionalComponents || [])], - [builtInComponents, additionalComponents] - ); - - const categories = useMemo( - () => getAllCategories(allComponents, componentTypeFilter), - [allComponents, componentTypeFilter] - ); - - const [searchQuery, setSearchQuery] = useState(''); - - const typeOptions = useMemo(() => { - const types = new Set(allComponents.map((c) => c.type)); - return [...types].sort().map((t) => ({ value: t, label: t.replace(/_/g, ' ') })); - }, [allComponents]); - - const filterColumns = useMemo( - () => [ - { - id: 'type', - displayName: 'Type', - displayNamePlural: 'Types', - type: 'multiOption', - options: typeOptions, - }, - { - id: 'category', - displayName: 'Category', - displayNamePlural: 'Categories', - type: 'multiOption', - options: categories.map((cat) => ({ value: cat.id, label: cat.name })), - }, - ], - [categories, typeOptions] - ); - - const defaultFilterValue = useMemo(() => { - if (!componentTypeFilter?.length) { - return []; - } - return [{ columnId: 'type', type: 'multiOption', operator: 'is', values: componentTypeFilter }]; - }, [componentTypeFilter]); - - const { filters, actions } = useDataTableFilter({ columns: filterColumns, defaultValue: defaultFilterValue }); - - const selectedTypes = useMemo(() => filters.find((f) => f.columnId === 'type')?.values ?? [], [filters]); - const selectedCategories = useMemo(() => filters.find((f) => f.columnId === 'category')?.values ?? [], [filters]); - - // Use filter-selected types if any, otherwise fall back to prop - const effectiveTypeFilter = - selectedTypes.length > 0 ? (selectedTypes as ConnectComponentType[]) : componentTypeFilter; - - const filteredComponents = useMemo( - () => - searchComponents( - allComponents, - searchQuery, - { - types: effectiveTypeFilter, - categories: selectedCategories, - }, - additionalComponents - ), - [effectiveTypeFilter, searchQuery, selectedCategories, allComponents, additionalComponents] - ); - - useEffect(() => { - requestAnimationFrame(() => { - checkScrollable(); - }); - }, [checkScrollable]); - - useEffect(() => { - onValidityChange?.(form.formState.isValid); - }, [form.formState.isValid, onValidityChange]); - - useImperativeHandle(ref, () => ({ - triggerSubmit: async () => { - const isValid = await form.trigger(); - if (isValid) { - const values = form.getValues(); - return { - success: true, - message: 'Connector selected', - data: values, - }; - } - return { - success: false, - message: 'Fix the form errors before proceeding', - error: 'Form validation failed', - }; - }, - isPending: false, - })); - - return ( - - {!hideHeader && ( - - -

{title ?? 'Select a connector'}

-
- - {description ?? ( -
- Redpanda Connect is a data streaming service for building scalable, high-performance data pipelines - that drive real-time analytics and actionable business insights. Integrate data across systems with - hundreds of prebuilt connectors, change data capture (CDC) capabilities, and YAML-configurable - pipelines.{' '} - - Learn more - -
- )} -
-
- )} - -
- {!hideFilters && ( -
- setSearchQuery(e.target.value)} - placeholder={searchPlaceholder ?? 'Search connectors...'} - value={searchQuery} - > - - - - - -
- )} - -
-
- { - const tiles = filteredComponents.map((component) => { - const uniqueKey = `${component.type}-${component.name}`; - const isChecked = - field.value === component.name && form.getValues('connectionType') === component.type; - - return ( - { - if (isChecked) { - field.onChange(''); - form.setValue('connectionType', '' as ConnectComponentType, { shouldValidate: true }); - } else { - field.onChange(component.name); - form.setValue('connectionType', component.type as ConnectComponentType, { - shouldValidate: true, - }); - onChange?.(component.name, component.type as ConnectComponentType); - } - }} - uniqueKey={uniqueKey} - /> - ); - }); - - const hasResults = filteredComponents.length > 0; - const showSkeleton = isLoading; - // biome-ignore lint/complexity/useSimplifiedLogicExpression: Logic is intentionally explicit for clarity - const hasNoResults = !showSkeleton && !hasResults; - - let content: React.ReactNode; - - if (hasNoResults) { - content = ( -
-
- No connections found matching your filters -
-
- Try adjusting your search or category filters -
-
- ); - } else if (showSkeleton) { - content = ( - - {tiles} - - ); - } else { - content = ( - -
{tiles}
-
- ); - } - - return ( - - {content} - - - ); - }} - /> -
- {Boolean(showScrollGradient) && ( -
- )} -
- - - - ); - } - ) -); diff --git a/frontend/src/components/pages/rp-connect/onboarding/onboarding-wizard.tsx b/frontend/src/components/pages/rp-connect/onboarding/onboarding-wizard.tsx deleted file mode 100644 index 10e9b3f1f1..0000000000 --- a/frontend/src/components/pages/rp-connect/onboarding/onboarding-wizard.tsx +++ /dev/null @@ -1,447 +0,0 @@ -import { create } from '@bufbuild/protobuf'; -import { getRouteApi, useNavigate } from '@tanstack/react-router'; - -const routeApi = getRouteApi('/rp-connect/wizard'); - -import PageContent from 'components/misc/page-content'; -import { Button } from 'components/redpanda-ui/components/button'; -import { Card, CardContent } from 'components/redpanda-ui/components/card'; -import { Spinner } from 'components/redpanda-ui/components/spinner'; -import { CheckIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; -import { AnimatePresence } from 'motion/react'; -import { ComponentSpecSchema } from 'protogen/redpanda/api/dataplane/v1/pipeline_pb'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useResetRpcnWizardStore, useRpcnWizardStore } from 'state/rpcn-wizard-store'; -import { useShallow } from 'zustand/react/shallow'; - -import { AddTopicStep } from './add-topic-step'; -import { AddUserStep } from './add-user-step'; -import { ConnectTiles } from './connect-tiles'; -import PipelinePage from '../pipeline'; -import { - REDPANDA_TOPIC_AND_USER_COMPONENTS, - stepMotionProps, - WizardStep, - WizardStepper, - type WizardStepperSteps, - wizardStepDefinitions, -} from '../types/constants'; -import type { ExtendedConnectComponentSpec } from '../types/schema'; -import type { AddTopicFormData, BaseStepRef, ConnectTilesListFormData, UserStepRef } from '../types/wizard'; -import { navigateToConnectClusters } from '../utils/navigation'; -import { useEnrichedComponents } from '../utils/use-enriched-components'; -import { handleStepResult, regenerateYamlForTopicUserComponents } from '../utils/wizard'; -import { getConnectTemplate } from '../utils/yaml'; - -export type ConnectOnboardingWizardProps = { - className?: string; - additionalComponents?: ExtendedConnectComponentSpec[]; - onChange?: (connectorName: string, connectorType: string) => void; - onCancel?: () => void; -}; - -export const ConnectOnboardingWizard = ({ - className, - additionalComponents = [ - create(ComponentSpecSchema, { - name: 'custom', - type: 'custom', - status: 0, - summary: 'Build your own pipeline from scratch.', - description: '', - categories: [], - version: '', - examples: [], - footnotes: '', - }) as ExtendedConnectComponentSpec, - ], - onChange, - onCancel: onCancelProp, -}: ConnectOnboardingWizardProps = {}) => { - const navigate = useNavigate(); - - const { components, componentList, isLoading: isComponentListLoading } = useEnrichedComponents(); - - const persistedInputConnectionName = useRpcnWizardStore(useShallow((state) => state.input?.connectionName)); - const persistedOutputConnectionName = useRpcnWizardStore(useShallow((state) => state.output?.connectionName)); - const persistedTopicName = useRpcnWizardStore(useShallow((state) => state.topicName)); - const persistedUserSaslMechanism = useRpcnWizardStore(useShallow((state) => state.saslMechanism)); - const persistedUsername = useRpcnWizardStore(useShallow((state) => state.username)); - const persistedConsumerGroup = useRpcnWizardStore(useShallow((state) => state.consumerGroup)); - const resetRpcnWizardStore = useResetRpcnWizardStore(); - const setWizardData = useRpcnWizardStore(useShallow((state) => state.setWizardData)); - const setTopicData = useRpcnWizardStore(useShallow((state) => state.setTopicData)); - const setUserData = useRpcnWizardStore(useShallow((state) => state.setUserData)); - - const persistedInputIsRedpandaComponent = useMemo( - () => - Boolean(persistedInputConnectionName) && - REDPANDA_TOPIC_AND_USER_COMPONENTS.includes(persistedInputConnectionName ?? ''), - [persistedInputConnectionName] - ); - - useEffect(() => { - const store = useRpcnWizardStore; - store.persist.rehydrate(); - }, []); - - useEffect(() => { - return () => { - // Only clear when navigating away from the wizard. - const currentPath = window.location.pathname; - if (!currentPath.includes('/rp-connect/wizard')) { - resetRpcnWizardStore(); - } - }; - }, [resetRpcnWizardStore]); - - const search = routeApi.useSearch(); - - const initialStep = useMemo(() => { - const stepSearchParam = search.step; - switch (stepSearchParam) { - case 'add-input': - return WizardStep.ADD_INPUT; - case 'add-output': - return WizardStep.ADD_OUTPUT; - case 'add-topic': - return WizardStep.ADD_TOPIC; - case 'add-user': - return WizardStep.ADD_USER; - case 'create-config': - return WizardStep.CREATE_CONFIG; - default: - return WizardStep.ADD_INPUT; - } - }, [search.step]); - - const addInputStepRef = useRef>(null); - const addOutputStepRef = useRef>(null); - const addTopicStepRef = useRef>(null); - const addUserStepRef = useRef(null); - - const [isSubmitting, setIsSubmitting] = useState(false); - - const [stepValidity, setStepValidity] = useState>({ - [WizardStep.ADD_INPUT]: false, - [WizardStep.ADD_OUTPUT]: false, - [WizardStep.ADD_TOPIC]: false, - [WizardStep.ADD_USER]: false, - }); - - const handleSkipToCreatePipeline = (methods: WizardStepperSteps) => { - if (methods.current.id === WizardStep.ADD_INPUT) { - resetRpcnWizardStore(); - } else if (methods.current.id === WizardStep.ADD_OUTPUT) { - setTopicData({}); - setUserData({}); - } - methods.goTo(WizardStep.CREATE_CONFIG); - }; - - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: helpers to reduce complexity wouldn't apply here - const handleNext = async (methods: WizardStepperSteps) => { - switch (methods.current.id) { - case WizardStep.ADD_INPUT: { - const result = await addInputStepRef.current?.triggerSubmit(); - const connectionName = result?.data?.connectionName; - const connectionType = result?.data?.connectionType; - - if (connectionType === 'custom') { - handleSkipToCreatePipeline(methods); - return; - } - if (result?.success && connectionName && connectionType) { - const yamlContent = getConnectTemplate({ - connectionName, - connectionType, - components, - existingYaml: useRpcnWizardStore.getState().yamlContent, - }); - - if (yamlContent) { - useRpcnWizardStore.getState().setYamlContent({ yamlContent }); - } - - if (connectionName === 'redpanda_common') { - setWizardData({ - input: { - connectionName, - connectionType, - }, - output: { - connectionName, - connectionType: 'output', - }, - }); - methods.goTo(WizardStep.ADD_TOPIC); - } else { - const { setWizardData: _, ...currentWizardData } = useRpcnWizardStore.getState(); - setWizardData({ - input: { - connectionName, - connectionType, - }, - ...(currentWizardData.output && { output: currentWizardData.output }), - }); - methods.next(); - } - onChange?.(connectionName, connectionType); - } - break; - } - case WizardStep.ADD_OUTPUT: { - const result = await addOutputStepRef.current?.triggerSubmit(); - const connectionName = result?.data?.connectionName; - const connectionType = result?.data?.connectionType; - - if (connectionType === 'custom') { - handleSkipToCreatePipeline(methods); - return; - } - - if (result?.success && connectionName && connectionType) { - const yamlContent = getConnectTemplate({ - connectionName, - connectionType, - components, - existingYaml: useRpcnWizardStore.getState().yamlContent, - }); - - if (yamlContent) { - useRpcnWizardStore.getState().setYamlContent({ yamlContent }); - } - - if (connectionName === 'redpanda_common') { - setWizardData({ - input: { - connectionName, - connectionType: 'input', - }, - output: { - connectionName, - connectionType, - }, - }); - } else { - const { setWizardData: _, ...currentWizardData } = useRpcnWizardStore.getState(); - setWizardData({ - output: { - connectionName, - connectionType, - }, - ...(currentWizardData.input && { input: currentWizardData.input }), - }); - } - onChange?.(connectionName, connectionType); - const outputNeedsTopicAndUser = REDPANDA_TOPIC_AND_USER_COMPONENTS.includes(connectionName); - if (persistedInputIsRedpandaComponent || outputNeedsTopicAndUser) { - methods.next(); - } else { - methods.goTo(WizardStep.CREATE_CONFIG); - } - } - break; - } - case WizardStep.ADD_TOPIC: { - setIsSubmitting(true); - const topicResult = await addTopicStepRef.current?.triggerSubmit().finally(() => setIsSubmitting(false)); - if (topicResult?.success && topicResult.data) { - setTopicData({ topicName: topicResult.data.topicName }); - regenerateYamlForTopicUserComponents(components); - } - handleStepResult(topicResult, methods.next); - break; - } - case WizardStep.ADD_USER: { - setIsSubmitting(true); - const userResult = await addUserStepRef.current?.triggerSubmit().finally(() => setIsSubmitting(false)); - if (userResult?.success && userResult.data) { - if ('authMethod' in userResult.data && userResult.data.authMethod === 'service-account') { - setUserData({ - authMethod: 'service-account', - username: '', - saslMechanism: 'SCRAM-SHA-256', - consumerGroup: '', - serviceAccountName: userResult.data.serviceAccountName, - serviceAccountId: userResult.data.serviceAccountId, - serviceAccountSecretName: userResult.data.serviceAccountSecretName, - }); - } else if ('username' in userResult.data) { - setUserData({ - authMethod: 'sasl', - username: userResult.data.username, - saslMechanism: userResult.data.saslMechanism, - consumerGroup: userResult.data.consumerGroup || '', - }); - } - regenerateYamlForTopicUserComponents(components); - methods.next(); - } - break; - } - default: - methods.next(); - } - }; - - const handleCancel = useCallback(() => { - resetRpcnWizardStore(); - if (onCancelProp) { - onCancelProp(); - } else if (search.serverless === 'true') { - navigate({ to: '/overview' }); - window.location.reload(); // Required because we want to load Cloud UI's overview, not Console UI. - } else { - navigateToConnectClusters(navigate); - } - }, [onCancelProp, navigate, resetRpcnWizardStore, search.serverless]); - - const handleInputValidityChange = useCallback((isValid: boolean) => { - setStepValidity((prev) => ({ ...prev, [WizardStep.ADD_INPUT]: isValid })); - }, []); - - const handleOutputValidityChange = useCallback((isValid: boolean) => { - setStepValidity((prev) => ({ ...prev, [WizardStep.ADD_OUTPUT]: isValid })); - }, []); - - const handleTopicValidityChange = useCallback((isValid: boolean) => { - setStepValidity((prev) => ({ ...prev, [WizardStep.ADD_TOPIC]: isValid })); - }, []); - - const handleUserValidityChange = useCallback((isValid: boolean) => { - setStepValidity((prev) => ({ ...prev, [WizardStep.ADD_USER]: isValid })); - }, []); - - return ( - - - {({ methods }) => ( -
-
-
- - {wizardStepDefinitions.map((step) => ( - s.id === step.id) < - wizardStepDefinitions.findIndex((s) => s.id === methods.current.id) ? ( - - ) : undefined - } - key={step.id} - of={step.id} - onClick={() => methods.goTo(step.id)} - > - {step.title} - - ))} - -
- - {methods.switch({ - [WizardStep.ADD_INPUT]: () => ( - - ), - [WizardStep.ADD_OUTPUT]: () => ( - - ), - [WizardStep.ADD_TOPIC]: () => ( - - ), - [WizardStep.ADD_USER]: () => ( - - ), - [WizardStep.CREATE_CONFIG]: () => ( - - - - - - ), - })} - -
- -
- {!(methods.isFirst || methods.isLast) && ( - - )} - {!methods.isLast && ( - - )} -
-
- {!methods.isLast && ( - - )} - {!methods.isLast && ( - - )} -
-
-
- )} -
-
- ); -}; diff --git a/frontend/src/components/pages/rp-connect/pipeline/list.tsx b/frontend/src/components/pages/rp-connect/pipeline/list.tsx index b606876a18..4c2419352f 100644 --- a/frontend/src/components/pages/rp-connect/pipeline/list.tsx +++ b/frontend/src/components/pages/rp-connect/pipeline/list.tsx @@ -48,7 +48,6 @@ import { useDataTableFilter } from 'components/redpanda-ui/lib/use-data-table-fi import { cn } from 'components/redpanda-ui/lib/utils'; import { DeleteResourceAlertDialog, DeleteResourceMenuItem } from 'components/ui/delete-resource-alert-dialog'; import { PIPELINE_STATE_OPTIONS, STARTABLE_STATES, STOPPABLE_STATES } from 'components/ui/pipeline/constants'; -import { isEmbedded, isFeatureFlagEnabled } from 'config'; import { AlertCircle, Box, MoreHorizontal } from 'lucide-react'; import { DeletePipelineRequestSchema, @@ -643,12 +642,7 @@ const PipelineListPageContent = () => { const handleCreateClick = useCallback(() => { resetRpcnWizardStore(); - // enablePipelineDiagrams skips the wizard and goes straight to the editor. - if (isFeatureFlagEnabled('enablePipelineDiagrams') && isEmbedded()) { - navigate({ to: '/rp-connect/create', search: {} as never }); - } else { - navigate({ to: '/rp-connect/wizard', search: { step: undefined, serverless: undefined } }); - } + navigate({ to: '/rp-connect/create', search: { serverless: undefined } }); }, [resetRpcnWizardStore, navigate]); if (isLoading) { diff --git a/frontend/src/components/pages/rp-connect/pipelines-create.tsx b/frontend/src/components/pages/rp-connect/pipelines-create.tsx index 40c6990a4b..f8a32252aa 100644 --- a/frontend/src/components/pages/rp-connect/pipelines-create.tsx +++ b/frontend/src/components/pages/rp-connect/pipelines-create.tsx @@ -19,7 +19,7 @@ import { Button } from 'components/redpanda-ui/components/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'components/redpanda-ui/components/card'; import { Spinner } from 'components/redpanda-ui/components/spinner'; import { Link as UILink } from 'components/redpanda-ui/components/typography'; -import { isEmbedded, isFeatureFlagEnabled } from 'config'; +import { isFeatureFlagEnabled } from 'config'; import { AlertCircle, ArrowRight, PlusIcon, Sparkles } from 'lucide-react'; import type { editor, IDisposable, IPosition, languages } from 'monaco-editor'; import { AnimatePresence, motion } from 'motion/react'; @@ -29,7 +29,6 @@ import { toast } from 'sonner'; import { docsLinks } from 'utils/docs-links'; import { formatPipelineError } from './errors'; -import PipelinePage from './pipeline'; import { SecretsQuickAdd } from './secrets/secrets-quick-add'; import { cpuToTasks, MAX_TASKS, MIN_TASKS, tasksToCPU } from './tasks'; import { TemplateGalleryDialog } from './template-gallery/template-gallery-dialog'; @@ -62,9 +61,6 @@ class RpConnectPipelinesCreate extends PageComponent<{}> { } render() { - if (isFeatureFlagEnabled('enableRpcnTiles') && isEmbedded()) { - return ; - } if (!pipelinesApi.pipelines) { return DefaultSkeleton; } diff --git a/frontend/src/components/pages/rp-connect/pipelines-details.tsx b/frontend/src/components/pages/rp-connect/pipelines-details.tsx index 47215b1a96..358e853e73 100644 --- a/frontend/src/components/pages/rp-connect/pipelines-details.tsx +++ b/frontend/src/components/pages/rp-connect/pipelines-details.tsx @@ -14,14 +14,12 @@ import { Alert, AlertIcon, Box, Button, createStandaloneToast, DataTable, Flex, import { Link } from '@tanstack/react-router'; import type { ColumnDef, SortingState } from '@tanstack/react-table'; import { Button as RegistryButton } from 'components/redpanda-ui/components/button'; -import { isEmbedded, isFeatureFlagEnabled } from 'config'; import { RefreshCcw } from 'lucide-react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { toast as sonnerToast } from 'sonner'; import { formatToastErrorMessageGRPC } from 'utils/toast.utils'; import { openDeleteModal } from './modals'; -import PipelinePage from './pipeline'; import { PipelineStatus } from './pipelines-list'; import { cpuToTasks } from './tasks'; import usePaginationParams from '../../../hooks/use-pagination-params'; @@ -72,10 +70,6 @@ class RpConnectPipelinesDetails extends PageComponent<{ pipelineId: string }> { } render() { - if (isFeatureFlagEnabled('enableRpcnTiles') && isEmbedded()) { - return ; - } - if (!pipelinesApi.pipelines) { return DefaultSkeleton; } diff --git a/frontend/src/components/pages/rp-connect/pipelines-edit.tsx b/frontend/src/components/pages/rp-connect/pipelines-edit.tsx index a0cb8ef6a6..7fd669524f 100644 --- a/frontend/src/components/pages/rp-connect/pipelines-edit.tsx +++ b/frontend/src/components/pages/rp-connect/pipelines-edit.tsx @@ -13,7 +13,6 @@ import { create } from '@bufbuild/protobuf'; import { Button, Flex, FormField, Input, NumberInput, useToast } from '@redpanda-data/ui'; import { Link } from '@tanstack/react-router'; import { Link as UILink } from 'components/redpanda-ui/components/typography'; -import { isEmbedded, isFeatureFlagEnabled } from 'config'; import { type Pipeline, type Pipeline_ServiceAccount, @@ -23,7 +22,6 @@ import { useState } from 'react'; import { docsLinks } from 'utils/docs-links'; import { formatPipelineError } from './errors'; -import PipelinePage from './pipeline'; import { PipelineEditor } from './pipelines-create'; import { cpuToTasks, MAX_TASKS, MIN_TASKS, tasksToCPU } from './tasks'; import { appGlobal } from '../../../state/app-global'; @@ -51,9 +49,6 @@ class RpConnectPipelinesEdit extends PageComponent<{ pipelineId: string }> { } render() { - if (isFeatureFlagEnabled('enableRpcnTiles') && isEmbedded()) { - return ; - } if (!pipelinesApi.pipelines) { return DefaultSkeleton; } diff --git a/frontend/src/components/pages/rp-connect/types/constants.ts b/frontend/src/components/pages/rp-connect/types/constants.ts index e4f64048b1..7ce0e5bbd1 100644 --- a/frontend/src/components/pages/rp-connect/types/constants.ts +++ b/frontend/src/components/pages/rp-connect/types/constants.ts @@ -1,5 +1,4 @@ import { defineStepper } from 'components/redpanda-ui/components/stepper'; -import type { MotionProps } from 'motion/react'; /** * Components that include keys for redpanda topics and users/sasl/acls @@ -55,39 +54,6 @@ export const convertToScreamingSnakeCase = (value: string): string => value.toUp export const getSecretSyntax = (secretName: string): string => `\${secrets.${secretName}}`; -export const WizardStep = { - ADD_INPUT: 'add-input-step', - ADD_OUTPUT: 'add-output-step', - ADD_TOPIC: 'add-topic-step', - ADD_USER: 'add-user-step', - CREATE_CONFIG: 'create-config-step', -} as const; - -export type WizardStepType = (typeof WizardStep)[keyof typeof WizardStep]; - -export const wizardStepDefinitions = [ - { - id: WizardStep.ADD_INPUT, - title: 'Add an input', - }, - { id: WizardStep.ADD_OUTPUT, title: 'Add an output' }, - { id: WizardStep.ADD_TOPIC, title: 'Add a topic' }, - { id: WizardStep.ADD_USER, title: 'Add permissions' }, - - { id: WizardStep.CREATE_CONFIG, title: 'Edit pipeline' }, -]; - -const Stepper = defineStepper(...wizardStepDefinitions); -export const WizardStepper = Stepper.Stepper; -export type WizardStepperSteps = typeof Stepper.Steps; - -export const stepMotionProps: MotionProps = { - initial: { opacity: 0, x: 20 }, - animate: { opacity: 1, x: 0 }, - exit: { opacity: 0, x: -20 }, - transition: { duration: 0.3, ease: 'easeInOut' }, -}; - export const RedpandaConnectorSetupStep = { ADD_TOPIC: 'redpanda-connector-add-topic', ADD_USER: 'redpanda-connector-add-user', diff --git a/frontend/src/components/pages/rp-connect/types/wizard.ts b/frontend/src/components/pages/rp-connect/types/wizard.ts index d2a85b5ad0..273ee8a078 100644 --- a/frontend/src/components/pages/rp-connect/types/wizard.ts +++ b/frontend/src/components/pages/rp-connect/types/wizard.ts @@ -11,13 +11,11 @@ import { z } from 'zod'; import { CONNECT_COMPONENT_TYPE } from './schema'; -export const connectTilesListFormSchema = z.object({ +const connectTilesListFormSchema = z.object({ connectionName: z.string().min(1, { message: 'Please select a connection method.' }), connectionType: z.enum(CONNECT_COMPONENT_TYPE), }); -export type ConnectTilesListFormData = z.infer; - export type OperationResult = { operation: string; // e.g., "Create user", "Create ACL", "Create secret" success: boolean; diff --git a/frontend/src/components/pages/rp-connect/utils/wizard.ts b/frontend/src/components/pages/rp-connect/utils/wizard.ts index 0e93a3c932..f2ea7e41fc 100644 --- a/frontend/src/components/pages/rp-connect/utils/wizard.ts +++ b/frontend/src/components/pages/rp-connect/utils/wizard.ts @@ -1,82 +1,3 @@ -import { toast } from 'sonner'; -import { useRpcnWizardStore } from 'state/rpcn-wizard-store'; - -import { getConnectTemplate } from './yaml'; -import { REDPANDA_TOPIC_AND_USER_COMPONENTS } from '../types/constants'; -import type { ConnectComponentSpec } from '../types/schema'; -import type { StepSubmissionResult } from '../types/wizard'; - -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex business logic -export const handleStepResult = (result: StepSubmissionResult | undefined, onSuccess: () => void): boolean => { - if (!result) { - return false; - } - - if (result.operations && result.operations.length > 0) { - for (const operation of result.operations) { - if (operation.success && operation.message) { - toast.success(operation.message, { - description: operation.operation, - }); - } else if (!operation.success && operation.error) { - toast.error(operation.error, { - description: operation.operation, - }); - } - } - } - - if (result.success) { - if ((!result.operations || result.operations.length === 0) && result.message) { - toast.success(result.message); - } - onSuccess(); - return true; - } - - if (result.error && (!result.operations || result.operations.length === 0)) { - toast.error(result.error); - } - - return false; -}; - -/** Regenerates YAML for components needing topic/user data, at the ADD_TOPIC and ADD_USER steps. */ -export const regenerateYamlForTopicUserComponents = (components: ConnectComponentSpec[]): void => { - const { setWizardData: _, ...wizardData } = useRpcnWizardStore.getState(); - - const inputNeedsTopicUser = - wizardData.input?.connectionName && REDPANDA_TOPIC_AND_USER_COMPONENTS.includes(wizardData.input.connectionName); - const outputNeedsTopicUser = - wizardData.output?.connectionName && REDPANDA_TOPIC_AND_USER_COMPONENTS.includes(wizardData.output.connectionName); - - if (inputNeedsTopicUser || outputNeedsTopicUser) { - let yamlContent = useRpcnWizardStore.getState().yamlContent || ''; - - if (inputNeedsTopicUser && wizardData.input?.connectionName && wizardData.input?.connectionType) { - yamlContent = - getConnectTemplate({ - connectionName: wizardData.input.connectionName, - connectionType: wizardData.input.connectionType, - components, - existingYaml: yamlContent, - }) || yamlContent; - } - - if (outputNeedsTopicUser && wizardData.output?.connectionName && wizardData.output?.connectionType) { - yamlContent = - getConnectTemplate({ - connectionName: wizardData.output.connectionName, - connectionType: wizardData.output.connectionType, - components, - existingYaml: yamlContent, - }) || yamlContent; - } - - useRpcnWizardStore.getState().setYamlContent({ yamlContent }); - } -}; - /** Matches 'topic' (outputs/cache) or 'topics' (inputs). */ export const isTopicField = (fieldName: string): boolean => { const normalizedName = fieldName.toLowerCase(); diff --git a/frontend/src/federation/console-app.test.tsx b/frontend/src/federation/console-app.test.tsx index a62e6114b3..1e124c4746 100644 --- a/frontend/src/federation/console-app.test.tsx +++ b/frontend/src/federation/console-app.test.tsx @@ -151,7 +151,7 @@ describe('ConsoleApp', () => { }, }; - render(); + render(); await waitFor(() => { expect(setup).toHaveBeenCalledWith( @@ -160,7 +160,7 @@ describe('ConsoleApp', () => { clusterId: 'test-cluster-id', setSidebarItems: mockOnSidebarItemsChange, setBreadcrumbs: mockOnBreadcrumbsChange, - featureFlags: { enableRpcnTiles: true }, + featureFlags: { enablePipelineDiagrams: true }, urlOverride: { grpc: 'http://custom-grpc:9090' }, }) ); @@ -241,7 +241,7 @@ describe('ConsoleApp', () => { describe('Feature Flags', () => { test('passes feature flags to setup()', async () => { - const flags = { enableNewSecurityPage: false, enableRpcnTiles: true }; + const flags = { enableNewSecurityPage: false, enablePipelineDiagrams: true }; render(); await waitFor(() => { diff --git a/frontend/src/react-query/api/onboarding.tsx b/frontend/src/react-query/api/onboarding.tsx deleted file mode 100644 index a958b565c2..0000000000 --- a/frontend/src/react-query/api/onboarding.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useQuery as useTanstackQuery } from '@tanstack/react-query'; - -export const GITHUB_CODE_SNIPPETS_API_BASE_URL = - 'https://raw.githubusercontent.com/redpanda-data/how-to-connect-code-snippets'; - -type CodeSnippetRequest = { - language?: string; -}; - -const fetchHowToConnectSnippet = async (language?: string): Promise => { - if (!language) { - return ''; - } - const response = await fetch(`${GITHUB_CODE_SNIPPETS_API_BASE_URL}/main/${language}/readme.md`); - - if (!response.ok) { - throw new Error(`Failed to fetch onboarding code snippet: ${response.status} ${response.statusText}`); - } - - const content = await response.text(); - - return content; -}; - -export const useGetOnboardingCodeSnippetQuery = (input: CodeSnippetRequest) => - useTanstackQuery({ - queryKey: ['onboarding-code-snippet', input.language], - queryFn: () => fetchHowToConnectSnippet(input.language), - enabled: input.language !== '', - }); diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index e234a9625a..bd32d388d1 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -35,11 +35,9 @@ import { Route as SecretsCreateRouteImport } from './routes/secrets/create'; import { Route as SchemaRegistryEditModeRouteImport } from './routes/schema-registry/edit-mode'; import { Route as SchemaRegistryEditCompatibilityRouteImport } from './routes/schema-registry/edit-compatibility'; import { Route as SchemaRegistryCreateRouteImport } from './routes/schema-registry/create'; -import { Route as RpConnectWizardRouteImport } from './routes/rp-connect/wizard'; import { Route as RpConnectCreateRouteImport } from './routes/rp-connect/create'; import { Route as OverviewBrokerIdRouteImport } from './routes/overview/$brokerId'; import { Route as GroupsGroupIdRouteImport } from './routes/groups/$groupId'; -import { Route as GetStartedApiRouteImport } from './routes/get-started/api'; import { Route as TopicsTopicNameIndexRouteImport } from './routes/topics/$topicName/index'; import { Route as ShadowlinksNameIndexRouteImport } from './routes/shadowlinks/$name/index'; import { Route as SecurityUsersIndexRouteImport } from './routes/security/users/index'; @@ -207,11 +205,6 @@ const SchemaRegistryCreateRoute = SchemaRegistryCreateRouteImport.update({ path: '/schema-registry/create', getParentRoute: () => rootRouteImport, } as any); -const RpConnectWizardRoute = RpConnectWizardRouteImport.update({ - id: '/rp-connect/wizard', - path: '/rp-connect/wizard', - getParentRoute: () => rootRouteImport, -} as any); const RpConnectCreateRoute = RpConnectCreateRouteImport.update({ id: '/rp-connect/create', path: '/rp-connect/create', @@ -227,11 +220,6 @@ const GroupsGroupIdRoute = GroupsGroupIdRouteImport.update({ path: '/groups/$groupId', getParentRoute: () => rootRouteImport, } as any); -const GetStartedApiRoute = GetStartedApiRouteImport.update({ - id: '/get-started/api', - path: '/get-started/api', - getParentRoute: () => rootRouteImport, -} as any); const TopicsTopicNameIndexRoute = TopicsTopicNameIndexRouteImport.update({ id: '/topics/$topicName/', path: '/topics/$topicName/', @@ -439,11 +427,9 @@ export interface FileRoutesByFullPath { '/transforms-setup': typeof TransformsSetupRoute; '/trial-expired': typeof TrialExpiredRoute; '/upload-license': typeof UploadLicenseRoute; - '/get-started/api': typeof GetStartedApiRoute; '/groups/$groupId': typeof GroupsGroupIdRoute; '/overview/$brokerId': typeof OverviewBrokerIdRoute; '/rp-connect/create': typeof RpConnectCreateRoute; - '/rp-connect/wizard': typeof RpConnectWizardRoute; '/schema-registry/create': typeof SchemaRegistryCreateRoute; '/schema-registry/edit-compatibility': typeof SchemaRegistryEditCompatibilityRoute; '/schema-registry/edit-mode': typeof SchemaRegistryEditModeRoute; @@ -506,11 +492,9 @@ export interface FileRoutesByTo { '/transforms-setup': typeof TransformsSetupRoute; '/trial-expired': typeof TrialExpiredRoute; '/upload-license': typeof UploadLicenseRoute; - '/get-started/api': typeof GetStartedApiRoute; '/groups/$groupId': typeof GroupsGroupIdRoute; '/overview/$brokerId': typeof OverviewBrokerIdRoute; '/rp-connect/create': typeof RpConnectCreateRoute; - '/rp-connect/wizard': typeof RpConnectWizardRoute; '/schema-registry/create': typeof SchemaRegistryCreateRoute; '/schema-registry/edit-compatibility': typeof SchemaRegistryEditCompatibilityRoute; '/schema-registry/edit-mode': typeof SchemaRegistryEditModeRoute; @@ -575,11 +559,9 @@ export interface FileRoutesById { '/transforms-setup': typeof TransformsSetupRoute; '/trial-expired': typeof TrialExpiredRoute; '/upload-license': typeof UploadLicenseRoute; - '/get-started/api': typeof GetStartedApiRoute; '/groups/$groupId': typeof GroupsGroupIdRoute; '/overview/$brokerId': typeof OverviewBrokerIdRoute; '/rp-connect/create': typeof RpConnectCreateRoute; - '/rp-connect/wizard': typeof RpConnectWizardRoute; '/schema-registry/create': typeof SchemaRegistryCreateRoute; '/schema-registry/edit-compatibility': typeof SchemaRegistryEditCompatibilityRoute; '/schema-registry/edit-mode': typeof SchemaRegistryEditModeRoute; @@ -645,11 +627,9 @@ export interface FileRouteTypes { | '/transforms-setup' | '/trial-expired' | '/upload-license' - | '/get-started/api' | '/groups/$groupId' | '/overview/$brokerId' | '/rp-connect/create' - | '/rp-connect/wizard' | '/schema-registry/create' | '/schema-registry/edit-compatibility' | '/schema-registry/edit-mode' @@ -712,11 +692,9 @@ export interface FileRouteTypes { | '/transforms-setup' | '/trial-expired' | '/upload-license' - | '/get-started/api' | '/groups/$groupId' | '/overview/$brokerId' | '/rp-connect/create' - | '/rp-connect/wizard' | '/schema-registry/create' | '/schema-registry/edit-compatibility' | '/schema-registry/edit-mode' @@ -780,11 +758,9 @@ export interface FileRouteTypes { | '/transforms-setup' | '/trial-expired' | '/upload-license' - | '/get-started/api' | '/groups/$groupId' | '/overview/$brokerId' | '/rp-connect/create' - | '/rp-connect/wizard' | '/schema-registry/create' | '/schema-registry/edit-compatibility' | '/schema-registry/edit-mode' @@ -849,11 +825,9 @@ export interface RootRouteChildren { TransformsSetupRoute: typeof TransformsSetupRoute; TrialExpiredRoute: typeof TrialExpiredRoute; UploadLicenseRoute: typeof UploadLicenseRoute; - GetStartedApiRoute: typeof GetStartedApiRoute; GroupsGroupIdRoute: typeof GroupsGroupIdRoute; OverviewBrokerIdRoute: typeof OverviewBrokerIdRoute; RpConnectCreateRoute: typeof RpConnectCreateRoute; - RpConnectWizardRoute: typeof RpConnectWizardRoute; SchemaRegistryCreateRoute: typeof SchemaRegistryCreateRoute; SchemaRegistryEditCompatibilityRoute: typeof SchemaRegistryEditCompatibilityRoute; SchemaRegistryEditModeRoute: typeof SchemaRegistryEditModeRoute; @@ -1078,13 +1052,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SchemaRegistryCreateRouteImport; parentRoute: typeof rootRouteImport; }; - '/rp-connect/wizard': { - id: '/rp-connect/wizard'; - path: '/rp-connect/wizard'; - fullPath: '/rp-connect/wizard'; - preLoaderRoute: typeof RpConnectWizardRouteImport; - parentRoute: typeof rootRouteImport; - }; '/rp-connect/create': { id: '/rp-connect/create'; path: '/rp-connect/create'; @@ -1106,13 +1073,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof GroupsGroupIdRouteImport; parentRoute: typeof rootRouteImport; }; - '/get-started/api': { - id: '/get-started/api'; - path: '/get-started/api'; - fullPath: '/get-started/api'; - preLoaderRoute: typeof GetStartedApiRouteImport; - parentRoute: typeof rootRouteImport; - }; '/topics/$topicName/': { id: '/topics/$topicName/'; path: '/topics/$topicName'; @@ -1410,11 +1370,9 @@ const rootRouteChildren: RootRouteChildren = { TransformsSetupRoute: TransformsSetupRoute, TrialExpiredRoute: TrialExpiredRoute, UploadLicenseRoute: UploadLicenseRoute, - GetStartedApiRoute: GetStartedApiRoute, GroupsGroupIdRoute: GroupsGroupIdRoute, OverviewBrokerIdRoute: OverviewBrokerIdRoute, RpConnectCreateRoute: RpConnectCreateRoute, - RpConnectWizardRoute: RpConnectWizardRoute, SchemaRegistryCreateRoute: SchemaRegistryCreateRoute, SchemaRegistryEditCompatibilityRoute: SchemaRegistryEditCompatibilityRoute, SchemaRegistryEditModeRoute: SchemaRegistryEditModeRoute, diff --git a/frontend/src/routes/get-started/api.tsx b/frontend/src/routes/get-started/api.tsx deleted file mode 100644 index 47145916fd..0000000000 --- a/frontend/src/routes/get-started/api.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright 2026 Redpanda Data, Inc. - * - * Use of this software is governed by the Business Source License - * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md - * - * As of the Change Date specified in that file, in accordance with - * the Business Source License, use of this software will be governed - * by the Apache License, Version 2.0 - */ - -import { createFileRoute } from '@tanstack/react-router'; - -import { APIConnectWizard } from '../../components/pages/overview/api-connect-wizard'; - -export const Route = createFileRoute('/get-started/api')({ - staticData: { - title: 'Get Started - API', - }, - component: APIConnectWizard, -}); diff --git a/frontend/src/routes/rp-connect/$pipelineId/edit.tsx b/frontend/src/routes/rp-connect/$pipelineId/edit.tsx index e796996fb9..2e9bfdde2f 100644 --- a/frontend/src/routes/rp-connect/$pipelineId/edit.tsx +++ b/frontend/src/routes/rp-connect/$pipelineId/edit.tsx @@ -27,7 +27,7 @@ export const Route = createFileRoute('/rp-connect/$pipelineId/edit')({ function PipelineEditRoute() { const { pipelineId } = useParams({ from: '/rp-connect/$pipelineId/edit' }); // Tier 1: enablePipelineDiagrams → new pipeline page directly - // Tier 2/3: legacy wrapper (internally checks enableRpcnTiles → PipelinePage, else legacy form) + // Tier 2: legacy form if (isFeatureFlagEnabled('enablePipelineDiagrams') && isEmbedded()) { return ; } diff --git a/frontend/src/routes/rp-connect/$pipelineId/index.tsx b/frontend/src/routes/rp-connect/$pipelineId/index.tsx index 69a2715cc9..9aa804cc78 100644 --- a/frontend/src/routes/rp-connect/$pipelineId/index.tsx +++ b/frontend/src/routes/rp-connect/$pipelineId/index.tsx @@ -35,7 +35,7 @@ export const Route = createFileRoute('/rp-connect/$pipelineId/')({ function PipelineDetailsRoute() { const { pipelineId } = useParams({ from: '/rp-connect/$pipelineId/' }); // Tier 1: enablePipelineDiagrams → new pipeline page directly - // Tier 2/3: legacy wrapper (internally checks enableRpcnTiles → PipelinePage, else legacy form) + // Tier 2: legacy form if (isFeatureFlagEnabled('enablePipelineDiagrams') && isEmbedded()) { return ; } diff --git a/frontend/src/routes/rp-connect/create.tsx b/frontend/src/routes/rp-connect/create.tsx index 484eac9a59..8742d77492 100644 --- a/frontend/src/routes/rp-connect/create.tsx +++ b/frontend/src/routes/rp-connect/create.tsx @@ -32,7 +32,7 @@ export const Route = createFileRoute('/rp-connect/create')({ function CreatePipelineRoute() { // Tier 1: enablePipelineDiagrams → new pipeline page directly - // Tier 2/3: legacy wrapper (internally checks enableRpcnTiles → PipelinePage, else legacy form) + // Tier 2: legacy form if (isFeatureFlagEnabled('enablePipelineDiagrams') && isEmbedded()) { return ; } diff --git a/frontend/src/routes/rp-connect/wizard.tsx b/frontend/src/routes/rp-connect/wizard.tsx deleted file mode 100644 index 493fb00b8e..0000000000 --- a/frontend/src/routes/rp-connect/wizard.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2026 Redpanda Data, Inc. - * - * Use of this software is governed by the Business Source License - * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md - * - * As of the Change Date specified in that file, in accordance with - * the Business Source License, use of this software will be governed - * by the Apache License, Version 2.0 - */ - -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { isEmbedded, isFeatureFlagEnabled } from 'config'; -import { z } from 'zod'; - -import { ConnectOnboardingWizard } from '../../components/pages/rp-connect/onboarding/onboarding-wizard'; - -const searchSchema = z.object({ - step: z.string().optional().catch(undefined), - serverless: z.string().optional().catch(undefined), -}); - -export const Route = createFileRoute('/rp-connect/wizard')({ - staticData: { - title: 'Connect Wizard', - }, - validateSearch: searchSchema, - beforeLoad: ({ search }) => { - // Tier 1: enablePipelineDiagrams → redirect to pipeline editor, skip wizard entirely - // Tier 2/3: render wizard (enableRpcnTiles check happens inside the wizard's PipelinePage embed) - if (isFeatureFlagEnabled('enablePipelineDiagrams') && isEmbedded()) { - throw redirect({ - to: '/rp-connect/create', - search: { serverless: search.serverless }, - }); - } - }, - component: ConnectOnboardingWizard, -}); diff --git a/frontend/src/state/api-wizard-store.ts b/frontend/src/state/api-wizard-store.ts deleted file mode 100644 index 02b6c842c7..0000000000 --- a/frontend/src/state/api-wizard-store.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2025 Redpanda Data, Inc. - * - * Use of this software is governed by the Business Source License - * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md - * - * As of the Change Date specified in that file, in accordance with - * the Business Source License, use of this software will be governed - * by the Apache License, Version 2.0 - */ - -import { createFlatStorage } from 'utils/store'; -import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; - -export const API_WIZARD_CONNECTOR_NAME_KEY = 'api-wizard-connector-name'; - -export type APIConnectWizardFormData = { - connectionName?: string; -}; - -const initialAPIWizardData: Partial = {}; - -export const useAPIWizardStore = create< - Partial & { - setApiWizardData: (data: Partial) => void; - reset: () => void; - } ->()( - persist( - (set, get) => ({ - ...initialAPIWizardData, - setApiWizardData: (data) => set(data), - reset: () => set({ ...initialAPIWizardData, setApiWizardData: get().setApiWizardData, reset: get().reset }, true), - }), - { - name: API_WIZARD_CONNECTOR_NAME_KEY, - storage: createFlatStorage>(), - } - ) -);