Skip to content

Conversation

@artemmufazalov
Copy link
Member

@artemmufazalov artemmufazalov commented Nov 7, 2025

Closes #2892

CI Results

Test Status: ❌ FAILED

📊 Full Report

Total Passed Failed Flaky Skipped
378 374 1 1 2
Test Changes Summary ⏭️2

⏭️ Skipped Tests (2)

  1. Scroll to row, get shareable link, navigate to URL and verify row is scrolled into view (tenant/diagnostics/tabs/queries.test.ts)
  2. Copy result button copies to clipboard (tenant/queryEditor/queryEditor.test.ts)

Bundle Size: 🔺

Current: 66.14 MB | Main: 66.13 MB
Diff: +0.01 MB (0.02%)

⚠️ Bundle size increased. Please review.

ℹ️ CI Information
  • Test recordings for failed tests are available in the full report.
  • Bundle size is measured for the entire 'dist' directory.
  • 📊 indicates links to detailed reports.
  • 🔺 indicates increase, 🔽 decrease, and ✅ no change in bundle size.
## Key Changes - Added `MetaSettingsAPI` class with 100ms request batching to optimize multiple simultaneous setting requests - Implemented RTK Query endpoints (`getSingleSetting`, `setSingleSetting`, `getSettings`) with optimistic cache updates - Refactored `useSetting` hook to handle dual storage (localStorage + meta API) with configurable sync behavior - Added `useMetaSettings` flag in `uiFactory` to enable the feature conditionally - Extended authentication state to track anonymous user IDs (`UserID`) for settings attribution - Migrated saved queries and query history to use the new settings system with `preventSyncWithLS` flag - Removed legacy `services/settings.ts` and related Redux actions

Issues Found

  • Critical: useSetting hook has multiple React dependency issues causing stale closures and missed updates
  • Important: Timeout leak in MetaSettingsAPI.flushBatch() when batch is empty
  • Minor: Using delete localStorage[name] instead of proper localStorage.removeItem() method

Architecture

The implementation uses a layered approach: UI components call useSetting hook → hook manages Redux state + localStorage + meta API calls → MetaSettingsAPI batches requests → backend stores settings. Settings can be configured to sync with localStorage or only use external storage via SETTINGS_OPTIONS.

Confidence Score: 2/5

  • This PR has critical React hook dependency issues that will cause bugs in production
  • The core useSetting hook has multiple missing dependencies in useEffect hooks that will cause stale closures and missed updates. The empty dependency array on line 82 means the sync effect never re-runs when dependencies change. The debounced function memo is missing setMetaSetting causing it to capture stale references. The cleanup effect is also missing its dependency. Additionally, there's a timeout leak in the batch flushing logic. These are logical errors that will cause settings to not sync properly between localStorage and the backend, potentially losing user data.
  • Pay close attention to src/store/reducers/settings/useSetting.ts - it has multiple critical dependency issues that must be fixed before merging

Important Files Changed

File Analysis

Filename Score Overview
src/services/api/metaSettings.ts 3/5 new API class for YDB meta settings service with request batching - has timeout cleanup issue in flushBatch
src/store/reducers/settings/api.ts 4/5 RTK Query endpoints for settings - properly handles cache updates and batch API calls
src/store/reducers/settings/useSetting.ts 2/5 core hook for settings with localStorage and meta API sync - has multiple useEffect dependency issues causing stale closures and missed updates
src/store/reducers/settings/constants.ts 5/5 defines setting keys, defaults, and options for batching/localStorage sync
src/store/reducers/settings/utils.ts 4/5 localStorage utilities for settings - has improper localStorage deletion using delete operator

Sequence Diagram

