You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/07/18 01:20:24 UTC

(superset) branch fix/dev-echarts-i18n-locale-context created (now 34592676257)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a change to branch fix/dev-echarts-i18n-locale-context
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 34592676257 fix(build): deterministic echarts locale imports; fingerprint loaded config

This branch includes the following new commits:

     new 34592676257 fix(build): deterministic echarts locale imports; fingerprint loaded config

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(superset) 01/01: fix(build): deterministic echarts locale imports; fingerprint loaded config

Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch fix/dev-echarts-i18n-locale-context
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 34592676257eafba18ed4ea81da41f531657ce71
Author: Superset Dev <de...@superset.apache.org>
AuthorDate: Fri Jul 17 18:20:12 2026 -0700

    fix(build): deterministic echarts locale imports; fingerprint loaded config
    
    Two dev-stack reliability fixes:
    
    - plugin-chart-echarts loaded locales via a template-literal dynamic
      import, which makes bundlers build a context module over echarts/i18n.
      Resolving that directory through the echarts package exports map fails
      intermittently in webpack incremental builds ("Package path ./i18n is
      exported ... but no valid target file was found"), leaving the dev
      bundle broken until a full rebuild. Replace the computed import with an
      explicit per-locale loader map: no context module, deterministic
      resolution in every bundler, and code-splitting covers exactly the
      locales listed.
    
    - Log a short md5 fingerprint of the superset_config file bytes as read
      at import time, alongside the existing "Loaded your LOCAL
      configuration" line. Auto-reloaders re-import config on file change,
      and on some filesystems (notably macOS Docker mounts) the re-read can
      race the write and observe stale content while still loading
      "successfully" — with only the path logged, that skew is invisible.
      Comparing the logged digest against `md5 <path>` on the host makes it
      a one-line diagnosis.
    
    Co-Authored-By: Claude Fable 5 <no...@anthropic.com>
---
 .../plugin-chart-echarts/src/components/Echart.tsx | 47 ++++++++++++++++++++--
 .../plugin-chart-echarts/types/external.d.ts       |  8 ++++
 superset/config.py                                 | 31 +++++++++++++-
 3 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx
index bb2c1d643f1..2bf0814f1d2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx
@@ -121,14 +121,53 @@ use([
   LabelLayout,
 ]);
 
+// Explicit per-locale imports rather than a template-literal dynamic
+// import: a computed import makes bundlers build a "context module" over
+// echarts/i18n, and resolving that directory through the echarts package's
+// `exports` map fails intermittently in webpack incremental builds
+// ("Package path ./i18n is exported ... but no valid target file was
+// found"). A static map is also the only thing that lets bundlers
+// code-split exactly the locales listed here. Keys are Superset locales
+// uppercased; echarts locales absent from this map fall back to English.
+const LOCALE_LOADERS: Record<string, () => Promise<{ default: object }>> = {
+  AR: () => import('echarts/i18n/langAR.js'),
+  CS: () => import('echarts/i18n/langCS.js'),
+  DE: () => import('echarts/i18n/langDE.js'),
+  EL: () => import('echarts/i18n/langEL.js'),
+  EN: () => import('echarts/i18n/langEN.js'),
+  ES: () => import('echarts/i18n/langES.js'),
+  FA: () => import('echarts/i18n/langFA.js'),
+  FI: () => import('echarts/i18n/langFI.js'),
+  FR: () => import('echarts/i18n/langFR.js'),
+  HU: () => import('echarts/i18n/langHU.js'),
+  IT: () => import('echarts/i18n/langIT.js'),
+  JA: () => import('echarts/i18n/langJA.js'),
+  KO: () => import('echarts/i18n/langKO.js'),
+  LV: () => import('echarts/i18n/langLV.js'),
+  NL: () => import('echarts/i18n/langNL.js'),
+  PL: () => import('echarts/i18n/langPL.js'),
+  RO: () => import('echarts/i18n/langRO.js'),
+  RU: () => import('echarts/i18n/langRU.js'),
+  SI: () => import('echarts/i18n/langSI.js'),
+  SV: () => import('echarts/i18n/langSV.js'),
+  TH: () => import('echarts/i18n/langTH.js'),
+  TR: () => import('echarts/i18n/langTR.js'),
+  UK: () => import('echarts/i18n/langUK.js'),
+  VI: () => import('echarts/i18n/langVI.js'),
+  ZH: () => import('echarts/i18n/langZH.js'),
+};
+
 const loadLocale = async (locale: string) => {
-  let lang;
+  const loader = LOCALE_LOADERS[locale];
+  if (!loader) {
+    // Locale not supported in ECharts
+    return undefined;
+  }
   try {
-    lang = await import(`echarts/i18n/lang${locale}.js`);
+    return (await loader()).default;
   } catch {
-    // Locale not supported in ECharts
+    return undefined;
   }
-  return lang?.default;
 };
 
 // Report/thumbnail screenshots use standalone="true" (charts) or 3 (reports);
diff --git a/superset-frontend/plugins/plugin-chart-echarts/types/external.d.ts b/superset-frontend/plugins/plugin-chart-echarts/types/external.d.ts
index ecdf68a7452..b857dc2bfb6 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/types/external.d.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/types/external.d.ts
@@ -23,3 +23,11 @@ declare module '*.png' {
 }
 
 declare module '*.jpg';
+
+// echarts publishes its i18n bundles through the package `exports` map
+// without type declarations; the locale loaders in components/Echart.tsx
+// import them explicitly.
+declare module 'echarts/i18n/lang*' {
+  const lang: object;
+  export default lang;
+}
diff --git a/superset/config.py b/superset/config.py
index 230d5ec17b2..7c9a30927de 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -25,6 +25,7 @@ at the end of this file.
 # pylint: disable=too-many-lines
 from __future__ import annotations
 
+import hashlib
 import importlib.util
 import json
 import logging
@@ -3041,6 +3042,27 @@ TASKS_ABORT_CHANNEL_PREFIX = "gtf:abort:"
 # -------------------------------------------------------------------
 # Don't add config values below this line since local configs won't be
 # able to override them.
+
+def _config_fingerprint(config_file: str | None) -> str:
+    """
+    A short digest of the config file's bytes as read at import time.
+
+    Auto-reloaders (e.g. werkzeug's) re-import this module when the config
+    file changes, and on some filesystems (notably macOS Docker mounts) the
+    re-read can race the write and observe stale content while still
+    "loading successfully". Logging the digest of what was *actually read*
+    makes that skew diagnosable: compare it against
+    ``md5 <path>`` on the host.
+    """
+    if not config_file:
+        return "unknown"
+    try:
+        with open(config_file, "rb") as fh:
+            return hashlib.md5(fh.read()).hexdigest()[:12]  # noqa: S324
+    except OSError:
+        return "unreadable"
+
+
 if CONFIG_PATH_ENV_VAR in os.environ:
     # Explicitly import config module that is not necessarily in pythonpath; useful
     # for case where app is being executed via pex.
@@ -3054,7 +3076,11 @@ if CONFIG_PATH_ENV_VAR in os.environ:
             if key.isupper():
                 setattr(module, key, getattr(override_conf, key))
 
-        click.secho(f"Loaded your LOCAL configuration at [{cfg_path}]", fg="cyan")
+        click.secho(
+            f"Loaded your LOCAL configuration at [{cfg_path}] "
+            f"(md5:{_config_fingerprint(cfg_path)})",
+            fg="cyan",
+        )
     except Exception:
         logger.exception(
             "Failed to import config for %s=%s", CONFIG_PATH_ENV_VAR, cfg_path
@@ -3067,7 +3093,8 @@ elif importlib.util.find_spec("superset_config"):
         from superset_config import *  # noqa: F403, F401
 
         click.secho(
-            f"Loaded your LOCAL configuration at [{superset_config.__file__}]",
+            f"Loaded your LOCAL configuration at [{superset_config.__file__}] "
+            f"(md5:{_config_fingerprint(superset_config.__file__)})",
             fg="cyan",
         )
     except Exception: