Skip to content

Commit 7f283a0

Browse files
authored
fix(schema): use one cache entree for tenant path schemas (#1016)
1 parent e690771 commit 7f283a0

File tree

7 files changed

+81
-59
lines changed

7 files changed

+81
-59
lines changed

src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {Button, Card, Icon} from '@gravity-ui/uikit';
88
import {ResponseError} from '../../../../components/Errors/ResponseError';
99
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
1010
import {hotKeysApi} from '../../../../store/reducers/hotKeys/hotKeys';
11-
import {schemaApi} from '../../../../store/reducers/schema/schema';
11+
import {useGetSchemaQuery} from '../../../../store/reducers/schema/schema';
1212
import type {HotKey} from '../../../../types/api/hotkeys';
1313
import {cn} from '../../../../utils/cn';
1414
import {DEFAULT_TABLE_SETTINGS, IS_HOTKEYS_HELP_HIDDEN_KEY} from '../../../../utils/constants';
@@ -61,9 +61,7 @@ export function HotKeys({path}: HotKeysProps) {
6161
const {currentData: data, isFetching, error} = hotKeysApi.useGetHotKeysQuery({path});
6262
const loading = isFetching && data === undefined;
6363

64-
const {currentData: schemaData, isFetching: schemaIsFetching} =
65-
schemaApi.endpoints.getSchema.useQueryState({path});
66-
const schemaLoading = schemaIsFetching && schemaData === undefined;
64+
const {data: schemaData, isLoading: schemaLoading} = useGetSchemaQuery({path});
6765

6866
const keyColumnsIds = schemaData?.PathDescription?.Table?.KeyColumnNames;
6967

src/containers/Tenant/Diagnostics/Overview/Overview.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {TableIndexInfo} from '../../../../components/InfoViewer/schemaInfo';
88
import {Loader} from '../../../../components/Loader';
99
import {olapApi} from '../../../../store/reducers/olapStats';
1010
import {overviewApi} from '../../../../store/reducers/overview/overview';
11-
import {schemaApi, selectSchemaMergedChildrenPaths} from '../../../../store/reducers/schema/schema';
11+
import {
12+
selectSchemaMergedChildrenPaths,
13+
useGetSchemaQuery,
14+
} from '../../../../store/reducers/schema/schema';
1215
import {EPathType} from '../../../../types/api/schema';
1316
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
1417
import {ExternalDataSourceInfo} from '../../Info/ExternalDataSource/ExternalDataSource';
@@ -66,7 +69,7 @@ function Overview({type, path}: OverviewProps) {
6669
const overviewLoading = isFetching && currentData === undefined;
6770
const {data: rawData, additionalData} = currentData || {};
6871

69-
const {error: schemaError} = schemaApi.endpoints.getSchema.useQueryState({path});
72+
const {error: schemaError} = useGetSchemaQuery({path});
7073

7174
const entityLoading = overviewLoading || olapStatsLoading;
7275
const entityNotReady = isEntityWithMergedImpl && !mergedChildrenPaths;

src/containers/Tenant/ObjectSummary/ObjectSummary.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {LinkWithIcon} from '../../../components/LinkWithIcon/LinkWithIcon';
1515
import {Loader} from '../../../components/Loader';
1616
import SplitPane from '../../../components/SplitPane';
1717
import routes, {createExternalUILink, createHref} from '../../../routes';
18-
import {schemaApi, setShowPreview} from '../../../store/reducers/schema/schema';
18+
import {setShowPreview, useGetSchemaQuery} from '../../../store/reducers/schema/schema';
1919
import {
2020
TENANT_PAGES_IDS,
2121
TENANT_QUERY_TABS_ID,
@@ -92,7 +92,7 @@ export function ObjectSummary({
9292
ignoreQueryPrefix: true,
9393
});
9494

95-
const {currentData: currentObjectData} = schemaApi.endpoints.getSchema.useQueryState({path});
95+
const {data: currentObjectData} = useGetSchemaQuery({path});
9696
const currentSchemaData = currentObjectData?.PathDescription?.Self;
9797

9898
React.useEffect(() => {
@@ -399,14 +399,14 @@ export function ObjectSummary({
399399
}
400400

401401
function ObjectTree({tenantName, path}: {tenantName: string; path?: string}) {
402-
const {currentData: tenantData = {}, isFetching} = schemaApi.useGetSchemaQuery({
402+
const {data: tenantData = {}, isLoading} = useGetSchemaQuery({
403403
path: tenantName,
404404
});
405405
const pathData = tenantData?.PathDescription?.Self;
406406

407407
const [, setCurrentPath] = useQueryParam('schema', StringParam);
408408

409-
if (!pathData && isFetching) {
409+
if (!pathData && isLoading) {
410410
// If Loader isn't wrapped with div, SplitPane doesn't calculate panes height correctly
411411
return (
412412
<div>

src/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx

+19-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {NavigationTree} from 'ydb-ui-components';
77

88
import {USE_DIRECTORY_OPERATIONS} from '../../../../lib';
99
import {schemaApi} from '../../../../store/reducers/schema/schema';
10-
import type {EPathType} from '../../../../types/api/schema';
10+
import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
1111
import {useQueryModes, useSetting, useTypedDispatch} from '../../../../utils/hooks';
1212
import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
1313
import {getActions} from '../../utils/schemaActions';
@@ -33,15 +33,26 @@ export function SchemaTree(props: SchemaTreeProps) {
3333
const [schemaTreeKey, setSchemaTreeKey] = React.useState('');
3434

3535
const fetchPath = async (path: string) => {
36-
const promise = dispatch(
37-
schemaApi.endpoints.getSchema.initiate({path}, {forceRefetch: true}),
38-
);
39-
const {data} = await promise;
40-
promise.unsubscribe();
41-
if (!data) {
36+
let schemaData: TEvDescribeSchemeResult | undefined;
37+
do {
38+
const promise = dispatch(
39+
schemaApi.endpoints.getSchema.initiate({path}, {forceRefetch: true}),
40+
);
41+
const {data, originalArgs} = await promise;
42+
promise.unsubscribe();
43+
// Check if the result from the current request is received. rtk-query may skip the current request and
44+
// return data from a parallel request, due to the same cache key.
45+
if (originalArgs?.path === path) {
46+
schemaData = data?.[path];
47+
break;
48+
}
49+
// eslint-disable-next-line no-constant-condition
50+
} while (true);
51+
52+
if (!schemaData) {
4253
throw new Error(`no describe data about path ${path}`);
4354
}
44-
const {PathDescription: {Children = []} = {}} = data;
55+
const {PathDescription: {Children = []} = {}} = schemaData;
4556

4657
const childItems = Children.map((childData) => {
4758
const {Name = '', PathType, PathSubType} = childData;

src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {skipToken} from '@reduxjs/toolkit/query';
55

66
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
77
import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
8-
import {schemaApi} from '../../../../store/reducers/schema/schema';
8+
import {useGetSchemaQuery} from '../../../../store/reducers/schema/schema';
99
import {viewSchemaApi} from '../../../../store/reducers/viewSchema/viewSchema';
1010
import type {EPathType} from '../../../../types/api/schema';
1111
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
@@ -37,10 +37,7 @@ interface SchemaViewerProps {
3737
}
3838

3939
export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaViewerProps) => {
40-
const {currentData: schemaData, isFetching} = schemaApi.endpoints.getSchema.useQueryState({
41-
path,
42-
});
43-
const loading = isFetching && schemaData === undefined;
40+
const {data: schemaData, isLoading: loading} = useGetSchemaQuery({path});
4441

4542
const viewSchemaRequestParams = isViewType(type) ? {path, database: tenantName} : skipToken;
4643

src/containers/Tenant/Tenant.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {AccessDenied} from '../../components/Errors/403';
77
import {Loader} from '../../components/Loader';
88
import SplitPane from '../../components/SplitPane';
99
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
10-
import {schemaApi} from '../../store/reducers/schema/schema';
10+
import {useGetSchemaQuery} from '../../store/reducers/schema/schema';
1111
import type {AdditionalNodesProps, AdditionalTenantsProps} from '../../types/additionalProps';
1212
import {cn} from '../../utils/cn';
1313
import {DEFAULT_IS_TENANT_SUMMARY_COLLAPSED, DEFAULT_SIZE_TENANT_KEY} from '../../utils/constants';
@@ -74,11 +74,7 @@ export function Tenant(props: TenantProps) {
7474

7575
const path = schema ?? tenantName;
7676

77-
const {
78-
currentData: currentItem,
79-
error,
80-
isLoading,
81-
} = schemaApi.useGetSchemaQuery({path}, {refetchOnMountOrArgChange: true});
77+
const {data: currentItem, error, isLoading} = useGetSchemaQuery({path});
8278
const {PathType: currentPathType, PathSubType: currentPathSubType} =
8379
currentItem?.PathDescription?.Self || {};
8480

src/store/reducers/schema/schema.ts

+47-30
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React from 'react';
2+
13
import type {Reducer, Selector} from '@reduxjs/toolkit';
24
import {createSelector} from '@reduxjs/toolkit';
35

@@ -51,61 +53,60 @@ export const schemaApi = api.injectEndpoints({
5153
}
5254
},
5355
}),
54-
getSchema: builder.query<TEvDescribeSchemeResult & {partial?: boolean}, {path: string}>({
56+
getSchema: builder.query<
57+
{[path: string]: TEvDescribeSchemeResult & {partial?: boolean}},
58+
{path: string}
59+
>({
5560
queryFn: async ({path}, {signal}) => {
5661
try {
5762
const data = await window.api.getSchema({path}, {signal});
58-
return {data: data ?? {}};
63+
return {data: data ? {[path]: data, ...getSchemaChildren(data)} : {}};
5964
} catch (error) {
6065
return {error};
6166
}
6267
},
6368
keepUnusedDataFor: Infinity,
64-
forceRefetch: ({endpointState}) => {
65-
const data = endpointState?.data;
66-
if (data && typeof data === 'object' && 'partial' in data && data.partial) {
67-
return true;
68-
}
69-
return false;
69+
serializeQueryArgs: ({queryArgs: {path}}) => {
70+
const parts = path.split('/');
71+
return {path: parts[0] || parts[1]};
7072
},
71-
onQueryStarted: async ({path}, {dispatch, getState, queryFulfilled}) => {
72-
const {data} = await queryFulfilled;
73+
merge: (existing, incoming, {arg: {path}}) => {
74+
const {[path]: data, ...children} = incoming;
7375
if (data) {
74-
const state = getState();
75-
const {PathDescription: {Children = []} = {}} = data;
76-
for (const child of Children) {
77-
const {Name = ''} = child;
78-
const childPath = `${path}/${Name}`;
79-
const cachedData = schemaApi.endpoints.getSchema.select({path: childPath})(
80-
state,
81-
).data;
82-
if (!cachedData) {
83-
// not full data, but it contains PathType, which ensures seamless switch between nodes
84-
dispatch(
85-
schemaApi.util.upsertQueryData(
86-
'getSchema',
87-
{path: childPath},
88-
{PathDescription: {Self: child}, partial: true},
89-
),
90-
);
91-
}
92-
}
76+
return {
77+
...children,
78+
...existing,
79+
[path]: data,
80+
};
9381
}
82+
return existing;
9483
},
9584
}),
9685
}),
9786
overrideExisting: 'throw',
9887
});
9988

89+
function getSchemaChildren(data: TEvDescribeSchemeResult) {
90+
const children: {[path: string]: TEvDescribeSchemeResult & {partial?: boolean}} = {};
91+
const {PathDescription: {Children = []} = {}, Path: path} = data;
92+
for (const child of Children) {
93+
const {Name = ''} = child;
94+
const childPath = `${path}/${Name}`;
95+
children[childPath] = {PathDescription: {Self: child}, Path: childPath, partial: true};
96+
}
97+
return children;
98+
}
99+
100100
const getSchemaSelector = createSelector(
101101
(path: string) => path,
102102
(path) => schemaApi.endpoints.getSchema.select({path}),
103103
);
104104

105105
const selectGetSchema = createSelector(
106106
(state: RootState) => state,
107+
(_state: RootState, path: string) => path,
107108
(_state: RootState, path: string) => getSchemaSelector(path),
108-
(state, selectTabletsInfo) => selectTabletsInfo(state).data,
109+
(state, path, selectTabletsInfo) => selectTabletsInfo(state).data?.[path],
109110
);
110111

111112
const selectSchemaChildren = (state: RootState, path: string) =>
@@ -127,3 +128,19 @@ export const selectSchemaMergedChildrenPaths: Selector<
127128
: undefined;
128129
},
129130
);
131+
132+
export function useGetSchemaQuery({path}: {path: string}) {
133+
const {currentData, isFetching, error, refetch} = schemaApi.useGetSchemaQuery({path});
134+
135+
const data = currentData?.[path];
136+
const isLoading = isFetching && data === undefined;
137+
138+
const shouldLoad = !isLoading && ((!data && !error) || data?.partial);
139+
React.useEffect(() => {
140+
if (shouldLoad) {
141+
refetch();
142+
}
143+
}, [refetch, path, shouldLoad]);
144+
145+
return {data, isLoading, error};
146+
}

0 commit comments

Comments
 (0)