sequenceDiagram
    participant User
    participant Component
    participant useSetting
    participant Redux
    participant LocalStorage
    participant MetaSettingsAPI
    participant YDB

    User->>Component: Interact with UI
    Component->>useSetting: Call useSetting(name)
    
    alt useMetaSettings enabled
        useSetting->>Redux: Get user/id from auth state
        useSetting->>MetaSettingsAPI: getSingleSetting({name, user})
        
        alt Request batching
            MetaSettingsAPI->>MetaSettingsAPI: Add to requestQueue
            MetaSettingsAPI->>MetaSettingsAPI: setTimeout(100ms)
            MetaSettingsAPI->>YDB: Batch getSettings({user, name[]})
            YDB-->>MetaSettingsAPI: Return settings map
            MetaSettingsAPI-->>useSetting: Resolve batched requests
        end
        
        useSetting->>LocalStorage: Read initial value (if sync enabled)
        useSetting->>Redux: setSettingValue(name, value)
        
        alt Meta setting exists
            useSetting->>LocalStorage: Sync from backend (if enabled)
            useSetting->>Redux: Update with backend value
        else Meta setting empty AND localStorage has value
            useSetting->>MetaSettingsAPI: Upload localStorage value
            useSetting->>LocalStorage: Delete from LS (if preventSyncWithLS)
        end
        
    else useMetaSettings disabled
        useSetting->>LocalStorage: Read value
        useSetting->>Redux: setSettingValue(name, value)
    end
    
    useSetting-->>Component: Return {value, saveValue, isLoading}
    
    User->>Component: Change setting
    Component->>useSetting: Call saveValue(newValue)
    
    alt useMetaSettings enabled
        useSetting->>MetaSettingsAPI: setSingleSetting (debounced)
        MetaSettingsAPI->>YDB: POST /meta/user_settings
        YDB-->>MetaSettingsAPI: Success response
        MetaSettingsAPI->>Redux: Update cache optimistically
    end
    
    useSetting->>LocalStorage: Save value (if sync enabled)
    useSetting->>Redux: Update state
Loading

Greptile Summary

  • Adds YDB meta settings service integration with batched API requests and dual storage (localStorage + backend)
  • Migrates saved queries and query history to external storage with preventSyncWithLS flag
  • Extends authentication to track anonymous user IDs for settings attribution

Confidence Score: 2/5

  • This PR has critical React hook dependency issues that will cause bugs in production.
  • The core useSetting hook has stale closure issues due to missing setMetaSetting in the debounced function's useMemo dependencies and incorrect dependencies in the saveValue callback, causing settings to not sync properly between localStorage and backend.
  • Pay close attention to src/store/reducers/settings/useSetting.ts - it has critical dependency issues that must be fixed before merging.

Important Files Changed

Filename Overview
src/services/api/metaSettings.ts new API class implementing batched settings requests with 100ms timeout - has timeout cleanup issue in flushBatch when returning early
src/store/reducers/settings/api.ts RTK Query endpoints for settings with proper optimistic updates and cache management - correctly handles localStorage sync and backend communication
src/store/reducers/settings/useSetting.ts core settings hook managing Redux state and meta API - has stale closure issues due to missing setMetaSetting dependency in debounced function memo and incorrect deps in saveValue callback

Sequence Diagram

sequenceDiagram
    participant User
    participant Component
    participant useSetting
    participant Redux
    participant LocalStorage
    participant MetaSettingsAPI
    participant YDB

    User->>Component: "Interact with UI"
    Component->>useSetting: "Call useSetting(name)"
    
    alt useMetaSettings enabled
        useSetting->>Redux: "Get user/id from auth state"
        useSetting->>MetaSettingsAPI: "getSingleSetting({name, user})"
        
        alt Request batching
            MetaSettingsAPI->>MetaSettingsAPI: "Add to requestQueue"
            MetaSettingsAPI->>MetaSettingsAPI: "setTimeout(100ms)"
            MetaSettingsAPI->>YDB: "Batch getSettings({user, name[]})"
            YDB-->>MetaSettingsAPI: "Return settings map"
            MetaSettingsAPI-->>useSetting: "Resolve batched requests"
        end
        
        useSetting->>LocalStorage: "Read initial value (if sync enabled)"
        useSetting->>Redux: "setSettingValue(name, value)"
        
        alt Meta setting exists
            useSetting->>LocalStorage: "Sync from backend (if enabled)"
            useSetting->>Redux: "Update with backend value"
        else Meta setting empty AND localStorage has value
            useSetting->>MetaSettingsAPI: "Upload localStorage value"
            useSetting->>LocalStorage: "Delete from LS (if preventSyncWithLS)"
        end
        
    else useMetaSettings disabled
        useSetting->>LocalStorage: "Read value"
        useSetting->>Redux: "setSettingValue(name, value)"
    end
    
    useSetting-->>Component: "Return {value, saveValue, isLoading}"
    
    User->>Component: "Change setting"
    Component->>useSetting: "Call saveValue(newValue)"
    
    alt useMetaSettings enabled
        useSetting->>MetaSettingsAPI: "setSingleSetting (debounced)"
        MetaSettingsAPI->>YDB: "POST /meta/user_settings"
        YDB-->>MetaSettingsAPI: "Success response"
        MetaSettingsAPI->>Redux: "Update cache optimistically"
    end
    
    useSetting->>LocalStorage: "Save value (if sync enabled)"
    useSetting->>Redux: "Update state"
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

