YuriyKrasilnikov commented on PR #37790:
URL: https://github.com/apache/superset/pull/37790#issuecomment-3919062103

   ## Response to CodeAnt AI reviews (Feb 17 + Feb 18)
   
   Verified all 16 findings against the actual code. 5 were real bugs and have 
been fixed in `90614091ba`. 11 are false positives.
   
   ---
   
   ### Fixed (5) — commit `90614091ba`
   
   **1. TranslatableTextControl `prevValueRef` broken pattern** 
(`index.tsx:201-207`)
   
   Real bug. `prevValueRef.current` is updated on line 204 BEFORE the 
`displayValue` check on line 206, so `prevValueRef.current !== value` is always 
`false` after the ref update → `displayValue` always equals `localValue` → 
external value changes (undo, chart switch) are ignored.
   
   Replaced with `useEffect` sync — the established Superset pattern for 
functional components (`BoundsControl.tsx:79-81`).
   
   **2. LocaleSwitcher keyboard toggle bypassing `handleOpenChange`** 
(`LocaleSwitcher.tsx:233`)
   
   Real bug. `setDropdownOpen(prev => !prev)` bypasses `handleOpenChange`, so 
`onDropdownOpenChange` callback never fires for keyboard users. Two consumers 
rely on this callback for focus management: `AdhocMetricEditPopoverTitle` (uses 
`dropdownOpenRef` to track state) and `DndColumnSelectPopoverTitle` (calls 
`inputRef.current?.focus()` on close).
   
   One-line fix: `handleOpenChange(!dropdownOpen)` — same handler for mouse and 
keyboard, matching Superset's `DrillBySubmenu` pattern.
   
   **3. LocaleProvider `isLoading` stuck on success** 
(`LocaleProvider.tsx:122-124`)
   
   Real bug. `setIsLoading(true)` at line 118, but only reset in the `catch` 
path. When `controller.setLocale(currentLocale)` → controller early returns 
(line 132-134 in LocaleController) → `onChange` not fired → `isLoading` stuck 
`true`.
   
   Changed `catch` to `finally` — the dominant Superset pattern (3 of 4 loading 
state examples: `DrillDetailPane`, `SamplesPane`, `UploadDataModel`).
   
   **4. LocaleController `initializeLocale` race condition** 
(`LocaleController.ts:88`)
   
   Real bug. Constructor fires `this.initializeLocale(initialLocale)` without 
storing the promise in `pendingLocaleChange`. If `setLocale()` is called during 
init, the guard at line 137 (`if (this.pendingLocaleChange)`) does nothing → 
two concurrent fetches race → locale can revert to initial.
   
   One-line fix: `this.pendingLocaleChange = 
this.initializeLocale(initialLocale)` — leverages the existing dedup mechanism. 
Same pattern as `preamble.ts` storing init promises.
   
   **5. `schema_mixin` `translations=null` validation** (`schema_mixin.py:69`)
   
   Real bug (partial). The check `if "translations" not in data` misses the 
case where `translations` is explicitly `null` — Marshmallow passes `None` as 
the value when `allow_none=True`. The feature flag check at line 72 would block 
`null` when the feature is disabled, even though `null` means "no translations."
   
   However, the crash claim is incorrect — `validate_translations(None)` 
handles `None` safely (early return at `validation.py:128-129`).
   
   Fixed: `if "translations" not in data or data["translations"] is None: 
return`
   
   ---
   
   ### False positives (11)
   
   **6. `getLocalizedFormDataValue` empty string truthy check** 
(`getLocalizedFormDataValue.ts:41`)
   
   Not a bug. Empty string is not a valid translation value — the entire 
pipeline enforces this:
   
   - Frontend: `stripEmptyValues()` (`utils.ts:30-44`) removes empty strings 
before API call
   - Backend: `validate_translations()` (`validation.py:155`) enforces 
`min_length=1`
   - Backend: `sanitize_translations()` filters empty values after HTML 
stripping
   
   The truthy check is intentional and consistent across 
`getLocalizedFormDataValue`, `getLocalizedValue`, `countFieldTranslations`, and 
`buildLocalizedMetricLabelMap`.
   
   **7 + 13. MixedTimeseries `yAxisTitleSecondary` key casing** 
(`transformProps.ts:245`) — flagged twice across two reviews
   
   Not a bug. The suggestion assumes translations are keyed by snake_case 
(`y_axis_title_secondary`), but that's incorrect.
   
   The control panel defines `name: 'yAxisTitleSecondary'` (camelCase) at 
`controlPanel.tsx:486`. `convertKeysToCamelCase` 
(`convertKeysToCamelCase.ts:22-33`) is **shallow** — it uses `mapKeys` on 
top-level formData keys only. The `translations` dict is a nested value, so its 
internal keys are NOT converted. Both storage (TranslatableTextControl writes 
to `translations[control.name]`) and lookup 
(`getLocalizedFormDataValue(translations, 'yAxisTitleSecondary')`) use the same 
camelCase key from the control's `name` property.
   
   **8. `countFieldTranslations` counting `DEFAULT_LOCALE_KEY`** (`utils.ts:53`)
   
   Not a bug. `DEFAULT_LOCALE_KEY = 'default'` is a UI-only sentinel used in 
