diff --git a/frontend/src/components/Connect/List/ConnectorsTable/ConnectorsTable.tsx b/frontend/src/components/Connect/List/ConnectorsTable/ConnectorsTable.tsx new file mode 100644 index 000000000..44014c2b2 --- /dev/null +++ b/frontend/src/components/Connect/List/ConnectorsTable/ConnectorsTable.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { FullConnectorInfo } from 'generated-sources'; +import Table from 'components/common/NewTable'; +import { useLocalStoragePersister } from 'components/common/NewTable/ColumnResizer/lib'; +import { useQueryPersister } from 'components/common/NewTable/ColumnFilter'; +import { VisibilityState } from '@tanstack/react-table'; + +import { connectorsColumns } from './connectorsColumns/columns'; + +const setRowId = (originalRow: FullConnectorInfo) => + `${originalRow.name}-${originalRow.connect}`; + +type ConnectorsTableProps = { + connectors: FullConnectorInfo[]; + columnSizingPersistKey?: string; + columnVisibility?: VisibilityState; +}; + +export const ConnectorsTable = ({ + connectors, + columnSizingPersistKey = 'KafkaConnect', + columnVisibility, +}: ConnectorsTableProps) => { + const filterPersister = useQueryPersister(connectorsColumns); + const columnSizingPersister = useLocalStoragePersister( + columnSizingPersistKey + ); + + return ( + + ); +}; diff --git a/frontend/src/components/Connect/List/ActionsCell.tsx b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/ActionsCell.tsx similarity index 96% rename from frontend/src/components/Connect/List/ActionsCell.tsx rename to frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/ActionsCell.tsx index bd0a12e4d..8529b58f9 100644 --- a/frontend/src/components/Connect/List/ActionsCell.tsx +++ b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/ActionsCell.tsx @@ -7,7 +7,7 @@ import { ResourceType, } from 'generated-sources'; import { CellContext } from '@tanstack/react-table'; -import { ClusterNameRoute } from 'lib/paths'; +import { RouteParamsClusterTopic } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; import { Dropdown } from 'components/common/Dropdown'; import { @@ -24,7 +24,7 @@ const ActionsCell: React.FC> = ({ row, }) => { const { connect, name, status } = row.original; - const { clusterName } = useAppParams(); + const { clusterName, topicName } = useAppParams(); const { isReadOnly } = useContext(ClusterContext); const mutationsNumber = useIsMutating(); const isMutating = mutationsNumber > 0; @@ -33,16 +33,19 @@ const ActionsCell: React.FC> = ({ clusterName, connectName: connect, connectorName: name, + topicName, }); const stateMutation = useUpdateConnectorState({ clusterName, connectName: connect, connectorName: name, + topicName, }); const resetConnectorOffsetsMutation = useResetConnectorOffsets({ clusterName, connectName: connect, connectorName: name, + topicName, }); const handleDelete = () => { confirm( @@ -54,7 +57,7 @@ const ActionsCell: React.FC> = ({ } ); }; - // const stateMutation = useUpdateConnectorState(routerProps); + const resumeConnectorHandler = () => stateMutation.mutateAsync(ConnectorAction.RESUME); const restartConnectorHandler = () => diff --git a/frontend/src/components/Connect/List/KafkaConnectLinkCell.tsx b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/KafkaConnectLinkCell.tsx similarity index 100% rename from frontend/src/components/Connect/List/KafkaConnectLinkCell.tsx rename to frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/KafkaConnectLinkCell.tsx diff --git a/frontend/src/components/Connect/List/RunningTasksCell.tsx b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/RunningTasksCell.tsx similarity index 100% rename from frontend/src/components/Connect/List/RunningTasksCell.tsx rename to frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/RunningTasksCell.tsx diff --git a/frontend/src/components/Connect/List/List.styled.ts b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/TopicsCell.styled.ts similarity index 100% rename from frontend/src/components/Connect/List/List.styled.ts rename to frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/TopicsCell.styled.ts diff --git a/frontend/src/components/Connect/List/TopicsCell.tsx b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/TopicsCell.tsx similarity index 96% rename from frontend/src/components/Connect/List/TopicsCell.tsx rename to frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/TopicsCell.tsx index f5feb0f5a..1a3d7939d 100644 --- a/frontend/src/components/Connect/List/TopicsCell.tsx +++ b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/cells/TopicsCell.tsx @@ -6,7 +6,7 @@ import { MultiLineTag } from 'components/common/Tag/Tag.styled'; import { ClusterNameRoute, clusterTopicPath } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; -import * as S from './List.styled'; +import * as S from './TopicsCell.styled'; const TopicsCell: React.FC> = ({ row, diff --git a/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/columns.tsx b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/columns.tsx new file mode 100644 index 000000000..75179acf6 --- /dev/null +++ b/frontend/src/components/Connect/List/ConnectorsTable/connectorsColumns/columns.tsx @@ -0,0 +1,69 @@ +import { ColumnDef } from '@tanstack/react-table'; +import { FullConnectorInfo } from 'generated-sources'; +import BreakableTextCell from 'components/common/NewTable/BreakableTextCell'; +import { TagCell } from 'components/common/NewTable'; + +import { KafkaConnectLinkCell } from './cells/KafkaConnectLinkCell'; +import TopicsCell from './cells/TopicsCell'; +import RunningTasksCell from './cells/RunningTasksCell'; +import ActionsCell from './cells/ActionsCell'; + +export const connectorsColumns: ColumnDef[] = [ + { + header: 'Name', + accessorKey: 'name', + cell: KafkaConnectLinkCell, + enableResizing: true, + }, + { + header: 'Connect', + accessorKey: 'connect', + cell: BreakableTextCell, + filterFn: 'arrIncludesSome', + meta: { filterVariant: 'multi-select' }, + enableResizing: true, + }, + { + header: 'Type', + accessorKey: 'type', + meta: { filterVariant: 'multi-select' }, + filterFn: 'arrIncludesSome', + size: 120, + }, + { + header: 'Plugin', + accessorKey: 'connectorClass', + cell: BreakableTextCell, + meta: { filterVariant: 'multi-select' }, + filterFn: 'arrIncludesSome', + enableResizing: true, + }, + { + header: 'Topics', + accessorKey: 'topics', + cell: TopicsCell, + enableColumnFilter: true, + meta: { filterVariant: 'multi-select' }, + filterFn: 'arrIncludesSome', + enableResizing: true, + }, + { + header: 'Status', + accessorKey: 'status.state', + cell: TagCell, + meta: { filterVariant: 'multi-select' }, + filterFn: 'arrIncludesSome', + }, + { + id: 'running_task', + header: 'Running Tasks', + cell: RunningTasksCell, + size: 120, + }, + { + header: '', + id: 'action', + cell: ActionsCell, + size: 60, + }, +]; diff --git a/frontend/src/components/Connect/List/List.tsx b/frontend/src/components/Connect/List/List.tsx index c3a595688..7ded1583e 100644 --- a/frontend/src/components/Connect/List/List.tsx +++ b/frontend/src/components/Connect/List/List.tsx @@ -1,105 +1,19 @@ import React from 'react'; import useAppParams from 'lib/hooks/useAppParams'; import { ClusterNameRoute } from 'lib/paths'; -import Table, { TagCell } from 'components/common/NewTable'; -import { FullConnectorInfo } from 'generated-sources'; import { useConnectors } from 'lib/hooks/api/kafkaConnect'; -import { ColumnDef } from '@tanstack/react-table'; import { useSearchParams } from 'react-router-dom'; -import { useQueryPersister } from 'components/common/NewTable/ColumnFilter'; -import { useLocalStoragePersister } from 'components/common/NewTable/ColumnResizer/lib'; -import BreakableTextCell from 'components/common/NewTable/BreakableTextCell'; - -import ActionsCell from './ActionsCell'; -import TopicsCell from './TopicsCell'; -import RunningTasksCell from './RunningTasksCell'; -import { KafkaConnectLinkCell } from './KafkaConnectLinkCell'; - -const kafkaConnectColumns: ColumnDef[] = [ - { - header: 'Name', - accessorKey: 'name', - cell: KafkaConnectLinkCell, - enableResizing: true, - }, - { - header: 'Connect', - accessorKey: 'connect', - cell: BreakableTextCell, - filterFn: 'arrIncludesSome', - meta: { - filterVariant: 'multi-select', - }, - enableResizing: true, - }, - { - header: 'Type', - accessorKey: 'type', - meta: { filterVariant: 'multi-select' }, - filterFn: 'arrIncludesSome', - size: 120, - }, - { - header: 'Plugin', - accessorKey: 'connectorClass', - cell: BreakableTextCell, - meta: { filterVariant: 'multi-select' }, - filterFn: 'arrIncludesSome', - enableResizing: true, - }, - { - header: 'Topics', - accessorKey: 'topics', - cell: TopicsCell, - enableColumnFilter: true, - meta: { filterVariant: 'multi-select' }, - filterFn: 'arrIncludesSome', - enableResizing: true, - }, - { - header: 'Status', - accessorKey: 'status.state', - cell: TagCell, - meta: { filterVariant: 'multi-select' }, - filterFn: 'arrIncludesSome', - }, - { - id: 'running_task', - header: 'Running Tasks', - cell: RunningTasksCell, - size: 120, - }, - { - header: '', - id: 'action', - cell: ActionsCell, - size: 60, - }, -]; +import { ConnectorsTable } from 'components/Connect/List/ConnectorsTable/ConnectorsTable'; const List: React.FC = () => { const { clusterName } = useAppParams(); const [searchParams] = useSearchParams(); - const { data: connectors } = useConnectors( + const { data: connectors = [] } = useConnectors( clusterName, searchParams.get('q') || '' ); - const filterPersister = useQueryPersister(kafkaConnectColumns); - const columnSizingPersister = useLocalStoragePersister('KafkaConnect'); - - return ( -
`${originalRow.name}-${originalRow.connect}`} - filterPersister={filterPersister} - /> - ); + return ; }; export default List; diff --git a/frontend/src/components/Connect/List/ListPage.tsx b/frontend/src/components/Connect/List/ListPage.tsx index 3b257dd6d..d54085458 100644 --- a/frontend/src/components/Connect/List/ListPage.tsx +++ b/frontend/src/components/Connect/List/ListPage.tsx @@ -1,29 +1,21 @@ import React, { Suspense } from 'react'; -import useAppParams from 'lib/hooks/useAppParams'; -import { ClusterNameRoute } from 'lib/paths'; import Search from 'components/common/Search/Search'; import PageLoader from 'components/common/PageLoader/PageLoader'; -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 { clusterName } = useAppParams(); - const { data, isLoading } = useConnectors(clusterName); - - return ( - <> - - - - - }> - - - - ); -}; +const ListPage: React.FC = () => ( + <> + + + + + }> + + + +); export default ListPage; diff --git a/frontend/src/components/Connect/List/Statistics/Statistics.tsx b/frontend/src/components/Connect/List/Statistics/Statistics.tsx index 4b9dd6da7..7ad4d3c5e 100644 --- a/frontend/src/components/Connect/List/Statistics/Statistics.tsx +++ b/frontend/src/components/Connect/List/Statistics/Statistics.tsx @@ -1,17 +1,16 @@ import React, { useMemo } from 'react'; import * as Statistics from 'components/common/Statistics'; -import { FullConnectorInfo } from 'generated-sources'; +import useAppParams from 'lib/hooks/useAppParams'; +import { ClusterNameRoute } from 'lib/paths'; +import { useConnectors } from 'lib/hooks/api/kafkaConnect'; import { computeStatistics } from './models/computeStatistics'; -type Props = { - connectors: FullConnectorInfo[]; - isLoading: boolean; -}; -const ConnectorsStatistics = ({ connectors, isLoading }: Props) => { - const statistics = useMemo(() => { - return computeStatistics(connectors); - }, [connectors]); +const ConnectorsStatistics = () => { + const { clusterName } = useAppParams(); + const { data: connectors = [], isLoading } = useConnectors(clusterName); + + const statistics = useMemo(() => computeStatistics(connectors), [connectors]); return ( diff --git a/frontend/src/components/Connect/List/Statistics/__tests__/Statistics.spec.tsx b/frontend/src/components/Connect/List/Statistics/__tests__/Statistics.spec.tsx index a1f1f382d..02508b518 100644 --- a/frontend/src/components/Connect/List/Statistics/__tests__/Statistics.spec.tsx +++ b/frontend/src/components/Connect/List/Statistics/__tests__/Statistics.spec.tsx @@ -1,19 +1,31 @@ import React from 'react'; import { render } from 'lib/testHelpers'; import { screen, within } from '@testing-library/react'; -import Statistics from 'components/Connect/List/Statistics/Statistics'; -import { FullConnectorInfo } from 'generated-sources'; +import ConnectorsStatistics from 'components/Connect/List/Statistics/Statistics'; +import { useConnectors } from 'lib/hooks/api/kafkaConnect'; import { connectors } from 'lib/fixtures/kafkaConnect'; +import { FullConnectorInfo } from 'generated-sources'; + +jest.mock('lib/hooks/api/kafkaConnect'); +jest.mock('lib/hooks/useAppParams', () => ({ + __esModule: true, + default: () => ({ clusterName: 'test-cluster' }), +})); + +const useConnectorsMock = useConnectors as jest.MockedFunction< + typeof useConnectors +>; + +type RenderComponentProps = { + data: FullConnectorInfo[] | undefined; + isLoading: boolean; +}; describe('Kafka Connect Connectors Statistics', () => { - async function renderComponent({ - data, - isLoading, - }: { - data: FullConnectorInfo[] | undefined; - isLoading: boolean; - }) { - render(); + function renderComponent({ data = [], isLoading }: RenderComponentProps) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + useConnectorsMock.mockReturnValue({ data, isLoading } as any); + render(); } describe('when data loading', () => { diff --git a/frontend/src/components/Topics/Topic/Connectors/Connectors.tsx b/frontend/src/components/Topics/Topic/Connectors/Connectors.tsx new file mode 100644 index 000000000..dee21a0f9 --- /dev/null +++ b/frontend/src/components/Topics/Topic/Connectors/Connectors.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { FullConnectorInfo } from 'generated-sources'; +import { ConnectorsTable } from 'components/Connect/List/ConnectorsTable/ConnectorsTable'; + +type ConnectorsProps = { + connectors: FullConnectorInfo[]; +}; + +const Connectors: React.FC = ({ connectors }) => ( + +); + +export default Connectors; diff --git a/frontend/src/components/Topics/Topic/Topic.tsx b/frontend/src/components/Topics/Topic/Topic.tsx index 2840efe28..56ca0e989 100644 --- a/frontend/src/components/Topics/Topic/Topic.tsx +++ b/frontend/src/components/Topics/Topic/Topic.tsx @@ -1,6 +1,7 @@ import React, { Suspense } from 'react'; import { NavLink, Route, Routes, useNavigate } from 'react-router-dom'; import { + clusterTopicConnectorsRelativePath, clusterTopicConsumerGroupsRelativePath, clusterTopicEditRelativePath, clusterTopicMessagesRelativePath, @@ -22,6 +23,7 @@ import { useClearTopicMessages, useDeleteTopic, useRecreateTopic, + useTopicConnectors, useTopicDetails, } from 'lib/hooks/api/topics'; import { @@ -43,6 +45,7 @@ import Settings from './Settings/Settings'; import TopicConsumerGroups from './ConsumerGroups/TopicConsumerGroups'; import Statistics from './Statistics/Statistics'; import Edit from './Edit/Edit'; +import Connectors from './Connectors/Connectors'; import SendMessage from './SendMessage/SendMessage'; const Topic: React.FC = () => { @@ -70,6 +73,10 @@ const Topic: React.FC = () => { const deleteTopic = useDeleteTopic(clusterName); const recreateTopic = useRecreateTopic({ clusterName, topicName }); const { data } = useTopicDetails({ clusterName, topicName }); + const { data: connectors = [] } = useTopicConnectors({ + clusterName, + topicName, + }); const { isReadOnly, isTopicDeletionAllowed } = React.useContext(ClusterContext); @@ -84,6 +91,7 @@ const Topic: React.FC = () => { await clearMessages.mutateAsync(topicName); }; const canCleanup = data?.cleanUpPolicy === CleanUpPolicy.DELETE; + const isConnectorsAvailable = connectors.length > 0; return ( <> @@ -225,6 +233,19 @@ const Topic: React.FC = () => { > Statistics + {isConnectorsAvailable && ( + (isActive ? 'is-active' : '')} + permission={{ + resource: ResourceType.TOPIC, + action: Action.ANALYSIS_VIEW, + value: topicName, + }} + > + Connectors + + )} }> @@ -246,6 +267,12 @@ const Topic: React.FC = () => { path={clusterTopicStatisticsRelativePath} element={} /> + {isConnectorsAvailable && ( + } + /> + )} } /> diff --git a/frontend/src/components/Topics/Topic/__test__/Topic.spec.tsx b/frontend/src/components/Topics/Topic/__test__/Topic.spec.tsx index 7b5731767..31e094292 100644 --- a/frontend/src/components/Topics/Topic/__test__/Topic.spec.tsx +++ b/frontend/src/components/Topics/Topic/__test__/Topic.spec.tsx @@ -21,6 +21,7 @@ import { useDeleteTopic, useRecreateTopic, useTopicDetails, + useTopicConnectors, } from 'lib/hooks/api/topics'; const mockNavigate = jest.fn(); @@ -33,6 +34,7 @@ jest.mock('lib/hooks/api/topics', () => ({ useDeleteTopic: jest.fn(), useRecreateTopic: jest.fn(), useClearTopicMessages: jest.fn(), + useTopicConnectors: jest.fn(), })); const clearTopicMessages = jest.fn(); @@ -98,6 +100,9 @@ describe('Details', () => { (useClearTopicMessages as jest.Mock).mockImplementation(() => ({ mutateAsync: clearTopicMessages, })); + (useTopicConnectors as jest.Mock).mockImplementation(() => ({ + data: [], + })); }); describe('Action Bar', () => { describe('when it has readonly flag', () => { @@ -132,7 +137,7 @@ describe('Details', () => { describe('when clear messages modal is open', () => { beforeEach(async () => { - await renderComponent(); + renderComponent(); const confirmButton = screen.getAllByText('Clear messages')[0]; await userEvent.click(confirmButton); }); diff --git a/frontend/src/components/common/NewTable/Table.styled.ts b/frontend/src/components/common/NewTable/Table.styled.ts index c18fbbe71..740c851c8 100644 --- a/frontend/src/components/common/NewTable/Table.styled.ts +++ b/frontend/src/components/common/NewTable/Table.styled.ts @@ -129,6 +129,7 @@ export const Th = styled.th( width: ${expander ? '5px' : 'auto'}; white-space: nowrap; position: relative; + user-select: none; & > ${TableHeaderContent} { cursor: default; @@ -144,13 +145,6 @@ export const Th = styled.th( ` ); -export const TableHeaderFilter = styled.div` - padding: 4px; - position: absolute; - right: 0px; - z-index: 10000; -`; - interface RowProps { clickable?: boolean; expanded?: boolean; diff --git a/frontend/src/components/common/NewTable/Table.tsx b/frontend/src/components/common/NewTable/Table.tsx index cbbb9c0c0..b77b0e758 100644 --- a/frontend/src/components/common/NewTable/Table.tsx +++ b/frontend/src/components/common/NewTable/Table.tsx @@ -6,6 +6,7 @@ import type { PaginationState, Row, SortingState, + VisibilityState, } from '@tanstack/react-table'; import { flexRender, @@ -61,6 +62,9 @@ export interface TableProps { filterPersister?: Persister; resetPaginationOnFilter?: boolean; + // Columns visibility + columnVisibility?: VisibilityState; + // Placeholder for empty table emptyMessage?: React.ReactNode; @@ -158,6 +162,7 @@ function Table({ setRowId, filterPersister, resetPaginationOnFilter = true, + columnVisibility, }: TableProps) { const [searchParams, setSearchParams] = useSearchParams(); const location = useLocation(); @@ -209,6 +214,7 @@ function Table({ columnFilters: filterPersister?.getPrevState() ?? [], rowSelection, columnSizing: columnSizingPersister?.columnSizing ?? {}, + columnVisibility, }, getRowId: (originalRow, index) => { if (setRowId) { diff --git a/frontend/src/lib/hooks/api/kafkaConnect.ts b/frontend/src/lib/hooks/api/kafkaConnect.ts index 64ce2af41..4233d7a19 100644 --- a/frontend/src/lib/hooks/api/kafkaConnect.ts +++ b/frontend/src/lib/hooks/api/kafkaConnect.ts @@ -3,14 +3,17 @@ import { Connector, ConnectorAction, NewConnector, + Topic, } from 'generated-sources'; import { kafkaConnectApiClient as api } from 'lib/api'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ClusterName } from 'lib/interfaces/cluster'; import { showSuccessAlert } from 'lib/errorHandling'; +import { topicKeys } from 'lib/hooks/api/topics'; interface UseConnectorProps { clusterName: ClusterName; + topicName?: Topic['name']; connectName: Connect['name']; connectorName: Connector['name']; } @@ -102,6 +105,13 @@ export function useUpdateConnectorState(props: UseConnectorProps) { Promise.all([ client.invalidateQueries(connectorsKey(props.clusterName)), client.invalidateQueries(connectorKey(props)), + props.topicName && + client.invalidateQueries( + topicKeys.connectors({ + clusterName: props.clusterName, + topicName: props.topicName, + }) + ), ]), } ); diff --git a/frontend/src/lib/hooks/api/topics.ts b/frontend/src/lib/hooks/api/topics.ts index fb05ee219..9f330432a 100644 --- a/frontend/src/lib/hooks/api/topics.ts +++ b/frontend/src/lib/hooks/api/topics.ts @@ -19,6 +19,8 @@ import { TopicCreation, TopicDetails, TopicUpdate, + GetTopicConnectorsRequest, + FullConnectorInfo, } from 'generated-sources'; import { showServerError, showSuccessAlert } from 'lib/errorHandling'; import { ClusterName } from 'lib/interfaces/cluster'; @@ -45,6 +47,8 @@ export const topicKeys = { [...topicKeys.details(props), 'consumerGroups'] as const, statistics: (props: GetTopicDetailsRequest) => [...topicKeys.details(props), 'statistics'] as const, + connectors: (props: GetTopicConnectorsRequest) => + [...topicKeys.details(props), 'connectors'] as const, }; export function useTopics(props: GetTopicsRequest) { @@ -81,6 +85,17 @@ export function useTopicConsumerGroups(props: GetTopicDetailsRequest) { ); } +export function useTopicConnectors( + props: GetTopicConnectorsRequest, + queryOptions?: UseQueryOptions +) { + return useQuery( + topicKeys.connectors(props), + () => api.getTopicConnectors(props), + queryOptions + ); +} + const topicReducer = ( result: TopicFormFormattedParams, customParam: TopicConfig diff --git a/frontend/src/lib/paths.ts b/frontend/src/lib/paths.ts index dbef8246b..757f91006 100644 --- a/frontend/src/lib/paths.ts +++ b/frontend/src/lib/paths.ts @@ -156,6 +156,7 @@ export const clusterTopicSettingsRelativePath = 'settings'; export const clusterTopicMessagesRelativePath = 'messages'; export const clusterTopicConsumerGroupsRelativePath = 'consumer-groups'; export const clusterTopicStatisticsRelativePath = 'statistics'; +export const clusterTopicConnectorsRelativePath = 'connectors'; export const clusterTopicEditRelativePath = 'edit'; export const clusterTopicPath = ( clusterName: ClusterName = RouteParams.clusterName,