From 70cc5175169a9f7b5abc5cc404639f68f029024a Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Thu, 11 Sep 2025 17:38:53 +0300 Subject: [PATCH 1/5] fix: refactor relative paths support --- .../ConnectToDB/ConnectToDBDialog.tsx | 2 +- src/components/NetworkTable/NetworkTable.tsx | 6 +- .../QueriesActivityBar/QueriesActivityBar.tsx | 8 +- .../QueriesActivityCharts.tsx | 8 +- .../QueriesActivityExpandable.tsx | 6 +- .../useQueriesActivityData.ts | 10 +- src/components/ShardsTable/ShardsTable.tsx | 8 +- src/components/ShardsTable/columns.tsx | 4 +- src/components/ShardsTable/types.ts | 5 +- src/components/VDisk/VDisk.tsx | 2 +- src/containers/Header/Header.tsx | 2 +- src/containers/Header/breadcrumbs.tsx | 24 ++-- src/containers/Heatmap/Heatmap.tsx | 5 +- src/containers/Node/Node.tsx | 24 ++-- src/containers/Nodes/Nodes.tsx | 3 + src/containers/Nodes/NodesTable.tsx | 4 + .../PaginatedNodes/GroupedNodesComponent.tsx | 17 ++- .../Nodes/PaginatedNodes/NodesComponent.tsx | 3 + .../Nodes/PaginatedNodes/PaginatedNodes.tsx | 1 + src/containers/Nodes/getNodes.ts | 8 +- .../StorageGroupPage/StorageGroupPage.tsx | 2 +- src/containers/Tablet/Tablet.tsx | 20 ++-- src/containers/Tablets/Tablets.tsx | 24 +++- .../Diagnostics/AccessRights/AccessRights.tsx | 4 +- .../components/ChangeOwnerDialog.tsx | 20 +++- .../AccessRights/components/Owner.tsx | 10 +- .../components/RightsTable/Actions.tsx | 6 +- .../RightsTable/RevokeAllRightsDialog.tsx | 8 +- .../components/RightsTable/RightsTable.tsx | 6 +- .../Diagnostics/Consumers/Consumers.tsx | 11 +- .../Tenant/Diagnostics/Describe/Describe.tsx | 5 +- .../DetailedOverview/DetailedOverview.tsx | 18 ++- .../Tenant/Diagnostics/Diagnostics.tsx | 60 ++++++++-- .../Tenant/Diagnostics/HotKeys/HotKeys.tsx | 10 +- .../Tenant/Diagnostics/Network/Network.tsx | 14 ++- .../Diagnostics/Network/NetworkWrapper.tsx | 5 +- .../ChangefeedInfo/ChangefeedInfo.tsx | 5 +- .../Tenant/Diagnostics/Overview/Overview.tsx | 26 ++++- .../Overview/TopicInfo/TopicInfo.tsx | 5 +- .../Overview/TopicStats/TopicStats.tsx | 14 ++- .../Overview/TransferInfo/TransferInfo.tsx | 8 +- .../Diagnostics/Partitions/Partitions.tsx | 13 ++- .../Healthcheck/HealthcheckPreview.tsx | 6 +- .../TenantOverview/TenantCpu/TenantCpu.tsx | 18 +-- .../TenantCpu/TopNodesByCpu.tsx | 8 +- .../TenantCpu/TopNodesByLoad.tsx | 8 +- .../TenantOverview/TenantCpu/TopQueries.tsx | 6 +- .../TenantOverview/TenantCpu/TopShards.tsx | 19 +--- .../TenantMemory/TenantMemory.tsx | 13 +-- .../TenantMemory/TopNodesByMemory.tsx | 8 +- .../TenantNetwork/TenantNetwork.tsx | 14 +-- .../TenantNetwork/TopNodesByPing.tsx | 8 +- .../TenantNetwork/TopNodesBySkew.tsx | 8 +- .../TenantOverview/TenantOverview.tsx | 27 +++-- .../TenantStorage/TenantStorage.tsx | 12 +- .../TopQueries/RunningQueriesData.tsx | 6 +- .../Diagnostics/TopQueries/TopQueries.tsx | 8 +- .../Diagnostics/TopQueries/TopQueriesData.tsx | 6 +- .../Diagnostics/TopShards/TopShards.tsx | 20 +--- .../Diagnostics/TopicData/TopicData.tsx | 5 +- .../Tenant/GrantAccess/GrantAccess.tsx | 19 +++- src/containers/Tenant/GrantAccess/utils.ts | 12 +- .../Tenant/Healthcheck/Healthcheck.tsx | 6 +- .../Tenant/Healthcheck/useHealthcheck.ts | 6 +- .../CreateDirectoryDialog.tsx | 8 +- .../Tenant/ObjectSummary/ObjectSummary.tsx | 14 ++- .../Tenant/ObjectSummary/ObjectTree.tsx | 14 +-- .../ObjectSummary/SchemaTree/SchemaTree.tsx | 30 ++--- .../Tenant/ObjectSummary/transformPath.ts | 14 +-- .../Query/Preview/components/TablePreview.tsx | 6 +- .../Query/Preview/components/TopicPreview.tsx | 4 +- src/containers/Tenant/Query/Preview/types.ts | 1 + .../Tenant/Query/QueryEditor/QueryEditor.tsx | 37 +++--- .../QueryEditorControls.tsx | 8 +- .../Query/QueryResult/QueryResultViewer.tsx | 6 +- .../Schema/SchemaViewer/SchemaViewer.tsx | 15 ++- src/containers/Tenant/Tenant.tsx | 16 +-- .../Tenant/TenantDrawerHealthcheck.tsx | 2 +- src/containers/Tenant/utils/schemaActions.tsx | 18 +-- src/containers/VDiskPage/VDiskPage.tsx | 1 - src/services/api/base.ts | 12 +- src/services/api/meta.ts | 8 +- src/services/api/scheme.ts | 6 +- src/services/api/viewer.ts | 65 ++++++----- src/store/reducers/header/types.ts | 6 +- src/store/reducers/heatmap.ts | 12 +- src/store/reducers/hotKeys/hotKeys.ts | 11 +- src/store/reducers/network/network.ts | 7 +- src/store/reducers/nodes/types.ts | 1 + src/store/reducers/overview/overview.ts | 9 +- src/store/reducers/partitions/partitions.ts | 12 +- src/store/reducers/replication.ts | 15 ++- src/store/reducers/schema/schema.ts | 42 +++++-- src/store/reducers/schemaAcl/schemaAcl.ts | 78 +++++++++---- .../reducers/shardsWorkload/shardsWorkload.ts | 105 +++++------------- src/store/reducers/tableSchemaData.ts | 11 +- src/store/reducers/tenant/tenant.ts | 30 +++-- .../topShards/tenantOverviewTopShards.ts | 29 ++--- src/store/reducers/topic.ts | 25 ++++- src/store/reducers/viewSchema/viewSchema.ts | 5 +- src/types/api/common.ts | 2 + src/types/api/nodes.ts | 11 +- src/types/store/heatmap.ts | 1 + src/types/store/tablets.ts | 4 +- src/utils/shardsQueryBuilders.ts | 99 +++++++++++++++++ tests/suites/sidebar/sidebar.test.ts | 6 +- .../tenant/diagnostics/tabs/info.test.ts | 26 ++--- .../tenant/diagnostics/tabs/nodes.test.ts | 6 +- .../diagnostics/tabs/operations.test.ts | 34 +++--- .../tenant/diagnostics/tabs/queries.test.ts | 38 +++---- .../tenant/diagnostics/tabs/schema.test.ts | 4 +- .../tenant/diagnostics/tabs/storage.test.ts | 6 +- .../tenant/diagnostics/tabs/topShards.test.ts | 26 ++--- tests/suites/tenant/initialLoad.test.ts | 6 +- .../tenant/queryEditor/planToSvg.test.ts | 6 +- .../tenant/queryEditor/queryEditor.test.ts | 6 +- .../tenant/queryEditor/querySettings.test.ts | 6 +- .../tenant/queryEditor/queryStatus.test.ts | 6 +- .../tenant/queryEditor/queryTemplates.test.ts | 4 +- .../tenant/queryHistory/queryHistory.test.ts | 6 +- .../tenant/savedQueries/savedQueries.test.ts | 4 +- .../tenant/summary/objectSummary.test.ts | 14 +-- tests/utils/constants.ts | 2 +- 123 files changed, 1034 insertions(+), 659 deletions(-) create mode 100644 src/utils/shardsQueryBuilders.ts diff --git a/src/components/ConnectToDB/ConnectToDBDialog.tsx b/src/components/ConnectToDB/ConnectToDBDialog.tsx index 78cf1edd01..78f88cda78 100644 --- a/src/components/ConnectToDB/ConnectToDBDialog.tsx +++ b/src/components/ConnectToDB/ConnectToDBDialog.tsx @@ -56,7 +56,7 @@ function ConnectToDBDialog({ // Since there is no ControlPlane data in this case const shouldRequestTenantData = database && !endpointFromProps && !singleClusterMode; const params = shouldRequestTenantData - ? {path: database, clusterName, isMetaDatabasesAvailable} + ? {database, clusterName, isMetaDatabasesAvailable} : skipToken; const {currentData: tenantData, isLoading: isTenantDataLoading} = tenantApi.useGetTenantInfoQuery(params); diff --git a/src/components/NetworkTable/NetworkTable.tsx b/src/components/NetworkTable/NetworkTable.tsx index 57d6d963cc..a744d16f4f 100644 --- a/src/components/NetworkTable/NetworkTable.tsx +++ b/src/components/NetworkTable/NetworkTable.tsx @@ -11,11 +11,12 @@ import { type NetworkWrapperProps = Pick< NodesProps, - 'path' | 'scrollContainerRef' | 'additionalNodesProps' | 'database' + 'path' | 'scrollContainerRef' | 'additionalNodesProps' | 'database' | 'databaseFullPath' >; export function NetworkTable({ database, + databaseFullPath, path, scrollContainerRef, additionalNodesProps, @@ -24,11 +25,12 @@ export function NetworkTable({ void; } @@ -21,7 +21,7 @@ const QUERIES_PER_SECOND_CHART_HEIGHT = 292; const QUERIES_LATENCIES_CHART_HEIGHT = 292 + LEGEND_HEIGHT; export function QueriesActivityCharts({ - tenantName, + database, expanded, onChartDataStatusChange, }: QueriesActivityChartsProps) { @@ -76,7 +76,7 @@ export function QueriesActivityCharts({ > )} - + ); diff --git a/src/components/QueriesActivityBar/useQueriesActivityData.ts b/src/components/QueriesActivityBar/useQueriesActivityData.ts index e786a59ce5..081d69d780 100644 --- a/src/components/QueriesActivityBar/useQueriesActivityData.ts +++ b/src/components/QueriesActivityBar/useQueriesActivityData.ts @@ -23,18 +23,18 @@ interface UseQueriesActivityDataResult { areChartsAvailable: boolean | null; // null = loading, boolean = result } -export function useQueriesActivityData(tenantName: string): UseQueriesActivityDataResult { +export function useQueriesActivityData(database: string): UseQueriesActivityDataResult { const [autoRefreshInterval] = useAutoRefreshInterval(); const shouldRefresh = autoRefreshInterval; // Respect GraphShardExists if explicitly false for the specific tenant - const graphShardExists = useTypedSelector((state) => selectGraphShardExists(state, tenantName)); + const graphShardExists = useTypedSelector((state) => selectGraphShardExists(state, database)); const skipCharts = graphShardExists === false; const {data: runningQueriesData} = topQueriesApi.useGetRunningQueriesQuery( { - database: tenantName, + database, filters: {}, }, {pollingInterval: shouldRefresh}, @@ -46,7 +46,7 @@ export function useQueriesActivityData(tenantName: string): UseQueriesActivityDa isError: queriesError, } = chartApi.useGetChartDataQuery( { - database: tenantName, + database, metrics: [{target: 'queries.requests'}], timeFrame: QUERIES_TIME_FRAME, maxDataPoints: 30, @@ -56,7 +56,7 @@ export function useQueriesActivityData(tenantName: string): UseQueriesActivityDa const {data: latencyData} = chartApi.useGetChartDataQuery( { - database: tenantName, + database, metrics: [{target: 'queries.latencies.p99'}], timeFrame: LATENCIES_TIME_FRAME, maxDataPoints: 30, diff --git a/src/components/ShardsTable/ShardsTable.tsx b/src/components/ShardsTable/ShardsTable.tsx index cacf3e60ba..b672fa0c44 100644 --- a/src/components/ShardsTable/ShardsTable.tsx +++ b/src/components/ShardsTable/ShardsTable.tsx @@ -13,14 +13,14 @@ export interface ShardsTableProps extends Omit, 'columnsWidthLSKey' | 'columns'> { columnsIds: TopShardsColumnId[]; database: string; + databaseFullPath: string; overrideColumns?: ShardsColumn[]; - schemaPath?: string; } export function ShardsTable({ columnsIds, - schemaPath, database, + databaseFullPath, overrideColumns = [], ...props }: ShardsTableProps) { @@ -33,14 +33,14 @@ export function ShardsTable({ return overridedColumn; } - const column = shardsColumnIdToGetColumn[id]({database, schemaPath}); + const column = shardsColumnIdToGetColumn[id]({database, databaseFullPath}); return { ...column, sortable: isSortableTopShardsColumn(column.name), }; }); - }, [columnsIds, database, overrideColumns, schemaPath]); + }, [columnsIds, database, overrideColumns, databaseFullPath]); return ( { +export const getPathColumn: GetShardsColumn = ({databaseFullPath = ''}) => { return { name: TOP_SHARDS_COLUMNS_IDS.Path, header: TOP_SHARDS_COLUMNS_TITLES.Path, render: ({row}) => { // row.RelativePath - relative schema path without start slash return ( - + {row.RelativePath} ); diff --git a/src/components/ShardsTable/types.ts b/src/components/ShardsTable/types.ts index 4fcf954372..b4ca98afb3 100644 --- a/src/components/ShardsTable/types.ts +++ b/src/components/ShardsTable/types.ts @@ -4,4 +4,7 @@ import type {KeyValueRow} from '../../types/api/query'; export type ShardsColumn = Column; -export type GetShardsColumn = (params: {database: string; schemaPath?: string}) => ShardsColumn; +export type GetShardsColumn = (params: { + database: string; + databaseFullPath?: string; +}) => ShardsColumn; diff --git a/src/components/VDisk/VDisk.tsx b/src/components/VDisk/VDisk.tsx index 235f8e9d8e..716c96f94b 100644 --- a/src/components/VDisk/VDisk.tsx +++ b/src/components/VDisk/VDisk.tsx @@ -37,7 +37,7 @@ export const VDisk = ({ }: VDiskProps) => { const database = useDatabaseFromQuery(); const vDiskPath = getVDiskLink(data, { - database: database, + database, }); return ( diff --git a/src/containers/Header/Header.tsx b/src/containers/Header/Header.tsx index 6ec700328c..9ab5cc2fad 100644 --- a/src/containers/Header/Header.tsx +++ b/src/containers/Header/Header.tsx @@ -86,7 +86,7 @@ function Header() { database && isDatabasePage && (isEditDBAvailable || isDeleteDBAvailable); const params = shouldRequestTenantData - ? {path: database, clusterName, isMetaDatabasesAvailable} + ? {database, clusterName, isMetaDatabasesAvailable} : skipToken; const {currentData: databaseData, isLoading: isDatabaseDataLoading} = diff --git a/src/containers/Header/breadcrumbs.tsx b/src/containers/Header/breadcrumbs.tsx index aa8be9b390..a5730d2c41 100644 --- a/src/containers/Header/breadcrumbs.tsx +++ b/src/containers/Header/breadcrumbs.tsx @@ -84,12 +84,12 @@ const getClusterBreadcrumbs: GetBreadcrumbs = (option }; const getTenantBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { - const {tenantName, database} = options; + const {databaseName, database} = options; const breadcrumbs = getClusterBreadcrumbs(options, query); - const text = tenantName || headerKeyset('breadcrumbs.tenant'); - const link = tenantName ? getTenantPath({...query, database}) : undefined; + const text = databaseName || headerKeyset('breadcrumbs.tenant'); + const link = database ? getTenantPath({...query, database}) : undefined; const lastItem = {text, link, icon: }; breadcrumbs.push(lastItem); @@ -98,11 +98,11 @@ const getTenantBreadcrumbs: GetBreadcrumbs = (options, }; const getNodeBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { - const {nodeId, nodeRole, nodeActiveTab, tenantName} = options; + const {nodeId, nodeRole, nodeActiveTab, database} = options; const tenantQuery = getQueryForTenant(nodeActiveTab === 'tablets' ? 'tablets' : 'nodes'); - const breadcrumbs = tenantName + const breadcrumbs = database ? getTenantBreadcrumbs(options, {...query, ...tenantQuery}) : getClusterBreadcrumbs(options, query); @@ -113,9 +113,7 @@ const getNodeBreadcrumbs: GetBreadcrumbs = (options, que const lastItem = { text, - link: nodeId - ? getDefaultNodePath(nodeId, {database: tenantName, ...query}, nodeActiveTab) - : undefined, + link: nodeId ? getDefaultNodePath(nodeId, {database, ...query}, nodeActiveTab) : undefined, icon: getNodeIcon(nodeRole), }; @@ -167,9 +165,9 @@ const getStorageGroupBreadcrumbs: GetBreadcrumbs options, query = {}, ) => { - const {groupId, tenantName} = options; + const {groupId, database} = options; - const breadcrumbs = tenantName + const breadcrumbs = database ? getTenantBreadcrumbs(options, query) : getClusterBreadcrumbs(options, query); @@ -180,7 +178,7 @@ const getStorageGroupBreadcrumbs: GetBreadcrumbs const lastItem = { text, - link: groupId ? getStorageGroupPath(groupId, {database: tenantName}) : undefined, + link: groupId ? getStorageGroupPath(groupId, {database}) : undefined, }; breadcrumbs.push(lastItem); @@ -206,9 +204,9 @@ const getVDiskBreadcrumbs: GetBreadcrumbs = (options, q }; const getTabletBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { - const {tabletId, tabletType, tenantName} = options; + const {tabletId, tabletType, database} = options; - const breadcrumbs = tenantName + const breadcrumbs = database ? getTenantBreadcrumbs(options, query) : getClusterBreadcrumbs(options, query); diff --git a/src/containers/Heatmap/Heatmap.tsx b/src/containers/Heatmap/Heatmap.tsx index 1a3897071a..4e988d2215 100644 --- a/src/containers/Heatmap/Heatmap.tsx +++ b/src/containers/Heatmap/Heatmap.tsx @@ -23,10 +23,11 @@ const COLORS_RANGE = getColorRange(COLORS_RANGE_SIZE); interface HeatmapProps { database: string; + databaseFullPath: string; path: string; } -export const Heatmap = ({path, database}: HeatmapProps) => { +export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => { const dispatch = useTypedDispatch(); const itemsContainer = React.createRef(); @@ -34,7 +35,7 @@ export const Heatmap = ({path, database}: HeatmapProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = heatmapApi.useGetHeatmapTabletsInfoQuery( - {path, database}, + {path, database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); diff --git a/src/containers/Node/Node.tsx b/src/containers/Node/Node.tsx index 66d87e70ac..6de74259f0 100644 --- a/src/containers/Node/Node.tsx +++ b/src/containers/Node/Node.tsx @@ -88,23 +88,23 @@ export function Node() { return {activeTab: actualActiveTab, nodeTabs: actualNodeTabs}; }, [isStorageNode, isDiskPagesAvailable, activeTabId, threadsQuantity]); - const tenantName = node?.Tenants?.[0] || tenantNameFromQuery?.toString(); - const database = tenantNameFromQuery?.toString(); + const databaseName = node?.Tenants?.[0]; + React.useEffect(() => { // Dispatch only if loaded to get correct node role if (!isLoading) { dispatch( setHeaderBreadcrumbs('node', { - tenantName, database, + databaseName, nodeRole: isStorageNode ? 'Storage' : 'Compute', nodeId, }), ); } - }, [dispatch, tenantName, nodeId, isLoading, isStorageNode, database]); + }, [dispatch, database, nodeId, isLoading, isStorageNode, databaseName]); return (
@@ -116,7 +116,7 @@ export function Node() { {nodeId ? ( {tabs.map(({id, title}) => { - const path = getDefaultNodePath( - nodeId, - {database: tenantName}, - id as NodeTab, - ); + const path = getDefaultNodePath(nodeId, {database}, id as NodeTab); return ( @@ -234,7 +230,7 @@ function NodePageContent({ case 'storage': { return ( ); diff --git a/src/containers/Nodes/Nodes.tsx b/src/containers/Nodes/Nodes.tsx index b12fd2e699..b514bd585f 100644 --- a/src/containers/Nodes/Nodes.tsx +++ b/src/containers/Nodes/Nodes.tsx @@ -30,6 +30,7 @@ import './Nodes.scss'; export interface NodesProps { path?: string; database?: string; + databaseFullPath?: string; scrollContainerRef: React.RefObject; additionalNodesProps?: AdditionalNodesProps; withPeerRoleFilter?: boolean; @@ -43,6 +44,7 @@ export interface NodesProps { export function Nodes({ path, database, + databaseFullPath, scrollContainerRef, additionalNodesProps, withPeerRoleFilter, @@ -96,6 +98,7 @@ export function Nodes({ return ( { return { path, + databaseFullPath, database, searchValue, problemFilter, @@ -58,6 +61,7 @@ export function NodesTable({ }; }, [ path, + databaseFullPath, database, searchValue, problemFilter, diff --git a/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx b/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx index bf027248fa..b02eaf2748 100644 --- a/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx +++ b/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import {isNil} from 'lodash'; + import {ResponseError} from '../../../components/Errors/ResponseError'; import type {Column} from '../../../components/PaginatedTable'; import {PaginatedTableWithLayout} from '../../../components/PaginatedTable/PaginatedTableWithLayout'; @@ -24,6 +26,7 @@ interface NodeGroupProps { count: number; isExpanded: boolean; path?: string; + databaseFullPath?: string; database?: string; searchValue: string; peerRoleFilter?: NodesPeerRole; @@ -39,6 +42,7 @@ const NodeGroup = React.memo(function NodeGroup({ isExpanded, path, database, + databaseFullPath, searchValue, peerRoleFilter, groupByParam, @@ -60,6 +64,7 @@ const NodeGroup = React.memo(function NodeGroup({ table={ ; withPeerRoleFilter?: boolean; columns: Column[]; @@ -94,6 +100,7 @@ interface GroupedNodesComponentProps { export function GroupedNodesComponent({ path, + databaseFullPath, database, scrollContainerRef, withPeerRoleFilter, @@ -117,9 +124,16 @@ export function GroupedNodesComponent({ requiredColumnsIds, ); + const schemaPathParam = React.useMemo(() => { + if (!isNil(path) && !isNil(databaseFullPath)) { + return {path, databaseFullPath}; + } + return undefined; + }, [path, databaseFullPath]); + const {currentData, isFetching, error} = nodesApi.useGetNodesQuery( { - path, + path: schemaPathParam, database, filter: searchValue, filter_peer_role: peerRoleFilter, @@ -163,6 +177,7 @@ export function GroupedNodesComponent({ count={count} isExpanded={isExpanded} path={path} + databaseFullPath={databaseFullPath} database={database} searchValue={searchValue} peerRoleFilter={peerRoleFilter} diff --git a/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx b/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx index 820ff9b5b3..0f9ee68f63 100644 --- a/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx +++ b/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx @@ -17,6 +17,7 @@ import {NodesControlsWithTableState} from './NodesControlsWithTableState'; interface NodesComponentProps { path?: string; database?: string; + databaseFullPath?: string; scrollContainerRef: React.RefObject; withPeerRoleFilter?: boolean; columns: Column[]; @@ -29,6 +30,7 @@ interface NodesComponentProps { export function NodesComponent({ path, database, + databaseFullPath, scrollContainerRef, withPeerRoleFilter, columns, @@ -67,6 +69,7 @@ export function NodesComponent({ ; withPeerRoleFilter?: boolean; diff --git a/src/containers/Nodes/getNodes.ts b/src/containers/Nodes/getNodes.ts index 55c6b50bd5..16e0d01db7 100644 --- a/src/containers/Nodes/getNodes.ts +++ b/src/containers/Nodes/getNodes.ts @@ -1,3 +1,5 @@ +import {isNil} from 'lodash'; + import type {FetchData} from '../../components/PaginatedTable'; import { NODES_COLUMNS_TO_DATA_FIELDS, @@ -20,6 +22,7 @@ export const getNodes: FetchData< const {sortOrder, columnId} = sortParams ?? {}; const { path, + databaseFullPath, database, searchValue, problemFilter, @@ -34,13 +37,16 @@ export const getNodes: FetchData< const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS); + const schemePathParam = + !isNil(path) && !isNil(databaseFullPath) ? {path, databaseFullPath} : undefined; + const response = await window.api.viewer.getNodes({ type, storage, limit, offset, sort, - path, + path: schemePathParam, database, filter: searchValue, problems_only: getProblemParamValue(problemFilter), diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx index 620655c6ae..ccba0df9f7 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.tsx +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -37,7 +37,7 @@ export function StorageGroupPage() { const [{groupId}] = useQueryParams({groupId: StringParam}); React.useEffect(() => { - dispatch(setHeaderBreadcrumbs('storageGroup', {groupId, tenantName: database, database})); + dispatch(setHeaderBreadcrumbs('storageGroup', {groupId, database})); }, [dispatch, groupId, database]); const [autoRefreshInterval] = useAutoRefreshInterval(); diff --git a/src/containers/Tablet/Tablet.tsx b/src/containers/Tablet/Tablet.tsx index 2ec315016e..f9403525b2 100644 --- a/src/containers/Tablet/Tablet.tsx +++ b/src/containers/Tablet/Tablet.tsx @@ -66,6 +66,7 @@ export function Tablet() { const [{database: queryDatabase, clusterName: queryClusterName, followerId: queryFollowerId}] = useQueryParams(tabletPageQueryParams); + const database = queryDatabase?.toString(); const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = tabletApi.useGetTabletQuery( {id, database: queryDatabase ?? undefined, followerId: queryFollowerId ?? undefined}, @@ -75,31 +76,26 @@ export function Tablet() { const loading = isFetching && currentData === undefined; const {data: tablet = {}, history = []} = currentData || {}; - const {currentData: tenantPath} = tabletApi.useGetTabletDescribeQuery( - tablet.TenantId - ? {tenantId: tablet.TenantId, database: queryDatabase?.toString()} - : skipToken, + const {currentData: databaseFullPath} = tabletApi.useGetTabletDescribeQuery( + tablet.TenantId ? {tenantId: tablet.TenantId, database} : skipToken, ); - const database = (tenantPath || queryDatabase) ?? undefined; - const tabletType = tablet.Type; React.useEffect(() => { dispatch( setHeaderBreadcrumbs('tablet', { - tenantName: queryDatabase ?? undefined, - database: queryDatabase ?? undefined, + database, tabletId: id, tabletType, }), ); - }, [dispatch, queryDatabase, id, tabletType]); + }, [dispatch, database, id, tabletType]); const {Leader} = tablet; const metaItems: string[] = []; - if (database) { - metaItems.push(`${i18n('tablet.meta-database')}: ${database}`); + if (databaseFullPath) { + metaItems.push(`${i18n('tablet.meta-database')}: ${databaseFullPath}`); } // Add "Tablet" instead of tablet type to metadata metaItems.push(i18n('tablet.header')); @@ -111,7 +107,7 @@ export function Tablet() { {`${id} — ${i18n('tablet.header')} — ${ - database || queryClusterName || CLUSTER_DEFAULT_TITLE + databaseFullPath || queryClusterName || CLUSTER_DEFAULT_TITLE }`} diff --git a/src/containers/Tablets/Tablets.tsx b/src/containers/Tablets/Tablets.tsx index 2e8fea785d..bd64b8fe36 100644 --- a/src/containers/Tablets/Tablets.tsx +++ b/src/containers/Tablets/Tablets.tsx @@ -1,4 +1,7 @@ +import React from 'react'; + import {skipToken} from '@reduxjs/toolkit/query'; +import {isNil} from 'lodash'; import {selectTabletsWithFqdn, tabletsApi} from '../../store/reducers/tablets'; import type {TabletsApiRequestParams} from '../../types/store/tablets'; @@ -10,6 +13,7 @@ import {TabletsTable} from './TabletsTable'; interface TabletsProps { path?: string; database?: string; + databaseFullPath?: string; nodeId?: string | number; /** * Show/hide dead tablets: shown in pages needing complete statistics, @@ -19,16 +23,30 @@ interface TabletsProps { scrollContainerRef: React.RefObject; } -export function Tablets({nodeId, path, database, onlyActive, scrollContainerRef}: TabletsProps) { +export function Tablets({ + nodeId, + path, + database, + databaseFullPath, + onlyActive, + scrollContainerRef, +}: TabletsProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); let params: TabletsApiRequestParams = {}; const filter = onlyActive ? `(State!=Dead)` : undefined; + const schemaPathParam = React.useMemo(() => { + if (!isNil(path) && !isNil(databaseFullPath)) { + return {path, databaseFullPath}; + } + return undefined; + }, [path, databaseFullPath]); + if (valueIsDefined(nodeId)) { params = {nodeId, database, filter}; - } else if (path) { - params = {path, database, filter}; + } else if (schemaPathParam) { + params = {path: schemaPathParam, database, filter}; } const {isLoading, error} = tabletsApi.useGetTabletsInfoQuery( Object.keys(params).length === 0 ? skipToken : params, diff --git a/src/containers/Tenant/Diagnostics/AccessRights/AccessRights.tsx b/src/containers/Tenant/Diagnostics/AccessRights/AccessRights.tsx index 98d101f1de..7feb0c0e71 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/AccessRights.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/AccessRights.tsx @@ -19,12 +19,12 @@ import {block} from './shared'; import './AccessRights.scss'; export function AccessRights() { - const {path, database} = useCurrentSchema(); + const {path, database, databaseFullPath} = useCurrentSchema(); const editable = useEditAccessAvailable(); const [autoRefreshInterval] = useAutoRefreshInterval(); const dialect = useAclSyntax(); const {isFetching, currentData, error} = schemaAclApi.useGetSchemaAclQuery( - {path, database, dialect}, + {path, database, dialect, databaseFullPath}, { pollingInterval: autoRefreshInterval, }, diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx index 9b6e6bb118..62052f63db 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx @@ -15,6 +15,7 @@ const CHANGE_OWNER_DIALOG = 'change-owner-dialog'; interface GetChangeOwnerDialogProps { path: string; database: string; + databaseFullPath: string; } export async function getChangeOwnerDialog({ @@ -29,7 +30,7 @@ export async function getChangeOwnerDialog({ } const ChangeOwnerDialogNiceModal = NiceModal.create( - ({path, database}: GetChangeOwnerDialogProps) => { + ({path, database, databaseFullPath}: GetChangeOwnerDialogProps) => { const modal = NiceModal.useModal(); const handleClose = () => { @@ -46,6 +47,7 @@ const ChangeOwnerDialogNiceModal = NiceModal.create( open={modal.visible} path={path} database={database} + databaseFullPath={databaseFullPath} /> ); }, @@ -58,7 +60,13 @@ interface ChangeOwnerDialogProps extends GetChangeOwnerDialogProps { onClose: () => void; } -function ChangeOwnerDialog({open, onClose, path, database}: ChangeOwnerDialogProps) { +function ChangeOwnerDialog({ + open, + onClose, + path, + database, + databaseFullPath, +}: ChangeOwnerDialogProps) { const [newOwner, setNewOwner] = React.useState(''); const [requestErrorMessage, setRequestErrorMessage] = React.useState(''); const [updateOwner, updateOwnerResponse] = schemaAclApi.useUpdateAccessMutation(); @@ -69,7 +77,13 @@ function ChangeOwnerDialog({open, onClose, path, database}: ChangeOwnerDialogPro setRequestErrorMessage(''); }; const onApply = () => { - updateOwner({path, database, dialect, rights: {ChangeOwnership: {Subject: newOwner}}}) + updateOwner({ + path, + database, + databaseFullPath, + dialect, + rights: {ChangeOwnership: {Subject: newOwner}}, + }) .unwrap() .then(() => { onClose(); diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/Owner.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/Owner.tsx index a535e2b057..43b235947e 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/Owner.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/Owner.tsx @@ -13,9 +13,11 @@ import {getChangeOwnerDialog} from './ChangeOwnerDialog'; export function Owner() { const editable = useEditAccessAvailable(); - const {path, database} = useCurrentSchema(); + const {path, database, databaseFullPath} = useCurrentSchema(); const dialect = useAclSyntax(); - const owner = useTypedSelector((state) => selectSchemaOwner(state, path, database, dialect)); + const owner = useTypedSelector((state) => + selectSchemaOwner(state, path, database, databaseFullPath, dialect), + ); if (!owner) { return null; @@ -43,7 +45,9 @@ export function Owner() { diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/Actions.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/Actions.tsx index 40d9a28e92..cecd077593 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/Actions.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/Actions.tsx @@ -49,15 +49,15 @@ function GrantRightsToSubject({subject}: ActionProps) { } function RevokeAllRights({subject}: ActionProps) { - const {path, database} = useCurrentSchema(); + const {path, database, databaseFullPath} = useCurrentSchema(); const dialect = useAclSyntax(); const subjectExplicitRights = useTypedSelector((state) => - selectSubjectExplicitRights(state, subject, path, database, dialect), + selectSubjectExplicitRights(state, subject, path, database, databaseFullPath, dialect), ); const noRightsToRevoke = subjectExplicitRights.length === 0; const handleClick = async () => { - await getRevokeAllRightsDialog({path, database, subject}); + await getRevokeAllRightsDialog({path, database, databaseFullPath, subject}); }; return ( diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx index 90635282cb..fbdc3b7378 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx @@ -18,6 +18,7 @@ const REVOKE_ALL_RIGHTS_DIALOG = 'revoke-all-rights-dialog'; interface GetRevokeAllRightsDialogProps { path: string; database: string; + databaseFullPath: string; subject: string; } @@ -35,7 +36,7 @@ export async function getRevokeAllRightsDialog({ } const RevokeAllRightsDialogNiceModal = NiceModal.create( - ({path, database, subject}: GetRevokeAllRightsDialogProps) => { + ({path, database, subject, databaseFullPath}: GetRevokeAllRightsDialogProps) => { const modal = NiceModal.useModal(); const handleClose = () => { @@ -53,6 +54,7 @@ const RevokeAllRightsDialogNiceModal = NiceModal.create( path={path} database={database} subject={subject} + databaseFullPath={databaseFullPath} /> ); }, @@ -70,11 +72,12 @@ function RevokeAllRightsDialog({ onClose, path, database, + databaseFullPath, subject, }: RevokeAllRightsDialogProps) { const dialect = useAclSyntax(); const subjectExplicitRights = useTypedSelector((state) => - selectSubjectExplicitRights(state, subject, path, database, dialect), + selectSubjectExplicitRights(state, subject, path, database, databaseFullPath, dialect), ); const [requestErrorMessage, setRequestErrorMessage] = React.useState(''); @@ -84,6 +87,7 @@ function RevokeAllRightsDialog({ removeAccess({ path, database, + databaseFullPath, dialect, rights: { RemoveAccess: [ diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RightsTable.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RightsTable.tsx index 30bbc3c634..418d18458d 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RightsTable.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RightsTable.tsx @@ -15,9 +15,11 @@ const RIGHT_TABLE_COLUMNS_WIDTH_LS_KEY = 'right-table-columns-width'; const AccessRightsTableSettings: Settings = {...DEFAULT_TABLE_SETTINGS, dynamicRender: false}; export function RightsTable() { - const {path, database} = useCurrentSchema(); + const {path, database, databaseFullPath} = useCurrentSchema(); const dialect = useAclSyntax(); - const data = useTypedSelector((state) => selectPreparedRights(state, path, database, dialect)); + const data = useTypedSelector((state) => + selectPreparedRights(state, path, database, databaseFullPath, dialect), + ); return ( { +export const Consumers = ({path, database, type, databaseFullPath}: ConsumersProps) => { const isCdcStream = isCdcStreamEntityType(type); const [searchValue, setSearchValue] = React.useState(''); const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = topicApi.useGetTopicQuery( - {path, database}, + {path, database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); const loading = isFetching && currentData === undefined; const consumers = useTypedSelector((state) => - selectPreparedConsumersData(state, path, database), + selectPreparedConsumersData(state, path, database, databaseFullPath), + ); + const topic = useTypedSelector((state) => + selectPreparedTopicStats(state, path, database, databaseFullPath), ); - const topic = useTypedSelector((state) => selectPreparedTopicStats(state, path, database)); const dataToRender = React.useMemo(() => { if (!consumers) { diff --git a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx index 92eb62eb72..92c3eeb262 100644 --- a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx +++ b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx @@ -15,13 +15,14 @@ const b = cn('ydb-describe'); interface IDescribeProps { path: string; database: string; + databaseFullPath: string; } -const Describe = ({path, database}: IDescribeProps) => { +const Describe = ({path, database, databaseFullPath}: IDescribeProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = overviewApi.useGetOverviewQuery( - {path, database}, + {path, database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); diff --git a/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx b/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx index 53871adde5..7bf9c83645 100644 --- a/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +++ b/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx @@ -9,8 +9,9 @@ import './DetailedOverview.scss'; interface DetailedOverviewProps { type?: EPathType; className?: string; - tenantName: string; + database: string; path: string; + databaseFullPath: string; additionalTenantProps?: AdditionalTenantsProps; additionalNodesProps?: AdditionalNodesProps; } @@ -18,13 +19,15 @@ interface DetailedOverviewProps { const b = cn('kv-detailed-overview'); function DetailedOverview(props: DetailedOverviewProps) { - const {type, tenantName, path, additionalTenantProps, additionalNodesProps} = props; + const {type, database, databaseFullPath, path, additionalTenantProps, additionalNodesProps} = + props; const renderTenantOverview = () => { return (
@@ -32,14 +35,19 @@ function DetailedOverview(props: DetailedOverviewProps) { ); }; - const isTenant = tenantName === path; + const isTenant = databaseFullPath === path; return (
{isTenant ? ( renderTenantOverview() ) : ( - + )}
); diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index e4ffe64d58..c7daa945b5 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -59,7 +59,9 @@ function Diagnostics(props: DiagnosticsProps) { const getDiagnosticsPageLink = useDiagnosticsPageLinkGetter(); - const {controlPlane, databaseType} = useTenantBaseInfo(isDatabaseEntityType(type) ? path : ''); + const {controlPlane, databaseType} = useTenantBaseInfo( + isDatabaseEntityType(type) ? database : '', + ); const hasFeatureFlags = useFeatureFlagsAvailable(); const hasTopicData = useTopicDataAvailable(); @@ -91,23 +93,32 @@ function Diagnostics(props: DiagnosticsProps) { return ( ); } case TENANT_DIAGNOSTICS_TABS_IDS.schema: { - return ; + return ( + + ); } case TENANT_DIAGNOSTICS_TABS_IDS.topQueries: { - return ; + return ; } case TENANT_DIAGNOSTICS_TABS_IDS.topShards: { return ( @@ -117,6 +128,7 @@ function Diagnostics(props: DiagnosticsProps) { return ( + ); } case TENANT_DIAGNOSTICS_TABS_IDS.storage: { @@ -138,6 +155,7 @@ function Diagnostics(props: DiagnosticsProps) { return ( ; + return ( + + ); } case TENANT_DIAGNOSTICS_TABS_IDS.hotKeys: { - return ; + return ( + + ); } case TENANT_DIAGNOSTICS_TABS_IDS.graph: { - return ; + return ( + + ); } case TENANT_DIAGNOSTICS_TABS_IDS.consumers: { - return ; + return ( + + ); } case TENANT_DIAGNOSTICS_TABS_IDS.partitions: { - return ; + return ( + + ); } case TENANT_DIAGNOSTICS_TABS_IDS.topicData: { return ( diff --git a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx index f0159fb88b..2bbaf89e40 100644 --- a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx +++ b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx @@ -55,17 +55,23 @@ const getHotKeysColumns = (keyColumnsIds: string[] = []): Column[] => { interface HotKeysProps { database: string; + databaseFullPath: string; path: string; } -export function HotKeys({path, database}: HotKeysProps) { - const {currentData: data, isFetching, error} = hotKeysApi.useGetHotKeysQuery({path, database}); +export function HotKeys({path, database, databaseFullPath}: HotKeysProps) { + const { + currentData: data, + isFetching, + error, + } = hotKeysApi.useGetHotKeysQuery({path, database, databaseFullPath}); const loading = isFetching && data === undefined; const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData: schemaData, isLoading: schemaLoading} = overviewApi.useGetOverviewQuery( { path, database, + databaseFullPath, }, { pollingInterval: autoRefreshInterval, diff --git a/src/containers/Tenant/Diagnostics/Network/Network.tsx b/src/containers/Tenant/Diagnostics/Network/Network.tsx index f42934f128..f1281ac752 100644 --- a/src/containers/Tenant/Diagnostics/Network/Network.tsx +++ b/src/containers/Tenant/Diagnostics/Network/Network.tsx @@ -28,9 +28,10 @@ import './Network.scss'; const b = cn('network'); interface NetworkProps { - tenantName: string; + database: string; + databaseFullPath: string; } -export function Network({tenantName}: NetworkProps) { +export function Network({database, databaseFullPath}: NetworkProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const filter = useTypedSelector(selectProblemFilter); const dispatch = useTypedDispatch(); @@ -39,9 +40,12 @@ export function Network({tenantName}: NetworkProps) { const [showId, setShowId] = React.useState(false); const [showRacks, setShowRacks] = React.useState(false); - const {currentData, isFetching, error} = networkApi.useGetNetworkInfoQuery(tenantName, { - pollingInterval: autoRefreshInterval, - }); + const {currentData, isFetching, error} = networkApi.useGetNetworkInfoQuery( + {database, databaseFullPath}, + { + pollingInterval: autoRefreshInterval, + }, + ); const loading = isFetching && currentData === undefined; if (loading) { diff --git a/src/containers/Tenant/Diagnostics/Network/NetworkWrapper.tsx b/src/containers/Tenant/Diagnostics/Network/NetworkWrapper.tsx index 061329e49e..25c2602525 100644 --- a/src/containers/Tenant/Diagnostics/Network/NetworkWrapper.tsx +++ b/src/containers/Tenant/Diagnostics/Network/NetworkWrapper.tsx @@ -9,11 +9,13 @@ import {Network} from './Network'; interface NetworkWrapperProps extends Pick { database: string; + databaseFullPath: string; } export function NetworkWrapper({ database, path, + databaseFullPath, scrollContainerRef, additionalNodesProps, }: NetworkWrapperProps) { @@ -25,6 +27,7 @@ export function NetworkWrapper({ return ( ; + return ; }; return {renderContent()}; diff --git a/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx index d4d984ec4a..b0e668fab7 100644 --- a/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx @@ -33,13 +33,14 @@ const prepareChangefeedInfo = (changefeedData?: TEvDescribeSchemeResult): Array< interface ChangefeedProps { path: string; + databaseFullPath: string; database: string; data?: TEvDescribeSchemeResult; topic?: TEvDescribeSchemeResult; } /** Displays overview for CDCStream EPathType */ -export const ChangefeedInfo = ({path, database, data}: ChangefeedProps) => { +export const ChangefeedInfo = ({path, database, data, databaseFullPath}: ChangefeedProps) => { const entityName = getEntityName(data?.PathDescription); if (!data) { @@ -49,7 +50,7 @@ export const ChangefeedInfo = ({path, database, data}: ChangefeedProps) => { return (
- +
); }; diff --git a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx index 57c6169762..812878a749 100644 --- a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx @@ -20,13 +20,14 @@ interface OverviewProps { type?: EPathType; path: string; database: string; + databaseFullPath: string; } -function Overview({type, path, database}: OverviewProps) { +function Overview({type, path, database, databaseFullPath}: OverviewProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = overviewApi.useGetOverviewQuery( - {path, database}, + {path, database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); @@ -47,17 +48,32 @@ function Overview({type, path, database}: OverviewProps) { [EPathType.EPathTypeColumnStore]: undefined, [EPathType.EPathTypeColumnTable]: undefined, [EPathType.EPathTypeCdcStream]: () => ( - + ), [EPathType.EPathTypePersQueueGroup]: () => ( - + ), [EPathType.EPathTypeExternalTable]: () => , [EPathType.EPathTypeExternalDataSource]: () => , [EPathType.EPathTypeView]: () => , [EPathType.EPathTypeReplication]: () => , [EPathType.EPathTypeTransfer]: () => ( - + ), }; diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx index 314ca4f606..b4af76fc53 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx @@ -8,11 +8,12 @@ import {prepareTopicSchemaInfo} from '../utils'; interface TopicInfoProps { path: string; database: string; + databaseFullPath: string; data?: TEvDescribeSchemeResult; } /** Displays overview for PersQueueGroup EPathType */ -export const TopicInfo = ({data, path, database}: TopicInfoProps) => { +export const TopicInfo = ({data, path, database, databaseFullPath}: TopicInfoProps) => { const entityName = getEntityName(data?.PathDescription); if (!data) { @@ -25,7 +26,7 @@ export const TopicInfo = ({data, path, database}: TopicInfoProps) => { return null; } - return ; + return ; }; return ( diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx index 2eb7d3451f..018b6eb865 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx @@ -71,14 +71,22 @@ const prepareBytesWrittenInfo = (data: IPreparedTopicStats): Array { +interface TopicStatsProps { + path: string; + database: string; + databaseFullPath: string; +} + +export const TopicStats = ({path, database, databaseFullPath}: TopicStatsProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = topicApi.useGetTopicQuery( - {path, database}, + {path, database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); const loading = isFetching && currentData === undefined; - const data = useTypedSelector((state) => selectPreparedTopicStats(state, path, database)); + const data = useTypedSelector((state) => + selectPreparedTopicStats(state, path, database, databaseFullPath), + ); if (loading) { return ( diff --git a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx index f32c263762..bad33b63a6 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx @@ -14,11 +14,12 @@ import i18n from './i18n'; interface TransferProps { path: string; database: string; + databaseFullPath: string; data?: TEvDescribeSchemeResult; } /** Displays overview for Transfer EPathType */ -export function TransferInfo({path, database, data}: TransferProps) { +export function TransferInfo({path, database, data, databaseFullPath}: TransferProps) { const entityName = getEntityName(data?.PathDescription); if (!data) { @@ -29,7 +30,10 @@ export function TransferInfo({path, database, data}: TransferProps) { ); } - const {data: replicationData} = replicationApi.useGetReplicationQuery({path, database}, {}); + const {data: replicationData} = replicationApi.useGetReplicationQuery( + {path, database, databaseFullPath}, + {}, + ); const transferItems = prepareTransferItems(data, replicationData); return ( diff --git a/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx b/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx index 02b1a2ac06..221369b14c 100644 --- a/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +++ b/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx @@ -32,23 +32,26 @@ export const b = cn('ydb-diagnostics-partitions'); interface PartitionsProps { path: string; database: string; + databaseFullPath: string; } -export const Partitions = ({path, database}: PartitionsProps) => { +export const Partitions = ({path, database, databaseFullPath}: PartitionsProps) => { const dispatch = useTypedDispatch(); const [partitionsToRender, setPartitionsToRender] = React.useState< PreparedPartitionDataWithHosts[] >([]); - const consumers = useTypedSelector((state) => selectConsumersNames(state, path, database)); + const consumers = useTypedSelector((state) => + selectConsumersNames(state, path, database, databaseFullPath), + ); const [autoRefreshInterval] = useAutoRefreshInterval(); const {selectedConsumer} = useTypedSelector((state) => state.partitions); const { currentData: topicData, isFetching: topicIsFetching, error: topicError, - } = topicApi.useGetTopicQuery({path, database}); + } = topicApi.useGetTopicQuery({path, database, databaseFullPath}); const topicLoading = topicIsFetching && topicData === undefined; const { currentData: nodesData, @@ -62,7 +65,9 @@ export const Partitions = ({path, database}: PartitionsProps) => { const [columns, columnsIdsForSelector] = useGetPartitionsColumns(selectedConsumer); - const params = topicLoading ? skipToken : {path, database, consumerName: selectedConsumer}; + const params = topicLoading + ? skipToken + : {path, database, consumerName: selectedConsumer, databaseFullPath}; const { currentData: partitionsData, isFetching: partitionsIsFetching, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx index 8827205d7f..8c916b7e1a 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx @@ -16,7 +16,7 @@ import './HealthcheckPreview.scss'; const b = cn('ydb-healthcheck-preview'); interface HealthcheckPreviewProps { - tenantName: string; + database: string; active?: boolean; } @@ -29,7 +29,7 @@ const checkResultToAlertTheme: Record = { }; export function HealthcheckPreview(props: HealthcheckPreviewProps) { - const {tenantName} = props; + const {database} = props; const [autoRefreshInterval] = useAutoRefreshInterval(); const {handleShowHealthcheckChange} = useTenantQueryParams(); @@ -39,7 +39,7 @@ export function HealthcheckPreview(props: HealthcheckPreviewProps) { isFetching, error, } = healthcheckApi.useGetHealthcheckInfoQuery( - {database: tenantName}, + {database}, { pollingInterval: autoRefreshInterval, }, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index d59164678a..a317ce2f1e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -17,14 +17,14 @@ import {TopShards} from './TopShards'; import {cpuDashboardConfig} from './cpuDashboardConfig'; interface TenantCpuProps { - tenantName: string; - databaseFullPath?: string; + database: string; + databaseFullPath: string; additionalNodesProps?: AdditionalNodesProps; databaseType?: ETenantType; } export function TenantCpu({ - tenantName, + database, additionalNodesProps, databaseType, databaseFullPath, @@ -42,13 +42,13 @@ export function TenantCpu({ {!isServerless && ( <> - + @@ -57,7 +57,7 @@ export function TenantCpu({ allEntitiesLink={allNodesLink} > @@ -65,8 +65,8 @@ export function TenantCpu({ )} @@ -77,7 +77,7 @@ export function TenantCpu({ dispatch(setTopQueriesFilters({from: undefined, to: undefined})) } > - + ); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx index 49bb88f67e..fd621c52e6 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx @@ -42,20 +42,20 @@ function getTopNodesByCpuColumns( } interface TopNodesByCpuProps { - tenantName: string; + database: string; additionalNodesProps?: AdditionalNodesProps; } -export function TopNodesByCpu({tenantName, additionalNodesProps}: TopNodesByCpuProps) { +export function TopNodesByCpu({database, additionalNodesProps}: TopNodesByCpuProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const [columns, fieldsRequired] = getTopNodesByCpuColumns({ getNodeRef: additionalNodesProps?.getNodeRef, - database: tenantName, + database, }); const {currentData, isFetching, error} = nodesApi.useGetNodesQuery( { - tenant: tenantName, + tenant: database, type: 'any', sort: '-CPU', limit: TENANT_OVERVIEW_TABLES_LIMIT, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx index f8aa7108f0..a59345c327 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx @@ -47,20 +47,20 @@ function getTopNodesByLoadColumns( } interface TopNodesByLoadProps { - tenantName: string; + database: string; additionalNodesProps?: AdditionalNodesProps; } -export function TopNodesByLoad({tenantName, additionalNodesProps}: TopNodesByLoadProps) { +export function TopNodesByLoad({database, additionalNodesProps}: TopNodesByLoadProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const [columns, fieldsRequired] = getTopNodesByLoadColumns({ getNodeRef: additionalNodesProps?.getNodeRef, - database: tenantName, + database, }); const {currentData, isFetching, error} = nodesApi.useGetNodesQuery( { - tenant: tenantName, + tenant: database, type: 'any', sort: '-LoadAverage', limit: TENANT_OVERVIEW_TABLES_LIMIT, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx index f33b1dd43f..9f849fc46a 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx @@ -12,19 +12,19 @@ import {useTopQueriesSort} from '../../TopQueries/hooks/useTopQueriesSort'; import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout'; interface TopQueriesProps { - tenantName: string; + database: string; } const columns = getTenantOverviewTopQueriesColumns(); -export function TopQueries({tenantName}: TopQueriesProps) { +export function TopQueries({database}: TopQueriesProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const {backendSort} = useTopQueriesSort(); const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery( { - database: tenantName, + database, timeFrame: 'hour', limit: TENANT_OVERVIEW_TABLES_LIMIT, sortOrder: backendSort, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx index 2a6b219d1a..af04dab8a5 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx @@ -8,25 +8,18 @@ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout'; const columnsIds: TopShardsColumnId[] = ['TabletId', 'Path', 'CPUCores']; interface TopShardsProps { - tenantName: string; + database: string; path: string; - databaseFullPath?: string; + databaseFullPath: string; } -export const TopShards = ({tenantName, path, databaseFullPath = tenantName}: TopShardsProps) => { +export const TopShards = ({database, path, databaseFullPath}: TopShardsProps) => { const ShardsTable = useComponent('ShardsTable'); const [autoRefreshInterval] = useAutoRefreshInterval(); - let normalizedPath = path; - if (tenantName !== databaseFullPath) { - //tenantName may be database full path or database id. If it is database id, we must remove it from object's path and add database full path instead - const shrinkedPath = path.startsWith(tenantName) ? path.slice(tenantName.length) : path; - normalizedPath = databaseFullPath + shrinkedPath; - } - const {currentData, isFetching, error} = topShardsApi.useGetTopShardsQuery( - {database: tenantName, path: normalizedPath, databaseFullPath}, + {database, path, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); @@ -41,8 +34,8 @@ export const TopShards = ({tenantName, path, databaseFullPath = tenantName}: Top > diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx index ec39172ac0..f91a3df7c6 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx @@ -17,7 +17,7 @@ import {memoryDashboardConfig} from './memoryDashboardConfig'; import './TenantMemory.scss'; interface TenantMemoryProps { - tenantName: string; + database: string; memoryStats?: TMemoryStats; memoryUsed?: string; memoryLimit?: string; @@ -25,12 +25,7 @@ interface TenantMemoryProps { const b = cn('tenant-memory'); -export function TenantMemory({ - tenantName, - memoryStats, - memoryUsed, - memoryLimit, -}: TenantMemoryProps) { +export function TenantMemory({database, memoryStats, memoryUsed, memoryLimit}: TenantMemoryProps) { const query = useSearchQuery(); const renderMemoryDetails = () => { if (memoryStats) { @@ -64,7 +59,7 @@ export function TenantMemory({ return (
- + {renderMemoryDetails()} - +
); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx index 754f8dddb2..1f95e9b98c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx @@ -48,20 +48,20 @@ function getTopNodesByMemoryColumns( } interface TopNodesByMemoryProps { - tenantName: string; + database: string; additionalNodesProps?: AdditionalNodesProps; } -export function TopNodesByMemory({tenantName, additionalNodesProps}: TopNodesByMemoryProps) { +export function TopNodesByMemory({database, additionalNodesProps}: TopNodesByMemoryProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const [columns, fieldsRequired] = getTopNodesByMemoryColumns({ getNodeRef: additionalNodesProps?.getNodeRef, - database: tenantName, + database, }); const {currentData, isFetching, error} = nodesApi.useGetNodesQuery( { - tenant: tenantName, + tenant: database, type: 'any', tablets: true, sort: '-Memory', diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TenantNetwork.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TenantNetwork.tsx index 74843f3830..e66fa3c43e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TenantNetwork.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TenantNetwork.tsx @@ -17,11 +17,11 @@ import './TenantNetwork.scss'; const b = cn('tenant-network'); interface TenantNetworkProps { - tenantName: string; + database: string; additionalNodesProps?: AdditionalNodesProps; } -export function TenantNetwork({tenantName, additionalNodesProps}: TenantNetworkProps) { +export function TenantNetwork({database, additionalNodesProps}: TenantNetworkProps) { const query = useSearchQuery(); const [networkTableEnabled] = useSetting(ENABLE_NETWORK_TABLE_KEY); @@ -37,16 +37,10 @@ export function TenantNetwork({tenantName, additionalNodesProps}: TenantNetworkP return ( - + - + ); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesByPing.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesByPing.tsx index 48edb030a0..5fd9739410 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesByPing.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesByPing.tsx @@ -13,20 +13,20 @@ import i18n from '../i18n'; import {getTopNodesByPingColumns} from './columns'; interface TopNodesByPingProps { - tenantName: string; + database: string; additionalNodesProps?: AdditionalNodesProps; } -export function TopNodesByPing({tenantName, additionalNodesProps}: TopNodesByPingProps) { +export function TopNodesByPing({database, additionalNodesProps}: TopNodesByPingProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const [columns, fieldsRequired] = getTopNodesByPingColumns({ getNodeRef: additionalNodesProps?.getNodeRef, - database: tenantName, + database, }); const {currentData, isFetching, error} = nodesApi.useGetNodesQuery( { - tenant: tenantName, + tenant: database, type: 'any', sort: '-PingTime', limit: TENANT_OVERVIEW_TABLES_LIMIT, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesBySkew.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesBySkew.tsx index 345938fb15..de8d0e14d2 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesBySkew.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantNetwork/TopNodesBySkew.tsx @@ -13,20 +13,20 @@ import i18n from '../i18n'; import {getTopNodesBySkewColumns} from './columns'; interface TopNodesBySkewProps { - tenantName: string; + database: string; additionalNodesProps?: AdditionalNodesProps; } -export function TopNodesBySkew({tenantName, additionalNodesProps}: TopNodesBySkewProps) { +export function TopNodesBySkew({database, additionalNodesProps}: TopNodesBySkewProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const [columns, fieldsRequired] = getTopNodesBySkewColumns({ getNodeRef: additionalNodesProps?.getNodeRef, - database: tenantName, + database, }); const {currentData, isFetching, error} = nodesApi.useGetNodesQuery( { - tenant: tenantName, + tenant: database, type: 'any', sort: '-ClockSkew', limit: TENANT_OVERVIEW_TABLES_LIMIT, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index e6997c3811..39570a6f26 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -27,13 +27,15 @@ import {b} from './utils'; import './TenantOverview.scss'; interface TenantOverviewProps { - tenantName: string; + database: string; + databaseFullPath: string; additionalTenantProps?: AdditionalTenantsProps; additionalNodesProps?: AdditionalNodesProps; } export function TenantOverview({ - tenantName, + database, + databaseFullPath, additionalTenantProps, additionalNodesProps, }: TenantOverviewProps) { @@ -44,7 +46,7 @@ export function TenantOverview({ const isMetaDatabasesAvailable = useDatabasesAvailable(); const {currentData: tenant, isFetching} = tenantApi.useGetTenantInfoQuery( - {path: tenantName, clusterName, isMetaDatabasesAvailable}, + {database, clusterName, isMetaDatabasesAvailable}, {pollingInterval: autoRefreshInterval}, ); const tenantLoading = isFetching && tenant === undefined; @@ -61,8 +63,9 @@ export function TenantOverview({ // FIXME: remove after correct data is added to tenantInfo const {currentData: tenantSchemaData} = overviewApi.useGetOverviewQuery( { - path: tenantName, - database: tenantName, + path: database, + database, + databaseFullPath, }, { pollingInterval: autoRefreshInterval, @@ -140,17 +143,17 @@ export function TenantOverview({ case TENANT_METRICS_TABS_IDS.cpu: { return ( ); } case TENANT_METRICS_TABS_IDS.storage: { return ( @@ -159,7 +162,7 @@ export function TenantOverview({ case TENANT_METRICS_TABS_IDS.memory: { return ( ); @@ -204,8 +207,8 @@ export function TenantOverview({ - - + + - + ); @@ -86,10 +86,10 @@ export function TenantStorage({tenantName, metrics, databaseType}: TenantStorage return ( - + - + - + ); diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx index d1382e4cf4..103df2a61f 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -29,13 +29,13 @@ import {TOP_QUERIES_TABLE_SETTINGS} from './utils'; const b = cn('kv-top-queries'); interface RunningQueriesDataProps { - tenantName: string; + database: string; renderQueryModeControl: () => React.ReactNode; handleTextSearchUpdate: (text: string) => void; } export const RunningQueriesData = ({ - tenantName, + database, renderQueryModeControl, handleTextSearchUpdate, }: RunningQueriesDataProps) => { @@ -60,7 +60,7 @@ export const RunningQueriesData = ({ const {currentData, isFetching, isLoading, error} = topQueriesApi.useGetRunningQueriesQuery( { - database: tenantName, + database, filters, sortOrder: backendSort, }, diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index 1ebae60d66..fcc3568eb2 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -25,10 +25,10 @@ const queryModeSchema = z.nativeEnum(QueryModeIds).catch(QueryModeIds.top); const timeFrameSchema = z.nativeEnum(TimeFrameIds).catch(TimeFrameIds.hour); interface TopQueriesProps { - tenantName: string; + database: string; } -export const TopQueries = ({tenantName}: TopQueriesProps) => { +export const TopQueries = ({database}: TopQueriesProps) => { const dispatch = useTypedDispatch(); const [rawQueryMode = QueryModeIds.top, setQueryMode] = useQueryParam('queryMode', StringParam); const [rawTimeFrame = TimeFrameIds.hour, setTimeFrame] = useQueryParam( @@ -68,7 +68,7 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => { return isTopQueries ? ( { /> ) : ( diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx index 17c5483874..4cbc09abc6 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx @@ -38,7 +38,7 @@ import {generateShareableUrl} from './utils/generateShareableUrl'; const b = cn('kv-top-queries'); interface TopQueriesDataProps { - tenantName: string; + database: string; timeFrame: TimeFrame; renderQueryModeControl: () => React.ReactNode; handleTimeFrameChange: (value: string[]) => void; @@ -47,7 +47,7 @@ interface TopQueriesDataProps { } export const TopQueriesData = ({ - tenantName, + database, timeFrame, renderQueryModeControl, handleTimeFrameChange, @@ -80,7 +80,7 @@ export const TopQueriesData = ({ const {tableSort, handleTableSort, backendSort} = useTopQueriesSort(); const {currentData, isFetching, isLoading, error} = topQueriesApi.useGetTopQueriesQuery( { - database: tenantName, + database, filters, sortOrder: backendSort, timeFrame, diff --git a/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx b/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx index 8a983e2b84..35cc89eedc 100644 --- a/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +++ b/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx @@ -40,12 +40,12 @@ function fillDateRangeFor(value: ShardsWorkloadFilters) { } interface TopShardsProps { - tenantName: string; + database: string; path: string; databaseFullPath: string; } -export const TopShards = ({tenantName, path, databaseFullPath}: TopShardsProps) => { +export const TopShards = ({database, path, databaseFullPath}: TopShardsProps) => { const ShardsTable = useComponent('ShardsTable'); const dispatch = useTypedDispatch(); @@ -72,22 +72,14 @@ export const TopShards = ({tenantName, path, databaseFullPath}: TopShardsProps) const {tableSort, handleTableSort, backendSort} = useTopShardSort(); - let normalizedPath = path; - - if (tenantName !== databaseFullPath) { - //tenantName may be database full path or database id. If it is database id, we must remove it from object's path and add database full path instead - const shrinkedPath = path.startsWith(tenantName) ? path.slice(tenantName.length) : path; - normalizedPath = databaseFullPath + shrinkedPath; - } - const { currentData: result, isFetching, error, } = shardApi.useSendShardQueryQuery( { - database: tenantName, - path: normalizedPath, + database, + path, sortOrder: backendSort, filters, databaseFullPath, @@ -151,8 +143,8 @@ export const TopShards = ({tenantName, path, databaseFullPath}: TopShardsProps) return ( ; } const PAGINATED_TABLE_LIMIT = 50_000; const columns = getAllColumns(); -export function TopicData({scrollContainerRef, path, database}: TopicDataProps) { +export function TopicData({scrollContainerRef, path, database, databaseFullPath}: TopicDataProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const [startOffset, setStartOffset] = React.useState(); const [endOffset, setEndOffset] = React.useState(); @@ -105,7 +106,7 @@ export function TopicData({scrollContainerRef, path, database}: TopicDataProps) isLoading: partitionsLoading, error: partitionsError, } = partitionsApi.useGetPartitionsQuery( - {path, database}, + {path, database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); diff --git a/src/containers/Tenant/GrantAccess/GrantAccess.tsx b/src/containers/Tenant/GrantAccess/GrantAccess.tsx index e8d1be2f57..09a0e47769 100644 --- a/src/containers/Tenant/GrantAccess/GrantAccess.tsx +++ b/src/containers/Tenant/GrantAccess/GrantAccess.tsx @@ -35,20 +35,22 @@ export function GrantAccess({handleCloseDrawer}: GrantAccessProps) { const [newSubjects, setNewSubjects] = React.useState([]); const [rightView, setRightsView] = React.useState('Groups'); - const {path, database} = useCurrentSchema(); + const {path, database, databaseFullPath} = useCurrentSchema(); const dialect = useAclSyntax(); const {currentRightsMap, setExplicitRightsChanges, rightsToGrant, rightsToRevoke, hasChanges} = - useRights({aclSubject: aclSubject ?? undefined, path, database}); + useRights({aclSubject: aclSubject ?? undefined, path, database, databaseFullPath}); const {isFetching: aclIsFetching} = schemaAclApi.useGetSchemaAclQuery( { path, database, + databaseFullPath, dialect, }, {skip: !aclSubject}, ); const {isFetching: availableRightsAreFetching} = schemaAclApi.useGetAvailablePermissionsQuery({ database, + databaseFullPath, dialect, }); const [updateRights, updateRightsResponse] = schemaAclApi.useUpdateAccessMutation(); @@ -56,7 +58,14 @@ export function GrantAccess({handleCloseDrawer}: GrantAccessProps) { const [updateRightsError, setUpdateRightsError] = React.useState(''); const inheritedRightsSet = useTypedSelector((state) => - selectSubjectInheritedRights(state, aclSubject ?? undefined, path, database, dialect), + selectSubjectInheritedRights( + state, + aclSubject ?? undefined, + path, + database, + databaseFullPath, + dialect, + ), ); const handleDiscardRightsChanges = React.useCallback(() => { @@ -71,6 +80,7 @@ export function GrantAccess({handleCloseDrawer}: GrantAccessProps) { updateRights({ path, database, + databaseFullPath, dialect, rights: { AddAccess: subjects.map((subj) => ({ @@ -108,10 +118,11 @@ export function GrantAccess({handleCloseDrawer}: GrantAccessProps) { rightsToRevoke, newSubjects, handleCloseDrawer, + databaseFullPath, ]); const availablePermissions = useTypedSelector((state) => - selectAvailablePermissions(state, database, dialect), + selectAvailablePermissions(state, database, databaseFullPath, dialect), ); const handleChangeRightGetter = React.useCallback( (right: string) => { diff --git a/src/containers/Tenant/GrantAccess/utils.ts b/src/containers/Tenant/GrantAccess/utils.ts index c0a4177dd4..1a6d31dce1 100644 --- a/src/containers/Tenant/GrantAccess/utils.ts +++ b/src/containers/Tenant/GrantAccess/utils.ts @@ -7,12 +7,20 @@ interface UseRightsProps { aclSubject?: string; path: string; database: string; + databaseFullPath: string; } -export function useRights({aclSubject, path, database}: UseRightsProps) { +export function useRights({aclSubject, path, database, databaseFullPath}: UseRightsProps) { const dialect = useAclSyntax(); const subjectExplicitRights = useTypedSelector((state) => - selectSubjectExplicitRights(state, aclSubject ?? undefined, path, database, dialect), + selectSubjectExplicitRights( + state, + aclSubject ?? undefined, + path, + database, + databaseFullPath, + dialect, + ), ); const [explicitRightsChanges, setExplicitRightsChanges] = React.useState( () => new Map(), diff --git a/src/containers/Tenant/Healthcheck/Healthcheck.tsx b/src/containers/Tenant/Healthcheck/Healthcheck.tsx index b8b041ee3a..2b3133c0c7 100644 --- a/src/containers/Tenant/Healthcheck/Healthcheck.tsx +++ b/src/containers/Tenant/Healthcheck/Healthcheck.tsx @@ -27,19 +27,19 @@ import cryCatIcon from '../../../assets/icons/cry-cat.svg'; import './Healthcheck.scss'; interface HealthcheckDetailsProps { - tenantName: string; + database: string; countIssueTypes?: ( issueTrees: IssuesTree[], ) => Record & Record; } export function Healthcheck({ - tenantName, + database, countIssueTypes = uiFactory.healthcheck.countHealthcheckIssuesByType, }: HealthcheckDetailsProps) { const fullscreen = useTypedSelector((state) => state.fullscreen); const {loading, error, selfCheckResult, fulfilledTimeStamp, leavesIssues, refetch} = - useHealthcheck(tenantName); + useHealthcheck(database); const issuesCount = React.useMemo( () => countIssueTypes(leavesIssues), diff --git a/src/containers/Tenant/Healthcheck/useHealthcheck.ts b/src/containers/Tenant/Healthcheck/useHealthcheck.ts index e52a2c9bb6..9e7d3fa76e 100644 --- a/src/containers/Tenant/Healthcheck/useHealthcheck.ts +++ b/src/containers/Tenant/Healthcheck/useHealthcheck.ts @@ -16,7 +16,7 @@ interface HealthcheckParams { } export const useHealthcheck = ( - tenantName: string, + database: string, {autorefresh}: {autorefresh?: number} = {}, ): HealthcheckParams => { const { @@ -26,14 +26,14 @@ export const useHealthcheck = ( refetch, fulfilledTimeStamp, } = healthcheckApi.useGetHealthcheckInfoQuery( - {database: tenantName}, + {database}, { pollingInterval: autorefresh, }, ); const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED; - const leavesIssues = useTypedSelector((state) => selectLeavesIssues(state, tenantName)); + const leavesIssues = useTypedSelector((state) => selectLeavesIssues(state, database)); return { loading: data === undefined && isFetching, diff --git a/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx b/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx index 00b3cd63fb..8ac6f6a34a 100644 --- a/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx +++ b/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx @@ -17,6 +17,7 @@ interface CreateDirectoryDialogProps { open: boolean; onClose: VoidFunction; database: string; + databaseFullPath: string; parentPath: string; onSuccess: (value: string) => void; } @@ -35,12 +36,14 @@ export function CreateDirectoryDialog({ open, onClose, database, + databaseFullPath, parentPath, onSuccess, }: CreateDirectoryDialogProps) { const [validationError, setValidationError] = React.useState(''); const [relativePath, setRelativePath] = React.useState(''); const [create, response] = schemaApi.useCreateDirectoryMutation(); + const inputRef = React.useRef(null); const resetErrors = () => { setValidationError(''); @@ -60,8 +63,10 @@ export function CreateDirectoryDialog({ const handleSubmit = () => { const path = `${parentPath}/${relativePath}`; + create({ database, + databaseFullPath, path, }) .unwrap() @@ -72,7 +77,7 @@ export function CreateDirectoryDialog({ }; return ( - +
{ @@ -93,6 +98,7 @@ export function CreateDirectoryDialog({
{ switch (summaryTab) { case TENANT_SUMMARY_TABS_IDS.schema: { - return ; + return ( + + ); } default: { return renderObjectOverview(); @@ -386,7 +394,7 @@ export function ObjectSummary({ dispatchCommonInfoVisibilityState(PaneVisibilityActionTypes.clear); }; - const relativePath = transformPath(path, database, databaseFullPath); + const relativePath = transformPath(path, databaseFullPath); const renderCommonInfoControls = () => { const showPreview = isTableType(type) && !isIndexTableType(subType); @@ -449,7 +457,7 @@ export function ObjectSummary({ collapsedSizes={[100, 0]} > diff --git a/src/containers/Tenant/ObjectSummary/ObjectTree.tsx b/src/containers/Tenant/ObjectSummary/ObjectTree.tsx index bb38178831..7b4c2c17ec 100644 --- a/src/containers/Tenant/ObjectSummary/ObjectTree.tsx +++ b/src/containers/Tenant/ObjectSummary/ObjectTree.tsx @@ -7,7 +7,7 @@ import i18n from './i18n'; import {b} from './shared'; interface ObjectTreeProps { - tenantName: string; + database: string; databaseFullPath: string; path?: string; } @@ -20,10 +20,11 @@ function prepareSchemaRootName(name: string | undefined, fallback: string): stri return fallback.startsWith('/') ? fallback : `/${fallback}`; } -export function ObjectTree({tenantName, path, databaseFullPath}: ObjectTreeProps) { +export function ObjectTree({database, path, databaseFullPath}: ObjectTreeProps) { const {data: tenantData = {}, isLoading} = useGetSchemaQuery({ - path: tenantName, - database: tenantName, + path: databaseFullPath, + databaseFullPath, + database, }); const pathData = tenantData?.PathDescription?.Self; @@ -44,11 +45,10 @@ export function ObjectTree({tenantName, path, databaseFullPath}: ObjectTreeProps
{pathData ? ( void; databaseFullPath: string; + database: string; } export function SchemaTree(props: SchemaTreeProps) { const createDirectoryFeatureAvailable = useCreateDirectoryFeatureAvailable(); - const {rootPath, rootName, rootType, currentPath, onActivePathUpdate, databaseFullPath} = props; + const {rootName, rootType, currentPath, onActivePathUpdate, databaseFullPath, database} = props; const dispatch = useTypedDispatch(); const input = useTypedSelector(selectUserInput); const isDirty = useTypedSelector(selectIsDirty); @@ -56,7 +56,7 @@ export function SchemaTree(props: SchemaTreeProps) { const setSchemaTreeKey = useDispatchTreeKey(); const schemaTreeKey = useTreeKey(); - const rootNodeType = isDomain(rootPath, rootType) + const rootNodeType = isDomain(databaseFullPath, rootType) ? 'database' : mapPathTypeToNavigationTreeType(rootType); @@ -65,7 +65,7 @@ export function SchemaTree(props: SchemaTreeProps) { do { const promise = dispatch( schemaApi.endpoints.getSchema.initiate( - {path, database: rootPath}, + {path, database, databaseFullPath}, {forceRefetch: true}, ), ); @@ -106,13 +106,14 @@ export function SchemaTree(props: SchemaTreeProps) { }; React.useEffect(() => { // if the cached path is not in the current tree, show root - if (!currentPath?.startsWith(rootPath)) { - onActivePathUpdate(rootPath); + if (!currentPath?.startsWith(databaseFullPath)) { + onActivePathUpdate(databaseFullPath); } - }, [currentPath, onActivePathUpdate, rootPath]); + }, [currentPath, onActivePathUpdate, databaseFullPath]); const handleSuccessSubmit = (relativePath: string) => { - const newPath = `${parentPath}/${relativePath}`; + const prefix = databaseFullPath === parentPath ? '' : `${databaseFullPath}/`; + const newPath = `${prefix}${parentPath}/${relativePath}`; onActivePathUpdate(newPath); setSchemaTreeKey(newPath); }; @@ -138,9 +139,9 @@ export function SchemaTree(props: SchemaTreeProps) { getConnectToDBDialog, schemaData: actionsSchemaData, isSchemaDataLoading: isActionsDataFetching, - databaseFullPath, }, - rootPath, + databaseFullPath, + database, ); }, [ actionsSchemaData, @@ -150,8 +151,8 @@ export function SchemaTree(props: SchemaTreeProps) { isActionsDataFetching, isDirty, onActivePathUpdate, - rootPath, databaseFullPath, + database, ]); return ( @@ -159,14 +160,15 @@ export function SchemaTree(props: SchemaTreeProps) { key={schemaTreeKey} rootState={{ - path: rootPath, + path: databaseFullPath, name: rootName, type: rootNodeType, collapsed: false, @@ -176,7 +178,7 @@ export function SchemaTree(props: SchemaTreeProps) { onActionsOpenToggle={({path, type, isOpen}) => { const pathType = nodeTableTypeToPathType[type]; if (isOpen && pathType) { - getTableSchemaDataQuery({path, tenantName: rootPath, type: pathType}); + getTableSchemaDataQuery({path, database, type: pathType, databaseFullPath}); } return []; diff --git a/src/containers/Tenant/ObjectSummary/transformPath.ts b/src/containers/Tenant/ObjectSummary/transformPath.ts index 1259326dcc..1a0879f81a 100644 --- a/src/containers/Tenant/ObjectSummary/transformPath.ts +++ b/src/containers/Tenant/ObjectSummary/transformPath.ts @@ -1,23 +1,15 @@ import {EPathType} from '../../../types/api/schema'; -/** - * Transforms an absolute database object path to a relative path from the specified database. - * @param path - source path to the database object - * @param dbName - database name to make path relative to - * @param databaseFullPath - full database path (defaults to dbName) - * @returns transformed relative path - */ -export function transformPath(path: string, dbName: string, databaseFullPath = dbName): string { +export function transformPath(path: string, databaseFullPath: string): string { // Normalize the path and dbName by removing leading/trailing slashes const normalizedPath = path.replace(/^\/+|\/+$/g, ''); - const normalizedDbName = dbName.replace(/^\/+|\/+$/g, ''); - const normalizedDbFullPath = databaseFullPath.replace(/^\/+|\/+$/g, ''); + const normalizedDbName = databaseFullPath.replace(/^\/+|\/+$/g, ''); if (!normalizedPath.startsWith(normalizedDbName)) { return normalizedPath || '/'; } if (normalizedPath === normalizedDbName) { - return `/${normalizedDbFullPath}`; + return `/${normalizedPath}`; } let result = normalizedPath.slice(normalizedDbName.length); diff --git a/src/containers/Tenant/Query/Preview/components/TablePreview.tsx b/src/containers/Tenant/Query/Preview/components/TablePreview.tsx index 66e7606f70..e2a18b1e8f 100644 --- a/src/containers/Tenant/Query/Preview/components/TablePreview.tsx +++ b/src/containers/Tenant/Query/Preview/components/TablePreview.tsx @@ -1,7 +1,6 @@ import {QueryResultTable} from '../../../../../components/QueryResultTable'; import {previewApi} from '../../../../../store/reducers/preview'; import {prepareQueryWithPragmas} from '../../../../../store/reducers/query/utils'; -import {useTenantBaseInfo} from '../../../../../store/reducers/tenant/tenant'; import {useQueryExecutionSettings} from '../../../../../utils/hooks/useQueryExecutionSettings'; import {transformPath} from '../../../ObjectSummary/transformPath'; import {isExternalTableType} from '../../../utils/schema'; @@ -11,11 +10,10 @@ import {Preview} from './PreviewView'; const TABLE_PREVIEW_LIMIT = 100; -export function TablePreview({database, path, type}: PreviewContainerProps) { +export function TablePreview({database, path, type, databaseFullPath}: PreviewContainerProps) { const [querySettings] = useQueryExecutionSettings(); - const {name} = useTenantBaseInfo(database); - const relativePath = transformPath(path, database, name); + const relativePath = transformPath(path, databaseFullPath); const baseQuery = `select * from \`${relativePath}\` limit 101`; const query = prepareQueryWithPragmas(baseQuery, querySettings.pragmas); diff --git a/src/containers/Tenant/Query/Preview/components/TopicPreview.tsx b/src/containers/Tenant/Query/Preview/components/TopicPreview.tsx index ba93547219..404dc38299 100644 --- a/src/containers/Tenant/Query/Preview/components/TopicPreview.tsx +++ b/src/containers/Tenant/Query/Preview/components/TopicPreview.tsx @@ -17,12 +17,12 @@ import {TopicPreviewTable} from './TopicPreviewTable'; const TOPIC_PREVIEW_LIMIT = 100; -export function TopicPreview({database, path}: PreviewContainerProps) { +export function TopicPreview({database, path, databaseFullPath}: PreviewContainerProps) { const { data: partitions, isLoading: partitionsLoading, error: partitionsError, - } = partitionsApi.useGetPartitionsQuery({path, database}); + } = partitionsApi.useGetPartitionsQuery({path, database, databaseFullPath}); const firstPartition = React.useMemo(() => { return partitions?.[0]; diff --git a/src/containers/Tenant/Query/Preview/types.ts b/src/containers/Tenant/Query/Preview/types.ts index b79c95811a..ee91fc81b7 100644 --- a/src/containers/Tenant/Query/Preview/types.ts +++ b/src/containers/Tenant/Query/Preview/types.ts @@ -2,6 +2,7 @@ import type {EPathSubType, EPathType} from '../../../../types/api/schema'; export interface PreviewContainerProps { database: string; + databaseFullPath: string; path: string; type?: EPathType; subType?: EPathSubType; diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index 659ace4de2..ce2450ee61 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -72,7 +72,7 @@ interface QueryEditorProps { export default function QueryEditor(props: QueryEditorProps) { const dispatch = useTypedDispatch(); - const {database: tenantName, path, type, subType} = useCurrentSchema(); + const {database, path, type, subType, databaseFullPath} = useCurrentSchema(); const {theme, changeUserInput} = props; const savedPath = useTypedSelector(selectTenantPath); const result = useTypedSelector(selectResult); @@ -113,10 +113,10 @@ export default function QueryEditor(props: QueryEditorProps) { }, [isStreamingEnabled, querySettings.limitRows]); React.useEffect(() => { - if (savedPath !== tenantName) { - dispatch(setTenantPath(tenantName)); + if (savedPath !== database) { + dispatch(setTenantPath(database)); } - }, [dispatch, tenantName, savedPath]); + }, [dispatch, database, savedPath]); const [resultVisibilityState, dispatchResultVisibilityState] = React.useReducer( paneVisibilityToggleReducerCreator(DEFAULT_IS_QUERY_RESULT_COLLAPSED), @@ -151,7 +151,7 @@ export default function QueryEditor(props: QueryEditorProps) { const query = streamQuery({ actionType: 'execute', query: text, - database: tenantName, + database, querySettings, enableTracingLevel, }); @@ -161,7 +161,7 @@ export default function QueryEditor(props: QueryEditorProps) { const query = sendQuery({ actionType: 'execute', query: text, - database: tenantName, + database, querySettings, enableTracingLevel, queryId, @@ -199,7 +199,7 @@ export default function QueryEditor(props: QueryEditorProps) { const query = sendQuery({ actionType: 'explain', query: text, - database: tenantName, + database, querySettings, enableTracingLevel, queryId, @@ -231,7 +231,7 @@ export default function QueryEditor(props: QueryEditorProps) { isLoading={Boolean(result?.isLoading)} handleGetExplainQueryClick={handleGetExplainQueryClick} highlightedAction={lastUsedQueryAction} - tenantName={tenantName} + database={database} queryId={result?.queryId} isStreamingEnabled={isStreamingEnabled} /> @@ -276,7 +276,8 @@ export default function QueryEditor(props: QueryEditorProps) { theme={theme} key={result?.queryId} result={result} - tenantName={tenantName} + database={database} + databaseFullPath={databaseFullPath} path={path} showPreview={showPreview} queryText={lastExecutedQueryText} @@ -297,7 +298,8 @@ interface ResultProps { subType?: EPathSubType; theme: string; result?: QueryResult; - tenantName: string; + database: string; + databaseFullPath: string; path: string; showPreview?: boolean; queryText: string; @@ -311,14 +313,23 @@ function Result({ subType, theme, result, - tenantName, + database, + databaseFullPath, path, showPreview, queryText, tableSettings, }: ResultProps) { if (showPreview) { - return ; + return ( + + ); } if (result) { @@ -327,7 +338,7 @@ function Result({ result={result} resultType={result?.type} theme={theme} - tenantName={tenantName} + database={database} isResultsCollapsed={resultVisibilityState.collapsed} tableSettings={tableSettings} onExpandResults={onExpandResultHandler} diff --git a/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx b/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx index 796cb358e2..001d3431bc 100644 --- a/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +++ b/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx @@ -22,7 +22,7 @@ interface QueryEditorControlsProps { disabled?: boolean; highlightedAction: QueryAction; queryId?: string; - tenantName: string; + database: string; isStreamingEnabled?: boolean; handleGetExplainQueryClick: (text: string) => void; @@ -75,7 +75,7 @@ export const QueryEditorControls = ({ isLoading, highlightedAction, queryId, - tenantName, + database, isStreamingEnabled, handleSendExecuteClick, @@ -94,7 +94,7 @@ export const QueryEditorControls = ({ if (isStreamingEnabled) { queryManagerInstance.abortQuery(); } else if (queryId) { - await sendCancelQuery({queryId, database: tenantName}).unwrap(); + await sendCancelQuery({queryId, database}).unwrap(); } } catch { createToast({ @@ -113,7 +113,7 @@ export const QueryEditorControls = ({ setCancelQueryError(false); }, CANCEL_ERROR_ANIMATION_DURATION); } - }, [isStreamingEnabled, queryId, sendCancelQuery, tenantName]); + }, [isStreamingEnabled, queryId, sendCancelQuery, database]); const isRunHighlighted = highlightedAction === 'execute'; const isExplainHighlighted = highlightedAction === 'explain'; diff --git a/src/containers/Tenant/Query/QueryResult/QueryResultViewer.tsx b/src/containers/Tenant/Query/QueryResult/QueryResultViewer.tsx index bca4fb7bcb..6ee609b68d 100644 --- a/src/containers/Tenant/Query/QueryResult/QueryResultViewer.tsx +++ b/src/containers/Tenant/Query/QueryResult/QueryResultViewer.tsx @@ -80,7 +80,7 @@ interface ExecuteResultProps { resultType?: QueryAction; isResultsCollapsed?: boolean; theme?: string; - tenantName: string; + database: string; queryText?: string; tableSettings?: Partial; @@ -93,7 +93,7 @@ export function QueryResultViewer({ resultType = 'execute', isResultsCollapsed, theme, - tenantName, + database, queryText, tableSettings, onCollapseResults, @@ -209,7 +209,7 @@ export function QueryResultViewer({ plan: data.plan, }} error={error} - database={tenantName} + database={database} hasPlanToSvg={Boolean(data?.plan && useShowPlanToSvg && isExecute)} /> ); diff --git a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx index 552788e273..267367ae13 100644 --- a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +++ b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx @@ -30,11 +30,18 @@ import './SchemaViewer.scss'; interface SchemaViewerProps { type?: EPathType; path: string; - tenantName: string; + databaseFullPath: string; + database: string; extended?: boolean; } -export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaViewerProps) => { +export const SchemaViewer = ({ + type, + path, + database, + extended = false, + databaseFullPath, +}: SchemaViewerProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); // Refresh table only in Diagnostics @@ -42,12 +49,12 @@ export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaV const {currentData: tableSchemaData, isFetching: isTableSchemaFetching} = overviewApi.useGetOverviewQuery( - {path, database: tenantName}, + {path, database, databaseFullPath}, {pollingInterval, skip: isViewType(type)}, ); const {currentData: viewColumnsData, isFetching: isViewSchemaFetching} = viewSchemaApi.useGetViewSchemaQuery( - {path, database: tenantName}, + {path, database, databaseFullPath}, {pollingInterval, skip: !isViewType(type)}, ); diff --git a/src/containers/Tenant/Tenant.tsx b/src/containers/Tenant/Tenant.tsx index 67383785d4..1208046e6c 100644 --- a/src/containers/Tenant/Tenant.tsx +++ b/src/containers/Tenant/Tenant.tsx @@ -57,7 +57,7 @@ export function Tenant(props: TenantProps) { const {database, schema} = useTenantQueryParams(); - const {controlPlane, name} = useTenantBaseInfo(database ?? ''); + const {name, isLoading: tenantBaseInfoLoading} = useTenantBaseInfo(database ?? ''); if (!database) { throw new Error('Tenant name is not defined'); @@ -77,23 +77,23 @@ export function Tenant(props: TenantProps) { } }, [database]); - const path = schema ?? database; + const databaseName = name ?? ''; + + const path = schema ?? databaseName; const { currentData: currentItem, error, isLoading, - } = overviewApi.useGetOverviewQuery({path, database: database}); - - const databaseName = name ?? controlPlane?.name ?? database; + } = overviewApi.useGetOverviewQuery({path, database, databaseFullPath: databaseName}); const dispatch = useTypedDispatch(); React.useEffect(() => { - dispatch(setHeaderBreadcrumbs('tenant', {tenantName: databaseName, database})); + dispatch(setHeaderBreadcrumbs('tenant', {databaseName, database})); }, [databaseName, database, dispatch]); const preloadedData = useTypedSelector((state) => - selectSchemaObjectData(state, path, database), + selectSchemaObjectData(state, path, database, databaseName), ); // Use preloaded data if there is no current item data yet @@ -120,7 +120,7 @@ export function Tenant(props: TenantProps) { }; const [initialLoading, setInitialLoading] = React.useState(true); - if (initialLoading && !isLoading) { + if (initialLoading && !isLoading && !tenantBaseInfoLoading) { setInitialLoading(false); } diff --git a/src/containers/Tenant/TenantDrawerHealthcheck.tsx b/src/containers/Tenant/TenantDrawerHealthcheck.tsx index 1d8f93316b..babc7b866e 100644 --- a/src/containers/Tenant/TenantDrawerHealthcheck.tsx +++ b/src/containers/Tenant/TenantDrawerHealthcheck.tsx @@ -45,7 +45,7 @@ export function TenantDrawerHealthcheck({children}: TenantDrawerWrapperProps) { }, [handleShowHealthcheckChange, handleIssuesFilterChange, handleHealthcheckViewChange]); const renderDrawerContent = React.useCallback(() => { - return ; + return ; }, [database]); return ( diff --git a/src/containers/Tenant/utils/schemaActions.tsx b/src/containers/Tenant/utils/schemaActions.tsx index 1bc5481952..ef57a1e589 100644 --- a/src/containers/Tenant/utils/schemaActions.tsx +++ b/src/containers/Tenant/utils/schemaActions.tsx @@ -48,11 +48,10 @@ interface ActionsAdditionalParams { getConnectToDBDialog?: (params: SnippetParams) => Promise; schemaData?: SchemaData[]; isSchemaDataLoading?: boolean; - databaseFullPath: string; } interface BindActionParams { - tenantName: string; + database: string; type: NavigationTreeNodeType; path: string; databaseFullPath: string; @@ -98,7 +97,7 @@ const bindActions = ( showCreateDirectoryDialog(params.relativePath); } : undefined, - getConnectToDBDialog: () => getConnectToDBDialog?.({database: params.tenantName}), + getConnectToDBDialog: () => getConnectToDBDialog?.({database: params.database}), createTable: inputQuery(createTableTemplate), createColumnTable: inputQuery(createColumnTableTemplate), createAsyncReplication: inputQuery(createAsyncReplicationTemplate), @@ -162,16 +161,21 @@ const getActionWithLoader = ({text, action, isLoading}: ActionConfig) => ({ }); export const getActions = - (dispatch: AppDispatch, additionalEffects: ActionsAdditionalParams, rootPath = '') => + ( + dispatch: AppDispatch, + additionalEffects: ActionsAdditionalParams, + rootPath = '', + database: string, + ) => (path: string, type: NavigationTreeNodeType) => { - const relativePath = transformPath(path, rootPath, additionalEffects.databaseFullPath); + const relativePath = transformPath(path, rootPath); const actions = bindActions( { path, relativePath, - tenantName: rootPath, + database, type, - databaseFullPath: additionalEffects.databaseFullPath, + databaseFullPath: rootPath, }, dispatch, additionalEffects, diff --git a/src/containers/VDiskPage/VDiskPage.tsx b/src/containers/VDiskPage/VDiskPage.tsx index 2f3942794d..414b3501e0 100644 --- a/src/containers/VDiskPage/VDiskPage.tsx +++ b/src/containers/VDiskPage/VDiskPage.tsx @@ -100,7 +100,6 @@ export function VDiskPage() { dispatch( setHeaderBreadcrumbs('vDisk', { groupId: vDiskData?.VDiskId?.GroupID, - tenantName: database, database, vDiskId: vDiskData?.StringifiedId, }), diff --git a/src/services/api/base.ts b/src/services/api/base.ts index c7177ddf4b..28322d711e 100644 --- a/src/services/api/base.ts +++ b/src/services/api/base.ts @@ -3,6 +3,7 @@ import type {AxiosWrapperOptions} from '@gravity-ui/axios-wrapper'; import axiosRetry from 'axios-retry'; import {backend as BACKEND, clusterName} from '../../store'; +import type {SchemaPathParam} from '../../types/api/common'; import {DEV_ENABLE_TRACING_FOR_ALL_REQUESTS} from '../../utils/constants'; import {prepareBackendWithMetaProxy} from '../../utils/parseBalancer'; import {isRedirectToAuth} from '../../utils/response'; @@ -94,17 +95,18 @@ export class BaseYdbAPI extends AxiosWrapper { return `${BACKEND ?? ''}${path}`; } - getSchemaPath({path, database}: {path?: string; database?: string}) { - if (!this.useRelativePath || !path || !database) { + getSchemaPath(params?: SchemaPathParam) { + const {path, databaseFullPath} = params ?? {}; + if (!this.useRelativePath || !path || !databaseFullPath) { return path; } - if (path === database) { + if (path === databaseFullPath) { return ''; } - if (path.startsWith(database + '/')) { - return path.slice(database.length + 1); + if (path.startsWith(databaseFullPath + '/')) { + return path.slice(databaseFullPath.length + 1); } return path; } diff --git a/src/services/api/meta.ts b/src/services/api/meta.ts index 4ca9c42293..3322ddee17 100644 --- a/src/services/api/meta.ts +++ b/src/services/api/meta.ts @@ -43,28 +43,28 @@ export class MetaAPI extends BaseYdbAPI { } getTenants( - {clusterName, path}: {clusterName?: string; path?: string}, + {clusterName, database}: {clusterName?: string; database?: string}, {signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/meta/cp_databases', clusterName), { cluster_name: clusterName, - database_name: path, + database_name: database, }, {requestConfig: {signal}}, ).then(parseMetaTenants); } getTenantsV2( - {path, clusterName}: {clusterName?: string; path?: string}, + {database, clusterName}: {clusterName?: string; database?: string}, {signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/meta/databases', clusterName), { cluster_name: clusterName, - database: path, + database, }, {requestConfig: {signal}}, ).then(parseMetaTenants); diff --git a/src/services/api/scheme.ts b/src/services/api/scheme.ts index 5febade721..5343d15ce6 100644 --- a/src/services/api/scheme.ts +++ b/src/services/api/scheme.ts @@ -1,8 +1,10 @@ +import type {SchemaPathParam} from '../../types/api/common'; + import {BaseYdbAPI} from './base'; export class SchemeAPI extends BaseYdbAPI { createSchemaDirectory( - {database, path}: {database: string; path: string}, + {database, path}: {database: string; path: SchemaPathParam}, {signal}: {signal?: AbortSignal} = {}, ) { return this.post<{test: string}>( @@ -10,7 +12,7 @@ export class SchemeAPI extends BaseYdbAPI { {}, { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), }, { requestConfig: {signal}, diff --git a/src/services/api/viewer.ts b/src/services/api/viewer.ts index 1cea2eb266..1826d51ee5 100644 --- a/src/services/api/viewer.ts +++ b/src/services/api/viewer.ts @@ -7,6 +7,7 @@ import type { import type {TQueryAutocomplete} from '../../types/api/autocomplete'; import type {CapabilitiesResponse} from '../../types/api/capabilities'; import type {TClusterInfo} from '../../types/api/cluster'; +import type {SchemaPathParam} from '../../types/api/common'; import type {DescribeConsumerResult} from '../../types/api/consumer'; import type {FeatureFlagConfigs} from '../../types/api/featureFlags'; import type {HealthCheckAPIResponse} from '../../types/api/healthcheck'; @@ -87,15 +88,12 @@ export class ViewerAPI extends BaseYdbAPI { ); } - getTenantInfo( - {path, database = path}: {path: string; database?: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { + getTenantInfo({database}: {database: string}, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/tenantinfo'), { database, - path, + path: database, tablets: false, storage: true, memory: true, @@ -132,7 +130,7 @@ export class ViewerAPI extends BaseYdbAPI { // TODO: remove after remove tenant param database: database || tenant, fields_required: preparedFieldsRequired, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), ...params, }, {concurrentId, requestConfig: {signal}}, @@ -148,7 +146,7 @@ export class ViewerAPI extends BaseYdbAPI { { database, node_id: nodeId, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), enums: true, filter, }, @@ -157,14 +155,14 @@ export class ViewerAPI extends BaseYdbAPI { } getSchema( - {path, database}: {path: string; database: string}, + {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get>( this.getPath('/viewer/json/describe'), { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), enums: true, backup: false, private: true, @@ -178,14 +176,14 @@ export class ViewerAPI extends BaseYdbAPI { } getDescribe( - {path, database, timeout}: {path: string; database: string; timeout?: number}, + {path, database, timeout}: {path: SchemaPathParam; database: string; timeout?: number}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get>( this.getPath('/viewer/json/describe'), { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), enums: true, partition_stats: true, subs: 0, @@ -195,14 +193,14 @@ export class ViewerAPI extends BaseYdbAPI { } getSchemaAcl( - {path, database, dialect}: {path: string; database: string; dialect: string}, + {path, database, dialect}: {path: SchemaPathParam; database: string; dialect: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/acl'), { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), merge_rules: true, dialect, }, @@ -210,14 +208,14 @@ export class ViewerAPI extends BaseYdbAPI { ); } getAvailablePermissions( - {path, database, dialect}: {path: string; database: string; dialect: string}, + {path, database, dialect}: {path: SchemaPathParam; database: string; dialect: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/acl'), { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), merge_rules: true, dialect, list_permissions: true, @@ -231,7 +229,12 @@ export class ViewerAPI extends BaseYdbAPI { database, rights, dialect, - }: {path: string; database: string; rights: AccessRightsUpdateRequest; dialect: string}, + }: { + path: SchemaPathParam; + database: string; + rights: AccessRightsUpdateRequest; + dialect: string; + }, {concurrentId, signal}: AxiosOptions = {}, ) { return this.post( @@ -239,7 +242,7 @@ export class ViewerAPI extends BaseYdbAPI { rights, { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), merge_rules: true, dialect, }, @@ -248,14 +251,14 @@ export class ViewerAPI extends BaseYdbAPI { } getHeatmapData( - {path, database}: {path: string; database: string}, + {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get>( this.getPath('/viewer/json/describe'), { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), enums: true, backup: false, children: false, @@ -267,7 +270,7 @@ export class ViewerAPI extends BaseYdbAPI { } getNetwork( - {path, database}: {path: string; database: string}, + {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( @@ -275,14 +278,14 @@ export class ViewerAPI extends BaseYdbAPI { { enums: true, database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), }, {concurrentId, requestConfig: {signal}}, ); } getReplication( - {path, database}: {path: string; database: string}, + {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( @@ -291,14 +294,14 @@ export class ViewerAPI extends BaseYdbAPI { enums: true, include_stats: true, database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), }, {concurrentId, requestConfig: {signal}}, ); } getTopic( - {path, database}: {path: string; database: string}, + {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( @@ -307,7 +310,7 @@ export class ViewerAPI extends BaseYdbAPI { enums: true, include_stats: true, database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), }, {concurrentId, requestConfig: {signal}}, ); @@ -321,7 +324,7 @@ export class ViewerAPI extends BaseYdbAPI { } getConsumer( - {path, consumer, database}: {path: string; consumer: string; database: string}, + {path, consumer, database}: {path: SchemaPathParam; consumer: string; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( @@ -330,7 +333,7 @@ export class ViewerAPI extends BaseYdbAPI { enums: true, include_stats: true, database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), consumer, }, {concurrentId: concurrentId || 'getConsumer', requestConfig: {signal}}, @@ -432,14 +435,18 @@ export class ViewerAPI extends BaseYdbAPI { } getHotKeys( - {path, database, enableSampling}: {path: string; database: string; enableSampling: boolean}, + { + path, + database, + enableSampling, + }: {path: SchemaPathParam; database: string; enableSampling: boolean}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/hotkeys'), { database, - path: this.getSchemaPath({path, database}), + path: this.getSchemaPath(path), enable_sampling: enableSampling, }, {concurrentId: concurrentId || 'getHotKeys', requestConfig: {signal}}, diff --git a/src/store/reducers/header/types.ts b/src/store/reducers/header/types.ts index 6b236b82ef..d3e89a7fc4 100644 --- a/src/store/reducers/header/types.ts +++ b/src/store/reducers/header/types.ts @@ -23,13 +23,13 @@ export interface ClusterBreadcrumbsOptions extends ClustersBreadcrumbsOptions { } export interface TenantBreadcrumbsOptions extends ClusterBreadcrumbsOptions { - tenantName?: string; database?: string; + databaseName?: string; } export interface StorageGroupBreadcrumbsOptions extends ClusterBreadcrumbsOptions { groupId?: string; - tenantName?: string; + database?: string; } export interface NodeBreadcrumbsOptions extends TenantBreadcrumbsOptions { @@ -38,7 +38,7 @@ export interface NodeBreadcrumbsOptions extends TenantBreadcrumbsOptions { nodeRole?: 'Storage' | 'Compute'; } -export interface PDiskBreadcrumbsOptions extends Omit { +export interface PDiskBreadcrumbsOptions extends Omit { pDiskId?: string | number; } diff --git a/src/store/reducers/heatmap.ts b/src/store/reducers/heatmap.ts index c23cf80c86..2198278301 100644 --- a/src/store/reducers/heatmap.ts +++ b/src/store/reducers/heatmap.ts @@ -41,13 +41,19 @@ export const heatmapApi = api.injectEndpoints({ endpoints: (builder) => ({ getHeatmapTabletsInfo: builder.query({ queryFn: async ( - {path, database}: IHeatmapApiRequestParams, + {path, database, databaseFullPath}: IHeatmapApiRequestParams, {signal, getState, dispatch}, ) => { try { const response = await Promise.all([ - window.api.viewer.getTabletsInfo({path, database}, {signal}), - window.api.viewer.getHeatmapData({path, database}, {signal}), + window.api.viewer.getTabletsInfo( + {path: {path, databaseFullPath}, database}, + {signal}, + ), + window.api.viewer.getHeatmapData( + {path: {path, databaseFullPath}, database}, + {signal}, + ), ]); const data = transformResponse(response); diff --git a/src/store/reducers/hotKeys/hotKeys.ts b/src/store/reducers/hotKeys/hotKeys.ts index 290d05de84..0b2a8b1a85 100644 --- a/src/store/reducers/hotKeys/hotKeys.ts +++ b/src/store/reducers/hotKeys/hotKeys.ts @@ -3,12 +3,15 @@ import {api} from '../api'; export const hotKeysApi = api.injectEndpoints({ endpoints: (builder) => ({ - getHotKeys: builder.query({ - queryFn: async ({path, database}, {signal}) => { + getHotKeys: builder.query< + HotKey[] | null, + {path: string; database: string; databaseFullPath: string} + >({ + queryFn: async ({path, database, databaseFullPath}, {signal}) => { try { // Send request that will trigger hot keys sampling (enable_sampling = true) const initialResponse = await window.api.viewer.getHotKeys( - {path, database, enableSampling: true}, + {path: {path, databaseFullPath}, database, enableSampling: true}, {signal}, ); @@ -30,7 +33,7 @@ export const hotKeysApi = api.injectEndpoints({ // And request these samples (enable_sampling = false) const response = await window.api.viewer.getHotKeys( - {path, database, enableSampling: false}, + {path: {path, databaseFullPath}, database, enableSampling: false}, {signal}, ); return {data: response.hotkeys ?? null}; diff --git a/src/store/reducers/network/network.ts b/src/store/reducers/network/network.ts index a7f9bbcb60..81d432af5e 100644 --- a/src/store/reducers/network/network.ts +++ b/src/store/reducers/network/network.ts @@ -3,10 +3,13 @@ import {api} from '../api'; export const networkApi = api.injectEndpoints({ endpoints: (build) => ({ getNetworkInfo: build.query({ - queryFn: async (tenant: string, {signal}) => { + queryFn: async ( + {database, databaseFullPath}: {database: string; databaseFullPath: string}, + {signal}, + ) => { try { const data = await window.api.viewer.getNetwork( - {path: tenant, database: tenant}, + {path: {path: databaseFullPath, databaseFullPath}, database}, {signal}, ); return {data}; diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts index 894ed2f418..ece5888ee6 100644 --- a/src/store/reducers/nodes/types.ts +++ b/src/store/reducers/nodes/types.ts @@ -55,6 +55,7 @@ export interface NodesFilters { peerRoleFilter?: NodesPeerRole; path?: string; + databaseFullPath?: string; database?: string; filterGroup?: string; diff --git a/src/store/reducers/overview/overview.ts b/src/store/reducers/overview/overview.ts index 2beb11b2f5..a8f0f0a71d 100644 --- a/src/store/reducers/overview/overview.ts +++ b/src/store/reducers/overview/overview.ts @@ -4,13 +4,18 @@ export const overviewApi = api.injectEndpoints({ endpoints: (build) => ({ getOverview: build.query({ queryFn: async ( - {path, database, timeout}: {path: string; database: string; timeout?: number}, + { + path, + database, + timeout, + databaseFullPath, + }: {path: string; database: string; timeout?: number; databaseFullPath: string}, {signal}, ) => { try { const data = await window.api.viewer.getDescribe( { - path, + path: {path, databaseFullPath}, database, timeout, }, diff --git a/src/store/reducers/partitions/partitions.ts b/src/store/reducers/partitions/partitions.ts index b7150ff641..fbd7869920 100644 --- a/src/store/reducers/partitions/partitions.ts +++ b/src/store/reducers/partitions/partitions.ts @@ -30,13 +30,19 @@ export const partitionsApi = api.injectEndpoints({ path, database, consumerName, - }: {path: string; database: string; consumerName?: string}, + databaseFullPath, + }: { + path: string; + database: string; + consumerName?: string; + databaseFullPath: string; + }, {signal}, ) => { try { if (consumerName) { const response = await window.api.viewer.getConsumer( - {path, database, consumer: consumerName}, + {path: {path, databaseFullPath}, database, consumer: consumerName}, {signal}, ); const rawPartitions = response.partitions; @@ -44,7 +50,7 @@ export const partitionsApi = api.injectEndpoints({ return {data}; } else { const response = await window.api.viewer.getTopic( - {path, database}, + {path: {path, databaseFullPath}, database}, {signal}, ); const rawPartitions = response.partitions; diff --git a/src/store/reducers/replication.ts b/src/store/reducers/replication.ts index dfd6dea84b..54994033f2 100644 --- a/src/store/reducers/replication.ts +++ b/src/store/reducers/replication.ts @@ -3,9 +3,20 @@ import {api} from './api'; export const replicationApi = api.injectEndpoints({ endpoints: (build) => ({ getReplication: build.query({ - queryFn: async (params: {path: string; database: string}) => { + queryFn: async ({ + path, + database, + databaseFullPath, + }: { + path: string; + database: string; + databaseFullPath: string; + }) => { try { - const data = await window.api.viewer.getReplication(params); + const data = await window.api.viewer.getReplication({ + path: {path, databaseFullPath}, + database, + }); // On older version it can return HTML page of Developer UI with an error if (typeof data !== 'object') { return {error: {}}; diff --git a/src/store/reducers/schema/schema.ts b/src/store/reducers/schema/schema.ts index a9f8ca473d..37559fff94 100644 --- a/src/store/reducers/schema/schema.ts +++ b/src/store/reducers/schema/schema.ts @@ -33,11 +33,14 @@ export const {selectShowPreview} = slice.selectors; export const schemaApi = api.injectEndpoints({ endpoints: (builder) => ({ - createDirectory: builder.mutation({ - queryFn: async ({database, path}, {signal}) => { + createDirectory: builder.mutation< + unknown, + {database: string; path: string; databaseFullPath: string} + >({ + queryFn: async ({database, path, databaseFullPath}, {signal}) => { try { const data = await window.api.scheme.createSchemaDirectory( - {database, path}, + {database, path: {path, databaseFullPath}}, {signal}, ); return {data}; @@ -48,11 +51,14 @@ export const schemaApi = api.injectEndpoints({ }), getSchema: builder.query< {[path: string]: TEvDescribeSchemeResult & {partial?: boolean}}, - {path: string; database: string} + {path: string; database: string; databaseFullPath: string} >({ - queryFn: async ({path, database}, {signal}) => { + queryFn: async ({path, database, databaseFullPath}, {signal}) => { try { - const data = await window.api.viewer.getSchema({path, database}, {signal}); + const data = await window.api.viewer.getSchema( + {path: {path, databaseFullPath}, database}, + {signal}, + ); if (!data) { return {error: new Error('Schema is not available')}; } @@ -92,10 +98,19 @@ function getSchemaChildren(data: TEvDescribeSchemeResult) { return children; } -export function useGetSchemaQuery({path, database}: {path: string; database: string}) { +export function useGetSchemaQuery({ + path, + database, + databaseFullPath, +}: { + path: string; + database: string; + databaseFullPath: string; +}) { const {currentData, isFetching, error, refetch, originalArgs} = schemaApi.useGetSchemaQuery({ path, database, + databaseFullPath, }); const data = currentData?.[path]; @@ -113,15 +128,20 @@ export function useGetSchemaQuery({path, database}: {path: string; database: str } const getSchemaSelector = createSelector( - (path: string) => path, - (_path: string, database: string) => database, - (path, database) => schemaApi.endpoints.getSchema.select({path, database}), + [ + (path: string) => path, + (_path: string, database: string) => database, + (_path: string, _database: string, databaseFullPath: string) => databaseFullPath, + ], + (path, database, databaseFullPath) => + schemaApi.endpoints.getSchema.select({path, database, databaseFullPath}), ); export const selectSchemaObjectData = createSelector( (state: RootState) => state, (_state: RootState, path: string) => path, - (_state: RootState, path: string, database: string) => getSchemaSelector(path, database), + (_state: RootState, path: string, database: string, databaseFullPath: string) => + getSchemaSelector(path, database, databaseFullPath), (state, path, selectSchemaData) => { return selectSchemaData(state).data?.[path]; }, diff --git a/src/store/reducers/schemaAcl/schemaAcl.ts b/src/store/reducers/schemaAcl/schemaAcl.ts index 230d3f7d2b..5ecc9bcf88 100644 --- a/src/store/reducers/schemaAcl/schemaAcl.ts +++ b/src/store/reducers/schemaAcl/schemaAcl.ts @@ -12,16 +12,18 @@ export const schemaAclApi = api.injectEndpoints({ path, database, dialect, + databaseFullPath, }: { path: string; database: string; dialect: string; + databaseFullPath: string; }, {signal}, ) => { try { const data = await window.api.viewer.getSchemaAcl( - {path, database, dialect}, + {path: {path, databaseFullPath}, database, dialect}, {signal}, ); return { @@ -43,15 +45,17 @@ export const schemaAclApi = api.injectEndpoints({ { database, dialect, + databaseFullPath, }: { database: string; + databaseFullPath: string; dialect: string; }, {signal}, ) => { try { const data = await window.api.viewer.getAvailablePermissions( - {path: database, database, dialect}, + {path: {path: databaseFullPath, databaseFullPath}, database, dialect}, {signal}, ); @@ -64,14 +68,26 @@ export const schemaAclApi = api.injectEndpoints({ }, }), updateAccess: build.mutation({ - queryFn: async (props: { + queryFn: async ({ + database, + databaseFullPath, + path, + rights, + dialect, + }: { database: string; + databaseFullPath: string; path: string; rights: AccessRightsUpdateRequest; dialect: string; }) => { try { - const data = await window.api.viewer.updateAccessRights(props); + const data = await window.api.viewer.updateAccessRights({ + database, + rights, + dialect, + path: {path, databaseFullPath}, + }); return {data}; } catch (error) { return {error}; @@ -86,28 +102,39 @@ export const schemaAclApi = api.injectEndpoints({ const createGetSchemaAclSelector = createSelector( (path: string) => path, (_path: string, database: string) => database, - (_path: string, _database: string, dialect: string) => dialect, - (path, database, dialect) => - schemaAclApi.endpoints.getSchemaAcl.select({path, database, dialect}), + (_path: string, _database: string, databaseFullPath: string) => databaseFullPath, + (_path: string, _database: string, _databaseFullPath: string, dialect: string) => dialect, + (path, database, databaseFullPath, dialect) => + schemaAclApi.endpoints.getSchemaAcl.select({path, database, databaseFullPath, dialect}), ); export const selectSchemaOwner = createSelector( (state: RootState) => state, - (_state: RootState, path: string, database: string, dialect: string) => - createGetSchemaAclSelector(path, database, dialect), + ( + _state: RootState, + path: string, + database: string, + databaseFullPath: string, + dialect: string, + ) => createGetSchemaAclSelector(path, database, databaseFullPath, dialect), (state, selectGetSchemaAcl) => selectGetSchemaAcl(state).data?.owner, ); const selectAccessRights = createSelector( (state: RootState) => state, - (_state: RootState, path: string, database: string, dialect: string) => - createGetSchemaAclSelector(path, database, dialect), + ( + _state: RootState, + path: string, + database: string, + databaseFullPath: string, + dialect: string, + ) => createGetSchemaAclSelector(path, database, databaseFullPath, dialect), (state, selectGetSchemaAcl) => selectGetSchemaAcl(state).data, ); const selectRightsMap = createSelector( - (state: RootState, path: string, database: string, dialect: string) => - selectAccessRights(state, path, database, dialect), + (state: RootState, path: string, database: string, databaseFullPath: string, dialect: string) => + selectAccessRights(state, path, database, databaseFullPath, dialect), (data) => { if (!data) { return null; @@ -153,8 +180,8 @@ const selectRightsMap = createSelector( ); export const selectPreparedRights = createSelector( - (state: RootState, path: string, database: string, dialect: string) => - selectRightsMap(state, path, database, dialect), + (state: RootState, path: string, database: string, databaseFullPath: string, dialect: string) => + selectRightsMap(state, path, database, databaseFullPath, dialect), (data) => { if (!data) { return null; @@ -175,8 +202,9 @@ export const selectSubjectExplicitRights = createSelector( _subject: string | undefined, path: string, database: string, + databaseFullPath: string, dialect: string, - ) => selectRightsMap(state, path, database, dialect), + ) => selectRightsMap(state, path, database, databaseFullPath, dialect), ], (subject, rightsMap) => { if (!subject || !rightsMap) { @@ -196,8 +224,9 @@ export const selectSubjectInheritedRights = createSelector( _subject: string | undefined, path: string, database: string, + databaseFullPath: string, dialect: string, - ) => selectRightsMap(state, path, database, dialect), + ) => selectRightsMap(state, path, database, databaseFullPath, dialect), ], (subject, rightsMap) => { if (!subject || !rightsMap) { @@ -216,15 +245,20 @@ export const selectSubjectInheritedRights = createSelector( const createGetAvailablePermissionsSelector = createSelector( (database: string) => database, - (_database: string, dialect: string) => dialect, - (database, dialect) => - schemaAclApi.endpoints.getAvailablePermissions.select({database, dialect}), + (_database: string, databaseFullPath: string) => databaseFullPath, + (_database: string, _databaseFullPath: string, dialect: string) => dialect, + (database, databaseFullPath, dialect) => + schemaAclApi.endpoints.getAvailablePermissions.select({ + database, + dialect, + databaseFullPath, + }), ); // Then create the main selector that extracts the available permissions data export const selectAvailablePermissions = createSelector( (state: RootState) => state, - (_state: RootState, database: string, dialect: string) => - createGetAvailablePermissionsSelector(database, dialect), + (_state: RootState, database: string, databaseFullPath: string, dialect: string) => + createGetAvailablePermissionsSelector(database, databaseFullPath, dialect), (state, selectGetAvailablePermissions) => selectGetAvailablePermissions(state).data, ); diff --git a/src/store/reducers/shardsWorkload/shardsWorkload.ts b/src/store/reducers/shardsWorkload/shardsWorkload.ts index 5b449d0a3b..6b1dc86a9d 100644 --- a/src/store/reducers/shardsWorkload/shardsWorkload.ts +++ b/src/store/reducers/shardsWorkload/shardsWorkload.ts @@ -1,11 +1,14 @@ -import {dateTimeParse, isLikeRelative} from '@gravity-ui/date-utils'; +import {isLikeRelative} from '@gravity-ui/date-utils'; import type {SortOrder} from '@gravity-ui/react-data-table'; import {createSlice} from '@reduxjs/toolkit'; import type {PayloadAction} from '@reduxjs/toolkit'; -import {QUERY_TECHNICAL_MARK} from '../../../utils/constants'; -import {prepareOrderByFromTableSort} from '../../../utils/hooks/useTableSort'; import {isQueryErrorResponse, parseQueryAPIResponse} from '../../../utils/query'; +import { + createPartitionStatsQuery, + createTimeConditions, + createTopPartitionsHistoryQuery, +} from '../../../utils/shardsQueryBuilders'; import {api} from '../api'; import type {ShardsWorkloadFilters} from './types'; @@ -13,88 +16,34 @@ import {EShardsWorkloadMode} from './types'; const initialState: ShardsWorkloadFilters = {}; -function getFiltersConditions(filters?: ShardsWorkloadFilters) { - const conditions: string[] = []; - const to = dateTimeParse(Number(filters?.to) || filters?.to)?.valueOf(); - const from = dateTimeParse(Number(filters?.from) || filters?.from)?.valueOf(); - - if (from && to && from > to) { - throw new Error('Invalid date range'); - } - - if (from) { - // matching `from` & `to` is an edge case - // other cases should not include the starting point, since intervals are stored using the ending time - const gt = to === from ? '>=' : '>'; - conditions.push(`IntervalEnd ${gt} Timestamp('${new Date(from).toISOString()}')`); - } - - if (to) { - conditions.push(`IntervalEnd <= Timestamp('${new Date(to).toISOString()}')`); - } - - return conditions.join(' AND '); -} - function createShardQueryHistorical( path: string, - database: string, + databaseFullPath: string, filters?: ShardsWorkloadFilters, sortOrder?: SortOrder[], ) { - const pathSelect = `CAST(SUBSTRING(CAST(Path AS String), ${database.length + 1}) AS Utf8) AS RelativePath`; - - let where = ''; - - let pathFilter = ''; - - if (path) { - pathFilter = `Path='${path}' OR Path LIKE '${path}/%'`; - } - - if (pathFilter) { - where = `(${pathFilter})`; - } - const filterConditions = getFiltersConditions(filters); - - if (filterConditions.length) { - where = where ? `${where} AND ${filterConditions}` : filterConditions; - } - - const orderBy = prepareOrderByFromTableSort(sortOrder); - - const whereStatement = where ? `WHERE ${where}` : ''; - - return `${QUERY_TECHNICAL_MARK} -SELECT - ${pathSelect}, - \`.sys/top_partitions_one_hour\`.* -FROM \`.sys/top_partitions_one_hour\` -${whereStatement} -${orderBy} -LIMIT 20`; + const timeConditions = createTimeConditions(filters?.from, filters?.to); + + return createTopPartitionsHistoryQuery({ + databaseFullPath, + path, + timeConditions, + sortOrder, + limit: 20, + }); } -function createShardQueryImmediate(path: string, database: string, sortOrder?: SortOrder[]) { - const pathSelect = `CAST(SUBSTRING(CAST(Path AS String), ${database.length + 1}) AS Utf8) AS RelativePath`; - - const orderBy = prepareOrderByFromTableSort(sortOrder); - - const where = path - ? `WHERE - Path='${path}' - OR Path LIKE '${path}/%' - ` - : ''; - - return `${QUERY_TECHNICAL_MARK} -SELECT - ${pathSelect}, - \`.sys/partition_stats\`.* -FROM \`.sys/partition_stats\` -${where} -${orderBy} -LIMIT 20`; +function createShardQueryImmediate( + path: string, + databaseFullPath: string, + sortOrder?: SortOrder[], +) { + return createPartitionStatsQuery({ + databaseFullPath, + path, + sortOrder, + limit: 20, + }); } const queryAction = 'execute-scan'; diff --git a/src/store/reducers/tableSchemaData.ts b/src/store/reducers/tableSchemaData.ts index 1c5eb18acf..5be701c021 100644 --- a/src/store/reducers/tableSchemaData.ts +++ b/src/store/reducers/tableSchemaData.ts @@ -14,7 +14,8 @@ import {viewSchemaApi} from './viewSchema/viewSchema'; interface GetTableSchemaDataParams { path: string; - tenantName: string; + database: string; + databaseFullPath: string; type: EPathType; } @@ -23,13 +24,14 @@ const TABLE_SCHEMA_TIMEOUT = SECOND_IN_MS * 5; export const tableSchemaDataApi = api.injectEndpoints({ endpoints: (build) => ({ getTableSchemaData: build.query({ - queryFn: async ({path, tenantName, type}, {dispatch}) => { + queryFn: async ({path, database, type, databaseFullPath}, {dispatch}) => { try { if (isViewType(type)) { const response = await dispatch( viewSchemaApi.endpoints.getViewSchema.initiate({ - database: tenantName, + database, path, + databaseFullPath, timeout: TABLE_SCHEMA_TIMEOUT, }), ); @@ -45,8 +47,9 @@ export const tableSchemaDataApi = api.injectEndpoints({ const schemaData = await dispatch( overviewApi.endpoints.getOverview.initiate({ path, - database: tenantName, + database, timeout: TABLE_SCHEMA_TIMEOUT, + databaseFullPath, }), ); const result = prepareSchemaData(type, schemaData.data); diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts index abf18a72fe..395f535d96 100644 --- a/src/store/reducers/tenant/tenant.ts +++ b/src/store/reducers/tenant/tenant.ts @@ -64,11 +64,11 @@ export const tenantApi = api.injectEndpoints({ getTenantInfo: builder.query({ queryFn: async ( { - path, + database, clusterName, isMetaDatabasesAvailable, }: { - path: string; + database: string; clusterName?: string; isMetaDatabasesAvailable: boolean; }, @@ -78,21 +78,25 @@ export const tenantApi = api.injectEndpoints({ let tenantData: TTenantInfo; if (window.api.meta && clusterName && isMetaDatabasesAvailable) { tenantData = await window.api.meta.getTenantsV2( - {path, clusterName}, + {database, clusterName}, {signal}, ); } else if (window.api.meta && clusterName) { tenantData = await window.api.meta.getTenants( - {path, clusterName}, + {database, clusterName}, {signal}, ); } else { - tenantData = await window.api.viewer.getTenantInfo({path}, {signal}); + tenantData = await window.api.viewer.getTenantInfo({database}, {signal}); } const databases = prepareTenants(tenantData.TenantInfo || []); // previous meta versions do not support filtering databases by name const data = - databases.find((tenant) => tenant.Name === path) ?? databases[0] ?? null; + databases.find( + (tenant) => tenant.Name === database || tenant.Id === database, + ) ?? + databases[0] ?? + null; return {data}; } catch (error) { return {error}; @@ -100,8 +104,8 @@ export const tenantApi = api.injectEndpoints({ }, providesTags: ['All'], serializeQueryArgs: ({queryArgs}) => { - const {clusterName, path} = queryArgs; - return {clusterName, path}; + const {clusterName, database} = queryArgs; + return {clusterName, database}; }, }), @@ -122,17 +126,17 @@ export const tenantApi = api.injectEndpoints({ overrideExisting: 'throw', }); -export function useTenantBaseInfo(path: string) { +export function useTenantBaseInfo(database: string) { const clusterNameFromQuery = useClusterNameFromQuery(); const isMetaDatabasesAvailable = useDatabasesAvailable(); - const {currentData} = tenantApi.useGetTenantInfoQuery( + const {currentData, isLoading, isError} = tenantApi.useGetTenantInfoQuery( { - path, + database, clusterName: clusterNameFromQuery, isMetaDatabasesAvailable, }, - {skip: !path}, + {skip: !database}, ); const {ControlPlane, Name, Id, Type} = currentData || {}; @@ -142,5 +146,7 @@ export function useTenantBaseInfo(path: string) { name: Name, id: Id, databaseType: Type, + isLoading, + isError, }; } diff --git a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts index 161b494461..665c2124e9 100644 --- a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +++ b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts @@ -1,21 +1,16 @@ -import {QUERY_TECHNICAL_MARK, TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../utils/constants'; +import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../utils/constants'; import {isQueryErrorResponse, parseQueryAPIResponse} from '../../../../utils/query'; +import {createPartitionStatsQuery} from '../../../../utils/shardsQueryBuilders'; import {api} from '../../api'; -function createShardQuery(path: string, database: string) { - const pathSelect = `CAST(SUBSTRING(CAST(Path AS String), ${database.length + 1}) AS Utf8) AS RelativePath`; - return `${QUERY_TECHNICAL_MARK} -SELECT - ${pathSelect}, - Path, - TabletId, - CPUCores, -FROM \`.sys/partition_stats\` -WHERE - Path='${path}' - OR Path LIKE '${path}/%' -ORDER BY CPUCores DESC -LIMIT ${TENANT_OVERVIEW_TABLES_LIMIT}`; +function createShardQuery(path: string, databaseFullPath: string) { + return createPartitionStatsQuery({ + databaseFullPath, + path, + selectFields: ['Path', 'TabletId', 'CPUCores'], + sortOrder: [{columnId: 'CPUCores', order: -1}], + limit: TENANT_OVERVIEW_TABLES_LIMIT, + }); } const queryAction = 'execute-scan'; @@ -26,9 +21,9 @@ export const topShardsApi = api.injectEndpoints({ queryFn: async ( { database, - path = '', + path, databaseFullPath, - }: {database: string; path?: string; databaseFullPath: string}, + }: {database: string; path: string; databaseFullPath: string}, {signal}, ) => { try { diff --git a/src/store/reducers/topic.ts b/src/store/reducers/topic.ts index 3f64f058a8..a36e506b44 100644 --- a/src/store/reducers/topic.ts +++ b/src/store/reducers/topic.ts @@ -13,9 +13,20 @@ export const TOPIC_MESSAGE_SIZE_LIMIT = 100; export const topicApi = api.injectEndpoints({ endpoints: (build) => ({ getTopic: build.query({ - queryFn: async (params: {path: string; database: string}) => { + queryFn: async ({ + path, + database, + databaseFullPath, + }: { + path: string; + database: string; + databaseFullPath: string; + }) => { try { - const data = await window.api.viewer.getTopic(params); + const data = await window.api.viewer.getTopic({ + path: {path, databaseFullPath}, + database, + }); // On older version it can return HTML page of Developer UI with an error if (typeof data !== 'object') { return {error: {}}; @@ -48,17 +59,21 @@ export const topicApi = api.injectEndpoints({ const createGetTopicSelector = createSelector( (path: string) => path, (_path: string, database: string) => database, - (path, database) => topicApi.endpoints.getTopic.select({path, database}), + (_path: string, _database: string, databaseFullPath: string) => databaseFullPath, + (path, database, databaseFullPath) => + topicApi.endpoints.getTopic.select({path, database, databaseFullPath}), ); const selectTopicStats = createSelector( (state: RootState) => state, - (_state: RootState, path: string, database: string) => createGetTopicSelector(path, database), + (_state: RootState, path: string, database: string, databaseFullPath: string) => + createGetTopicSelector(path, database, databaseFullPath), (state, selectGetTopic) => selectGetTopic(state).data?.topic_stats, ); const selectConsumers = createSelector( (state: RootState) => state, - (_state: RootState, path: string, database: string) => createGetTopicSelector(path, database), + (_state: RootState, path: string, database: string, databaseFullPath: string) => + createGetTopicSelector(path, database, databaseFullPath), (state, selectGetTopic) => selectGetTopic(state).data?.consumers, ); diff --git a/src/store/reducers/viewSchema/viewSchema.ts b/src/store/reducers/viewSchema/viewSchema.ts index 83a30be80d..5b1faa83c7 100644 --- a/src/store/reducers/viewSchema/viewSchema.ts +++ b/src/store/reducers/viewSchema/viewSchema.ts @@ -11,16 +11,19 @@ export const viewSchemaApi = api.injectEndpoints({ queryFn: async ({ database, path, + databaseFullPath, timeout, }: { database: string; path: string; + databaseFullPath: string; timeout?: number; }) => { try { + const relativePath = path.replace(databaseFullPath, ''); const response = await window.api.viewer.sendQuery( { - query: createViewSchemaQuery(path), + query: createViewSchemaQuery(relativePath), database, action: 'execute-scan', timeout, diff --git a/src/types/api/common.ts b/src/types/api/common.ts index 97e6c662e8..f9f467b33b 100644 --- a/src/types/api/common.ts +++ b/src/types/api/common.ts @@ -5,3 +5,5 @@ export interface IProtobufTimeObject { } export type BackendSortParam = `-${T}` | `+${T}` | T; + +export type SchemaPathParam = {path: string; databaseFullPath: string}; diff --git a/src/types/api/nodes.ts b/src/types/api/nodes.ts index 7677e2e803..2436d492a4 100644 --- a/src/types/api/nodes.ts +++ b/src/types/api/nodes.ts @@ -1,4 +1,4 @@ -import type {BackendSortParam} from './common'; +import type {BackendSortParam, SchemaPathParam} from './common'; import type {EFlag} from './enums'; import type {TPDiskStateInfo} from './pdisk'; import type {TTabletStateInfo} from './tablet'; @@ -325,11 +325,18 @@ export type NodesSortValue = export type NodesSort = BackendSortParam; +export interface NodesPathParam { + path: string; + databaseFullPath: string; +} + export interface NodesRequestParams { /** @deprecated use database instead */ tenant?: string; database?: string; - path?: string; + + path?: SchemaPathParam; + node_id?: string | number; group_id?: string | number; pool?: string; diff --git a/src/types/store/heatmap.ts b/src/types/store/heatmap.ts index e2184ad9d2..b369029c65 100644 --- a/src/types/store/heatmap.ts +++ b/src/types/store/heatmap.ts @@ -17,4 +17,5 @@ export interface IHeatmapState { export interface IHeatmapApiRequestParams { path: string; database: string; + databaseFullPath: string; } diff --git a/src/types/store/tablets.ts b/src/types/store/tablets.ts index 77b6aaf7dc..78baef390c 100644 --- a/src/types/store/tablets.ts +++ b/src/types/store/tablets.ts @@ -1,6 +1,8 @@ +import type {SchemaPathParam} from '../api/common'; + export interface TabletsApiRequestParams { nodeId?: string | number; - path?: string; + path?: SchemaPathParam; database?: string; filter?: string; } diff --git a/src/utils/shardsQueryBuilders.ts b/src/utils/shardsQueryBuilders.ts new file mode 100644 index 0000000000..e98f0691d4 --- /dev/null +++ b/src/utils/shardsQueryBuilders.ts @@ -0,0 +1,99 @@ +import {dateTimeParse} from '@gravity-ui/date-utils'; +import type {SortOrder} from '@gravity-ui/react-data-table'; + +import {QUERY_TECHNICAL_MARK} from './constants'; +import {prepareOrderByFromTableSort} from './hooks/useTableSort'; + +export function createTimeConditions(from?: string | number, to?: string | number): string { + const conditions: string[] = []; + const toTime = dateTimeParse(Number(to) || to)?.valueOf(); + const fromTime = dateTimeParse(Number(from) || from)?.valueOf(); + + if (fromTime && toTime && fromTime > toTime) { + throw new Error('Invalid date range'); + } + + if (fromTime) { + // matching `from` & `to` is an edge case + // other cases should not include the starting point, since intervals are stored using the ending time + const gt = toTime === fromTime ? '>=' : '>'; + conditions.push(`IntervalEnd ${gt} Timestamp('${new Date(fromTime).toISOString()}')`); + } + + if (toTime) { + conditions.push(`IntervalEnd <= Timestamp('${new Date(toTime).toISOString()}')`); + } + + return conditions.join(' AND '); +} + +export function createPartitionStatsQuery(options: { + databaseFullPath: string; + path?: string; + selectFields?: string[]; + sortOrder?: SortOrder[]; + limit?: number; +}): string { + const {databaseFullPath, path = '', selectFields = ['*'], sortOrder, limit = 20} = options; + + const pathSelect = createRelativePathSelect(databaseFullPath); + const pathCondition = createPathWhereCondition(path); + const orderBy = prepareOrderByFromTableSort(sortOrder); + + const whereClause = pathCondition ? `WHERE ${pathCondition}` : ''; + const fields = selectFields.includes('*') + ? `${pathSelect}, \`.sys/partition_stats\`.*` + : `${pathSelect}, ${selectFields.join(', ')}`; + + return `${QUERY_TECHNICAL_MARK} +SELECT + ${fields} +FROM \`.sys/partition_stats\` +${whereClause} +${orderBy} +LIMIT ${limit}`; +} + +export function createTopPartitionsHistoryQuery(options: { + databaseFullPath: string; + path?: string; + timeConditions?: string; + sortOrder?: SortOrder[]; + limit?: number; +}): string { + const {databaseFullPath, path = '', timeConditions = '', sortOrder, limit = 20} = options; + + const pathSelect = createRelativePathSelect(databaseFullPath); + const pathCondition = createPathWhereCondition(path); + const orderBy = prepareOrderByFromTableSort(sortOrder); + + const conditions: string[] = []; + if (pathCondition) { + conditions.push(`(${pathCondition})`); + } + if (timeConditions) { + conditions.push(timeConditions); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + return `${QUERY_TECHNICAL_MARK} +SELECT + ${pathSelect}, + \`.sys/top_partitions_one_hour\`.* +FROM \`.sys/top_partitions_one_hour\` +${whereClause} +${orderBy} +LIMIT ${limit}`; +} + +function createRelativePathSelect(databaseFullPath: string): string { + return `CAST(SUBSTRING(CAST(Path AS String), ${databaseFullPath.length + 1}) AS Utf8) AS RelativePath`; +} + +function createPathWhereCondition(path: string): string { + if (!path) { + return ''; + } + return `Path='${path}' OR Path LIKE '${path}/%'`; +} diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts index ff864ff82b..20314fa984 100644 --- a/tests/suites/sidebar/sidebar.test.ts +++ b/tests/suites/sidebar/sidebar.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {PageModel} from '../../models/PageModel'; -import {tenantName} from '../../utils/constants'; +import {database} from '../../utils/constants'; import {toggleExperiment} from '../../utils/toggleExperiment'; import {TenantPage} from '../tenant/TenantPage'; @@ -111,8 +111,8 @@ test.describe('Test Sidebar', async () => { test('Pressing Ctrl+K in editor page opens hotkeys panel', async ({page}) => { // Open editor page const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts b/tests/suites/tenant/diagnostics/tabs/info.test.ts index f73cca7413..7661ff3c88 100644 --- a/tests/suites/tenant/diagnostics/tabs/info.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/info.test.ts @@ -1,14 +1,14 @@ import {expect, test} from '@playwright/test'; -import {tenantName} from '../../../../utils/constants'; +import {database} from '../../../../utils/constants'; import {TenantPage} from '../../TenantPage'; import {Diagnostics, DiagnosticsTab} from '../Diagnostics'; test.describe('Diagnostics Info tab', async () => { test('Info tab shows main page elements', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); @@ -21,8 +21,8 @@ test.describe('Diagnostics Info tab', async () => { test('Info tab shows resource utilization', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); @@ -56,7 +56,7 @@ test.describe('Diagnostics Info tab', async () => { message: 'Some degraded component', location: { database: { - name: tenantName, + name: database, }, }, }, @@ -66,8 +66,8 @@ test.describe('Diagnostics Info tab', async () => { }); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); @@ -99,8 +99,8 @@ test.describe('Diagnostics Info tab', async () => { }); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); @@ -127,7 +127,7 @@ test.describe('Diagnostics Info tab', async () => { message: 'Some informational issue', location: { database: { - name: tenantName, + name: database, }, }, }, @@ -137,8 +137,8 @@ test.describe('Diagnostics Info tab', async () => { }); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); diff --git a/tests/suites/tenant/diagnostics/tabs/nodes.test.ts b/tests/suites/tenant/diagnostics/tabs/nodes.test.ts index 375e854ff7..c755bd63a7 100644 --- a/tests/suites/tenant/diagnostics/tabs/nodes.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/nodes.test.ts @@ -1,6 +1,6 @@ import {expect, test} from '@playwright/test'; -import {tenantName} from '../../../../utils/constants'; +import {database} from '../../../../utils/constants'; import {DiagnosticsNodesTable} from '../../../paginatedTable/paginatedTable'; import {TenantPage} from '../../TenantPage'; import {Diagnostics, DiagnosticsTab} from '../Diagnostics'; @@ -8,8 +8,8 @@ import {Diagnostics, DiagnosticsTab} from '../Diagnostics'; test.describe('Diagnostics Nodes tab', async () => { test('Nodes tab shows nodes table with memory viewer', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); diff --git a/tests/suites/tenant/diagnostics/tabs/operations.test.ts b/tests/suites/tenant/diagnostics/tabs/operations.test.ts index 4a42aa4e52..19a313e479 100644 --- a/tests/suites/tenant/diagnostics/tabs/operations.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/operations.test.ts @@ -1,6 +1,6 @@ import {expect, test} from '@playwright/test'; -import {tenantName} from '../../../../utils/constants'; +import {database} from '../../../../utils/constants'; import {TenantPage} from '../../TenantPage'; import {Diagnostics, DiagnosticsTab} from '../Diagnostics'; @@ -19,8 +19,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupOperationsMock(page, {totalOperations: 80}); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -63,8 +63,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupOperationsMock(page, {totalOperations: 80}); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -112,8 +112,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupEmptyOperationsMock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -141,8 +141,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupOperation403Mock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -168,8 +168,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupOperationNetworkErrorMock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -199,8 +199,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupMalformedOperationsMock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -235,8 +235,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupPartialMalformedOperationsMock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; @@ -290,8 +290,8 @@ test.describe('Operations Tab - Infinite Query', () => { await setupOperationsMock(page, {totalOperations: 80}); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; diff --git a/tests/suites/tenant/diagnostics/tabs/queries.test.ts b/tests/suites/tenant/diagnostics/tabs/queries.test.ts index e1985405e2..73cceee422 100644 --- a/tests/suites/tenant/diagnostics/tabs/queries.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/queries.test.ts @@ -2,7 +2,7 @@ import {expect, test} from '@playwright/test'; import {prepareQueryWithPragmas} from '../../../../../src/store/reducers/query/utils'; import {defaultPragma} from '../../../../../src/utils/query'; -import {tenantName} from '../../../../utils/constants'; +import {database} from '../../../../utils/constants'; import {NavigationTabs, TenantPage} from '../../TenantPage'; import {longRunningQuery, longRunningStreamQuery} from '../../constants'; import {QueryEditor} from '../../queryEditor/models/QueryEditor'; @@ -19,8 +19,8 @@ import {setupTopQueriesMock} from '../mocks'; test.describe('Diagnostics Queries tab', async () => { test('No runnning queries in Queries if no queries are running', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); @@ -34,8 +34,8 @@ test.describe('Diagnostics Queries tab', async () => { test('Running query is shown if query is running', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'query', }; const tenantPage = new TenantPage(page); @@ -61,8 +61,8 @@ test.describe('Diagnostics Queries tab', async () => { test('Query tab defaults to Top mode', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topQueries', }; @@ -78,8 +78,8 @@ test.describe('Diagnostics Queries tab', async () => { test('Query Top tab shows expected column headers', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topQueries', }; @@ -103,8 +103,8 @@ test.describe('Diagnostics Queries tab', async () => { test('Query tab first row has values for all columns in Top mode', async ({page}) => { // First, run some CPU-intensive queries to generate data const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'query', }; const tenantPage = new TenantPage(page); @@ -145,8 +145,8 @@ test.describe('Diagnostics Queries tab', async () => { test('Query tab can switch between Top and Running modes', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topQueries', }; @@ -171,8 +171,8 @@ test.describe('Diagnostics Queries tab', async () => { test('Query tab allows changing between Per hour and Per minute views', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topQueries', }; @@ -211,8 +211,8 @@ test.describe('Diagnostics Queries tab', async () => { await setupTopQueriesMock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topQueries', }; @@ -256,8 +256,8 @@ test.describe('Diagnostics Queries tab', async () => { await setupTopQueriesMock(page); const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topQueries', }; diff --git a/tests/suites/tenant/diagnostics/tabs/schema.test.ts b/tests/suites/tenant/diagnostics/tabs/schema.test.ts index 1501947138..465b5a59bd 100644 --- a/tests/suites/tenant/diagnostics/tabs/schema.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/schema.test.ts @@ -1,6 +1,6 @@ import {expect, test} from '@playwright/test'; -import {dsVslotsSchema, tenantName} from '../../../../utils/constants'; +import {database, dsVslotsSchema} from '../../../../utils/constants'; import {TenantPage} from '../../TenantPage'; import {Diagnostics, DiagnosticsTab} from '../Diagnostics'; @@ -8,7 +8,7 @@ test.describe('Diagnostics Schema tab', async () => { test('Primary keys header is visible in Schema tab', async ({page}) => { const pageQueryParams = { schema: dsVslotsSchema, - database: tenantName, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); diff --git a/tests/suites/tenant/diagnostics/tabs/storage.test.ts b/tests/suites/tenant/diagnostics/tabs/storage.test.ts index 2be426183c..8e336b9ed6 100644 --- a/tests/suites/tenant/diagnostics/tabs/storage.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/storage.test.ts @@ -1,14 +1,14 @@ import {expect, test} from '@playwright/test'; -import {tenantName} from '../../../../utils/constants'; +import {database} from '../../../../utils/constants'; import {TenantPage} from '../../TenantPage'; import {Diagnostics, DiagnosticsTab} from '../Diagnostics'; test.describe('Diagnostics Storage tab', async () => { test('Storage tab shows Groups and Nodes views', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; const tenantPage = new TenantPage(page); diff --git a/tests/suites/tenant/diagnostics/tabs/topShards.test.ts b/tests/suites/tenant/diagnostics/tabs/topShards.test.ts index ce0581f89d..3279480214 100644 --- a/tests/suites/tenant/diagnostics/tabs/topShards.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/topShards.test.ts @@ -1,6 +1,6 @@ import {expect, test} from '@playwright/test'; -import {tenantName} from '../../../../utils/constants'; +import {database} from '../../../../utils/constants'; import {TenantPage} from '../../TenantPage'; import { Diagnostics, @@ -13,8 +13,8 @@ import {setupTopShardsHistoryMock} from '../mocks'; test.describe('Diagnostics TopShards tab', async () => { test('TopShards tab defaults to Immediate mode', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topShards', }; @@ -29,8 +29,8 @@ test.describe('Diagnostics TopShards tab', async () => { test('TopShards immediate tab shows all expected column headers', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topShards', }; @@ -48,8 +48,8 @@ test.describe('Diagnostics TopShards tab', async () => { test('TopShards history tab shows all expected column headers', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topShards', }; @@ -68,8 +68,8 @@ test.describe('Diagnostics TopShards tab', async () => { test('TopShards tab first row has values for all columns in Immediate mode', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topShards', }; @@ -95,8 +95,8 @@ test.describe('Diagnostics TopShards tab', async () => { // Now navigate to diagnostics page to check topShards const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topShards', }; @@ -119,8 +119,8 @@ test.describe('Diagnostics TopShards tab', async () => { test('TopShards tab can switch back to Immediate mode from Historical mode', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', diagnosticsTab: 'topShards', }; diff --git a/tests/suites/tenant/initialLoad.test.ts b/tests/suites/tenant/initialLoad.test.ts index d9f7414767..07afb780c5 100644 --- a/tests/suites/tenant/initialLoad.test.ts +++ b/tests/suites/tenant/initialLoad.test.ts @@ -1,12 +1,12 @@ import {expect, test} from '@playwright/test'; -import {backend, tenantName} from '../../utils/constants'; +import {backend, database} from '../../utils/constants'; import {TenantPage} from './TenantPage'; const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, tenantPage: 'diagnostics', }; diff --git a/tests/suites/tenant/queryEditor/planToSvg.test.ts b/tests/suites/tenant/queryEditor/planToSvg.test.ts index cca33d4254..9e0c27d412 100644 --- a/tests/suites/tenant/queryEditor/planToSvg.test.ts +++ b/tests/suites/tenant/queryEditor/planToSvg.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {STATISTICS_MODES} from '../../../../src/utils/query'; -import {tenantName} from '../../../utils/constants'; +import {database} from '../../../utils/constants'; import {toggleExperiment} from '../../../utils/toggleExperiment'; import {TenantPage} from '../TenantPage'; @@ -12,8 +12,8 @@ test.describe('Test Plan to SVG functionality', async () => { test.beforeEach(async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; diff --git a/tests/suites/tenant/queryEditor/queryEditor.test.ts b/tests/suites/tenant/queryEditor/queryEditor.test.ts index 5d395dbac7..edf3e33704 100644 --- a/tests/suites/tenant/queryEditor/queryEditor.test.ts +++ b/tests/suites/tenant/queryEditor/queryEditor.test.ts @@ -2,7 +2,7 @@ import {expect, test} from '@playwright/test'; import {QUERY_MODES, STATISTICS_MODES} from '../../../../src/utils/query'; import {getClipboardContent} from '../../../utils/clipboard'; -import {tenantName} from '../../../utils/constants'; +import {database} from '../../../utils/constants'; import {toggleExperiment} from '../../../utils/toggleExperiment'; import {NavigationTabs, TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage'; import { @@ -28,8 +28,8 @@ test.describe('Test Query Editor', async () => { test.beforeEach(async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; diff --git a/tests/suites/tenant/queryEditor/querySettings.test.ts b/tests/suites/tenant/queryEditor/querySettings.test.ts index 660208d6e1..aac385c7ce 100644 --- a/tests/suites/tenant/queryEditor/querySettings.test.ts +++ b/tests/suites/tenant/queryEditor/querySettings.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {QUERY_MODES, TRANSACTION_MODES} from '../../../../src/utils/query'; -import {tenantName} from '../../../utils/constants'; +import {database} from '../../../utils/constants'; import {toggleExperiment} from '../../../utils/toggleExperiment'; import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage'; import {longRunningQuery} from '../constants'; @@ -13,8 +13,8 @@ test.describe('Test Query Settings', async () => { test.beforeEach(async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; diff --git a/tests/suites/tenant/queryEditor/queryStatus.test.ts b/tests/suites/tenant/queryEditor/queryStatus.test.ts index 02eb1a89d8..e13cffc6e6 100644 --- a/tests/suites/tenant/queryEditor/queryStatus.test.ts +++ b/tests/suites/tenant/queryEditor/queryStatus.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {STATISTICS_MODES} from '../../../../src/utils/query'; -import {tenantName} from '../../../utils/constants'; +import {database} from '../../../utils/constants'; import {TenantPage} from '../TenantPage'; import {longRunningQuery} from '../constants'; @@ -12,8 +12,8 @@ test.describe('Test Query Execution Status', async () => { test.beforeEach(async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; diff --git a/tests/suites/tenant/queryEditor/queryTemplates.test.ts b/tests/suites/tenant/queryEditor/queryTemplates.test.ts index c27872e488..f143a5108c 100644 --- a/tests/suites/tenant/queryEditor/queryTemplates.test.ts +++ b/tests/suites/tenant/queryEditor/queryTemplates.test.ts @@ -1,6 +1,6 @@ import {expect, test} from '@playwright/test'; -import {dsVslotsSchema, dsVslotsTableName, tenantName} from '../../../utils/constants'; +import {database, dsVslotsSchema, dsVslotsTableName} from '../../../utils/constants'; import {TenantPage} from '../TenantPage'; import {SavedQueriesTable} from '../savedQueries/models/SavedQueriesTable'; import {ObjectSummary} from '../summary/ObjectSummary'; @@ -20,7 +20,7 @@ test.describe('Query Templates', () => { test.beforeEach(async ({page}) => { const pageQueryParams = { schema: dsVslotsSchema, - database: tenantName, + database, general: 'query', }; const tenantPage = new TenantPage(page); diff --git a/tests/suites/tenant/queryHistory/queryHistory.test.ts b/tests/suites/tenant/queryHistory/queryHistory.test.ts index e69a56acf9..126fbc535c 100644 --- a/tests/suites/tenant/queryHistory/queryHistory.test.ts +++ b/tests/suites/tenant/queryHistory/queryHistory.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {QUERY_MODES} from '../../../../src/utils/query'; -import {tenantName} from '../../../utils/constants'; +import {database} from '../../../utils/constants'; import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage'; import {QueryEditor, QueryTabs} from '../queryEditor/models/QueryEditor'; @@ -13,8 +13,8 @@ test.describe('Query History', () => { test.beforeEach(async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; diff --git a/tests/suites/tenant/savedQueries/savedQueries.test.ts b/tests/suites/tenant/savedQueries/savedQueries.test.ts index 66d8d097ea..6d82c45292 100644 --- a/tests/suites/tenant/savedQueries/savedQueries.test.ts +++ b/tests/suites/tenant/savedQueries/savedQueries.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {v4 as uuidv4} from 'uuid'; -import {dsVslotsSchema, tenantName} from '../../../utils/constants'; +import {database, dsVslotsSchema} from '../../../utils/constants'; import {TenantPage} from '../TenantPage'; import {QueryTabs} from '../queryEditor/models/QueryEditor'; @@ -11,7 +11,7 @@ test.describe('Saved Queries', () => { test.beforeEach(async ({page}) => { const pageQueryParams = { schema: dsVslotsSchema, - database: tenantName, + database, general: 'query', }; diff --git a/tests/suites/tenant/summary/objectSummary.test.ts b/tests/suites/tenant/summary/objectSummary.test.ts index ca828c0d7a..448d81b56b 100644 --- a/tests/suites/tenant/summary/objectSummary.test.ts +++ b/tests/suites/tenant/summary/objectSummary.test.ts @@ -4,10 +4,10 @@ import {wait} from '../../../../src/utils'; import {getClipboardContent} from '../../../utils/clipboard'; import { backend, + database, dsStoragePoolsTableName, dsVslotsSchema, dsVslotsTableName, - tenantName, } from '../../../utils/constants'; import {TenantPage} from '../TenantPage'; import {QueryEditor} from '../queryEditor/models/QueryEditor'; @@ -19,7 +19,7 @@ test.describe('Object Summary', async () => { test.beforeEach(async ({page}) => { const pageQueryParams = { schema: dsVslotsSchema, - database: tenantName, + database, general: 'query', }; const tenantPage = new TenantPage(page); @@ -161,7 +161,7 @@ test.describe('Object Summary', async () => { test('Copy path copies correct path to clipboard', async ({page}) => { const pageQueryParams = { schema: dsVslotsSchema, - database: tenantName, + database, general: 'query', }; const tenantPage = new TenantPage(page); @@ -187,8 +187,8 @@ test.describe('Object Summary', async () => { test('Create directory in local node', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; const tenantPage = new TenantPage(page); @@ -213,8 +213,8 @@ test.describe('Object Summary', async () => { test('Refresh button updates tree view after creating table', async ({page}) => { const pageQueryParams = { - schema: tenantName, - database: tenantName, + schema: database, + database, general: 'query', }; const tenantPage = new TenantPage(page); diff --git a/tests/utils/constants.ts b/tests/utils/constants.ts index 01f320fae3..55ec13180b 100644 --- a/tests/utils/constants.ts +++ b/tests/utils/constants.ts @@ -7,7 +7,7 @@ export const authPage = 'auth'; export const tenantPage = 'tenant'; // Entities -export const tenantName = '/local'; +export const database = '/local'; export const dsVslotsSchema = '/local/.sys/ds_vslots'; export const dsVslotsTableName = 'ds_vslots'; export const dsStorageStatsTableName = 'ds_storage_stats'; From 16d17b159fc0a71d04ba30b73dda66428f7ce51b Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Fri, 12 Sep 2025 10:17:59 +0300 Subject: [PATCH 2/5] fix: review --- .../CreateDirectoryDialog/CreateDirectoryDialog.tsx | 5 ++++- .../Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx | 3 +-- src/containers/Tenant/utils/schemaActions.tsx | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx b/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx index 8ac6f6a34a..8afb0224d3 100644 --- a/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx +++ b/src/containers/Tenant/ObjectSummary/CreateDirectoryDialog/CreateDirectoryDialog.tsx @@ -6,6 +6,7 @@ import {ResponseError} from '../../../../components/Errors/ResponseError'; import {schemaApi} from '../../../../store/reducers/schema/schema'; import {cn} from '../../../../utils/cn'; import i18n from '../../i18n'; +import {transformPath} from '../transformPath'; import './CreateDirectoryDialog.scss'; @@ -76,6 +77,8 @@ export function CreateDirectoryDialog({ }); }; + const relativeParentPath = transformPath(parentPath, databaseFullPath); + return ( @@ -94,7 +97,7 @@ export function CreateDirectoryDialog({ {i18n('schema.tree.dialog.description')} - {`${parentPath}/`} + {`${relativeParentPath}/`}
{ - const prefix = databaseFullPath === parentPath ? '' : `${databaseFullPath}/`; - const newPath = `${prefix}${parentPath}/${relativePath}`; + const newPath = `${parentPath}/${relativePath}`; onActivePathUpdate(newPath); setSchemaTreeKey(newPath); }; diff --git a/src/containers/Tenant/utils/schemaActions.tsx b/src/containers/Tenant/utils/schemaActions.tsx index ef57a1e589..d1781b1926 100644 --- a/src/containers/Tenant/utils/schemaActions.tsx +++ b/src/containers/Tenant/utils/schemaActions.tsx @@ -94,7 +94,7 @@ const bindActions = ( return { createDirectory: showCreateDirectoryDialog ? () => { - showCreateDirectoryDialog(params.relativePath); + showCreateDirectoryDialog(params.path); } : undefined, getConnectToDBDialog: () => getConnectToDBDialog?.({database: params.database}), From e6c6aa20a686e5cdc0ca807a3d23397397c64324 Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Fri, 12 Sep 2025 10:36:11 +0300 Subject: [PATCH 3/5] fix: review --- .../AccessRights/components/ChangeOwnerDialog.tsx | 2 ++ .../components/RightsTable/RevokeAllRightsDialog.tsx | 2 ++ .../Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx | 6 +----- .../Diagnostics/TenantOverview/TenantCpu/TopShards.tsx | 5 ++--- .../tenantOverview/topShards/tenantOverviewTopShards.ts | 8 ++------ src/store/reducers/viewSchema/viewSchema.ts | 3 ++- src/utils/shardsQueryBuilders.ts | 4 +++- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx index 62052f63db..b523d870b5 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx @@ -21,11 +21,13 @@ interface GetChangeOwnerDialogProps { export async function getChangeOwnerDialog({ path, database, + databaseFullPath, }: GetChangeOwnerDialogProps): Promise { return await NiceModal.show(CHANGE_OWNER_DIALOG, { id: CHANGE_OWNER_DIALOG, path, database, + databaseFullPath, }); } diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx index fbdc3b7378..2ea01846c8 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/RightsTable/RevokeAllRightsDialog.tsx @@ -26,12 +26,14 @@ export async function getRevokeAllRightsDialog({ path, database, subject, + databaseFullPath, }: GetRevokeAllRightsDialogProps): Promise { return await NiceModal.show(REVOKE_ALL_RIGHTS_DIALOG, { id: REVOKE_ALL_RIGHTS_DIALOG, path, database, subject, + databaseFullPath, }); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index a317ce2f1e..f6afbc81a0 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -64,11 +64,7 @@ export function TenantCpu({ )} - + { +export const TopShards = ({database, databaseFullPath}: TopShardsProps) => { const ShardsTable = useComponent('ShardsTable'); const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, isFetching, error} = topShardsApi.useGetTopShardsQuery( - {database, path, databaseFullPath}, + {database, databaseFullPath}, {pollingInterval: autoRefreshInterval}, ); diff --git a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts index 665c2124e9..e4b6ebbc54 100644 --- a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +++ b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts @@ -19,17 +19,13 @@ export const topShardsApi = api.injectEndpoints({ endpoints: (builder) => ({ getTopShards: builder.query({ queryFn: async ( - { - database, - path, - databaseFullPath, - }: {database: string; path: string; databaseFullPath: string}, + {database, databaseFullPath}: {database: string; databaseFullPath: string}, {signal}, ) => { try { const response = await window.api.viewer.sendQuery( { - query: createShardQuery(path, databaseFullPath), + query: createShardQuery(databaseFullPath, databaseFullPath), database, action: queryAction, internal_call: true, diff --git a/src/store/reducers/viewSchema/viewSchema.ts b/src/store/reducers/viewSchema/viewSchema.ts index 5b1faa83c7..56a1f22bc9 100644 --- a/src/store/reducers/viewSchema/viewSchema.ts +++ b/src/store/reducers/viewSchema/viewSchema.ts @@ -1,3 +1,4 @@ +import {transformPath} from '../../../containers/Tenant/ObjectSummary/transformPath'; import {isQueryErrorResponse} from '../../../utils/query'; import {api} from '../api'; @@ -20,7 +21,7 @@ export const viewSchemaApi = api.injectEndpoints({ timeout?: number; }) => { try { - const relativePath = path.replace(databaseFullPath, ''); + const relativePath = transformPath(path, databaseFullPath); const response = await window.api.viewer.sendQuery( { query: createViewSchemaQuery(relativePath), diff --git a/src/utils/shardsQueryBuilders.ts b/src/utils/shardsQueryBuilders.ts index e98f0691d4..16d9414cbe 100644 --- a/src/utils/shardsQueryBuilders.ts +++ b/src/utils/shardsQueryBuilders.ts @@ -95,5 +95,7 @@ function createPathWhereCondition(path: string): string { if (!path) { return ''; } - return `Path='${path}' OR Path LIKE '${path}/%'`; + // Escape single quotes to prevent SQL injection + const escapedPath = path.replace(/'/g, "''"); + return `Path='${escapedPath}' OR Path LIKE '${escapedPath}/%'`; } From 82ddfebeb173ec8bd1535ea050caa828f2e981bb Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Fri, 12 Sep 2025 10:44:31 +0300 Subject: [PATCH 4/5] fix: focus fixes --- .../AccessRights/components/ChangeOwnerDialog.tsx | 11 ++++++++++- .../Tenant/GrantAccess/components/SubjectInput.tsx | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx b/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx index b523d870b5..d8a32b664a 100644 --- a/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx +++ b/src/containers/Tenant/Diagnostics/AccessRights/components/ChangeOwnerDialog.tsx @@ -74,6 +74,8 @@ function ChangeOwnerDialog({ const [updateOwner, updateOwnerResponse] = schemaAclApi.useUpdateAccessMutation(); const dialect = useAclSyntax(); + const inputRef = React.useRef(null); + const handleTyping = (value: string) => { setNewOwner(value); setRequestErrorMessage(''); @@ -100,11 +102,18 @@ function ChangeOwnerDialog({ }); }; return ( - +
{!renderInline && renderLabels('wrap')} From 9c03d37f07b6ba632221e3ae59935fdb0c235345 Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Mon, 15 Sep 2025 08:15:43 +0300 Subject: [PATCH 5/5] fix: review --- src/containers/Tablet/Tablet.tsx | 3 ++- src/types/api/nodes.ts | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/containers/Tablet/Tablet.tsx b/src/containers/Tablet/Tablet.tsx index f9403525b2..da5964550c 100644 --- a/src/containers/Tablet/Tablet.tsx +++ b/src/containers/Tablet/Tablet.tsx @@ -88,9 +88,10 @@ export function Tablet() { database, tabletId: id, tabletType, + databaseName: databaseFullPath, }), ); - }, [dispatch, database, id, tabletType]); + }, [dispatch, database, id, tabletType, databaseFullPath]); const {Leader} = tablet; const metaItems: string[] = []; diff --git a/src/types/api/nodes.ts b/src/types/api/nodes.ts index 2436d492a4..1d3e504a3b 100644 --- a/src/types/api/nodes.ts +++ b/src/types/api/nodes.ts @@ -325,11 +325,6 @@ export type NodesSortValue = export type NodesSort = BackendSortParam; -export interface NodesPathParam { - path: string; - databaseFullPath: string; -} - export interface NodesRequestParams { /** @deprecated use database instead */ tenant?: string;