From 9f377674b830350652bbe4e2a9015d96053a7aec Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Mon, 28 Jul 2025 16:00:42 +0300 Subject: [PATCH 01/12] FE: Clusters as separated page --- .../components/ClusterPage/ClusterPage.tsx | 10 ++++ .../KafkaConnectClustersPage.tsx | 35 +++++++++++++ .../ui/List/Cells/ConnectorsCell.tsx | 22 ++++++++ .../ui/List/Cells/NameCell.tsx | 9 ++++ .../ui/List/Cells/TasksCell.tsx | 26 ++++++++++ .../KafkaConnectClustersPage/ui/List/List.tsx | 50 +++++++++++++++++++ .../Statistics/Statistic/Statistic.styled.tsx | 27 ++++++++++ .../ui/Statistics/Statistic/Statistic.tsx | 28 +++++++++++ .../ui/Statistics/Statistics.styled.tsx | 8 +++ .../ui/Statistics/Statistics.tsx | 30 +++++++++++ .../ui/Statistics/models/computeStatistics.ts | 31 ++++++++++++ .../Nav/ClusterMenu/ClusterMenu.tsx | 8 +++ .../common/AlertBadge/AlertBadge.styled.tsx | 18 +++++++ .../common/AlertBadge/AlertBadge.tsx | 26 ++++++++++ .../src/components/common/Icons/AlertIcon.tsx | 8 ++- frontend/src/lib/paths.ts | 6 +++ frontend/src/theme/theme.ts | 50 +++++++++++++++++++ 17 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/KafkaConnectClustersPage/KafkaConnectClustersPage.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/ConnectorsCell.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/NameCell.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/List/List.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.styled.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.styled.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx create mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/Statistics/models/computeStatistics.ts create mode 100644 frontend/src/components/common/AlertBadge/AlertBadge.styled.tsx create mode 100644 frontend/src/components/common/AlertBadge/AlertBadge.tsx diff --git a/frontend/src/components/ClusterPage/ClusterPage.tsx b/frontend/src/components/ClusterPage/ClusterPage.tsx index 29d2015f6..37ba8a73f 100644 --- a/frontend/src/components/ClusterPage/ClusterPage.tsx +++ b/frontend/src/components/ClusterPage/ClusterPage.tsx @@ -14,6 +14,7 @@ import { clusterConfigRelativePath, getNonExactPath, clusterAclRelativePath, + kafkaConnectsPaths, } from 'lib/paths'; import ClusterContext from 'components/contexts/ClusterContext'; import PageLoader from 'components/common/PageLoader/PageLoader'; @@ -24,6 +25,9 @@ const Brokers = React.lazy(() => import('components/Brokers/Brokers')); const Topics = React.lazy(() => import('components/Topics/Topics')); const Schemas = React.lazy(() => import('components/Schemas/Schemas')); const Connect = React.lazy(() => import('components/Connect/Connect')); +const KafkaConnectClusters = React.lazy( + () => import('components/KafkaConnectClustersPage/KafkaConnectClustersPage') +); const KsqlDb = React.lazy(() => import('components/KsqlDb/KsqlDb')); const ClusterConfigPage = React.lazy( () => import('components/ClusterPage/ClusterConfigPage') @@ -88,6 +92,12 @@ const ClusterPage: React.FC = () => { element={} /> )} + {contextValue.hasKafkaConnectConfigured && ( + } + /> + )} {contextValue.hasKafkaConnectConfigured && ( { + return ( +
+ + + +
+ ); +}; + +export default KafkaConnectClustersPage; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/ConnectorsCell.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/ConnectorsCell.tsx new file mode 100644 index 000000000..00325c5af --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/ConnectorsCell.tsx @@ -0,0 +1,22 @@ +import AlertBadge from 'components/common/AlertBadge/AlertBadge'; +import { Connect } from 'generated-sources'; +import React from 'react'; + +type Props = { connect: Connect }; +const ConnectorsCell = ({ connect }: Props) => { + const count = connect.connectorsCount ?? 0; + const failedCount = connect.failedConnectorsCount ?? 0; + const text = `${count - failedCount}/${count}`; + + if (failedCount > 0) { + return ( + + + + + ); + } + + return text; +}; +export default ConnectorsCell; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/NameCell.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/NameCell.tsx new file mode 100644 index 000000000..dece56545 --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/NameCell.tsx @@ -0,0 +1,9 @@ +import { CellContext } from '@tanstack/react-table'; +import { Connect } from 'generated-sources'; +import React from 'react'; + +type Props = CellContext; +const NameCell = ({ getValue }: Props) => { + return
{getValue()}
; +}; +export default NameCell; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx new file mode 100644 index 000000000..9fc5f13b9 --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx @@ -0,0 +1,26 @@ +import AlertBadge from 'components/common/AlertBadge/AlertBadge'; +import { Connect } from 'generated-sources'; +import React from 'react'; + +type Props = { connect: Connect }; +const TasksCell = ({ connect }: Props) => { + const count = connect.tasksCount ?? 0; + const failedCount = connect.failedTasksCount ?? 0; + const text = `${count - failedCount}/${count}`; + + if (failedCount > 0) { + return ( + + + + + ); + } + + return ( +
+ {count - failedCount}/{count} +
+ ); +}; +export default TasksCell; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/List.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/List/List.tsx new file mode 100644 index 000000000..93efd18ac --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/List/List.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Connect } from 'generated-sources'; +import Table from 'components/common/NewTable'; +import useAppParams from 'lib/hooks/useAppParams'; +import { ClusterName } from 'lib/interfaces/cluster'; +import { useNavigate } from 'react-router-dom'; +import { clusterConnectorsPath } from 'lib/paths'; +import { createColumnHelper } from '@tanstack/react-table'; + +import ConnectorsCell from './Cells/ConnectorsCell'; +import NameCell from './Cells/NameCell'; +import TasksCell from './Cells/TasksCell'; + +const helper = createColumnHelper(); +export const columns = [ + helper.accessor('name', { cell: NameCell, size: 600 }), + helper.display({ + header: 'Connectors', + id: 'connectors', + cell: (props) => , + size: 100, + }), + helper.display({ + header: 'Running tasks', + id: 'tasks', + cell: (props) => , + size: 100, + }), +]; + +interface Props { + connects: Connect[]; +} +const List = ({ connects }: Props) => { + const navigate = useNavigate(); + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + + return ( + { + navigate(`${clusterConnectorsPath(clusterName)}?connect=${name}`); + }} + emptyMessage="No kafka connect clusters" + /> + ); +}; + +export default List; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.styled.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.styled.tsx new file mode 100644 index 000000000..6e61c39bc --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.styled.tsx @@ -0,0 +1,27 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + width: 216px; + height: 68px; + border-radius: 12px; + padding: 12px 16px; + background: ${({ theme }) => theme.kafkaConectClusters.statistic.background}; +`; + +export const Header = styled.span` + font-weight: 500; + font-size: 12px; + line-height: 16px; + color: ${({ theme }) => theme.kafkaConectClusters.statistic.header.color}; +`; +export const Footer = styled.div` + display: flex; + justify-content: space-between; +`; + +export const Count = styled.div` + font-weight: 500; + font-size: 16px; + line-height: 24px; + color: ${({ theme }) => theme.kafkaConectClusters.statistic.count.color}; +`; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx new file mode 100644 index 000000000..d7f572583 --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import AlertBadge from 'components/common/AlertBadge/AlertBadge'; + +import * as S from './Statistic.styled'; + +type Props = { + title: string; + count: number; + warningCount?: number; +}; +const Statistic = ({ title, count, warningCount }: Props) => { + return ( + + {title} + + {count} + {warningCount && ( + + + + + )} + + + ); +}; + +export default Statistic; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.styled.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.styled.tsx new file mode 100644 index 000000000..06299e8cb --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.styled.tsx @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + padding: 16px; + display: flex; + gap: 4px; + background: ${({ theme }) => theme.kafkaConectClusters.statistics.background}; +`; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx new file mode 100644 index 000000000..ad59ab5d6 --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx @@ -0,0 +1,30 @@ +import React, { useMemo } from 'react'; +import { Connect } from 'generated-sources'; + +import * as S from './Statistics.styled'; +import { computeStatistic } from './models/computeStatistics'; +import Statistic from './Statistic/Statistic'; + +type Props = { connects: Connect[] }; +const Statistics = ({ connects }: Props) => { + const statistic = useMemo(() => { + return computeStatistic(connects); + }, [connects]); + return ( + + + + + + ); +}; + +export default Statistics; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/models/computeStatistics.ts b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/models/computeStatistics.ts new file mode 100644 index 000000000..e90640957 --- /dev/null +++ b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/models/computeStatistics.ts @@ -0,0 +1,31 @@ +import { Connect } from 'generated-sources'; + +interface Statistic { + clustersCount: number; + connectorsCount: number; + failedConnectorsCount: number; + tasksCount: number; + failedTasksCount: number; +} +export const computeStatistic = (connects: Connect[]): Statistic => { + const clustersCount = connects.length; + let connectorsCount = 0; + let failedConnectorsCount = 0; + let tasksCount = 0; + let failedTasksCount = 0; + + connects.forEach((connect) => { + connectorsCount += connect.connectorsCount ?? 0; + failedConnectorsCount += connect.failedConnectorsCount ?? 0; + tasksCount += connect.failedConnectorsCount ?? 0; + failedTasksCount += connect.failedConnectorsCount ?? 0; + }); + + return { + clustersCount, + connectorsCount, + failedConnectorsCount, + tasksCount, + failedTasksCount, + }; +}; diff --git a/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx b/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx index 8f73a96ec..f84c59778 100644 --- a/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx +++ b/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx @@ -12,6 +12,7 @@ import { clusterKsqlDbPath, clusterSchemasPath, clusterTopicsPath, + kafkaConnectsPath, } from 'lib/paths'; import { useLocation } from 'react-router-dom'; import { useLocalStorage } from 'lib/hooks/useLocalStorage'; @@ -80,6 +81,13 @@ const ClusterMenu: FC = ({ title="Schema Registry" /> )} + {hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_CONNECT) && ( + + )} {hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_CONNECT) && ( theme.alertBadge.background}; + display: inline-flex; + padding: 2px 8px; + gap: 4px; + align-items: center; +`; + +export const Content = styled.div` + font-weight: 400; + size: 14px; + line-height: 20px; + color: ${({ theme }) => theme.alertBadge.content.color}; +`; diff --git a/frontend/src/components/common/AlertBadge/AlertBadge.tsx b/frontend/src/components/common/AlertBadge/AlertBadge.tsx new file mode 100644 index 000000000..c2760011b --- /dev/null +++ b/frontend/src/components/common/AlertBadge/AlertBadge.tsx @@ -0,0 +1,26 @@ +import React, { PropsWithChildren } from 'react'; +import { useTheme } from 'styled-components'; +import AlertIcon from 'components/common/Icons/AlertIcon'; + +import * as S from './AlertBadge.styled'; + +interface AlertBadgeProps {} +function AlertBadge({ children }: PropsWithChildren) { + return {children}; +} + +const Icon = () => { + const theme = useTheme(); + return ; +}; + +interface AlertBadgeContentProps { + content: string | number; +} +const Content = ({ content }: AlertBadgeContentProps) => { + return {content}; +}; + +AlertBadge.Icon = Icon; +AlertBadge.Content = Content; +export default AlertBadge; diff --git a/frontend/src/components/common/Icons/AlertIcon.tsx b/frontend/src/components/common/Icons/AlertIcon.tsx index 3c79f78e6..c8312947f 100644 --- a/frontend/src/components/common/Icons/AlertIcon.tsx +++ b/frontend/src/components/common/Icons/AlertIcon.tsx @@ -1,6 +1,10 @@ import React from 'react'; -const AlertIcon: React.FC = () => { +interface Props { + fill?: string; +} + +const AlertIcon: React.FC = ({ fill }) => { return ( { fillRule="evenodd" clipRule="evenodd" d="M9.09265 2.06673C8.60703 1.25046 7.39297 1.25046 6.90735 2.06673L1.17092 11.7088C0.685293 12.5251 1.29232 13.5454 2.26357 13.5454H13.7364C14.7077 13.5454 15.3147 12.5251 14.8291 11.7088L9.09265 2.06673ZM7 6C7 5.44772 7.44772 5 8 5C8.55228 5 9 5.44772 9 6V8C9 8.55228 8.55228 9 8 9C7.44772 9 7 8.55228 7 8V6ZM7 11C7 10.4477 7.44772 10 8 10C8.55228 10 9 10.4477 9 11C9 11.5523 8.55228 12 8 12C7.44772 12 7 11.5523 7 11Z" - fill="#E63B19" + fill={fill ?? '#E63B19'} /> ); diff --git a/frontend/src/lib/paths.ts b/frontend/src/lib/paths.ts index b39ee5e6a..1ffe13fb6 100644 --- a/frontend/src/lib/paths.ts +++ b/frontend/src/lib/paths.ts @@ -207,6 +207,7 @@ export type RouteParamsClusterTopic = { // Kafka Connect export const clusterConnectsRelativePath = 'connects'; export const clusterConnectorsRelativePath = 'connectors'; +export const kafkaConnectsPaths = 'kafka-connects'; export const clusterConnectorNewRelativePath = 'create-new'; export const clusterConnectConnectorsRelativePath = `${RouteParams.connectName}/connectors`; export const clusterConnectConnectorRelativePath = `${clusterConnectConnectorsRelativePath}/${RouteParams.connectorName}`; @@ -216,6 +217,11 @@ export const clusterConnectConnectorConfigRelativePath = 'config'; export const clusterConnectsPath = ( clusterName: ClusterName = RouteParams.clusterName ) => `${clusterPath(clusterName)}/connects`; + +export const kafkaConnectsPath = ( + clusterName: ClusterName = RouteParams.clusterName +) => `${clusterPath(clusterName)}/kafka-connects`; + export const clusterConnectorsPath = ( clusterName: ClusterName = RouteParams.clusterName ) => `${clusterPath(clusterName)}/connectors`; diff --git a/frontend/src/theme/theme.ts b/frontend/src/theme/theme.ts index c2ccdb449..cfe47f721 100644 --- a/frontend/src/theme/theme.ts +++ b/frontend/src/theme/theme.ts @@ -61,6 +61,10 @@ const Colors = { '55': '#CF1717', '60': '#B81414', }, + orange: { + '10': '#BF83401A', + '100': '#FF9D00', + }, yellow: { '10': '#FFEECC', '20': '#FFDD57', @@ -404,6 +408,29 @@ const baseTheme = { export const theme = { ...baseTheme, + alertBadge: { + background: Colors.orange[10], + content: { + color: Colors.neutral[90], + }, + icon: { + color: Colors.orange[100], + }, + }, + kafkaConectClusters: { + statistics: { + background: Colors.neutral[5], + }, + statistic: { + background: Colors.neutral[0], + count: { + color: Colors.neutral[90], + }, + header: { + color: Colors.neutral[50], + }, + }, + }, logo: { color: Colors.brand[90], }, @@ -929,6 +956,29 @@ export type ClusterColorKey = export const darkTheme: ThemeType = { ...baseTheme, + alertBadge: { + background: Colors.orange[10], + content: { + color: Colors.neutral[0], + }, + icon: { + color: Colors.orange[100], + }, + }, + kafkaConectClusters: { + statistics: { + background: Colors.neutral[95], + }, + statistic: { + background: Colors.neutral[90], + count: { + color: Colors.neutral[0], + }, + header: { + color: Colors.neutral[50], + }, + }, + }, auth_page: { backgroundColor: Colors.neutral[90], fontFamily: baseTheme.auth_page.fontFamily, From e02e902f15ac1d0fd8d6c7a30ec2994462e441f8 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Tue, 29 Jul 2025 14:50:55 +0300 Subject: [PATCH 02/12] FE: Move cluster and connetors under kafka connect --- .../components/ClusterPage/ClusterPage.tsx | 34 +++++--- .../Clusters/Clusters.tsx} | 10 +-- .../ui/List/Cells/ConnectorsCell.tsx | 0 .../Clusters}/ui/List/Cells/NameCell.tsx | 0 .../Clusters}/ui/List/Cells/TasksCell.tsx | 4 + .../Clusters}/ui/List/List.tsx | 0 .../Clusters}/ui/Statistics/Statistics.tsx | 25 +++--- .../ui/Statistics/models/computeStatistics.ts | 0 frontend/src/components/Connect/Connect.tsx | 51 +++++++++++- .../src/components/Connect/Header/Header.tsx | 42 ++++++++++ .../Connect/List/ListPage.styled.ts | 6 ++ .../src/components/Connect/List/ListPage.tsx | 81 ++----------------- .../Connect/List/RunningTasksCell.tsx | 21 +++-- .../Connect/List/Statistics/Statistics.tsx | 34 ++++++++ .../Statistics/models/computeStatistics.ts | 33 ++++++++ .../ui/Statistics/Statistic/Statistic.tsx | 28 ------- .../Nav/ClusterMenu/ClusterMenu.tsx | 18 +---- .../Statistics/Statistic/Statistic.styled.tsx | 0 .../common/Statistics/Statistic/Statistic.tsx | 37 +++++++++ .../Statistics/Statistics.styled.tsx | 0 .../src/components/common/Statistics/index.ts | 4 + frontend/src/lib/paths.ts | 21 +++-- 22 files changed, 288 insertions(+), 161 deletions(-) rename frontend/src/components/{KafkaConnectClustersPage/KafkaConnectClustersPage.tsx => Connect/Clusters/Clusters.tsx} (66%) rename frontend/src/components/{KafkaConnectClustersPage => Connect/Clusters}/ui/List/Cells/ConnectorsCell.tsx (100%) rename frontend/src/components/{KafkaConnectClustersPage => Connect/Clusters}/ui/List/Cells/NameCell.tsx (100%) rename frontend/src/components/{KafkaConnectClustersPage => Connect/Clusters}/ui/List/Cells/TasksCell.tsx (94%) rename frontend/src/components/{KafkaConnectClustersPage => Connect/Clusters}/ui/List/List.tsx (100%) rename frontend/src/components/{KafkaConnectClustersPage => Connect/Clusters}/ui/Statistics/Statistics.tsx (50%) rename frontend/src/components/{KafkaConnectClustersPage => Connect/Clusters}/ui/Statistics/models/computeStatistics.ts (100%) create mode 100644 frontend/src/components/Connect/Header/Header.tsx create mode 100644 frontend/src/components/Connect/List/ListPage.styled.ts create mode 100644 frontend/src/components/Connect/List/Statistics/Statistics.tsx create mode 100644 frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts delete mode 100644 frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx rename frontend/src/components/{KafkaConnectClustersPage/ui => common}/Statistics/Statistic/Statistic.styled.tsx (100%) create mode 100644 frontend/src/components/common/Statistics/Statistic/Statistic.tsx rename frontend/src/components/{KafkaConnectClustersPage/ui => common}/Statistics/Statistics.styled.tsx (100%) create mode 100644 frontend/src/components/common/Statistics/index.ts diff --git a/frontend/src/components/ClusterPage/ClusterPage.tsx b/frontend/src/components/ClusterPage/ClusterPage.tsx index 37ba8a73f..95f8d613a 100644 --- a/frontend/src/components/ClusterPage/ClusterPage.tsx +++ b/frontend/src/components/ClusterPage/ClusterPage.tsx @@ -4,8 +4,6 @@ import useAppParams from 'lib/hooks/useAppParams'; import { ClusterFeaturesEnum } from 'generated-sources'; import { clusterBrokerRelativePath, - clusterConnectorsRelativePath, - clusterConnectsRelativePath, clusterConsumerGroupsRelativePath, clusterKsqlDbRelativePath, ClusterNameRoute, @@ -14,20 +12,23 @@ import { clusterConfigRelativePath, getNonExactPath, clusterAclRelativePath, - kafkaConnectsPaths, + kafkaConnectRelativePath, + clusterConnectorNewRelativePath, + clusterConnectConnectorRelativePath, } from 'lib/paths'; import ClusterContext from 'components/contexts/ClusterContext'; import PageLoader from 'components/common/PageLoader/PageLoader'; import { useClusters } from 'lib/hooks/api/clusters'; import { GlobalSettingsContext } from 'components/contexts/GlobalSettingsContext'; +import New from 'components/Connect/New/New'; +import SuspenseQueryComponent from 'components/common/SuspenseQueryComponent/SuspenseQueryComponent'; +import DetailsPage from 'components/Connect/Details/DetailsPage'; const Brokers = React.lazy(() => import('components/Brokers/Brokers')); const Topics = React.lazy(() => import('components/Topics/Topics')); const Schemas = React.lazy(() => import('components/Schemas/Schemas')); -const Connect = React.lazy(() => import('components/Connect/Connect')); -const KafkaConnectClusters = React.lazy( - () => import('components/KafkaConnectClustersPage/KafkaConnectClustersPage') -); +const KafkaConnect = React.lazy(() => import('components/Connect/Connect')); + const KsqlDb = React.lazy(() => import('components/KsqlDb/KsqlDb')); const ClusterConfigPage = React.lazy( () => import('components/ClusterPage/ClusterConfigPage') @@ -86,22 +87,29 @@ const ClusterPage: React.FC = () => { element={} /> )} + {contextValue.hasKafkaConnectConfigured && ( + } /> + )} {contextValue.hasKafkaConnectConfigured && ( } + path={getNonExactPath(clusterConnectConnectorRelativePath)} + element={ + + + + } /> )} {contextValue.hasKafkaConnectConfigured && ( } + path={getNonExactPath(kafkaConnectRelativePath)} + element={} /> )} {contextValue.hasKafkaConnectConfigured && ( } + path={getNonExactPath(kafkaConnectRelativePath)} + element={} /> )} {contextValue.hasKsqlDbConfigured && ( diff --git a/frontend/src/components/KafkaConnectClustersPage/KafkaConnectClustersPage.tsx b/frontend/src/components/Connect/Clusters/Clusters.tsx similarity index 66% rename from frontend/src/components/KafkaConnectClustersPage/KafkaConnectClustersPage.tsx rename to frontend/src/components/Connect/Clusters/Clusters.tsx index e7ef6c093..da9df771a 100644 --- a/frontend/src/components/KafkaConnectClustersPage/KafkaConnectClustersPage.tsx +++ b/frontend/src/components/Connect/Clusters/Clusters.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import ResourcePageHeading from 'components/common/ResourcePageHeading/ResourcePageHeading'; import { Connect } from 'generated-sources'; import List from './ui/List/List'; -import Statistics from './ui/Statistics/Statistics'; +import ClustersStatistics from './ui/Statistics/Statistics'; const connects: Connect[] = [ { @@ -24,11 +23,10 @@ const connects: Connect[] = [ const KafkaConnectClustersPage = () => { return ( -
- - + <> + -
+ ); }; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/ConnectorsCell.tsx b/frontend/src/components/Connect/Clusters/ui/List/Cells/ConnectorsCell.tsx similarity index 100% rename from frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/ConnectorsCell.tsx rename to frontend/src/components/Connect/Clusters/ui/List/Cells/ConnectorsCell.tsx diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/NameCell.tsx b/frontend/src/components/Connect/Clusters/ui/List/Cells/NameCell.tsx similarity index 100% rename from frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/NameCell.tsx rename to frontend/src/components/Connect/Clusters/ui/List/Cells/NameCell.tsx diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx b/frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx similarity index 94% rename from frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx rename to frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx index 9fc5f13b9..23749e7c2 100644 --- a/frontend/src/components/KafkaConnectClustersPage/ui/List/Cells/TasksCell.tsx +++ b/frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx @@ -8,6 +8,10 @@ const TasksCell = ({ connect }: Props) => { const failedCount = connect.failedTasksCount ?? 0; const text = `${count - failedCount}/${count}`; + if (!count) { + return null; + } + if (failedCount > 0) { return ( diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/List/List.tsx b/frontend/src/components/Connect/Clusters/ui/List/List.tsx similarity index 100% rename from frontend/src/components/KafkaConnectClustersPage/ui/List/List.tsx rename to frontend/src/components/Connect/Clusters/ui/List/List.tsx diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx b/frontend/src/components/Connect/Clusters/ui/Statistics/Statistics.tsx similarity index 50% rename from frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx rename to frontend/src/components/Connect/Clusters/ui/Statistics/Statistics.tsx index ad59ab5d6..699840f67 100644 --- a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.tsx +++ b/frontend/src/components/Connect/Clusters/ui/Statistics/Statistics.tsx @@ -1,30 +1,35 @@ import React, { useMemo } from 'react'; import { Connect } from 'generated-sources'; +import * as Statistics from 'components/common/Statistics'; -import * as S from './Statistics.styled'; import { computeStatistic } from './models/computeStatistics'; -import Statistic from './Statistic/Statistic'; -type Props = { connects: Connect[] }; -const Statistics = ({ connects }: Props) => { +type Props = { connects: Connect[]; isLoading: boolean }; +const ClustersStatistics = ({ connects, isLoading }: Props) => { const statistic = useMemo(() => { return computeStatistic(connects); }, [connects]); return ( - - - + + - - + ); }; -export default Statistics; +export default ClustersStatistics; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/models/computeStatistics.ts b/frontend/src/components/Connect/Clusters/ui/Statistics/models/computeStatistics.ts similarity index 100% rename from frontend/src/components/KafkaConnectClustersPage/ui/Statistics/models/computeStatistics.ts rename to frontend/src/components/Connect/Clusters/ui/Statistics/models/computeStatistics.ts diff --git a/frontend/src/components/Connect/Connect.tsx b/frontend/src/components/Connect/Connect.tsx index 095055cb6..d239bae15 100644 --- a/frontend/src/components/Connect/Connect.tsx +++ b/frontend/src/components/Connect/Connect.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Navigate, Routes, Route } from 'react-router-dom'; +import { Navigate, Routes, Route, NavLink } from 'react-router-dom'; import { RouteParams, clusterConnectConnectorRelativePath, @@ -7,20 +7,63 @@ import { clusterConnectorNewRelativePath, getNonExactPath, clusterConnectorsPath, + ClusterNameRoute, + kafkaConnectClustersPath, + kafkaConnectClustersRelativePath, + clusterConnectorsRelativePath, } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; import SuspenseQueryComponent from 'components/common/SuspenseQueryComponent/SuspenseQueryComponent'; +import Navbar from 'components/common/Navigation/Navbar.styled'; +import Clusters from 'components/Connect/Clusters/Clusters'; -import ListPage from './List/ListPage'; -import New from './New/New'; import DetailsPage from './Details/DetailsPage'; +import New from './New/New'; +import Connectors from './List/ListPage'; +import Header from './Header/Header'; const Connect: React.FC = () => { + const { clusterName } = useAppParams(); + + return ( + <> +
+ + (isActive ? 'is-active' : '')} + end + > + Clusters + + (isActive ? 'is-active' : '')} + end + > + Connectors + + + + + } + /> + } /> + } /> + + + ); +}; + +const ConnectOld: React.FC = () => { const { clusterName } = useAppParams(); return ( - } /> + } /> } /> { + const { isReadOnly } = React.useContext(ClusterContext); + const { clusterName } = useAppParams(); + const { data: connects = [] } = useConnects(clusterName); + return ( + + {!isReadOnly && ( + + Create Connector + + } + showTooltip={!connects.length} + content="No Connects available" + placement="left" + /> + )} + + ); +}; + +export default Header; diff --git a/frontend/src/components/Connect/List/ListPage.styled.ts b/frontend/src/components/Connect/List/ListPage.styled.ts new file mode 100644 index 000000000..636351d47 --- /dev/null +++ b/frontend/src/components/Connect/List/ListPage.styled.ts @@ -0,0 +1,6 @@ +import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled'; +import styled from 'styled-components'; + +export const Search = styled(ControlPanelWrapper)` + padding-top: 24px; +`; diff --git a/frontend/src/components/Connect/List/ListPage.tsx b/frontend/src/components/Connect/List/ListPage.tsx index 3397d339f..3b257dd6d 100644 --- a/frontend/src/components/Connect/List/ListPage.tsx +++ b/frontend/src/components/Connect/List/ListPage.tsx @@ -1,89 +1,24 @@ import React, { Suspense } from 'react'; import useAppParams from 'lib/hooks/useAppParams'; -import { ClusterNameRoute, clusterConnectorNewRelativePath } from 'lib/paths'; -import ClusterContext from 'components/contexts/ClusterContext'; +import { ClusterNameRoute } from 'lib/paths'; import Search from 'components/common/Search/Search'; -import * as Metrics from 'components/common/Metrics'; -import Tooltip from 'components/common/Tooltip/Tooltip'; -import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled'; import PageLoader from 'components/common/PageLoader/PageLoader'; -import { ConnectorState, Action, ResourceType } from 'generated-sources'; -import { useConnectors, useConnects } from 'lib/hooks/api/kafkaConnect'; -import { ActionButton } from 'components/common/ActionComponent'; -import ResourcePageHeading from 'components/common/ResourcePageHeading/ResourcePageHeading'; +import { useConnectors } from 'lib/hooks/api/kafkaConnect'; +import * as S from './ListPage.styled'; import List from './List'; +import ConnectorsStatistics from './Statistics/Statistics'; const ListPage: React.FC = () => { - const { isReadOnly } = React.useContext(ClusterContext); const { clusterName } = useAppParams(); - const { data: connects = [] } = useConnects(clusterName); - - // Fetches all connectors from the API, without search criteria. Used to display general metrics. - const { data: connectorsMetrics, isLoading } = useConnectors(clusterName); - - const numberOfFailedConnectors = connectorsMetrics?.filter( - ({ status: { state } }) => state === ConnectorState.FAILED - ).length; - - const numberOfFailedTasks = connectorsMetrics?.reduce( - (acc, metric) => acc + (metric.failedTasksCount ?? 0), - 0 - ); + const { data, isLoading } = useConnectors(clusterName); return ( <> - - {!isReadOnly && ( - - Create Connector - - } - showTooltip={!connects.length} - content="No Connects available" - placement="left" - /> - )} - - - - - {connectorsMetrics?.length || '-'} - - - {numberOfFailedConnectors ?? '-'} - - - {numberOfFailedTasks ?? '-'} - - - - + + - + }> diff --git a/frontend/src/components/Connect/List/RunningTasksCell.tsx b/frontend/src/components/Connect/List/RunningTasksCell.tsx index 4c3293d44..fc829df49 100644 --- a/frontend/src/components/Connect/List/RunningTasksCell.tsx +++ b/frontend/src/components/Connect/List/RunningTasksCell.tsx @@ -1,21 +1,32 @@ import React from 'react'; import { FullConnectorInfo } from 'generated-sources'; import { CellContext } from '@tanstack/react-table'; +import AlertBadge from 'components/common/AlertBadge/AlertBadge'; const RunningTasksCell: React.FC> = ({ row, }) => { const { tasksCount, failedTasksCount } = row.original; + const failedCount = failedTasksCount ?? 0; + const count = tasksCount ?? 0; + + const text = `${count - failedCount}/${count}`; + if (!tasksCount) { return null; } - return ( - <> - {tasksCount - (failedTasksCount || 0)} of {tasksCount} - - ); + if (failedCount > 0) { + return ( + + + + + ); + } + + return text; }; export default RunningTasksCell; diff --git a/frontend/src/components/Connect/List/Statistics/Statistics.tsx b/frontend/src/components/Connect/List/Statistics/Statistics.tsx new file mode 100644 index 000000000..50d19b2b8 --- /dev/null +++ b/frontend/src/components/Connect/List/Statistics/Statistics.tsx @@ -0,0 +1,34 @@ +import React, { useMemo } from 'react'; +import * as Statistics from 'components/common/Statistics'; +import { FullConnectorInfo } from 'generated-sources'; + +import { computeStatistics } from './models/computeStatistics'; + +type Props = { + connectors: FullConnectorInfo[]; + isLoading: boolean; +}; +const ConnectorsStatistics = ({ connectors, isLoading }: Props) => { + const statistics = useMemo(() => { + return computeStatistics(connectors); + }, [connectors]); + + return ( + + + + + ); +}; + +export default ConnectorsStatistics; diff --git a/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts b/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts new file mode 100644 index 000000000..9d1f153fa --- /dev/null +++ b/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts @@ -0,0 +1,33 @@ +import { ConnectorState, FullConnectorInfo } from 'generated-sources'; + +interface Statistic { + connectorsCount: number; + failedConnectorsCount: number; + tasksCount: number; + failedTasksCount: number; +} + +export const computeStatistics = ( + connectors: FullConnectorInfo[] +): Statistic => { + const connectorsCount = connectors.length; + let failedConnectorsCount = 0; + let tasksCount = 0; + let failedTasksCount = 0; + + connectors.forEach((connector) => { + if (connector.status.state !== ConnectorState.RUNNING) { + failedConnectorsCount += 1; + } + + tasksCount += connector.tasksCount ?? 0; + failedTasksCount += connector.failedTasksCount ?? 0; + }); + + return { + connectorsCount, + failedConnectorsCount, + tasksCount, + failedTasksCount, + }; +}; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx b/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx deleted file mode 100644 index d7f572583..000000000 --- a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import AlertBadge from 'components/common/AlertBadge/AlertBadge'; - -import * as S from './Statistic.styled'; - -type Props = { - title: string; - count: number; - warningCount?: number; -}; -const Statistic = ({ title, count, warningCount }: Props) => { - return ( - - {title} - - {count} - {warningCount && ( - - - - - )} - - - ); -}; - -export default Statistic; diff --git a/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx b/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx index f84c59778..7cda76f59 100644 --- a/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx +++ b/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx @@ -6,13 +6,11 @@ import MenuItem from 'components/Nav/Menu/MenuItem'; import { clusterACLPath, clusterBrokersPath, - clusterConnectorsPath, - clusterConnectsPath, clusterConsumerGroupsPath, clusterKsqlDbPath, clusterSchemasPath, clusterTopicsPath, - kafkaConnectsPath, + kafkaConnectPath, } from 'lib/paths'; import { useLocation } from 'react-router-dom'; import { useLocalStorage } from 'lib/hooks/useLocalStorage'; @@ -83,18 +81,8 @@ const ClusterMenu: FC = ({ )} {hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_CONNECT) && ( - )} - {hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_CONNECT) && ( - )} diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.styled.tsx b/frontend/src/components/common/Statistics/Statistic/Statistic.styled.tsx similarity index 100% rename from frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistic/Statistic.styled.tsx rename to frontend/src/components/common/Statistics/Statistic/Statistic.styled.tsx diff --git a/frontend/src/components/common/Statistics/Statistic/Statistic.tsx b/frontend/src/components/common/Statistics/Statistic/Statistic.tsx new file mode 100644 index 000000000..0a1503d53 --- /dev/null +++ b/frontend/src/components/common/Statistics/Statistic/Statistic.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import AlertBadge from 'components/common/AlertBadge/AlertBadge'; +import SpinnerIcon from 'components/common/Icons/SpinnerIcon'; + +import * as S from './Statistic.styled'; + +type Props = { + title: string; + count: number; + isLoading: boolean; + warningCount?: number; +}; +const Statistic = ({ title, count, warningCount, isLoading }: Props) => { + const showWarning = warningCount !== undefined && warningCount > 0; + return ( + + {title} + + {isLoading ? ( + + ) : ( + <> + {count} + {showWarning && ( + + + + + )} + + )} + + + ); +}; + +export default Statistic; diff --git a/frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.styled.tsx b/frontend/src/components/common/Statistics/Statistics.styled.tsx similarity index 100% rename from frontend/src/components/KafkaConnectClustersPage/ui/Statistics/Statistics.styled.tsx rename to frontend/src/components/common/Statistics/Statistics.styled.tsx diff --git a/frontend/src/components/common/Statistics/index.ts b/frontend/src/components/common/Statistics/index.ts new file mode 100644 index 000000000..ceb22b87e --- /dev/null +++ b/frontend/src/components/common/Statistics/index.ts @@ -0,0 +1,4 @@ +import Item from './Statistic/Statistic'; + +export { Container } from './Statistics.styled'; +export { Item }; diff --git a/frontend/src/lib/paths.ts b/frontend/src/lib/paths.ts index 1ffe13fb6..dbef8246b 100644 --- a/frontend/src/lib/paths.ts +++ b/frontend/src/lib/paths.ts @@ -207,10 +207,12 @@ export type RouteParamsClusterTopic = { // Kafka Connect export const clusterConnectsRelativePath = 'connects'; export const clusterConnectorsRelativePath = 'connectors'; -export const kafkaConnectsPaths = 'kafka-connects'; -export const clusterConnectorNewRelativePath = 'create-new'; +export const kafkaConnectRelativePath = 'kafka-connect'; +export const kafkaConnectClustersRelativePath = 'clusters'; + export const clusterConnectConnectorsRelativePath = `${RouteParams.connectName}/connectors`; -export const clusterConnectConnectorRelativePath = `${clusterConnectConnectorsRelativePath}/${RouteParams.connectorName}`; +export const clusterConnectConnectorRelativePath = `${clusterConnectsRelativePath}/${clusterConnectConnectorsRelativePath}/${RouteParams.connectorName}`; +export const clusterConnectorNewRelativePath = 'create-new'; const clusterConnectConnectorTasksRelativePath = 'tasks'; export const clusterConnectConnectorConfigRelativePath = 'config'; @@ -218,13 +220,18 @@ export const clusterConnectsPath = ( clusterName: ClusterName = RouteParams.clusterName ) => `${clusterPath(clusterName)}/connects`; -export const kafkaConnectsPath = ( +export const kafkaConnectPath = ( clusterName: ClusterName = RouteParams.clusterName -) => `${clusterPath(clusterName)}/kafka-connects`; +) => `${clusterPath(clusterName)}/kafka-connect`; +export const kafkaConnectClustersPath = ( + clusterName: ClusterName = RouteParams.clusterName +) => { + return `${kafkaConnectPath(clusterName)}/clusters`; +}; export const clusterConnectorsPath = ( clusterName: ClusterName = RouteParams.clusterName -) => `${clusterPath(clusterName)}/connectors`; +) => `${kafkaConnectPath(clusterName)}/connectors`; export const clusterConnectorNameSubPath = ( clusterName: ClusterName = RouteParams.connectorName ) => { @@ -233,7 +240,7 @@ export const clusterConnectorNameSubPath = ( export const clusterConnectorNewPath = ( clusterName: ClusterName = RouteParams.clusterName -) => `${clusterConnectorsPath(clusterName)}/create-new`; +) => `${clusterPath(clusterName)}/create-new`; export const clusterConnectConnectorsPath = ( clusterName: ClusterName = RouteParams.clusterName, connectName: Connect['name'] = RouteParams.connectName From 3c5b70d03bce5cfed33531f4a911122509d81baa Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Wed, 30 Jul 2025 10:21:19 +0300 Subject: [PATCH 03/12] FE: Fix active element --- frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx b/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx index 7cda76f59..6eeca0e48 100644 --- a/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx +++ b/frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx @@ -6,6 +6,8 @@ import MenuItem from 'components/Nav/Menu/MenuItem'; import { clusterACLPath, clusterBrokersPath, + clusterConnectsPath, + clusterConnectorsPath, clusterConsumerGroupsPath, clusterKsqlDbPath, clusterSchemasPath, @@ -81,7 +83,11 @@ const ClusterMenu: FC = ({ )} {hasFeatureConfigured(ClusterFeaturesEnum.KAFKA_CONNECT) && ( From c57b75b54c7d3a228416e6c6f40a077debf19f42 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Wed, 30 Jul 2025 10:21:43 +0300 Subject: [PATCH 04/12] FE: Use api query --- .../components/Connect/Clusters/Clusters.tsx | 13 ++++--- .../Clusters/ui/List/Cells/ConnectorsCell.tsx | 4 +++ frontend/src/components/Connect/Connect.tsx | 35 ------------------- .../src/components/Connect/Header/Header.tsx | 2 +- frontend/src/lib/hooks/api/kafkaConnect.ts | 9 ++--- 5 files changed, 19 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/Connect/Clusters/Clusters.tsx b/frontend/src/components/Connect/Clusters/Clusters.tsx index da9df771a..daafa5098 100644 --- a/frontend/src/components/Connect/Clusters/Clusters.tsx +++ b/frontend/src/components/Connect/Clusters/Clusters.tsx @@ -1,10 +1,13 @@ import React from 'react'; import { Connect } from 'generated-sources'; +import { useConnects } from 'lib/hooks/api/kafkaConnect'; +import useAppParams from 'lib/hooks/useAppParams'; +import { ClusterNameRoute } from 'lib/paths'; -import List from './ui/List/List'; import ClustersStatistics from './ui/Statistics/Statistics'; +import List from './ui/List/List'; -const connects: Connect[] = [ +const testConnects: Connect[] = [ { name: 'local', connectorsCount: 5, @@ -22,10 +25,12 @@ const connects: Connect[] = [ ]; const KafkaConnectClustersPage = () => { + const { clusterName } = useAppParams(); + const { data: connects, isLoading } = useConnects(clusterName, true); return ( <> - - + + ); }; diff --git a/frontend/src/components/Connect/Clusters/ui/List/Cells/ConnectorsCell.tsx b/frontend/src/components/Connect/Clusters/ui/List/Cells/ConnectorsCell.tsx index 00325c5af..2319cc07d 100644 --- a/frontend/src/components/Connect/Clusters/ui/List/Cells/ConnectorsCell.tsx +++ b/frontend/src/components/Connect/Clusters/ui/List/Cells/ConnectorsCell.tsx @@ -8,6 +8,10 @@ const ConnectorsCell = ({ connect }: Props) => { const failedCount = connect.failedConnectorsCount ?? 0; const text = `${count - failedCount}/${count}`; + if (count === 0) { + return null; + } + if (failedCount > 0) { return ( diff --git a/frontend/src/components/Connect/Connect.tsx b/frontend/src/components/Connect/Connect.tsx index d239bae15..48787f5ae 100644 --- a/frontend/src/components/Connect/Connect.tsx +++ b/frontend/src/components/Connect/Connect.tsx @@ -1,11 +1,6 @@ import React from 'react'; import { Navigate, Routes, Route, NavLink } from 'react-router-dom'; import { - RouteParams, - clusterConnectConnectorRelativePath, - clusterConnectConnectorsRelativePath, - clusterConnectorNewRelativePath, - getNonExactPath, clusterConnectorsPath, ClusterNameRoute, kafkaConnectClustersPath, @@ -13,12 +8,9 @@ import { clusterConnectorsRelativePath, } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; -import SuspenseQueryComponent from 'components/common/SuspenseQueryComponent/SuspenseQueryComponent'; import Navbar from 'components/common/Navigation/Navbar.styled'; import Clusters from 'components/Connect/Clusters/Clusters'; -import DetailsPage from './Details/DetailsPage'; -import New from './New/New'; import Connectors from './List/ListPage'; import Header from './Header/Header'; @@ -58,31 +50,4 @@ const Connect: React.FC = () => { ); }; -const ConnectOld: React.FC = () => { - const { clusterName } = useAppParams(); - - return ( - - } /> - } /> - - - - } - /> - } - /> - } - /> - - ); -}; - export default Connect; diff --git a/frontend/src/components/Connect/Header/Header.tsx b/frontend/src/components/Connect/Header/Header.tsx index ab30b4049..75b19a6a8 100644 --- a/frontend/src/components/Connect/Header/Header.tsx +++ b/frontend/src/components/Connect/Header/Header.tsx @@ -11,7 +11,7 @@ import React from 'react'; const Header = () => { const { isReadOnly } = React.useContext(ClusterContext); const { clusterName } = useAppParams(); - const { data: connects = [] } = useConnects(clusterName); + const { data: connects = [] } = useConnects(clusterName, true); return ( {!isReadOnly && ( diff --git a/frontend/src/lib/hooks/api/kafkaConnect.ts b/frontend/src/lib/hooks/api/kafkaConnect.ts index 743d78307..0164feeeb 100644 --- a/frontend/src/lib/hooks/api/kafkaConnect.ts +++ b/frontend/src/lib/hooks/api/kafkaConnect.ts @@ -19,10 +19,11 @@ interface CreateConnectorProps { newConnector: NewConnector; } -const connectsKey = (clusterName: ClusterName) => [ +const connectsKey = (clusterName: ClusterName, withStats: boolean) => [ 'clusters', clusterName, 'connects', + withStats, ]; const connectorsKey = (clusterName: ClusterName, search?: string) => { const base = ['clusters', clusterName, 'connectors']; @@ -44,9 +45,9 @@ const connectorTasksKey = (props: UseConnectorProps) => [ 'tasks', ]; -export function useConnects(clusterName: ClusterName) { - return useQuery(connectsKey(clusterName), () => - api.getConnects({ clusterName }) +export function useConnects(clusterName: ClusterName, withStats = false) { + return useQuery(connectsKey(clusterName, withStats), () => + api.getConnects({ clusterName, withStats }) ); } export function useConnectors(clusterName: ClusterName, search?: string) { From 60a0553724785a58ea177439b37c6eebe5781ba0 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Thu, 31 Jul 2025 21:48:39 +0300 Subject: [PATCH 05/12] FE: Add tests for kafka connect connectors --- .../components/ClusterPage/ClusterPage.tsx | 6 - .../__tests__/ClusterPage.spec.tsx | 6 +- .../components/Connect/Clusters/Clusters.tsx | 18 --- .../Connect/Header/__tests__/Header.spec.tsx | 62 ++++++++ .../Connect/List/Statistics/Statistics.tsx | 2 +- .../Statistics/__tests__/Statistics.spec.tsx | 97 ++++++++++++ .../Statistics/models/computeStatistics.ts | 2 +- .../Connect/List/__tests__/ListPage.spec.tsx | 145 ------------------ .../Connect/__tests__/Connect.spec.tsx | 41 ++--- .../common/AlertBadge/AlertBadge.tsx | 2 +- .../common/Statistics/Statistic/Statistic.tsx | 8 +- frontend/src/lib/__test__/paths.spec.ts | 11 +- frontend/src/lib/hooks/api/kafkaConnect.ts | 4 +- 13 files changed, 195 insertions(+), 209 deletions(-) create mode 100644 frontend/src/components/Connect/Header/__tests__/Header.spec.tsx create mode 100644 frontend/src/components/Connect/List/Statistics/__tests__/Statistics.spec.tsx diff --git a/frontend/src/components/ClusterPage/ClusterPage.tsx b/frontend/src/components/ClusterPage/ClusterPage.tsx index 95f8d613a..a0cc13e13 100644 --- a/frontend/src/components/ClusterPage/ClusterPage.tsx +++ b/frontend/src/components/ClusterPage/ClusterPage.tsx @@ -106,12 +106,6 @@ const ClusterPage: React.FC = () => { element={} /> )} - {contextValue.hasKafkaConnectConfigured && ( - } - /> - )} {contextValue.hasKsqlDbConfigured && ( { itCorrectlyHandlesConfiguredSchema( ClusterFeaturesEnum.KAFKA_CONNECT, CLusterCompText.Connect, - clusterConnectsPath(onlineClusterPayload.name) + kafkaConnectPath(onlineClusterPayload.name) ); itCorrectlyHandlesConfiguredSchema( ClusterFeaturesEnum.KAFKA_CONNECT, diff --git a/frontend/src/components/Connect/Clusters/Clusters.tsx b/frontend/src/components/Connect/Clusters/Clusters.tsx index daafa5098..2b98ea953 100644 --- a/frontend/src/components/Connect/Clusters/Clusters.tsx +++ b/frontend/src/components/Connect/Clusters/Clusters.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Connect } from 'generated-sources'; import { useConnects } from 'lib/hooks/api/kafkaConnect'; import useAppParams from 'lib/hooks/useAppParams'; import { ClusterNameRoute } from 'lib/paths'; @@ -7,23 +6,6 @@ import { ClusterNameRoute } from 'lib/paths'; import ClustersStatistics from './ui/Statistics/Statistics'; import List from './ui/List/List'; -const testConnects: Connect[] = [ - { - name: 'local', - connectorsCount: 5, - failedConnectorsCount: 0, - tasksCount: 5, - failedTasksCount: 0, - }, - { - name: 'Cluster name 2', - connectorsCount: 5, - failedConnectorsCount: 1, - tasksCount: 6, - failedTasksCount: 3, - }, -]; - const KafkaConnectClustersPage = () => { const { clusterName } = useAppParams(); const { data: connects, isLoading } = useConnects(clusterName, true); diff --git a/frontend/src/components/Connect/Header/__tests__/Header.spec.tsx b/frontend/src/components/Connect/Header/__tests__/Header.spec.tsx new file mode 100644 index 000000000..8f1bd93d1 --- /dev/null +++ b/frontend/src/components/Connect/Header/__tests__/Header.spec.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import Header from 'components/Connect/Header/Header'; +import ClusterContext, { + initialValue, +} from 'components/contexts/ClusterContext'; +import { render } from 'lib/testHelpers'; +import { screen } from '@testing-library/react'; +import { connects } from 'lib/fixtures/kafkaConnect'; +import { useConnects } from 'lib/hooks/api/kafkaConnect'; + +jest.mock('lib/hooks/api/kafkaConnect', () => ({ + useConnects: jest.fn(), +})); + +describe('Kafka Connect header', () => { + beforeEach(() => { + (useConnects as jest.Mock).mockImplementation(() => ({ + data: connects, + })); + }); + + async function renderComponent({ isReadOnly }: { isReadOnly: boolean }) { + render( + +
+ + ); + } + + it('render create connector button', () => { + renderComponent({ isReadOnly: false }); + + const btn = screen.getByRole('button', { name: 'Create Connector' }); + + expect(btn).toBeInTheDocument(); + expect(btn).toBeEnabled(); + }); + + describe('when no connects', () => { + it('create connector button is disabled', () => { + renderComponent({ isReadOnly: false }); + + const btn = screen.getByRole('button', { name: 'Create Connector' }); + + expect(btn).toBeInTheDocument(); + expect(btn).toBeEnabled(); + }); + }); + + describe('when cluster is readonly', () => { + it('doesnt render create connector button', () => { + (useConnects as jest.Mock).mockImplementation(() => ({ + data: [], + })); + renderComponent({ isReadOnly: false }); + + const btn = screen.queryByRole('button', { name: 'Create Connector' }); + + expect(btn).toBeDisabled(); + }); + }); +}); diff --git a/frontend/src/components/Connect/List/Statistics/Statistics.tsx b/frontend/src/components/Connect/List/Statistics/Statistics.tsx index 50d19b2b8..4b9dd6da7 100644 --- a/frontend/src/components/Connect/List/Statistics/Statistics.tsx +++ b/frontend/src/components/Connect/List/Statistics/Statistics.tsx @@ -14,7 +14,7 @@ const ConnectorsStatistics = ({ connectors, isLoading }: Props) => { }, [connectors]); return ( - + { + async function renderComponent({ + data, + isLoading, + }: { + data: FullConnectorInfo[] | undefined; + isLoading: boolean; + }) { + render(); + } + + describe('when data loading', () => { + let statistics: HTMLElement; + beforeEach(() => { + renderComponent({ data: undefined, isLoading: true }); + statistics = screen.getByRole('group'); + }); + + it('renders statistic container elements', () => { + expect(statistics).toBeInTheDocument(); + }); + + describe('Connectors statistic', () => { + let connectorStatisticsCell: HTMLElement; + beforeEach(() => { + connectorStatisticsCell = within(statistics).getByRole('cell', { + name: 'Connectors', + }); + }); + it('exists', () => { + expect(connectorStatisticsCell).toBeInTheDocument(); + }); + + it('shows loader', () => { + const loader = within(connectorStatisticsCell).getByRole('status'); + expect(loader).toBeInTheDocument(); + }); + }); + describe('Tasks statistic', () => { + let tasksStatisticsCell: HTMLElement; + beforeEach(() => { + tasksStatisticsCell = within(statistics).getByRole('cell', { + name: 'Tasks', + }); + }); + it('exists', () => { + expect(tasksStatisticsCell).toBeInTheDocument(); + }); + + it('shows loader', () => { + const loader = within(tasksStatisticsCell).getByRole('status'); + expect(loader).toBeInTheDocument(); + }); + }); + }); + + describe('when data is loaded', () => { + let connectorsStatistic: HTMLElement; + let tasksStatistics: HTMLElement; + beforeEach(() => { + renderComponent({ data: connectors, isLoading: false }); + [connectorsStatistic, tasksStatistics] = screen.getAllByRole('cell'); + }); + + describe('Connectors statistics', () => { + it('renders statistic', () => { + expect(connectorsStatistic).toBeInTheDocument(); + + const count = within(connectorsStatistic).getByText(3); + expect(count).toBeInTheDocument(); + + const alert = within(connectorsStatistic).getByRole('alert'); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent('1'); + }); + }); + describe('Tasks statistics', () => { + it('renders statistic', () => { + expect(tasksStatistics).toBeInTheDocument(); + + const count = within(tasksStatistics).getByText(5); + expect(count).toBeInTheDocument(); + + const alert = within(tasksStatistics).getByRole('alert'); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent('1'); + }); + }); + }); +}); diff --git a/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts b/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts index 9d1f153fa..ad3b85a1d 100644 --- a/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts +++ b/frontend/src/components/Connect/List/Statistics/models/computeStatistics.ts @@ -16,7 +16,7 @@ export const computeStatistics = ( let failedTasksCount = 0; connectors.forEach((connector) => { - if (connector.status.state !== ConnectorState.RUNNING) { + if (connector.status.state === ConnectorState.FAILED) { failedConnectorsCount += 1; } diff --git a/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx b/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx index 0cbe4dd78..7d5c5b419 100644 --- a/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx +++ b/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx @@ -45,28 +45,6 @@ describe('Connectors List Page', () => { { initialEntries: [clusterConnectorsPath(clusterName)] } ); - describe('Heading', () => { - it('renders header without create button for readonly cluster', async () => { - await renderComponent({ ...initialValue, isReadOnly: true }); - expect( - screen.getByRole('heading', { name: 'Connectors' }) - ).toBeInTheDocument(); - expect( - screen.queryByRole('link', { name: 'Create Connector' }) - ).not.toBeInTheDocument(); - }); - - it('renders header with create button for read/write cluster', async () => { - await renderComponent(); - expect( - screen.getByRole('heading', { name: 'Connectors' }) - ).toBeInTheDocument(); - expect( - screen.getByRole('link', { name: 'Create Connector' }) - ).toBeInTheDocument(); - }); - }); - it('renders search input', async () => { await renderComponent(); expect( @@ -78,127 +56,4 @@ describe('Connectors List Page', () => { await renderComponent(); expect(screen.getByText('Connectors List')).toBeInTheDocument(); }); - - describe('Metrics', () => { - it('renders indicators in loading state', async () => { - (useConnectors as jest.Mock).mockImplementation(() => ({ - isLoading: true, - data: connectors, - })); - - await renderComponent(); - const metrics = screen.getByRole('group'); - expect(metrics).toBeInTheDocument(); - expect(within(metrics).getAllByText('progressbar').length).toEqual(3); - }); - - it('renders indicators for empty list of connectors', async () => { - await renderComponent(); - const metrics = screen.getByRole('group'); - expect(metrics).toBeInTheDocument(); - - const connectorsIndicator = within(metrics).getByTitle( - 'Total number of connectors' - ); - expect(connectorsIndicator).toBeInTheDocument(); - expect(connectorsIndicator).toHaveTextContent('Connectors -'); - - const failedConnectorsIndicator = within(metrics).getByTitle( - 'Number of failed connectors' - ); - expect(failedConnectorsIndicator).toBeInTheDocument(); - expect(failedConnectorsIndicator).toHaveTextContent( - 'Failed Connectors 0' - ); - - const failedTasksIndicator = within(metrics).getByTitle( - 'Number of failed tasks' - ); - expect(failedTasksIndicator).toBeInTheDocument(); - expect(failedTasksIndicator).toHaveTextContent('Failed Tasks 0'); - }); - - it('renders indicators when connectors list is undefined', async () => { - (useConnectors as jest.Mock).mockImplementation(() => ({ - isFetching: false, - data: undefined, - })); - - await renderComponent(); - const metrics = screen.getByRole('group'); - expect(metrics).toBeInTheDocument(); - - const connectorsIndicator = within(metrics).getByTitle( - 'Total number of connectors' - ); - expect(connectorsIndicator).toBeInTheDocument(); - expect(connectorsIndicator).toHaveTextContent('Connectors -'); - - const failedConnectorsIndicator = within(metrics).getByTitle( - 'Number of failed connectors' - ); - expect(failedConnectorsIndicator).toBeInTheDocument(); - expect(failedConnectorsIndicator).toHaveTextContent( - 'Failed Connectors -' - ); - - const failedTasksIndicator = within(metrics).getByTitle( - 'Number of failed tasks' - ); - expect(failedTasksIndicator).toBeInTheDocument(); - expect(failedTasksIndicator).toHaveTextContent('Failed Tasks -'); - }); - - it('renders indicators list of connectors', async () => { - (useConnectors as jest.Mock).mockImplementation(() => ({ - isLoading: false, - data: connectors, - })); - - await renderComponent(); - - const metrics = screen.getByRole('group'); - expect(metrics).toBeInTheDocument(); - - const connectorsIndicator = within(metrics).getByTitle( - 'Total number of connectors' - ); - expect(connectorsIndicator).toBeInTheDocument(); - expect(connectorsIndicator).toHaveTextContent( - `Connectors ${connectors.length}` - ); - - const failedConnectorsIndicator = within(metrics).getByTitle( - 'Number of failed connectors' - ); - expect(failedConnectorsIndicator).toBeInTheDocument(); - expect(failedConnectorsIndicator).toHaveTextContent( - 'Failed Connectors 1' - ); - - const failedTasksIndicator = within(metrics).getByTitle( - 'Number of failed tasks' - ); - expect(failedTasksIndicator).toBeInTheDocument(); - expect(failedTasksIndicator).toHaveTextContent('Failed Tasks 1'); - }); - }); - - describe('Create new connector', () => { - it('Create new connector button is enabled when connects list is not empty', async () => { - await renderComponent(); - - expect(screen.getByText('Create Connector')).toBeEnabled(); - }); - - it('Create new connector button is disabled when connects list is empty', async () => { - (useConnects as jest.Mock).mockImplementation(() => ({ - data: [], - })); - - await renderComponent(); - - expect(screen.getByText('Create Connector')).toBeDisabled(); - }); - }); }); diff --git a/frontend/src/components/Connect/__tests__/Connect.spec.tsx b/frontend/src/components/Connect/__tests__/Connect.spec.tsx index 855e54645..4f96d7931 100644 --- a/frontend/src/components/Connect/__tests__/Connect.spec.tsx +++ b/frontend/src/components/Connect/__tests__/Connect.spec.tsx @@ -2,13 +2,7 @@ import React from 'react'; import { render, WithRoute } from 'lib/testHelpers'; import { screen } from '@testing-library/react'; import Connect from 'components/Connect/Connect'; -import { - clusterConnectorsPath, - clusterConnectorNewPath, - clusterConnectConnectorPath, - getNonExactPath, - clusterConnectsPath, -} from 'lib/paths'; +import { getNonExactPath, kafkaConnectPath } from 'lib/paths'; const ConnectCompText = { new: 'New Page', @@ -35,27 +29,22 @@ describe('Connect', () => { { initialEntries: [pathname] } ); - it('renders ListPage', () => { - renderComponent( - clusterConnectorsPath('my-cluster'), - clusterConnectorsPath() - ); - expect(screen.getByText(ConnectCompText.list)).toBeInTheDocument(); - }); + it('renders header', () => { + renderComponent(kafkaConnectPath('my-cluster'), kafkaConnectPath()); - it('renders New Page', () => { - renderComponent( - clusterConnectorNewPath('my-cluster'), - clusterConnectorsPath() - ); - expect(screen.getByText(ConnectCompText.new)).toBeInTheDocument(); + const header = screen.getByRole('heading', { name: 'Kafka Connect' }); + expect(header).toBeInTheDocument(); }); - it('renders Details Page', () => { - renderComponent( - clusterConnectConnectorPath('my-cluster', 'my-connect', 'my-connector'), - clusterConnectsPath() - ); - expect(screen.getByText(ConnectCompText.details)).toBeInTheDocument(); + it('renders navigation', () => { + renderComponent(kafkaConnectPath('my-cluster'), kafkaConnectPath()); + + const clusterNavigation = screen.getByRole('link', { name: 'Clusters' }); + expect(clusterNavigation).toBeInTheDocument(); + + const connectorsNavigation = screen.getByRole('link', { + name: 'Connectors', + }); + expect(connectorsNavigation).toBeInTheDocument(); }); }); diff --git a/frontend/src/components/common/AlertBadge/AlertBadge.tsx b/frontend/src/components/common/AlertBadge/AlertBadge.tsx index c2760011b..4a5839b9e 100644 --- a/frontend/src/components/common/AlertBadge/AlertBadge.tsx +++ b/frontend/src/components/common/AlertBadge/AlertBadge.tsx @@ -6,7 +6,7 @@ import * as S from './AlertBadge.styled'; interface AlertBadgeProps {} function AlertBadge({ children }: PropsWithChildren) { - return {children}; + return {children}; } const Icon = () => { diff --git a/frontend/src/components/common/Statistics/Statistic/Statistic.tsx b/frontend/src/components/common/Statistics/Statistic/Statistic.tsx index 0a1503d53..f71c03c60 100644 --- a/frontend/src/components/common/Statistics/Statistic/Statistic.tsx +++ b/frontend/src/components/common/Statistics/Statistic/Statistic.tsx @@ -13,14 +13,16 @@ type Props = { const Statistic = ({ title, count, warningCount, isLoading }: Props) => { const showWarning = warningCount !== undefined && warningCount > 0; return ( - + {title} {isLoading ? ( - +
+ +
) : ( <> - {count} + {count} {showWarning && ( diff --git a/frontend/src/lib/__test__/paths.spec.ts b/frontend/src/lib/__test__/paths.spec.ts index c6abb7707..4a731e393 100644 --- a/frontend/src/lib/__test__/paths.spec.ts +++ b/frontend/src/lib/__test__/paths.spec.ts @@ -240,17 +240,22 @@ describe('Paths', () => { paths.clusterConnectsPath(RouteParams.clusterName) ); }); - it('clusterConnectorsPath', () => { + it('kafkaConnectConnectorsPath', () => { expect(paths.clusterConnectorsPath(clusterName)).toEqual( - `${paths.clusterPath(clusterName)}/connectors` + `${paths.clusterPath(clusterName)}/kafka-connect/connectors` ); expect(paths.clusterConnectorsPath()).toEqual( paths.clusterConnectorsPath(RouteParams.clusterName) ); }); + it('kafkaConnectConnectorsPath', () => { + expect(paths.kafkaConnectClustersPath(clusterName)).toEqual( + `${paths.clusterPath(clusterName)}/kafka-connect/clusters` + ); + }); it('clusterConnectorNewPath', () => { expect(paths.clusterConnectorNewPath(clusterName)).toEqual( - `${paths.clusterConnectorsPath(clusterName)}/create-new` + `${paths.clusterPath(clusterName)}/create-new` ); expect(paths.clusterConnectorNewPath()).toEqual( paths.clusterConnectorNewPath(RouteParams.clusterName) diff --git a/frontend/src/lib/hooks/api/kafkaConnect.ts b/frontend/src/lib/hooks/api/kafkaConnect.ts index 0164feeeb..64ce2af41 100644 --- a/frontend/src/lib/hooks/api/kafkaConnect.ts +++ b/frontend/src/lib/hooks/api/kafkaConnect.ts @@ -19,7 +19,7 @@ interface CreateConnectorProps { newConnector: NewConnector; } -const connectsKey = (clusterName: ClusterName, withStats: boolean) => [ +const connectsKey = (clusterName: ClusterName, withStats?: boolean) => [ 'clusters', clusterName, 'connects', @@ -45,7 +45,7 @@ const connectorTasksKey = (props: UseConnectorProps) => [ 'tasks', ]; -export function useConnects(clusterName: ClusterName, withStats = false) { +export function useConnects(clusterName: ClusterName, withStats?: boolean) { return useQuery(connectsKey(clusterName, withStats), () => api.getConnects({ clusterName, withStats }) ); From 2955d91f337a33e1fbc6f304b6e91b991d2eaeb0 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Fri, 1 Aug 2025 21:24:19 +0300 Subject: [PATCH 06/12] FE: fix linting --- .../src/components/Connect/List/__tests__/ListPage.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx b/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx index 7d5c5b419..77196a1be 100644 --- a/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx +++ b/frontend/src/components/Connect/List/__tests__/ListPage.spec.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { connectors, connects } from 'lib/fixtures/kafkaConnect'; +import { connects } from 'lib/fixtures/kafkaConnect'; import ClusterContext, { ContextProps, initialValue, } from 'components/contexts/ClusterContext'; import ListPage from 'components/Connect/List/ListPage'; -import { screen, within } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { render, WithRoute } from 'lib/testHelpers'; import { clusterConnectorsPath } from 'lib/paths'; import { useConnectors, useConnects } from 'lib/hooks/api/kafkaConnect'; From 8d0e0f7385cb747dd191a562df953c14c7ae2e10 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Fri, 1 Aug 2025 21:28:11 +0300 Subject: [PATCH 07/12] E2E: Change KAFKA_CONNECT_LIST_URL --- e2e-tests/src/main/java/io/kafbat/ui/variables/Url.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/src/main/java/io/kafbat/ui/variables/Url.java b/e2e-tests/src/main/java/io/kafbat/ui/variables/Url.java index 93eee2676..29591b882 100644 --- a/e2e-tests/src/main/java/io/kafbat/ui/variables/Url.java +++ b/e2e-tests/src/main/java/io/kafbat/ui/variables/Url.java @@ -6,6 +6,6 @@ public interface Url { String TOPICS_LIST_URL = "http://%s:8080/ui/clusters/local/all-topics"; String CONSUMERS_LIST_URL = "http://%s:8080/ui/clusters/local/consumer-groups"; String SCHEMA_REGISTRY_LIST_URL = "http://%s:8080/ui/clusters/local/schemas"; - String KAFKA_CONNECT_LIST_URL = "http://%s:8080/ui/clusters/local/connectors"; + String KAFKA_CONNECT_LIST_URL = "http://%s:8080/ui/clusters/local/kafka-connect/connectors"; String KSQL_DB_LIST_URL = "http://%s:8080/ui/clusters/local/ksqldb/tables"; } From 9b2c377e7bf13553fc11cbdd803959566a4d2553 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Fri, 1 Aug 2025 22:05:36 +0300 Subject: [PATCH 08/12] E2E: Change MenuItem for kafka_connect --- .../main/java/io/kafbat/ui/screens/panels/enums/MenuItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/src/main/java/io/kafbat/ui/screens/panels/enums/MenuItem.java b/e2e-tests/src/main/java/io/kafbat/ui/screens/panels/enums/MenuItem.java index 283d428a1..caa6025eb 100644 --- a/e2e-tests/src/main/java/io/kafbat/ui/screens/panels/enums/MenuItem.java +++ b/e2e-tests/src/main/java/io/kafbat/ui/screens/panels/enums/MenuItem.java @@ -10,7 +10,7 @@ public enum MenuItem { TOPICS("Topics", "Topics"), CONSUMERS("Consumers", "Consumers"), SCHEMA_REGISTRY("Schema Registry", "Schema Registry"), - KAFKA_CONNECT("Kafka Connect", "Connectors"), + KAFKA_CONNECT("Kafka Connect", "Kafka Connect"), KSQL_DB("KSQL DB", "KSQL DB"); private final String naviTitle; From bc93bb72057efd265b46c2dbf9fada9717c14acf Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sat, 2 Aug 2025 00:36:43 +0300 Subject: [PATCH 09/12] E2E: Add click connectors step --- .../io/kafbat/ui/screens/connectors/KafkaConnectList.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/e2e-tests/src/main/java/io/kafbat/ui/screens/connectors/KafkaConnectList.java b/e2e-tests/src/main/java/io/kafbat/ui/screens/connectors/KafkaConnectList.java index 5045e99d0..6dddae78b 100644 --- a/e2e-tests/src/main/java/io/kafbat/ui/screens/connectors/KafkaConnectList.java +++ b/e2e-tests/src/main/java/io/kafbat/ui/screens/connectors/KafkaConnectList.java @@ -13,13 +13,21 @@ public class KafkaConnectList extends BasePage { protected SelenideElement createConnectorBtn = $x("//button[contains(text(),'Create Connector')]"); + protected SelenideElement connectorsTab = $x("//a[contains(text(),'Connectors')]"); public KafkaConnectList() { tableElementNameLocator = "//tbody//td[contains(text(),'%s')]"; } + @Step + public KafkaConnectList clickConnectorsTab() { + WebUtil.clickByJavaScript(connectorsTab); + return this; + } + @Step public KafkaConnectList waitUntilScreenReady() { + clickConnectorsTab(); waitUntilSpinnerDisappear(); getPageTitleFromHeader(KAFKA_CONNECT).shouldBe(Condition.visible); return this; From f53f1f4e61facc8950593fc9b84fb1ab923ab6d9 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Mon, 4 Aug 2025 18:10:17 +0300 Subject: [PATCH 10/12] FE: fix statistics computing --- .../Clusters/ui/Statistics/models/computeStatistics.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Connect/Clusters/ui/Statistics/models/computeStatistics.ts b/frontend/src/components/Connect/Clusters/ui/Statistics/models/computeStatistics.ts index e90640957..b1452f2fc 100644 --- a/frontend/src/components/Connect/Clusters/ui/Statistics/models/computeStatistics.ts +++ b/frontend/src/components/Connect/Clusters/ui/Statistics/models/computeStatistics.ts @@ -17,8 +17,8 @@ export const computeStatistic = (connects: Connect[]): Statistic => { connects.forEach((connect) => { connectorsCount += connect.connectorsCount ?? 0; failedConnectorsCount += connect.failedConnectorsCount ?? 0; - tasksCount += connect.failedConnectorsCount ?? 0; - failedTasksCount += connect.failedConnectorsCount ?? 0; + tasksCount += connect.tasksCount ?? 0; + failedTasksCount += connect.failedTasksCount ?? 0; }); return { From 4d9b6d740f1d495853c7b48a0e6495f80a735b53 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Wed, 13 Aug 2025 13:19:28 +0300 Subject: [PATCH 11/12] FE: Add version to kafka connect->clusters overview --- frontend/src/components/Connect/Clusters/ui/List/List.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/Connect/Clusters/ui/List/List.tsx b/frontend/src/components/Connect/Clusters/ui/List/List.tsx index 93efd18ac..f0cd4e693 100644 --- a/frontend/src/components/Connect/Clusters/ui/List/List.tsx +++ b/frontend/src/components/Connect/Clusters/ui/List/List.tsx @@ -14,6 +14,11 @@ import TasksCell from './Cells/TasksCell'; const helper = createColumnHelper(); export const columns = [ helper.accessor('name', { cell: NameCell, size: 600 }), + helper.accessor('version', { + header: 'Version', + cell: ({ getValue }) => getValue(), + enableSorting: true, + }), helper.display({ header: 'Connectors', id: 'connectors', @@ -43,6 +48,7 @@ const List = ({ connects }: Props) => { navigate(`${clusterConnectorsPath(clusterName)}?connect=${name}`); }} emptyMessage="No kafka connect clusters" + enableSorting /> ); }; From 62bf0730be530e8df212eef3c6adc58a289e7701 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Wed, 13 Aug 2025 14:09:39 +0300 Subject: [PATCH 12/12] FE: Reuse text var in TasksCell component --- .../components/Connect/Clusters/ui/List/Cells/TasksCell.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx b/frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx index 23749e7c2..041237e0a 100644 --- a/frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx +++ b/frontend/src/components/Connect/Clusters/ui/List/Cells/TasksCell.tsx @@ -21,10 +21,6 @@ const TasksCell = ({ connect }: Props) => { ); } - return ( -
- {count - failedCount}/{count} -
- ); + return
{text}
; }; export default TasksCell;