LocaleSwitcher to represent "editing the default column value." It is never 
stored in the `translations` JSON column in the database. The database format 
is `{field: {locale_code: value}}` where locale codes are real codes like 
`"en"`, `"de"`, `"ru"`. The `default` key cannot appear in the translations map 
— `stripEmptyValues()` at save time and `validate_translations()` at API level 
both operate on real locale codes only.
   
   **9 + 15. Object spread `undefined`** — FiltersConfigForm 
(`FiltersConfigForm.tsx:644`) + DndColumnSelectPopoverTitle 
(`DndColumnSelectPopoverTitle.tsx:160`) — flagged twice across two reviews
   
   Not a bug. Object spread with `undefined` is explicitly safe per ECMAScript 
spec. **CopyDataProperties** (ECMA-262, §14.18) step 3: if source is 
`undefined` or `null`, return target. `({ ...undefined, de: "German" })` → `{ 
de: "German" }`.
   
   Pre-existing Superset code uses the same pattern: 
`useStoredSidebarWidth.ts:30,45` (Jul 2022) — `useRef<Record<string, 
number>>()` with no initial value, then `{ ...widthsMapRef.current, [id]: 
updatedWidth }` where `.current` can be `undefined`.
   
   The nested level in both components already has an explicit guard (`?.label 
?? {}`, `?.name ?? {}`), showing awareness of the undefined case — handled 
where the spec does NOT provide safety (property access on undefined).
   
   **10. PropertiesModal `useEffect` stale dependency** 
(`PropertiesModal/index.tsx:308`)
   
   Not a bug. The effect at line 294 intentionally loads chart data once when 
the modal opens (on `slice.slice_id` change). `userLocale` comes from Redux 
`state.common.locale` which is set in `preamble.ts` at app init and does not 
change during a session — changing locale in Superset requires a page reload. 
Adding `fetchChartProperties` to the deps would cause unnecessary re-fetches 
for a value that is effectively constant. The code passes 
`react-hooks/exhaustive-deps` in CI.
   
   **11. `dashboards/schemas.py` `del` → `pop` for guest user fields** 
(`schemas.py:273-275`)
   
   Pre-existing code — authored in commit `386d4e054` (Dec 2023), not part of 
this PR. The fields are always present in the serialized dict:
   
   - `owners`: SQLAlchemy `relationship()` (line 151 in `dashboard.py`) — 
always exists
   - `changed_by_name`: `@property` (line 260) — always returns `str`
   - `changed_by`: inherited from `AuditMixin` — always exists
   
   Marshmallow `@post_dump` includes all declared schema fields when the source 
object attributes exist. `KeyError` is impossible. The scenario of 
instantiating with `only=("id", "dashboard_title")` doesn't happen — this 
schema is used exclusively by the dashboard REST API at full field set.
   
   **12. SaveModal.tsx name overwrite race condition** (`SaveModal.tsx:176-178`)
   
   Not a practical issue. The theoretical race window is ~300-700ms (awaiting 
`loadDashboard` + `loadTabs` + translations fetch). The pre-existing code in 
the same `componentDidMount` (lines 150-157, commit `386d4e054`, Dec 2023) has 
the identical pattern for the `dashboard` field — `setState` after async fetch 
without loading gate.
   
   The name field already shows the correct value from `props.sliceName` at 
mount. The API response replaces it with the original (non-localized) name from 
`include_translations=true` — this is intentional for the editor mode. 
`chart.slice_name || this.state.newSliceName` also preserves user input if the 
API returns empty.
   
   **14. LocaleSwitcher warning for regional locales** 
(`LocaleSwitcher.tsx:83-84`)
   
   Not a bug. The scenario requires a hyphen-format locale like `de-AT`, but 
Superset `LANGUAGES` config (`config.py:421-440`) uses base codes and 
underscore variants only: `en`, `de`, `pt_BR`, `zh_TW`, etc. The 
`session["locale"]` value (`initialization/__init__.py:994`) is always a key 
from `LANGUAGES` — never hyphen-separated.
   
   More importantly, the proposed fix would make things **worse**. The 
LocaleSwitcher warning correctly reflects what the user actually sees: both 
`LocaleSwitcher` (exact match) and `getLocalizedValue` (`split('-')` only) 
behave identically for underscore-separated locales — `'pt_BR'.split('-')[0]` = 
`'pt_BR'` (no fallback). Adding base-language fallback only to LocaleSwitcher 
would make the warning say "translation exists" while the rendered text shows 
the default value.
   
   The backend `get_translation()` (`locale_utils.py:123`) handles both 
separators (`for sep in ("-", "_")`), but this is a separate backend-frontend 
consistency question, not a LocaleSwitcher bug.
   
   **16. `sanitize_translations(None)` crash in `@post_load`** 
(`schema_mixin.py:91`)
   
   Not a bug. `sanitize_translations()` explicitly accepts `None`:
   
   1. Type signature (`sanitization.py:64-66`): `translations: dict[str, 
dict[str, str]] | None`
   2. Guard clause (`sanitization.py:85-86`): `if translations is None: return 
None`
   3. Docstring example (`sanitization.py:82-83`): `>>> 
sanitize_translations(None)` → `None`
   4. Dedicated test (`xss_sanitization_test.py:119-121`): `assert 
sanitize_translations(None) is None`
   
   The call chain is: Marshmallow deserializes `"translations": null` → 
`data["translations"] = None` → `@post_load` calls 
`sanitize_translations(None)` → returns `None` → `data["translations"]` stays 
`None`. No TypeError possible.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to