Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const b = cn('kv-top-queries');
interface SimpleTableWithDrawerProps {
columns: Column<KeyValueRow>[];
data: KeyValueRow[];
loading?: boolean;
isFetching?: boolean;
isLoading?: boolean;
onRowClick?: (
row: KeyValueRow | null,
index?: number,
Expand All @@ -39,7 +40,8 @@ interface SimpleTableWithDrawerProps {
export function QueriesTableWithDrawer({
columns,
data,
loading,
isFetching,
isLoading,
onRowClick,
columnsWidthLSKey,
emptyDataMessage,
Expand Down Expand Up @@ -104,7 +106,8 @@ export function QueriesTableWithDrawer({
columnsWidthLSKey={columnsWidthLSKey}
columns={columns}
data={data}
isFetching={loading}
isFetching={isFetching}
isLoading={isLoading}
Comment on lines +109 to +110
Copy link
Member Author

Choose a reason for hiding this comment

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

Table loading changes are needed to synchronize settings and data loading states, without it tests somehow fail to find table container

settings={tableSettings}
onRowClick={handleRowClick}
rowClassName={(row) => b('row', {active: isEqual(row, selectedRow)})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ export const RunningQueriesData = ({
</TableWithControlsLayout.Controls>

{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
<TableWithControlsLayout.Table loading={isLoading}>
<TableWithControlsLayout.Table>
<QueriesTableWithDrawer
columns={columnsToShow}
data={rows || []}
loading={isFetching && currentData === undefined}
isFetching={isFetching && currentData === undefined}
isLoading={isLoading}
columnsWidthLSKey={RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY}
emptyDataMessage={i18n('no-data')}
sortOrder={tableSort}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,12 @@ export const TopQueriesData = ({
</TableWithControlsLayout.Controls>

{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
<TableWithControlsLayout.Table loading={isLoading}>
<TableWithControlsLayout.Table>
<QueriesTableWithDrawer
columns={columnsToShow}
data={rows || []}
loading={isFetching && currentData === undefined}
isFetching={isFetching && currentData === undefined}
isLoading={isLoading}
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
emptyDataMessage={i18n('no-data')}
sortOrder={tableSort}
Expand Down
5 changes: 3 additions & 2 deletions src/services/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type {AxiosWrapperOptions} from '@gravity-ui/axios-wrapper';
import type {AxiosRequestConfig} from 'axios';

import {codeAssistBackend} from '../../store';
import {uiFactory} from '../../uiFactory/uiFactory';

import {AuthAPI} from './auth';
import {CodeAssistAPI} from './codeAssist';
Expand All @@ -27,6 +26,7 @@ interface YdbEmbeddedAPIProps {
proxyMeta: undefined | boolean;
// this setting allows to use schema object path relative to database in api requests
useRelativePath: undefined | boolean;
useMetaSettings: undefined | boolean;
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved useMetaSettings from uiFactory to api setup. The reason is that uiFactory property requires api and uiFactory to be inited in the specific order: first we need to configureUIFactory and only after setup api. Otherwise we need two settings - for uiFactory and to api.

So this help to reduce it to only one setting without any additional requirements

csrfTokenGetter: undefined | (() => string | undefined);
defaults: undefined | AxiosRequestConfig;
}
Expand Down Expand Up @@ -54,6 +54,7 @@ export class YdbEmbeddedAPI {
csrfTokenGetter = () => undefined,
defaults = {},
useRelativePath = false,
useMetaSettings = false,
}: YdbEmbeddedAPIProps) {
const axiosParams: AxiosWrapperOptions = {config: {withCredentials, ...defaults}};
const baseApiParams = {singleClusterMode, proxyMeta, useRelativePath};
Expand All @@ -62,7 +63,7 @@ export class YdbEmbeddedAPI {
if (webVersion) {
this.meta = new MetaAPI(axiosParams, baseApiParams);
}
if (uiFactory.useMetaSettings) {
if (useMetaSettings) {
this.metaSettings = new MetaSettingsAPI(axiosParams, baseApiParams);
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/api/metaSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class MetaSettingsAPI extends BaseMetaAPI {
preventBatching,
}: GetSingleSettingParams & {preventBatching?: boolean}) {
if (preventBatching) {
return this.get<Setting>(this.getPath('/meta/user_settings'), {name, user});
return this.get<Setting | undefined>(this.getPath('/meta/user_settings'), {name, user});
}

return new Promise<Setting>((resolve, reject) => {
Expand Down
1 change: 1 addition & 0 deletions src/store/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function configureStore({
proxyMeta: false,
csrfTokenGetter: undefined,
useRelativePath: false,
useMetaSettings: false,
defaults: undefined,
}),
} = {}) {
Expand Down
5 changes: 3 additions & 2 deletions src/store/reducers/authentication/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const initialState: AuthenticationState = {
isAuthenticated: true,
user: undefined,
id: undefined,
metaUser: undefined,
Copy link
Member Author

Choose a reason for hiding this comment

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

I did not figured out how to properly receive metaUser value yet, so I decided to test everything with some hardcoded user and to do all metaUser code separately - there are additional user points in issue checklist.

What are the problems with metaUser:

  1. The logic may be different in different installations
  2. It should not break current authentication cases
  3. We need somehow to tackle the case, when user do not have access to meta cluster

};

export const slice = createSlice({
Expand Down Expand Up @@ -45,13 +46,13 @@ export const slice = createSlice({
selectIsUserAllowedToMakeChanges: (state) => state.isUserAllowedToMakeChanges,
selectIsViewerUser: (state) => state.isViewerUser,
selectUser: (state) => state.user,
selectID: (state) => state.id,
selectMetaUser: (state) => state.metaUser ?? state.id,
},
});

export default slice.reducer;
export const {setIsAuthenticated, setUser} = slice.actions;
export const {selectIsUserAllowedToMakeChanges, selectIsViewerUser, selectUser, selectID} =
export const {selectIsUserAllowedToMakeChanges, selectIsViewerUser, selectUser, selectMetaUser} =
slice.selectors;

export const authenticationApi = api.injectEndpoints({
Expand Down
2 changes: 2 additions & 0 deletions src/store/reducers/authentication/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export interface AuthenticationState {

user: string | undefined;
id: string | undefined;

metaUser: string | undefined;
}
6 changes: 2 additions & 4 deletions src/store/reducers/capabilities/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {createSelector} from '@reduxjs/toolkit';

import type {Capability, MetaCapability, SecuritySetting} from '../../../types/api/capabilities';
import type {AppDispatch, RootState} from '../../defaultStore';
import {serializeError} from '../../utils';

import {api} from './../api';

Expand Down Expand Up @@ -30,10 +31,7 @@ export const capabilitiesApi = api.injectEndpoints({
} catch (error) {
// If capabilities endpoint is not available, there will be an error
// That means no new features are available
// Serialize the error to make it Redux-compatible
const serializedError =
error instanceof Error ? {message: error.message, name: error.name} : error;
return {error: serializedError};
return {error: serializeError(error)};
}
},
}),
Expand Down
111 changes: 56 additions & 55 deletions src/store/reducers/settings/api.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,83 @@
import {isNil} from 'lodash';

import type {
GetSettingsParams,
GetSingleSettingParams,
SetSingleSettingParams,
Setting,
} from '../../../types/api/settings';
import type {AppDispatch} from '../../defaultStore';
import {serializeError} from '../../utils';
import {api} from '../api';

import {SETTINGS_OPTIONS} from './constants';
import {getSettingDefault, parseSettingValue, stringifySettingValue} from './utils';

export const settingsApi = api.injectEndpoints({
endpoints: (builder) => ({
getSingleSetting: builder.query({
queryFn: async ({name, user}: GetSingleSettingParams, baseApi) => {
getSingleSetting: builder.query<unknown, Partial<GetSingleSettingParams>>({
queryFn: async ({name, user}) => {
try {
if (!window.api.metaSettings) {
throw new Error('MetaSettings API is not available');
if (!name || !window.api.metaSettings) {
throw new Error(
'Cannot get setting, no MetaSettings API or neccessary params are missing',
);
}

const defaultValue = getSettingDefault(name) as unknown;

if (!user) {
return {data: defaultValue};
}

const data = await window.api.metaSettings.getSingleSetting({
name,
user,
// Directly access options here to avoid them in cache key
preventBatching: SETTINGS_OPTIONS[name]?.preventBatching,
});

const dispatch = baseApi.dispatch as AppDispatch;

// Try to sync local value if there is no backend value
syncLocalValueToMetaIfNoData(data, dispatch);

return {data};
return {data: parseSettingValue(data?.value) ?? defaultValue};
} catch (error) {
return {error};
return {error: serializeError(error)};
}
},
}),
setSingleSetting: builder.mutation({
queryFn: async (params: SetSingleSettingParams) => {
queryFn: async ({
name,
user,
value,
}: Partial<Omit<SetSingleSettingParams, 'value'>> & {value: unknown}) => {
try {
if (!window.api.metaSettings) {
throw new Error('MetaSettings API is not available');
if (!name || !user || !window.api.metaSettings) {
throw new Error(
'Cannot set setting, no MetaSettings API or neccessary params are missing',
);
}

const data = await window.api.metaSettings.setSingleSetting(params);
const data = await window.api.metaSettings.setSingleSetting({
name,
user,
value: stringifySettingValue(value),
});

if (data.status !== 'SUCCESS') {
throw new Error('Setting status is not SUCCESS');
throw new Error('Cannot set setting - status is not SUCCESS');
}

return {data};
} catch (error) {
return {error};
return {error: serializeError(error)};
}
},
async onQueryStarted(args, {dispatch, queryFulfilled}) {
const {name, user, value} = args;

if (!name) {
return;
}

// Optimistically update existing cache entry
const patchResult = dispatch(
settingsApi.util.updateQueryData('getSingleSetting', {name, user}, (draft) => {
return {...draft, name, user, value};
}),
settingsApi.util.updateQueryData('getSingleSetting', {name, user}, () => value),
);
try {
await queryFulfilled;
Expand All @@ -72,41 +87,41 @@ export const settingsApi = api.injectEndpoints({
},
}),
getSettings: builder.query({
queryFn: async ({name, user}: GetSettingsParams, baseApi) => {
queryFn: async ({name, user}: Partial<GetSettingsParams>, baseApi) => {
try {
if (!window.api.metaSettings) {
throw new Error('MetaSettings API is not available');
if (!window.api.metaSettings || !name || !user) {
throw new Error(
'Cannot get settings, no MetaSettings API or neccessary params are missing',
);
}
const data = await window.api.metaSettings.getSettings({name, user});

const patches: Promise<void>[] = [];
const patches: Promise<unknown>[] = [];
const dispatch = baseApi.dispatch as AppDispatch;

// Upsert received data in getSingleSetting cache
// Upsert received data in getSingleSetting cache to prevent further redundant requests
name.forEach((settingName) => {
const settingData = data[settingName] ?? {};
const settingData = data[settingName];

const defaultValue = getSettingDefault(settingName);

const cacheEntryParams: GetSingleSettingParams = {
name: settingName,
user,
};
const newValue = {name: settingName, user, value: settingData?.value};
const newSetting = {
name: settingName,
user,
value: parseSettingValue(settingData?.value) ?? defaultValue,
};

const patch = dispatch(
settingsApi.util.upsertQueryData(
'getSingleSetting',
cacheEntryParams,
newValue,
newSetting,
),
).then(() => {
// Try to sync local value if there is no backend value
// Do it after upsert if finished to ensure proper values update order
// 1. New entry added to cache with nil value
// 2. Positive entry update - local storage value replace nil in cache
// 3.1. Set is successful, local value in cache
// 3.2. Set is not successful, cache value reverted to previous nil
syncLocalValueToMetaIfNoData(settingData, dispatch);
});
);

patches.push(patch);
});
Expand All @@ -116,24 +131,10 @@ export const settingsApi = api.injectEndpoints({

return {data};
} catch (error) {
return {error};
return {error: serializeError(error)};
}
},
}),
}),
overrideExisting: 'throw',
});

function syncLocalValueToMetaIfNoData(params: Setting, dispatch: AppDispatch) {
const localValue = localStorage.getItem(params.name);

if (isNil(params.value) && !isNil(localValue)) {
dispatch(
settingsApi.endpoints.setSingleSetting.initiate({
name: params.name,
user: params.user,
value: localValue,
}),
);
}
}
8 changes: 1 addition & 7 deletions src/store/reducers/settings/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,8 @@ export const DEFAULT_USER_SETTINGS = {
[SETTING_KEYS.STORAGE_TYPE]: STORAGE_TYPES.groups,
} as const satisfies Record<SettingKey, unknown>;

export const SETTINGS_OPTIONS: Record<string, SettingOptions> = {
export const SETTINGS_OPTIONS: Record<string, SettingOptions | undefined> = {
[SETTING_KEYS.THEME]: {
preventBatching: true,
},
[SETTING_KEYS.SAVED_QUERIES]: {
preventSyncWithLS: true,
},
[SETTING_KEYS.QUERIES_HISTORY]: {
preventSyncWithLS: true,
},
} as const;
Loading
Loading