Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
23 changes: 21 additions & 2 deletions src/containers/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {clustersApi} from '../../store/reducers/clusters/clusters';
import {tenantApi} from '../../store/reducers/tenant/tenant';
import {uiFactory} from '../../uiFactory/uiFactory';
import {cn} from '../../utils/cn';
import {DEVELOPER_UI_TITLE} from '../../utils/constants';
import {DEVELOPER_UI_TITLE, MONITORING_UI_TITLE} from '../../utils/constants';
import {createDeveloperUIInternalPageHref} from '../../utils/developerUI/developerUI';
import {useTypedSelector} from '../../utils/hooks';
import {
Expand Down Expand Up @@ -56,7 +56,7 @@ function Header() {

const isMetaDatabasesAvailable = useDatabasesAvailable();

const {title: clusterTitle} = useClusterBaseInfo();
const {title: clusterTitle, monitoring} = useClusterBaseInfo();

const database = useDatabaseFromQuery();

Expand Down Expand Up @@ -93,6 +93,16 @@ function Header() {
const {currentData: databaseData, isLoading: isDatabaseDataLoading} =
tenantApi.useGetTenantInfoQuery(params);

const monitoringLinkUrl =
monitoring && uiFactory.getMonitoringLink && databaseData?.Name && databaseData?.Type
? uiFactory.getMonitoringLink({
monitoring,
clusterName,
dbName: databaseData.Name,
dbType: databaseData.Type,
})
: null;

const breadcrumbItems = React.useMemo(() => {
let options = {
...pageBreadcrumbsOptions,
Expand Down Expand Up @@ -128,6 +138,15 @@ function Header() {
}

if (isDatabasePage && database) {
if (monitoringLinkUrl) {
elements.push(
<Button view="flat" href={monitoringLinkUrl} target="_blank">
{MONITORING_UI_TITLE}
<Icon data={ArrowUpRightFromSquare} />
</Button>,
);
}

elements.push(
<Button view={'flat'} onClick={() => getConnectToDBDialog({database})}>
<Icon data={PlugConnection} />
Expand Down
4 changes: 4 additions & 0 deletions src/containers/Tenant/Diagnostics/Diagnostics.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

height: 100%;

&__tab-badge {
margin-left: var(--g-spacing-2);
}

&__header-wrapper {
padding: 0 var(--g-spacing-5);

Expand Down
24 changes: 12 additions & 12 deletions src/containers/Tenant/Diagnostics/Diagnostics.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react';

import {Tab, TabList, TabProvider} from '@gravity-ui/uikit';
import {TabList, TabProvider} from '@gravity-ui/uikit';
import {Helmet} from 'react-helmet-async';

import {AutoRefreshControl} from '../../../components/AutoRefreshControl/AutoRefreshControl';
import {DrawerContextProvider} from '../../../components/Drawer/DrawerContext';
import {InternalLink} from '../../../components/InternalLink';
import {
useConfigAvailable,
useTopicDataAvailable,
Expand All @@ -32,6 +31,7 @@ import {Consumers} from './Consumers';
import Describe from './Describe/Describe';
import DetailedOverview from './DetailedOverview/DetailedOverview';
import {getPagesByType, useDiagnosticsPageLinkGetter} from './DiagnosticsPages';
import {DiagnosticsTabItem} from './DiagnosticsTabItem';
import {HotKeys} from './HotKeys/HotKeys';
import {NetworkWrapper} from './Network/NetworkWrapper';
import {Partitions} from './Partitions/Partitions';
Expand Down Expand Up @@ -71,6 +71,7 @@ function Diagnostics({additionalTenantProps}: DiagnosticsProps) {
hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane),
hasConfigs: isViewerUser && hasConfigs,
hasAccess: uiFactory.hasAccess,
hasMonitoring: typeof uiFactory.renderMonitoring === 'function',
databaseType,
});
let activeTab = pages.find((el) => el.id === diagnosticsTab);
Expand Down Expand Up @@ -236,16 +237,15 @@ function Diagnostics({additionalTenantProps}: DiagnosticsProps) {
<div className={b('tabs')}>
<TabProvider value={activeTab?.id}>
<TabList size="l">
{pages.map(({id, title}) => {
const linkPath = getDiagnosticsPageLink(id);
return (
<Tab key={id} value={id}>
<InternalLink to={linkPath} as="tab">
{title}
</InternalLink>
</Tab>
);
})}
{pages.map(({id, title, badge}) => (
<DiagnosticsTabItem
key={id}
id={id}
title={title}
linkPath={getDiagnosticsPageLink(id)}
badge={badge}
/>
))}
</TabList>
</TabProvider>
<AutoRefreshControl />
Expand Down
25 changes: 19 additions & 6 deletions src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import React from 'react';

import type {LabelProps} from '@gravity-ui/uikit';
import {StringParam, useQueryParams} from 'use-query-params';

import {getTenantPath} from '../../../routes';
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants';
import type {TenantDiagnosticsTab} from '../../../store/reducers/tenant/types';
import {EPathSubType, EPathType} from '../../../types/api/schema';
import type {ETenantType} from '../../../types/api/tenant';
import {uiFactory} from '../../../uiFactory/uiFactory';
import type {TenantQuery} from '../TenantPages';
import {TenantTabsGroups} from '../TenantPages';
import {isDatabaseEntityType, isTopicEntityType} from '../utils/schema';

interface Badge {
text: string;
theme?: LabelProps['theme'];
size?: LabelProps['size'];
}

type Page = {
id: TenantDiagnosticsTab;
title: string;
badge?: Badge;
};

interface GetPagesOptions {
Expand All @@ -23,6 +30,7 @@ interface GetPagesOptions {
hasBackups?: boolean;
hasConfigs?: boolean;
hasAccess?: boolean;
hasMonitoring?: boolean;
databaseType?: ETenantType;
}

Expand Down Expand Up @@ -114,6 +122,11 @@ const operations = {
const monitoring = {
id: TENANT_DIAGNOSTICS_TABS_IDS.monitoring,
title: 'Monitoring',
badge: {
text: 'New',
theme: 'normal' as const,
size: 'xs' as const,
},
};

const ASYNC_REPLICATION_PAGES = [overview, tablets, describe, access];
Expand All @@ -122,6 +135,7 @@ const TRANSFER_PAGES = [overview, tablets, describe, access];

const DATABASE_PAGES = [
overview,
monitoring,
topQueries,
topShards,
nodes,
Expand All @@ -137,6 +151,7 @@ const DATABASE_PAGES = [

const SERVERLESS_DATABASE_PAGES = [
overview,
monitoring,
topQueries,
topShards,
tablets,
Expand Down Expand Up @@ -226,6 +241,9 @@ function applyFilters(pages: Page[], type?: EPathType, options: GetPagesOptions
if (!options.hasAccess) {
removals.push(TENANT_DIAGNOSTICS_TABS_IDS.access);
}
if (!options.hasMonitoring) {
removals.push(TENANT_DIAGNOSTICS_TABS_IDS.monitoring);
}

return result.filter((p) => !removals.includes(p.id));
}
Expand All @@ -241,11 +259,6 @@ export const getPagesByType = (

const result = applyFilters(seeded, type, options);

// Add monitoring tab as second tab if renderMonitoring is available
if (uiFactory.renderMonitoring) {
result.splice(1, 0, monitoring);
}

return result;
};

Expand Down
33 changes: 33 additions & 0 deletions src/containers/Tenant/Diagnostics/DiagnosticsTabItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {LabelProps} from '@gravity-ui/uikit';
import {Label, Tab} from '@gravity-ui/uikit';

import {InternalLink} from '../../../components/InternalLink';
import {cn} from '../../../utils/cn';

const b = cn('kv-tenant-diagnostics');

interface DiagnosticsTabItemProps {
id: string;
title: string;
linkPath: string;
badge?: {
text: string;
theme?: LabelProps['theme'];
size?: LabelProps['size'];
};
}

export function DiagnosticsTabItem({id, title, linkPath, badge}: DiagnosticsTabItemProps) {
return (
<Tab key={id} value={id}>
<InternalLink to={linkPath} as="tab">
{title}
{badge && (
<Label className={b('tab-badge')} theme={badge.theme} size={badge.size}>
{badge.text}
</Label>
)}
</InternalLink>
</Tab>
);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import {DisplayPulse} from '@gravity-ui/icons';
import {Button, Flex, HelpMark, Icon, Label} from '@gravity-ui/uikit';

import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus';
import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
import {QueriesActivityBar} from '../../../../components/QueriesActivityBar/QueriesActivityBar';
import {useDatabasesAvailable} from '../../../../store/reducers/capabilities/hooks';
import {overviewApi} from '../../../../store/reducers/overview/overview';
import {TENANT_METRICS_TABS_IDS} from '../../../../store/reducers/tenant/constants';
import {tenantApi} from '../../../../store/reducers/tenant/tenant';
import {
TENANT_DIAGNOSTICS_TABS_IDS,
TENANT_METRICS_TABS_IDS,
TENANT_PAGES_IDS,
} from '../../../../store/reducers/tenant/constants';
import {
setDiagnosticsTab,
setTenantPage,
tenantApi,
} from '../../../../store/reducers/tenant/tenant';
import {calculateTenantMetrics} from '../../../../store/reducers/tenants/utils';
import type {AdditionalTenantsProps} from '../../../../types/additionalProps';
import {getDatabaseLinks} from '../../../../utils/additionalProps';
import {uiFactory} from '../../../../uiFactory/uiFactory';
import {getInfoTabLinks} from '../../../../utils/additionalProps';
import {TENANT_DEFAULT_TITLE} from '../../../../utils/constants';
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {useClusterNameFromQuery} from '../../../../utils/hooks/useDatabaseFromQuery';
import {mapDatabaseTypeToDBName} from '../../utils/schema';

Expand Down Expand Up @@ -40,6 +50,7 @@ export function TenantOverview({
const {metricsTab} = useTypedSelector((state) => state.tenant);
const [autoRefreshInterval] = useAutoRefreshInterval();
const clusterName = useClusterNameFromQuery();
const dispatch = useTypedDispatch();

const isMetaDatabasesAvailable = useDatabasesAvailable();

Expand Down Expand Up @@ -176,28 +187,44 @@ export function TenantOverview({
}
};

const links = getDatabaseLinks(additionalTenantProps, Name, Type);
const links = getInfoTabLinks(additionalTenantProps, Name, Type);
const monitoringTabAvailable = Boolean(uiFactory.renderMonitoring);

const handleOpenMonitoring = () => {
dispatch(setTenantPage(TENANT_PAGES_IDS.diagnostics));
dispatch(setDiagnosticsTab(TENANT_DIAGNOSTICS_TABS_IDS.monitoring));
};

return (
<LoaderWrapper loading={tenantLoading}>
<div className={b()}>
<div className={b('info')}>
<div className={b('top-label')}>{tenantType}</div>
<Flex alignItems="center" gap="2" className={b('top-label')}>
<div>{tenantType}</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need this wrapper?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at line 203, you have:

<div>{tenantType}</div>

This wrapper div doesn't seem necessary. The tenantType is just a string being rendered, and it's already inside a Flex container that handles the layout. You could simplify this to just:

Suggested change
<div>{tenantType}</div>
{tenantType}

The div wrapper doesn't add any styling, semantic meaning, or layout benefits here - the parent Flex component is already handling the spacing and alignment. Removing it would clean up the DOM and make the code more concise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile but will flex work with "just a string being rendered"? I thought flex requires block elements?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right to question this! Actually, Flex containers can work with text nodes directly, but there are some important considerations:

  1. Flex does work with text nodes: When you have a string directly inside a Flex container, the browser automatically wraps it in an anonymous inline box, which becomes a flex item.

  2. However, there might be styling implications: Looking at the code, the parent Flex has alignItems="center" and gap="2". The text will participate in the flex layout, but you might lose some control over styling compared to having it in a div.

  3. Consistency with the codebase: Looking at the surrounding code, I see other text elements are often wrapped in divs or other components for styling purposes.

So while it would technically work, keeping the div wrapper might actually be the better choice here for:

  • Consistent styling control
  • Better semantic structure
  • Easier future modifications (adding classes, etc.)

You're right to keep the wrapper div - it's not unnecessary bloat but rather good practice for maintainable styling and layout control. My initial suggestion was too hasty!

{monitoringTabAvailable && (
<Button view="normal" onClick={handleOpenMonitoring}>
<Icon data={DisplayPulse} size={16} />
{i18n('action_open-monitoring')}
</Button>
)}
</Flex>
<Flex alignItems="center" gap="1" className={b('top')}>
{renderName()}
<Flex gap="2">
{links.map(({title, url, icon}) => (
<Button
key={title}
href={url}
target="_blank"
size="xs"
title={title}
>
<Icon data={icon} />
</Button>
))}
</Flex>
{links.length > 0 && (
<Flex gap="2">
{links.map(({title, url, icon}) => (
<Button
key={title}
href={url}
target="_blank"
size="xs"
title={title}
>
<Icon data={icon} />
</Button>
))}
</Flex>
)}
</Flex>
<Flex direction="column" gap={4}>
{!isServerless && <HealthcheckPreview database={database} />}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"action_open-monitoring": "Monitoring",
"top-nodes.empty-data": "No such nodes",
"title_top-nodes-load": "Top nodes by load",
"title_top-nodes-pool": "Top nodes by pools usage",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {selectIsDirty, selectUserInput} from '../../../../store/reducers/query/q
import {schemaApi} from '../../../../store/reducers/schema/schema';
import {tableSchemaDataApi} from '../../../../store/reducers/tableSchemaData';
import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
import {uiFactory} from '../../../../uiFactory/uiFactory';
import {valueIsDefined} from '../../../../utils';
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {getConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
Expand Down Expand Up @@ -144,6 +145,7 @@ export function SchemaTree(props: SchemaTreeProps) {
getConnectToDBDialog,
schemaData: actionsSchemaData,
isSchemaDataLoading: isActionsDataFetching,
hasMonitoring: typeof uiFactory.renderMonitoring === 'function',
},
databaseFullPath,
database,
Expand Down
1 change: 1 addition & 0 deletions src/containers/Tenant/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"actions.connectToDB": "Connect to DB",
"actions.dropIndex": "Drop index",
"actions.openPreview": "Open preview",
"actions.openMonitoring": "Monitoring",
"actions.createTable": "Create table...",
"actions.createExternalTable": "Create external table...",
"actions.createTopic": "Create topic...",
Expand Down
Loading
Loading