79 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

@artemmufazalov artemmufazalov force-pushed the use-external-settings branch 2 times, most recently from bf66b22 to e8d01e2 Compare November 17, 2025 13:42
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

63 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile
React with 👍 or 👎 to share your feedback on this new summary format

Comment on lines 81 to 31
const saveValue = React.useCallback<SaveSettingValue<T>>(
(value) => {
if (shouldUseMetaSettings) {
debouncedSetMetaSetting({
user,
name: name,
value: stringifySettingValue(value),
});
}

if (!shouldUseOnlyExternalSettings) {
setSettingValueToLS(name, value);
}
// if (shouldUseMetaSettings) {
debouncedSetMetaSetting({
user,
name: name,
value: value as SettingValue,
});
// }

// if (!shouldUseOnlyExternalSettings) {
// setSettingValueToLS(name, value);
// }
},
[shouldUseMetaSettings, shouldUseOnlyExternalSettings, user, name, debouncedSetMetaSetting],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: saveValue callback always calls debouncedSetMetaSetting even when meta settings aren't enabled, wasting API calls. Check shouldUseMetaSettings before calling.

Suggested change
const saveValue = React.useCallback<SaveSettingValue<T>>(
(value) => {
if (shouldUseMetaSettings) {
debouncedSetMetaSetting({
user,
name: name,
value: stringifySettingValue(value),
});
}
if (!shouldUseOnlyExternalSettings) {
setSettingValueToLS(name, value);
}
// if (shouldUseMetaSettings) {
debouncedSetMetaSetting({
user,
name: name,
value: value as SettingValue,
});
// }
// if (!shouldUseOnlyExternalSettings) {
// setSettingValueToLS(name, value);
// }
},
[shouldUseMetaSettings, shouldUseOnlyExternalSettings, user, name, debouncedSetMetaSetting],
);
const saveValue = React.useCallback<SaveSettingValue<T>>(
(value) => {
if (shouldUseMetaSettings) {
debouncedSetMetaSetting({
user,
name: name,
value: value as SettingValue,
});
}
// if (!shouldUseOnlyExternalSettings) {
// setSettingValueToLS(name, value);
// }
},
[shouldUseMetaSettings, shouldUseOnlyExternalSettings, user, name, debouncedSetMetaSetting],
);


const {currentData: metaSetting, isLoading: isSettingLoading} =
settingsApi.useGetSingleSettingQuery(params);
const {isLoading: isSettingLoading} = settingsApi.useGetSingleSettingQuery({user, name});
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: useGetSingleSettingQuery is called unconditionally even when user or name are undefined, causing unnecessary API calls. The query should be skipped using skipToken when these are missing.

Suggested change
const {isLoading: isSettingLoading} = settingsApi.useGetSingleSettingQuery({user, name});
const {isLoading: isSettingLoading} = settingsApi.useGetSingleSettingQuery(shouldUseMetaSettings ? {user, name} : skipToken);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support YDB settings service in EM

2 participants