diff --git a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx index ceb3d30508..b5bd408767 100644 --- a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx +++ b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx @@ -1,14 +1,14 @@ import {ClipboardButton} from '@gravity-ui/uikit'; -import {skipToken} from '@reduxjs/toolkit/query'; import JSONTree from 'react-json-inspector'; import {shallowEqual} from 'react-redux'; import {ResponseError} from '../../../../components/Errors/ResponseError'; import {Loader} from '../../../../components/Loader'; -import {overviewApi} from '../../../../store/reducers/overview/overview'; -import {selectSchemaMergedChildrenPaths} from '../../../../store/reducers/schema/schema'; +import { + selectSchemaMergedChildrenPaths, + useGetMultiOverviewQuery, +} from '../../../../store/reducers/overview/overview'; import type {EPathType} from '../../../../types/api/schema'; -import type {IDescribeData} from '../../../../types/store/describe'; import {cn} from '../../../../utils/cn'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; import {isEntityWithMergedImplementation} from '../../utils/schema'; @@ -26,8 +26,6 @@ interface IDescribeProps { type?: EPathType; } -const emptyObject: IDescribeData = {}; - const Describe = ({path, database, type}: IDescribeProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); @@ -44,37 +42,20 @@ const Describe = ({path, database, type}: IDescribeProps) => { } else if (mergedChildrenPaths) { paths = [path, ...mergedChildrenPaths]; } - const {currentDescribe, currentData, isFetching, error} = overviewApi.useGetOverviewQuery( - paths.length ? {paths, database} : skipToken, - { - pollingInterval: autoRefreshInterval, - selectFromResult: (props) => { - const {currentData} = props; - if (!currentData) { - return {currentDescribe: emptyObject, ...props}; - } - - const mergedData = [currentData.data, ...currentData.additionalData]; - const data = mergedData.reduce((acc, item) => { - if (item?.Path) { - acc[item.Path] = item; - } - return acc; - }, {}); - return {currentDescribe: data, ...props}; - }, - }, - ); - const loading = isFetching && currentData === undefined; + const {mergedDescribe, loading, error} = useGetMultiOverviewQuery({ + paths, + autoRefreshInterval, + database, + }); let preparedDescribeData: Object | undefined; - if (currentDescribe) { - const paths = Object.keys(currentDescribe); + if (mergedDescribe) { + const paths = Object.keys(mergedDescribe); if (paths.length === 1) { - preparedDescribeData = currentDescribe[paths[0]]; + preparedDescribeData = mergedDescribe[paths[0]]; } else { - preparedDescribeData = currentDescribe; + preparedDescribeData = mergedDescribe; } } diff --git a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx index afd4f82520..f0159fb88b 100644 --- a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx +++ b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx @@ -62,16 +62,15 @@ export function HotKeys({path, database}: HotKeysProps) { const {currentData: data, isFetching, error} = hotKeysApi.useGetHotKeysQuery({path, database}); const loading = isFetching && data === undefined; const [autoRefreshInterval] = useAutoRefreshInterval(); - const {currentData, isLoading: schemaLoading} = overviewApi.useGetOverviewQuery( + const {currentData: schemaData, isLoading: schemaLoading} = overviewApi.useGetOverviewQuery( { - paths: [path], + path, database, }, { pollingInterval: autoRefreshInterval, }, ); - const {data: schemaData} = currentData ?? {}; const keyColumnsIds = schemaData?.PathDescription?.Table?.KeyColumnNames; const tableColumns = React.useMemo(() => { diff --git a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx index b4aaf55a96..e40427177b 100644 --- a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx @@ -1,13 +1,14 @@ import React from 'react'; -import {skipToken} from '@reduxjs/toolkit/query'; import {shallowEqual} from 'react-redux'; import {ResponseError} from '../../../../components/Errors/ResponseError'; import {TableIndexInfo} from '../../../../components/InfoViewer/schemaInfo'; import {Loader} from '../../../../components/Loader'; -import {overviewApi} from '../../../../store/reducers/overview/overview'; -import {selectSchemaMergedChildrenPaths} from '../../../../store/reducers/schema/schema'; +import { + selectSchemaMergedChildrenPaths, + useGetMultiOverviewQuery, +} from '../../../../store/reducers/overview/overview'; import {EPathType} from '../../../../types/api/schema'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; import {ExternalDataSourceInfo} from '../../Info/ExternalDataSource/ExternalDataSource'; @@ -45,16 +46,17 @@ function Overview({type, path, database}: OverviewProps) { } const { - currentData, - isFetching, - error: overviewError, - } = overviewApi.useGetOverviewQuery(paths.length ? {paths, database} : skipToken, { - pollingInterval: autoRefreshInterval, + mergedDescribe, + loading: entityLoading, + error, + } = useGetMultiOverviewQuery({ + paths, + database, + autoRefreshInterval, }); - const overviewLoading = isFetching && currentData === undefined; - const {data: rawData, additionalData} = currentData || {}; - const entityLoading = overviewLoading; + const rawData = mergedDescribe[path]; + const entityNotReady = isEntityWithMergedImpl && !mergedChildrenPaths; const renderContent = () => { @@ -70,14 +72,20 @@ function Overview({type, path, database}: OverviewProps) { [EPathType.EPathTypeExtSubDomain]: undefined, [EPathType.EPathTypeColumnStore]: undefined, [EPathType.EPathTypeColumnTable]: undefined, - [EPathType.EPathTypeCdcStream]: () => ( - - ), + [EPathType.EPathTypeCdcStream]: () => { + const topicPath = mergedChildrenPaths?.[0]; + if (topicPath) { + return ( + + ); + } + return undefined; + }, [EPathType.EPathTypePersQueueGroup]: () => ( ), @@ -96,8 +104,8 @@ function Overview({type, path, database}: OverviewProps) { return ( - {overviewError ? : null} - {overviewError && !rawData ? null : renderContent()} + {error ? : null} + {error && !rawData ? null : renderContent()} ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index b2f73c7294..a16e1c2e81 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -45,16 +45,15 @@ export function TenantOverview({ const tenantType = mapDatabaseTypeToDBName(Type); // FIXME: remove after correct data is added to tenantInfo - const {currentData} = overviewApi.useGetOverviewQuery( + const {currentData: tenantSchemaData} = overviewApi.useGetOverviewQuery( { - paths: [tenantName], + path: tenantName, database: tenantName, }, { pollingInterval: autoRefreshInterval, }, ); - const {data: tenantSchemaData} = currentData ?? {}; const {Tables, Topics} = tenantSchemaData?.PathDescription?.DomainDescription?.DiskSpaceUsage || {}; diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx index 32bb56c06e..46a116800d 100644 --- a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx +++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx @@ -93,16 +93,15 @@ export function ObjectSummary({ ignoreQueryPrefix: true, }); - const {currentData} = overviewApi.useGetOverviewQuery( + const {currentData: currentObjectData} = overviewApi.useGetOverviewQuery( { - paths: [path], + path, database: tenantName, }, { pollingInterval: autoRefreshInterval, }, ); - const {data: currentObjectData} = currentData ?? {}; const currentSchemaData = currentObjectData?.PathDescription?.Self; React.useEffect(() => { diff --git a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx index 794d0af5ad..b12a1d83ad 100644 --- a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +++ b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx @@ -38,9 +38,9 @@ interface SchemaViewerProps { export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaViewerProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); - const {currentData, isLoading: loading} = overviewApi.useGetOverviewQuery( + const {currentData: schemaData, isLoading: loading} = overviewApi.useGetOverviewQuery( { - paths: [path], + path, database: tenantName, }, { @@ -48,8 +48,6 @@ export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaV }, ); - const {data: schemaData} = currentData ?? {}; - const viewSchemaRequestParams = isViewType(type) ? {path, database: tenantName} : skipToken; const {data: viewColumnsData, isLoading: isViewSchemaLoading} = diff --git a/src/containers/Tenant/Tenant.tsx b/src/containers/Tenant/Tenant.tsx index c346a300bb..91c974ca77 100644 --- a/src/containers/Tenant/Tenant.tsx +++ b/src/containers/Tenant/Tenant.tsx @@ -75,13 +75,16 @@ export function Tenant(props: TenantProps) { const path = schema ?? tenantName; - const {currentData, error, isLoading} = overviewApi.useGetOverviewQuery( - {paths: [path], database: tenantName}, + const { + currentData: currentItem, + error, + isLoading, + } = overviewApi.useGetOverviewQuery( + {path, database: tenantName}, { pollingInterval: autoRefreshInterval, }, ); - const {data: currentItem} = currentData ?? {}; const {PathType: currentPathType, PathSubType: currentPathSubType} = currentItem?.PathDescription?.Self || {}; diff --git a/src/store/reducers/overview/overview.ts b/src/store/reducers/overview/overview.ts index 070e1b175e..51a55cca00 100644 --- a/src/store/reducers/overview/overview.ts +++ b/src/store/reducers/overview/overview.ts @@ -1,11 +1,18 @@ +import {createSelector} from '@reduxjs/toolkit'; +import {skipToken} from '@reduxjs/toolkit/query'; + +import {isEntityWithMergedImplementation} from '../../../containers/Tenant/utils/schema'; +import type {EPathType} from '../../../types/api/schema'; +import type {IDescribeData} from '../../../types/store/describe'; +import type {RootState} from '../../defaultStore'; import {api} from '../api'; export const overviewApi = api.injectEndpoints({ endpoints: (build) => ({ - getOverview: build.query({ + getMultiOverview: build.query({ queryFn: async ({paths, database}: {paths: string[]; database: string}, {signal}) => { try { - const [data, ...additionalData] = await Promise.all( + const data = await Promise.all( paths.map((p) => window.api.getDescribe( { @@ -16,7 +23,25 @@ export const overviewApi = api.injectEndpoints({ ), ), ); - return {data: {data, additionalData}}; + return {data}; + } catch (error) { + return {error}; + } + }, + keepUnusedDataFor: 0, + providesTags: ['All'], + }), + getOverview: build.query({ + queryFn: async ({path, database}: {path: string; database: string}, {signal}) => { + try { + const data = await window.api.getDescribe( + { + path, + database, + }, + {signal}, + ); + return {data}; } catch (error) { return {error}; } @@ -26,3 +51,86 @@ export const overviewApi = api.injectEndpoints({ }), }), }); + +const getOverviewSelector = createSelector( + (path: string) => path, + (_path: string, database: string) => database, + (path, database) => overviewApi.endpoints.getOverview.select({path, database}), +); + +const selectGetOverview = createSelector( + (state: RootState) => state, + (_state: RootState, path: string, database: string) => getOverviewSelector(path, database), + (state, selectOverview) => selectOverview(state).data, +); + +const selectOverviewChildren = (state: RootState, path: string, database: string) => + selectGetOverview(state, path, database)?.PathDescription?.Children; + +export const selectSchemaMergedChildrenPaths = createSelector( + [ + (_, path: string) => path, + (_, _path, type: EPathType | undefined) => type, + (state, path, _type, database: string) => selectOverviewChildren(state, path, database), + ], + (path, type, children) => { + return isEntityWithMergedImplementation(type) + ? children?.map(({Name}) => path + '/' + Name) + : undefined; + }, +); + +//this hook is done not to refetch mainPath describe for entities with merged implementation +export function useGetMultiOverviewQuery({ + paths, + database, + autoRefreshInterval, +}: { + paths: string[]; + database: string; + autoRefreshInterval?: number; +}) { + const [mainPath, ...additionalPaths] = paths; + + const { + currentData: mainDescribe, + isFetching: mainDescribeIsFetching, + error: mainDescribeError, + } = overviewApi.useGetOverviewQuery( + { + path: mainPath, + database, + }, + { + pollingInterval: autoRefreshInterval, + }, + ); + + const { + currentData: currentChindrenDescribe = [], + isFetching: childrenDescribeIsFetching, + error: childrenDescribeError, + } = overviewApi.useGetMultiOverviewQuery( + additionalPaths.length ? {paths: additionalPaths, database} : skipToken, + { + pollingInterval: autoRefreshInterval, + }, + ); + + const childrenDescribeLoading = + childrenDescribeIsFetching && currentChindrenDescribe === undefined; + const mainDescribeLoading = mainDescribeIsFetching && mainDescribe === undefined; + + const loading = mainDescribeLoading || childrenDescribeLoading; + + const describe = [mainDescribe, ...currentChindrenDescribe]; + + const mergedDescribe = describe.reduce((acc, item) => { + if (item?.Path) { + acc[item.Path] = item; + } + return acc; + }, {}); + + return {loading, error: mainDescribeError || childrenDescribeError, mergedDescribe}; +} diff --git a/src/store/reducers/schema/schema.ts b/src/store/reducers/schema/schema.ts index 4ef6628ed3..1b07c12e5c 100644 --- a/src/store/reducers/schema/schema.ts +++ b/src/store/reducers/schema/schema.ts @@ -1,11 +1,8 @@ import React from 'react'; import type {Reducer} from '@reduxjs/toolkit'; -import {createSelector} from '@reduxjs/toolkit'; -import {isEntityWithMergedImplementation} from '../../../containers/Tenant/utils/schema'; -import type {EPathType, TEvDescribeSchemeResult} from '../../../types/api/schema'; -import type {RootState} from '../../defaultStore'; +import type {TEvDescribeSchemeResult} from '../../../types/api/schema'; import {api} from '../api'; import type {SchemaAction, SchemaState} from './types'; @@ -99,35 +96,6 @@ function getSchemaChildren(data: TEvDescribeSchemeResult) { return children; } -const getSchemaSelector = createSelector( - (path: string) => path, - (_path: string, database: string) => database, - (path, database) => schemaApi.endpoints.getSchema.select({path, database}), -); - -const selectGetSchema = createSelector( - (state: RootState) => state, - (_state: RootState, path: string) => path, - (_state: RootState, path: string, database: string) => getSchemaSelector(path, database), - (state, path, selectTabletsInfo) => selectTabletsInfo(state).data?.[path], -); - -const selectSchemaChildren = (state: RootState, path: string, database: string) => - selectGetSchema(state, path, database)?.PathDescription?.Children; - -export const selectSchemaMergedChildrenPaths = createSelector( - [ - (_, path: string) => path, - (_, _path, type: EPathType | undefined) => type, - (state, path, _type, database: string) => selectSchemaChildren(state, path, database), - ], - (path, type, children) => { - return isEntityWithMergedImplementation(type) - ? children?.map(({Name}) => path + '/' + Name) - : undefined; - }, -); - export function useGetSchemaQuery({path, database}: {path: string; database: string}) { const {currentData, isFetching, error, refetch, originalArgs} = schemaApi.useGetSchemaQuery({ path,