Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion frontend/src/components/Connect/Details/DetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { NavLink, Route, Routes } from 'react-router-dom';
import { NavLink, Route, Routes, useLocation } from 'react-router-dom';
import useAppParams from 'lib/hooks/useAppParams';
import { getConnectDetailsPageTitle } from 'lib/pageTitles';
import {
clusterConnectConnectorConfigPath,
clusterConnectConnectorConfigRelativePath,
Expand All @@ -25,6 +26,7 @@ import Topics from './Topics/Topics';
const DetailsPage: React.FC = () => {
const { clusterName, connectName, connectorName } =
useAppParams<RouterParamsClusterConnectConnector>();
const { pathname } = useLocation();

const connector = useConnector({ clusterName, connectName, connectorName });
const tasks = useConnectorTasks({ clusterName, connectName, connectorName });
Expand All @@ -43,6 +45,12 @@ const DetailsPage: React.FC = () => {
text={connectorName}
backTo={clusterConnectorsPath(clusterName)}
backText="Connectors"
documentTitle={getConnectDetailsPageTitle(
pathname,
clusterName,
connectName,
connectorName
)}
>
<Actions />
</ResourcePageHeading>
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/Connect/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ClusterContext from 'components/contexts/ClusterContext';
import { ResourceType, Action } from 'generated-sources';
import { useConnects } from 'lib/hooks/api/kafkaConnect';
import useAppParams from 'lib/hooks/useAppParams';
import { getKafkaConnectPageTitle } from 'lib/pageTitles';
import {
clusterConnectorNewPath,
clusterConnectorsRelativePath,
Expand Down Expand Up @@ -48,7 +49,10 @@ const Header = () => {
};

return (
<ResourcePageHeading text="Kafka Connect">
<ResourcePageHeading
text="Kafka Connect"
documentTitle={getKafkaConnectPageTitle(currentPath, clusterName)}
>
{!isReadOnly && (
<Tooltip
value={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { clusterConsumerGroupsPath, ClusterGroupParam } from 'lib/paths';
import { buildPageTitle } from 'lib/pageTitles';
import 'react-datepicker/dist/react-datepicker.css';
import useAppParams from 'lib/hooks/useAppParams';
import { useConsumerGroupDetails } from 'lib/hooks/api/consumers';
Expand Down Expand Up @@ -41,6 +42,11 @@ const ResetOffsets: React.FC = () => {
text={consumerGroupID}
backTo={clusterConsumerGroupsPath(routerParams.clusterName)}
backText="Consumers"
documentTitle={buildPageTitle(
'Reset Offsets',
consumerGroupID,
routerParams.clusterName
)}
/>
<Form
defaultValues={defaultValues}
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/components/KsqlDb/KsqlDb.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import Query from 'components/KsqlDb/Query/Query';
import useAppParams from 'lib/hooks/useAppParams';
import { getKsqlDbPageTitle } from 'lib/pageTitles';
import * as Metrics from 'components/common/Metrics';
import {
clusterKsqlDbQueryRelativePath,
Expand All @@ -12,7 +13,13 @@ import {
} from 'lib/paths';
import { ActionButton } from 'components/common/ActionComponent';
import Navbar from 'components/common/Navigation/Navbar.styled';
import { Navigate, NavLink, Route, Routes } from 'react-router-dom';
import {
Navigate,
NavLink,
Route,
Routes,
useLocation,
} from 'react-router-dom';
import { Action, ResourceType } from 'generated-sources';
import { useKsqlTables, useKsqlStreams } from 'lib/hooks/api/ksqlDb';
import 'ace-builds/src-noconflict/ace';
Expand All @@ -24,6 +31,7 @@ import TableView from './TableView';

const KsqlDb: React.FC = () => {
const { clusterName } = useAppParams<ClusterNameRoute>();
const { pathname } = useLocation();

const tables = useKsqlTables(clusterName);
const streams = useKsqlStreams(clusterName);
Expand All @@ -38,7 +46,10 @@ const KsqlDb: React.FC = () => {

return (
<>
<ResourcePageHeading text="KSQL DB">
<ResourcePageHeading
text="KSQL DB"
documentTitle={getKsqlDbPageTitle(pathname, clusterName)}
>
<ActionButton
to={clusterKsqlDbQueryRelativePath}
buttonType="primary"
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/Topics/Topic/Topic.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React, { Suspense } from 'react';
import { NavLink, Route, Routes, useNavigate } from 'react-router-dom';
import {
NavLink,
Route,
Routes,
useLocation,
useNavigate,
} from 'react-router-dom';
import {
clusterTopicAclsRelativePath,
clusterTopicConnectorsRelativePath,
Expand All @@ -11,6 +17,7 @@ import {
clusterTopicStatisticsRelativePath,
RouteParamsClusterTopic,
} from 'lib/paths';
import { getTopicPageTitle } from 'lib/pageTitles';
import ClusterContext from 'components/contexts/ClusterContext';
import {
ActionButton,
Expand Down Expand Up @@ -61,6 +68,7 @@ const Topic: React.FC = () => {
const { messageData, setMessage, clearMessage } = useProduceMessage();

const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
const { pathname } = useLocation();

const openSidebarWithMessage = (message: TopicMessage) => {
setMessage(message);
Expand Down Expand Up @@ -103,6 +111,7 @@ const Topic: React.FC = () => {
text={topicName}
backText="Topics"
backTo={clusterTopicsPath(clusterName)}
documentTitle={getTopicPageTitle(pathname, clusterName, topicName)}
>
<ActionButton
buttonSize="M"
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/common/PageHeading/PageHeading.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { PropsWithChildren } from 'react';
import Heading from 'components/common/heading/Heading.styled';
import { buildPageTitle } from 'lib/pageTitles';

import * as S from './PageHeading.styled';

Expand All @@ -8,6 +9,7 @@ interface PageHeadingProps {
backTo?: string;
backText?: string;
title?: string;
documentTitle?: string;
}

const PageHeading: React.FC<PropsWithChildren<PageHeadingProps>> = ({
Expand All @@ -16,8 +18,12 @@ const PageHeading: React.FC<PropsWithChildren<PageHeadingProps>> = ({
backText,
children,
title,
documentTitle,
}) => {
const isBackButtonVisible = backTo && backText;
React.useEffect(() => {
document.title = documentTitle || buildPageTitle(text, title);
}, [documentTitle, text, title]);

return (
<S.Wrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import PageHeading from 'components/common/PageHeading/PageHeading';

const originalTitle = document.title;

describe('PageHeading', () => {
afterEach(() => {
document.title = originalTitle;
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it('sets the browser title from heading content by default', () => {
render(<PageHeading text="Topics" title="local" />);

expect(document.title).toBe('Topics | local | Kafbat UI');
});

it('uses an explicit browser title override when provided', () => {
render(
<PageHeading
text="orders"
title="local"
documentTitle="Messages | orders | local | Kafbat UI"
/>
);

expect(document.title).toBe('Messages | orders | local | Kafbat UI');
});
});
54 changes: 54 additions & 0 deletions frontend/src/lib/__test__/pageTitles.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
clusterConnectConnectorConfigPath,
clusterConnectorsRelativePath,
clusterKsqlDbQueryPath,
clusterTopicMessagesPath,
} from 'lib/paths';
import {
buildPageTitle,
getConnectDetailsPageTitle,
getKafkaConnectPageTitle,
getKsqlDbPageTitle,
getTopicPageTitle,
} from 'lib/pageTitles';

describe('pageTitles', () => {
it('builds titles from non-empty parts', () => {
expect(buildPageTitle('Messages', '', 'orders')).toBe(
'Messages | orders | Kafbat UI'
);
});

it('maps topic detail routes to section titles', () => {
expect(
getTopicPageTitle(
clusterTopicMessagesPath('local', 'orders'),
'local',
'orders'
)
).toBe('Messages | orders | local | Kafbat UI');
});

it('maps connector detail routes to section titles', () => {
expect(
getConnectDetailsPageTitle(
clusterConnectConnectorConfigPath('local', 'main-connect', 'sink-a'),
'local',
'main-connect',
'sink-a'
)
).toBe('Config | sink-a | local | Kafbat UI');
});

it('maps kafka connect list routes to page titles', () => {
expect(
getKafkaConnectPageTitle(clusterConnectorsRelativePath, 'local')
).toBe('Connectors | Kafka Connect | local | Kafbat UI');
});

it('maps ksqldb routes to page titles', () => {
expect(getKsqlDbPageTitle(clusterKsqlDbQueryPath('local'), 'local')).toBe(
'Query | KSQL DB | local | Kafbat UI'
);
});
});
134 changes: 134 additions & 0 deletions frontend/src/lib/pageTitles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {
clusterConnectConnectorConfigPath,
clusterConnectConnectorPath,
clusterConnectConnectorTopicsPath,
clusterConnectConnectorTasksPath,
clusterKsqlDbPath,
clusterKsqlDbQueryPath,
clusterKsqlDbStreamsPath,
clusterKsqlDbTablesPath,
clusterTopicAclsRelativePath,
clusterTopicConnectorsRelativePath,
clusterTopicConsumerGroupsPath,
clusterTopicEditPath,
clusterTopicMessagesPath,
clusterTopicPath,
clusterTopicSettingsPath,
clusterTopicStatisticsPath,
kafkaConnectClustersRelativePath,
} from 'lib/paths';

const APP_NAME = 'Kafbat UI';

export const buildPageTitle = (...parts: Array<string | undefined | null>) =>
[...parts.filter((part): part is string => !!part?.trim()), APP_NAME].join(
' | '
);

export const getKafkaConnectPageTitle = (
currentPath: string | undefined,
clusterName: string
) => {
if (currentPath === kafkaConnectClustersRelativePath) {
return buildPageTitle('Clusters', 'Kafka Connect', clusterName);
}

return buildPageTitle('Connectors', 'Kafka Connect', clusterName);
};

export const getKsqlDbPageTitle = (pathname: string, clusterName: string) => {
if (pathname === clusterKsqlDbQueryPath(clusterName)) {
return buildPageTitle('Query', 'KSQL DB', clusterName);
}

if (pathname === clusterKsqlDbStreamsPath(clusterName)) {
return buildPageTitle('Streams', 'KSQL DB', clusterName);
}

if (
pathname === clusterKsqlDbTablesPath(clusterName) ||
pathname === clusterKsqlDbPath(clusterName)
) {
return buildPageTitle('Tables', 'KSQL DB', clusterName);
}

return buildPageTitle('Tables', 'KSQL DB', clusterName);
};

export const getConnectDetailsPageTitle = (
pathname: string,
clusterName: string,
connectName: string,
connectorName: string
) => {
if (
pathname ===
clusterConnectConnectorConfigPath(clusterName, connectName, connectorName)
) {
return buildPageTitle('Config', connectorName, clusterName);
}

if (
pathname ===
clusterConnectConnectorTopicsPath(clusterName, connectName, connectorName)
) {
return buildPageTitle('Topics', connectorName, clusterName);
}

if (
pathname ===
clusterConnectConnectorTasksPath(
clusterName,
connectName,
connectorName
) ||
pathname ===
clusterConnectConnectorPath(clusterName, connectName, connectorName)
) {
return buildPageTitle('Tasks', connectorName, clusterName);
}

return buildPageTitle(connectorName, clusterName);
};

export const getTopicPageTitle = (
pathname: string,
clusterName: string,
topicName: string
) => {
if (pathname === clusterTopicMessagesPath(clusterName, topicName)) {
return buildPageTitle('Messages', topicName, clusterName);
}

if (pathname === clusterTopicSettingsPath(clusterName, topicName)) {
return buildPageTitle('Settings', topicName, clusterName);
}

if (pathname === clusterTopicConsumerGroupsPath(clusterName, topicName)) {
return buildPageTitle('Consumers', topicName, clusterName);
}

if (pathname === clusterTopicStatisticsPath(clusterName, topicName)) {
return buildPageTitle('Statistics', topicName, clusterName);
}

if (
pathname ===
`${clusterTopicPath(clusterName, topicName)}/${clusterTopicAclsRelativePath}`
) {
return buildPageTitle('ACLs', topicName, clusterName);
}

if (
pathname ===
`${clusterTopicPath(clusterName, topicName)}/${clusterTopicConnectorsRelativePath}`
) {
return buildPageTitle('Connectors', topicName, clusterName);
}

if (pathname === clusterTopicEditPath(clusterName, topicName)) {
return buildPageTitle('Edit', topicName, clusterName);
}

return buildPageTitle(topicName, clusterName);
};
Loading