From d352baefd14cf11f5339f93d2b86d98c814d26ab Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 13 Nov 2025 18:34:03 +0300 Subject: [PATCH 1/3] fix: monitoring show conditions --- .../TenantNameWrapper/TenantNameWrapper.tsx | 11 +++++++++-- src/containers/Header/Header.tsx | 12 +++++++++++- .../Tenant/Diagnostics/Diagnostics.tsx | 6 +++++- .../TenantOverview/TenantOverview.tsx | 7 ++++++- .../ObjectSummary/SchemaTree/SchemaTree.tsx | 14 +++++++++++--- src/utils/monitoringVisibility.ts | 18 ++++++++++++++++++ 6 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 src/utils/monitoringVisibility.ts diff --git a/src/components/TenantNameWrapper/TenantNameWrapper.tsx b/src/components/TenantNameWrapper/TenantNameWrapper.tsx index f26bc07c34..d9c687b2c5 100644 --- a/src/components/TenantNameWrapper/TenantNameWrapper.tsx +++ b/src/components/TenantNameWrapper/TenantNameWrapper.tsx @@ -1,11 +1,13 @@ import {DefinitionList, Flex} from '@gravity-ui/uikit'; import {getTenantPath} from '../../routes'; +import {useClusterBaseInfo} from '../../store/reducers/cluster/cluster'; import type {PreparedTenant} from '../../store/reducers/tenants/types'; import type {AdditionalTenantsProps} from '../../types/additionalProps'; import {uiFactory} from '../../uiFactory/uiFactory'; import {getDatabaseLinks} from '../../utils/additionalProps'; import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; +import {canShowTenantMonitoring} from '../../utils/monitoringVisibility'; import {EntityStatus} from '../EntityStatus/EntityStatus'; import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon'; @@ -40,13 +42,18 @@ export function TenantNameWrapper({tenant, additionalTenantsProps}: TenantNameWr const isExternalLink = Boolean(backend); const links = getDatabaseLinks(additionalTenantsProps, tenant?.Name, tenant?.Type); + const {monitoring: clusterMonitoring} = useClusterBaseInfo(); + const showMonitoring = canShowTenantMonitoring(tenant?.ControlPlane, clusterMonitoring); + const filteredLinks = showMonitoring + ? links + : links.filter(({title}) => title !== i18n('field_monitoring-link')); const infoPopoverContent = - links.length > 0 && isUserAllowedToMakeChanges ? ( + filteredLinks.length > 0 && isUserAllowedToMakeChanges ? ( - {links.map(({title, url}) => ( + {filteredLinks.map(({title, url}) => ( ))} diff --git a/src/containers/Header/Header.tsx b/src/containers/Header/Header.tsx index fbbb4915f7..4fd360d76f 100644 --- a/src/containers/Header/Header.tsx +++ b/src/containers/Header/Header.tsx @@ -39,6 +39,7 @@ import { useIsUserAllowedToMakeChanges, useIsViewerUser, } from '../../utils/hooks/useIsUserAllowedToMakeChanges'; +import {canShowTenantMonitoring} from '../../utils/monitoringVisibility'; import {isAccessError} from '../../utils/response'; import {getBreadcrumbs} from './breadcrumbs'; @@ -92,8 +93,17 @@ function Header() { const {currentData: databaseData, isLoading: isDatabaseDataLoading} = tenantApi.useGetTenantInfoQuery(params); + // Show Monitoring only when: + // - ControlPlane exists AND has a non-empty id + // - OR ControlPlane is absent, but cluster-level monitoring meta exists + const controlPlane = databaseData?.ControlPlane; + const canShowMonitoring = canShowTenantMonitoring(controlPlane, monitoring); const monitoringLinkUrl = - monitoring && uiFactory.getMonitoringLink && databaseData?.Name && databaseData?.Type + canShowMonitoring && + monitoring && + uiFactory.getMonitoringLink && + databaseData?.Name && + databaseData?.Type ? uiFactory.getMonitoringLink({ monitoring, clusterName, diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index a3cc414db6..c5f0a85d0f 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -9,6 +9,7 @@ import { useConfigAvailable, useTopicDataAvailable, } from '../../../store/reducers/capabilities/hooks'; +import {useClusterBaseInfo} from '../../../store/reducers/cluster/cluster'; import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants'; import {setDiagnosticsTab, useTenantBaseInfo} from '../../../store/reducers/tenant/tenant'; import type {AdditionalTenantsProps} from '../../../types/additionalProps'; @@ -16,6 +17,7 @@ import {uiFactory} from '../../../uiFactory/uiFactory'; import {cn} from '../../../utils/cn'; import {useScrollPosition, useTypedDispatch, useTypedSelector} from '../../../utils/hooks'; import {useIsViewerUser} from '../../../utils/hooks/useIsUserAllowedToMakeChanges'; +import {canShowTenantMonitoring} from '../../../utils/monitoringVisibility'; import {Configs} from '../../Configs/Configs'; import {Heatmap} from '../../Heatmap'; import {Nodes} from '../../Nodes/Nodes'; @@ -61,17 +63,19 @@ function Diagnostics({additionalTenantProps}: DiagnosticsProps) { const {controlPlane, databaseType} = useTenantBaseInfo( isDatabaseEntityType(type) ? database : '', ); + const {monitoring: clusterMonitoring} = useClusterBaseInfo(); const hasConfigs = useConfigAvailable(); const hasTopicData = useTopicDataAvailable(); const isViewerUser = useIsViewerUser(); + const canShowMonitoring = canShowTenantMonitoring(controlPlane, clusterMonitoring); const pages = getPagesByType(type, subType, { hasTopicData, isTopLevel: path === database, hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane), hasConfigs: isViewerUser && hasConfigs, hasAccess: uiFactory.hasAccess, - hasMonitoring: typeof uiFactory.renderMonitoring === 'function', + hasMonitoring: typeof uiFactory.renderMonitoring === 'function' && canShowMonitoring, databaseType, }); let activeTab = pages.find((el) => el.id === diagnosticsTab); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index c9601d46f6..6c8fe3bc7d 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -5,6 +5,7 @@ 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 {useClusterBaseInfo} from '../../../../store/reducers/cluster/cluster'; import {overviewApi} from '../../../../store/reducers/overview/overview'; import { TENANT_DIAGNOSTICS_TABS_IDS, @@ -23,6 +24,7 @@ import {getInfoTabLinks} from '../../../../utils/additionalProps'; import {TENANT_DEFAULT_TITLE} from '../../../../utils/constants'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {useClusterNameFromQuery} from '../../../../utils/hooks/useDatabaseFromQuery'; +import {canShowTenantMonitoring} from '../../../../utils/monitoringVisibility'; import {mapDatabaseTypeToDBName} from '../../utils/schema'; import {HealthcheckPreview} from './Healthcheck/HealthcheckPreview'; @@ -188,7 +190,10 @@ export function TenantOverview({ }; const links = getInfoTabLinks(additionalTenantProps, Name, Type); - const monitoringTabAvailable = Boolean(uiFactory.renderMonitoring); + const {monitoring: clusterMonitoring} = useClusterBaseInfo(); + const cp = tenant?.ControlPlane; + const monitoringTabAvailable = + Boolean(uiFactory.renderMonitoring) && canShowTenantMonitoring(cp, clusterMonitoring); const handleOpenMonitoring = () => { dispatch(setTenantPage(TENANT_PAGES_IDS.diagnostics)); diff --git a/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx b/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx index 6196d8f1c9..7066f376ce 100644 --- a/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx +++ b/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx @@ -10,15 +10,18 @@ import { useCreateDirectoryFeatureAvailable, useTopicDataAvailable, } from '../../../../store/reducers/capabilities/hooks'; +import {useClusterBaseInfo} from '../../../../store/reducers/cluster/cluster'; import {selectIsDirty, selectUserInput} from '../../../../store/reducers/query/query'; import {schemaApi} from '../../../../store/reducers/schema/schema'; import {streamingQueriesApi} from '../../../../store/reducers/streamingQuery/streamingQuery'; import {tableSchemaDataApi} from '../../../../store/reducers/tableSchemaData'; +import {useTenantBaseInfo} from '../../../../store/reducers/tenant/tenant'; 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'; +import {canShowTenantMonitoring} from '../../../../utils/monitoringVisibility'; import {getSchemaControls} from '../../utils/controls'; import { isChildlessPathType, @@ -139,7 +142,11 @@ export function SchemaTree(props: SchemaTreeProps) { setCreateDirectoryOpen(true); }; + const {monitoring: clusterMonitoring} = useClusterBaseInfo(); + const {controlPlane} = useTenantBaseInfo(database); + const canShowMonitoring = canShowTenantMonitoring(controlPlane, clusterMonitoring); const getTreeNodeActions = React.useMemo(() => { + const hasMonitoring = typeof uiFactory.renderMonitoring === 'function' && canShowMonitoring; return getActions( dispatch, { @@ -151,7 +158,7 @@ export function SchemaTree(props: SchemaTreeProps) { getConnectToDBDialog, schemaData: actionsSchemaData, isSchemaDataLoading: isActionsDataFetching, - hasMonitoring: typeof uiFactory.renderMonitoring === 'function', + hasMonitoring, streamingQueryData: streamingSysData, isStreamingQueryTextLoading: isStreamingInfoFetching, }, @@ -167,9 +174,10 @@ export function SchemaTree(props: SchemaTreeProps) { isDirty, isStreamingInfoFetching, onActivePathUpdate, - databaseFullPath, - database, streamingSysData, + database, + databaseFullPath, + canShowMonitoring, ]); return ( diff --git a/src/utils/monitoringVisibility.ts b/src/utils/monitoringVisibility.ts new file mode 100644 index 0000000000..2185a6ffda --- /dev/null +++ b/src/utils/monitoringVisibility.ts @@ -0,0 +1,18 @@ +import type {MetaBaseClusterInfo} from '../types/api/meta'; +import type {ControlPlane} from '../types/api/tenant'; + +/** + * Centralized check for whether Monitoring should be shown for a database. + * Rule: + * - If ControlPlane exists, require a non-empty id. + * - If ControlPlane does not exist, fallback to cluster monitoring meta presence. + */ +export function canShowTenantMonitoring( + controlPlane?: ControlPlane, + clusterMonitoring?: MetaBaseClusterInfo['solomon'], +): boolean { + if (controlPlane) { + return Boolean(controlPlane.id); + } + return Boolean(clusterMonitoring); +} From 10e8605722a7a775fcc13f8a82d01ed8f06270b1 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 13 Nov 2025 18:46:22 +0300 Subject: [PATCH 2/3] fix: nanotweak --- .../Tenant/Diagnostics/TenantOverview/TenantOverview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 6c8fe3bc7d..c6e1c29397 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -191,9 +191,9 @@ export function TenantOverview({ const links = getInfoTabLinks(additionalTenantProps, Name, Type); const {monitoring: clusterMonitoring} = useClusterBaseInfo(); - const cp = tenant?.ControlPlane; const monitoringTabAvailable = - Boolean(uiFactory.renderMonitoring) && canShowTenantMonitoring(cp, clusterMonitoring); + Boolean(uiFactory.renderMonitoring) && + canShowTenantMonitoring(tenant?.ControlPlane, clusterMonitoring); const handleOpenMonitoring = () => { dispatch(setTenantPage(TENANT_PAGES_IDS.diagnostics)); From a8d60f24908a9e47a121e2206a7499c759eff804 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 14 Nov 2025 11:28:55 +0300 Subject: [PATCH 3/3] fix: create helper --- src/containers/Tenant/Diagnostics/Diagnostics.tsx | 5 ++--- .../Diagnostics/TenantOverview/TenantOverview.tsx | 10 +++++----- .../ObjectSummary/SchemaTree/SchemaTree.tsx | 9 ++++----- src/utils/monitoringVisibility.ts | 15 +++++++++++++++ 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index c5f0a85d0f..c739761a92 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -17,7 +17,7 @@ import {uiFactory} from '../../../uiFactory/uiFactory'; import {cn} from '../../../utils/cn'; import {useScrollPosition, useTypedDispatch, useTypedSelector} from '../../../utils/hooks'; import {useIsViewerUser} from '../../../utils/hooks/useIsUserAllowedToMakeChanges'; -import {canShowTenantMonitoring} from '../../../utils/monitoringVisibility'; +import {canShowTenantMonitoringTab} from '../../../utils/monitoringVisibility'; import {Configs} from '../../Configs/Configs'; import {Heatmap} from '../../Heatmap'; import {Nodes} from '../../Nodes/Nodes'; @@ -68,14 +68,13 @@ function Diagnostics({additionalTenantProps}: DiagnosticsProps) { const hasConfigs = useConfigAvailable(); const hasTopicData = useTopicDataAvailable(); const isViewerUser = useIsViewerUser(); - const canShowMonitoring = canShowTenantMonitoring(controlPlane, clusterMonitoring); const pages = getPagesByType(type, subType, { hasTopicData, isTopLevel: path === database, hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane), hasConfigs: isViewerUser && hasConfigs, hasAccess: uiFactory.hasAccess, - hasMonitoring: typeof uiFactory.renderMonitoring === 'function' && canShowMonitoring, + hasMonitoring: canShowTenantMonitoringTab(controlPlane, clusterMonitoring), databaseType, }); let activeTab = pages.find((el) => el.id === diagnosticsTab); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index c6e1c29397..bb1e6f248c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -19,12 +19,11 @@ import { } from '../../../../store/reducers/tenant/tenant'; import {calculateTenantMetrics} from '../../../../store/reducers/tenants/utils'; import type {AdditionalTenantsProps} from '../../../../types/additionalProps'; -import {uiFactory} from '../../../../uiFactory/uiFactory'; import {getInfoTabLinks} from '../../../../utils/additionalProps'; import {TENANT_DEFAULT_TITLE} from '../../../../utils/constants'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {useClusterNameFromQuery} from '../../../../utils/hooks/useDatabaseFromQuery'; -import {canShowTenantMonitoring} from '../../../../utils/monitoringVisibility'; +import {canShowTenantMonitoringTab} from '../../../../utils/monitoringVisibility'; import {mapDatabaseTypeToDBName} from '../../utils/schema'; import {HealthcheckPreview} from './Healthcheck/HealthcheckPreview'; @@ -191,9 +190,10 @@ export function TenantOverview({ const links = getInfoTabLinks(additionalTenantProps, Name, Type); const {monitoring: clusterMonitoring} = useClusterBaseInfo(); - const monitoringTabAvailable = - Boolean(uiFactory.renderMonitoring) && - canShowTenantMonitoring(tenant?.ControlPlane, clusterMonitoring); + const monitoringTabAvailable = canShowTenantMonitoringTab( + tenant?.ControlPlane, + clusterMonitoring, + ); const handleOpenMonitoring = () => { dispatch(setTenantPage(TENANT_PAGES_IDS.diagnostics)); diff --git a/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx b/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx index 7066f376ce..23588e593f 100644 --- a/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx +++ b/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx @@ -17,11 +17,10 @@ import {streamingQueriesApi} from '../../../../store/reducers/streamingQuery/str import {tableSchemaDataApi} from '../../../../store/reducers/tableSchemaData'; import {useTenantBaseInfo} from '../../../../store/reducers/tenant/tenant'; 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'; -import {canShowTenantMonitoring} from '../../../../utils/monitoringVisibility'; +import {canShowTenantMonitoringTab} from '../../../../utils/monitoringVisibility'; import {getSchemaControls} from '../../utils/controls'; import { isChildlessPathType, @@ -144,9 +143,8 @@ export function SchemaTree(props: SchemaTreeProps) { const {monitoring: clusterMonitoring} = useClusterBaseInfo(); const {controlPlane} = useTenantBaseInfo(database); - const canShowMonitoring = canShowTenantMonitoring(controlPlane, clusterMonitoring); const getTreeNodeActions = React.useMemo(() => { - const hasMonitoring = typeof uiFactory.renderMonitoring === 'function' && canShowMonitoring; + const hasMonitoring = canShowTenantMonitoringTab(controlPlane, clusterMonitoring); return getActions( dispatch, { @@ -177,7 +175,8 @@ export function SchemaTree(props: SchemaTreeProps) { streamingSysData, database, databaseFullPath, - canShowMonitoring, + controlPlane, + clusterMonitoring, ]); return ( diff --git a/src/utils/monitoringVisibility.ts b/src/utils/monitoringVisibility.ts index 2185a6ffda..0ef3dc5d94 100644 --- a/src/utils/monitoringVisibility.ts +++ b/src/utils/monitoringVisibility.ts @@ -1,5 +1,6 @@ import type {MetaBaseClusterInfo} from '../types/api/meta'; import type {ControlPlane} from '../types/api/tenant'; +import {uiFactory} from '../uiFactory/uiFactory'; /** * Centralized check for whether Monitoring should be shown for a database. @@ -16,3 +17,17 @@ export function canShowTenantMonitoring( } return Boolean(clusterMonitoring); } + +/** + * Unified visibility check for the Monitoring tab/button. + * Combines UI factory capability and tenant/cluster monitoring availability. + */ +export function canShowTenantMonitoringTab( + controlPlane?: ControlPlane, + clusterMonitoring?: MetaBaseClusterInfo['solomon'], +): boolean { + return ( + Boolean(uiFactory.renderMonitoring) && + canShowTenantMonitoring(controlPlane, clusterMonitoring) + ); +}