Skip to content

Commit

Permalink
Show active tab name in status bar (#51108)
Browse files Browse the repository at this point in the history
* Show active document in the status bar

* Update reading breadcrumbs value in tests

* Show db name first and then username

* Add helpers for making documents

* Show document icon in tabs and adjust styling

* Update gateway title from the document title

* Fix document helpers

* Put breadcrumbs in title

* Simplify breadcrumb key

* Optimize `useActiveDocumentClusterBreadcrumbs`

* Deprecate `isDocumentTshNodeWithLoginHost` and `isDocumentTshNodeWithServerId`

* Remove `aria-label="breadcrumbs"`

* Replace id with class

* Reuse `makeAppGateway`, `makeDatabaseGateway` and `makeKubeGateway` helpers in docs helpers

* Add a comment about updating gateway title

* Re-update the format of db gateways title

* Render entire TabHost instead of only tabs

* Make comment more specific

(cherry picked from commit d1862f3)
  • Loading branch information
gzdunek committed Jan 22, 2025
1 parent 2a91388 commit 81d6de7
Show file tree
Hide file tree
Showing 14 changed files with 466 additions and 82 deletions.
6 changes: 3 additions & 3 deletions web/packages/teleterm/src/services/tshd/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export const makeLoggedInUser = (
export const makeDatabaseGateway = (
props: Partial<tsh.Gateway> = {}
): tsh.Gateway => ({
uri: '/gateways/foo',
uri: '/gateways/db',
targetName: 'sales-production',
targetUri: databaseUri,
targetUser: 'alice',
Expand All @@ -265,7 +265,7 @@ export const makeDatabaseGateway = (
export const makeKubeGateway = (
props: Partial<tsh.Gateway> = {}
): tsh.Gateway => ({
uri: '/gateways/foo',
uri: '/gateways/kube',
targetName: 'foo',
targetUri: kubeUri,
targetUser: '',
Expand All @@ -285,7 +285,7 @@ export const makeKubeGateway = (
export const makeAppGateway = (
props: Partial<tsh.Gateway> = {}
): tsh.Gateway => ({
uri: '/gateways/bar',
uri: '/gateways/app',
targetName: 'sales-production',
targetUri: appUri,
localAddress: 'localhost',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,8 @@ test('activating a workspace via deep link overrides the previously active works
await userEvent.click(dialogSuccessButton);

// Check if the first activated workspace is the one from the deep link.
expect(await screen.findByTitle(/Current cluster:/)).toBeVisible();
expect(
screen.queryByTitle(`Current cluster: ${deepLinkCluster.name}`)
).toBeVisible();
const el = await screen.findByTitle(/Open Profiles/);
expect(el.title).toContain(deepLinkCluster.name);
});

test.each<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function DocumentGateway(props: {

const runCliCommand = () => {
const command = getCliCommandArgv0(gateway.gatewayCliCommand);
const title = `${command} · ${doc.targetUser}@${doc.targetName}`;
const title = `${command} · ${doc.targetName} (${doc.targetUser})`;

const cliDoc = documentsService.createGatewayCliDocument({
title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function ShareFeedback() {
onClick={openShareFeedback}
>
{!hasBeenShareFeedbackOpened && <NotOpenedYetIndicator />}
<ChatBubble size="small" mb={1} />
<ChatBubble size="small" />
</ButtonIcon>
<Popover
open={isShareFeedbackOpened}
Expand Down
46 changes: 38 additions & 8 deletions web/packages/teleterm/src/ui/StatusBar/StatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { Fragment } from 'react';

import { Flex, Text } from 'design';

import { AccessRequestCheckoutButton } from './AccessRequestCheckoutButton';
import { ShareFeedback } from './ShareFeedback';
import { useActiveDocumentClusterBreadcrumbs } from './useActiveDocumentClusterBreadcrumbs';

export function StatusBar() {
const clusterBreadcrumbs = useActiveDocumentClusterBreadcrumbs();
const breadcrumbs = useActiveDocumentClusterBreadcrumbs();

return (
<Flex
Expand All @@ -35,18 +37,46 @@ export function StatusBar() {
alignItems="center"
justifyContent="space-between"
px={2}
gap={2}
color="text.slightlyMuted"
overflow="hidden"
>
<Text
color="text.slightlyMuted"
fontSize="14px"
<Flex
css={`
white-space: nowrap;
// If the breadcrumbs are wider than the available space,
// allow scrolling them horizontally, but do not show the scrollbar.
width: 100%;
overflow: scroll;
&::-webkit-scrollbar {
display: none;
}
`}
title={clusterBreadcrumbs && `Current cluster: ${clusterBreadcrumbs}`}
>
{clusterBreadcrumbs}
</Text>
{breadcrumbs && (
<Flex
gap={2}
css={`
flex-shrink: 0;
font-size: 13px;
`}
title={breadcrumbs.map(({ name }) => name).join(' → ')}
>
{breadcrumbs.map((breadcrumb, index) => (
<Fragment key={`${index}-${breadcrumb.name}`}>
{breadcrumb.Icon && (
<breadcrumb.Icon color="text.muted" size="small" mr={-1} />
)}
<Text>{breadcrumb.name}</Text>
{index !== breadcrumbs.length - 1 && (
<Text color="text.disabled"></Text>
)}
</Fragment>
))}
</Flex>
)}
</Flex>

<Flex gap={2} alignItems="center">
<AccessRequestCheckoutButton />
<ShareFeedback />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,57 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { useAppContext } from 'teleterm/ui/appContextProvider';
import { ComponentType, useCallback } from 'react';

import { IconProps } from 'design/Icon/Icon';

import {
getResourceUri,
useWorkspaceServiceState,
getStaticNameAndIcon,
} from 'teleterm/ui/services/workspacesService';
import { routing } from 'teleterm/ui/uri';

export function useActiveDocumentClusterBreadcrumbs(): string {
const ctx = useAppContext();
useWorkspaceServiceState();
ctx.clustersService.useState();
import { useStoreSelector } from '../hooks/useStoreSelector';

const activeDocument = ctx.workspacesService
.getActiveWorkspaceDocumentService()
?.getActive();
interface Breadcrumb {
name: string;
Icon?: ComponentType<IconProps>;
}

if (!activeDocument) {
return;
}
export function useActiveDocumentClusterBreadcrumbs(): Breadcrumb[] {
const activeDocument = useStoreSelector(
'workspacesService',
useCallback(state => {
const workspace = state.workspaces[state.rootClusterUri];
return workspace?.documents.find(d => d.uri === workspace?.location);
}, [])
);
const resourceUri = activeDocument && getResourceUri(activeDocument);
const staticNameAndIcon =
activeDocument && getStaticNameAndIcon(activeDocument);
const clusterUri = resourceUri && routing.ensureClusterUri(resourceUri);
const rootClusterUri =
resourceUri && routing.ensureRootClusterUri(resourceUri);

const cluster = useStoreSelector(
'clustersService',
useCallback(state => state.clusters.get(clusterUri), [clusterUri])
);
const rootCluster = useStoreSelector(
'clustersService',
useCallback(state => state.clusters.get(rootClusterUri), [rootClusterUri])
);

const resourceUri = getResourceUri(activeDocument);
if (!resourceUri) {
if (!cluster || !rootCluster || !staticNameAndIcon) {
return;
}

const clusterUri = routing.ensureClusterUri(resourceUri);
const rootClusterUri = routing.ensureRootClusterUri(resourceUri);

const rootCluster = ctx.clustersService.findCluster(rootClusterUri);
const leafCluster =
clusterUri === rootClusterUri
? undefined
: ctx.clustersService.findCluster(clusterUri);

return [rootCluster, leafCluster]
.filter(Boolean)
.map(c => c.name)
.join(' > ');
return [
{ name: rootCluster.name },
clusterUri !== rootClusterUri && { name: cluster.name },
{
name: staticNameAndIcon.name,
Icon: staticNameAndIcon.Icon,
},
].filter(Boolean);
}
73 changes: 73 additions & 0 deletions web/packages/teleterm/src/ui/TabHost/TabHost.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { Meta } from '@storybook/react';
import { createRef } from 'react';

import { makeRootCluster } from 'teleterm/services/tshd/testHelpers';
import { ResourcesContextProvider } from 'teleterm/ui/DocumentCluster/resourcesContext';
import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider';
import { MockAppContext } from 'teleterm/ui/fixtures/mocks';
import { Document } from 'teleterm/ui/services/workspacesService';
import {
makeDocumentAccessRequests,
makeDocumentAuthorizeWebSession,
makeDocumentCluster,
makeDocumentConnectMyComputer,
makeDocumentGatewayApp,
makeDocumentGatewayCliClient,
makeDocumentGatewayDatabase,
makeDocumentGatewayKube,
makeDocumentPtySession,
makeDocumentTshNode,
} from 'teleterm/ui/services/workspacesService/documentsService/testHelpers';

import { TabHostContainer } from './TabHost';

const meta: Meta = {
title: 'Teleterm/TabHost',
};

export default meta;

const allDocuments: Document[] = [
makeDocumentCluster(),
makeDocumentTshNode(),
makeDocumentConnectMyComputer(),
makeDocumentGatewayDatabase(),
makeDocumentGatewayApp(),
makeDocumentGatewayCliClient(),
makeDocumentGatewayKube(),
makeDocumentAccessRequests(),
makeDocumentPtySession(),
makeDocumentAuthorizeWebSession(),
];

const cluster = makeRootCluster();

export function Story() {
const ctx = new MockAppContext();
ctx.addRootClusterWithDoc(cluster, allDocuments);
return (
<MockAppContextProvider appContext={ctx}>
<ResourcesContextProvider>
<TabHostContainer topBarContainerRef={createRef()} />
</ResourcesContextProvider>
</MockAppContextProvider>
);
}
Loading

0 comments on commit 81d6de7

Please sign in to comment.