codeant-ai-for-open-source[bot] commented on code in PR #41075:
URL: https://github.com/apache/superset/pull/41075#discussion_r3538003549


##########
superset/initialization/__init__.py:
##########
@@ -626,6 +626,229 @@ def init_extensions(self) -> None:
                         extension.manifest.id,
                     )
 
+    @staticmethod
+    def _remove_continuum_write_listeners() -> None:
+        """Detach SQLAlchemy-Continuum's own write listeners.
+
+        ``make_versioned()`` runs unconditionally at import of
+        ``superset.extensions`` and registers Continuum's mapper, session,
+        and engine listeners — the ones that write shadow rows and
+        ``version_transaction`` rows on every flush. Skipping only the
+        custom baseline/change-record listeners would leave those running,
+        so with the kill-switch off the shadow tables would silently keep
+        accumulating, contradicting the documented contract.
+
+        This is deliberately a *targeted subset* of
+        ``sqlalchemy_continuum.remove_versioning()``: that helper also
+        calls ``manager.reset()``, which clears ``version_class_map`` —
+        and ``version_class()`` would then silently return the live model
+        class, breaking the read-only ``/versions/`` endpoints this flag
+        promises to keep working.
+
+        Idempotent: guarded on a representative listener so repeated app
+        initializations in one process (test fixtures) don't raise on
+        double-removal.
+        """
+        # pylint: disable=import-outside-toplevel
+        import sqlalchemy as sa
+        from sqlalchemy_continuum import versioning_manager
+
+        if not sa.event.contains(
+            sa.orm.Mapper, "after_insert", versioning_manager.track_inserts
+        ):
+            return  # already detached by a prior init
+        versioning_manager.remove_operations_tracking(sa.orm.Mapper)
+        versioning_manager.remove_session_tracking(sa.orm.session.Session)
+        sa.event.remove(
+            sa.engine.Engine,
+            "before_execute",
+            versioning_manager.track_association_operations,
+        )
+        sa.event.remove(
+            sa.engine.Engine, "rollback", versioning_manager.clear_connection
+        )
+        sa.event.remove(
+            sa.engine.Engine,
+            "set_connection_execution_options",
+            versioning_manager.track_cloned_connections,
+        )
+
+        # Belt-and-suspenders: flip Continuum's master option off as well.
+        # Every write listener checks ``manager.options['versioning']`` before
+        # doing work (manager.py / unit_of_work.py), so if a future Continuum
+        # version registers an additional write listener this detach does not
+        # know to remove, that listener still no-ops. ``version_class()`` reads
+        # from ``version_class_map`` and ignores this option, so the read-only
+        # ``/versions/`` endpoints are unaffected.
+        versioning_manager.options["versioning"] = False
+
+        # Verify the known write listeners are actually gone. A Continuum
+        # upgrade that renamed a handler would make the removals above silently
+        # miss, leaving capture half-on while we report "disabled"; surface
+        # that rather than booting in a contradictory state.
+        if sa.event.contains(
+            sa.orm.Mapper, "after_insert", versioning_manager.track_inserts
+        ):
+            logger.warning(
+                "versioning: Continuum write listeners still attached after "
+                "detach; capture may not be fully disabled. This usually means 
"
+                "the pinned sqlalchemy-continuum version changed how it "
+                "registers listeners."
+            )
+
+    def init_versioning(self) -> None:
+        """Register SQLAlchemy-Continuum baseline and retention listeners.
+
+        Must be called after all versioned model classes have been imported so
+        that VERSIONED_MODELS can be populated and configure_mappers() has run.
+
+        ``ENABLE_VERSIONING_CAPTURE`` (ships default ``False``) gates the two
+        before-flush listener registrations. The flag is operational, not
+        feature: with it off the infrastructure is inert (no save writes
+        shadow rows); flipping it on activates capture. The switch also lets
+        an operator who observes a versioning-induced regression (e.g. a
+        save-path slowdown attributable to the change-record listener)
+        disable capture in ``superset_config.py`` and restart workers — a
+        30-second recovery instead of revert-and-redeploy. Shadow tables
+        already created by the migration stay; they just stop accumulating
+        new rows.
+
+        The fallback here is ``False`` so that any app-factory path that
+        does not load ``superset.config`` (some test factories, embedded
+        use) stays inert by default rather than silently enabling capture.
+        """
+        # Beat-schedule check first: the retention task is independent of
+        # save-path capture and remains useful for ageing-out rows already
+        # written by prior deploys. An operator hitting the kill-switch in
+        # anger may also be running a hand-rolled ``CeleryConfig`` that
+        # silently dropped the prune entry; surfacing both misconfigurations
+        # at the same restart is the cheap, observability-positive shape.
+        self._warn_if_retention_beat_missing()
+
+        if not self.config.get("ENABLE_VERSIONING_CAPTURE", False):
+            logger.warning(
+                "versioning: ENABLE_VERSIONING_CAPTURE is False; "
+                "skipping baseline + change-record listener registration "
+                "and detaching Continuum's write listeners. Save-path "
+                "capture is disabled; existing shadow tables and "
+                "/versions/ endpoints continue to work read-only."
+            )
+            self._remove_continuum_write_listeners()
+            return
+
+        # Symmetric with the OFF branch's ``options['versioning'] = False``:
+        # re-assert it on here so capture is restored even if a prior app
+        # init in the same process (multi-app / test reentrancy) flipped the
+        # process-global Continuum option off. Without this, an OFF app
+        # initialized before an ON app would leave the option False and the
+        # baseline listener — which gates on it — would silently write no
+        # baselines despite capture being "enabled".
+        from sqlalchemy_continuum import versioning_manager
+
+        versioning_manager.options["versioning"] = True

Review Comment:
   **Suggestion:** Enabling capture only flips the global Continuum option back 
to true, but it never re-attaches the write listeners that were removed in the 
OFF path. In a multi-app/test-reentrant process where an OFF init ran first, a 
later ON init will still not produce shadow/version rows because listener 
registration is still missing. Restore listener registration when turning 
capture back on (or avoid detaching listeners and rely on the option gate 
consistently). [incomplete implementation]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Versioning capture disabled despite configuration enabling it.
   - ⚠️ Version tables never populate, retention becomes moot.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In a multi-app or test-reentrant process, first create a Superset app 
using a config
   with ENABLE_VERSIONING_CAPTURE=False so that 
SupersetAppInitializer.init_app_in_ctx
   (superset/initialization/__init__.py:273-300) calls init_versioning(), hits 
the False
   branch at lines 728-734, and invokes _remove_continuum_write_listeners() at 
line 736.
   
   2. In that first init, _remove_continuum_write_listeners
   (superset/initialization/__init__.py:50-104) detaches Continuum's mapper, 
session, and
   engine write listeners via versioning_manager.remove_operations_tracking(),
   versioning_manager.remove_session_tracking(), and sa.event.remove(...) calls 
(lines
   81-95), then sets versioning_manager.options["versioning"] = False at line 
104, fully
   disabling Continuum's write path globally.
   
   3. In the same Python process, create a second Superset app whose config has
   ENABLE_VERSIONING_CAPTURE=True; init_app_in_ctx again calls 
init_versioning(), which skips
   _remove_continuum_write_listeners, flips 
versioning_manager.options["versioning"] back to
   True at lines 167-169, and registers the custom baseline and change-record 
listeners via
   register_baseline_listener() and register_change_record_listener() (lines 
225-226), but
   never re-attaches Continuum's own write listeners that were removed earlier.
   
   4. Perform a write through a versioned endpoint, for example PUT 
/api/v1/chart/<id>
   handled in superset/charts/api.py around line 519 (which expects capture to 
be enabled
   when ENABLE_VERSIONING_CAPTURE is True); because Continuum's write listeners 
remain
   detached, no shadow or version_transaction rows are emitted even though
   versioning_manager.options["versioning"] is True, and helpers like
   current_entity_version_info() in superset/versioning/api_helpers.py:16-39 
still treat
   capture as enabled and query version tables, yielding inconsistent or empty
   version-history data.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1810e65b537a4dc09266ddedb7816e6e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1810e65b537a4dc09266ddedb7816e6e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/initialization/__init__.py
   **Line:** 746:748
   **Comment:**
        *Incomplete Implementation: Enabling capture only flips the global 
Continuum option back to true, but it never re-attaches the write listeners 
that were removed in the OFF path. In a multi-app/test-reentrant process where 
an OFF init ran first, a later ON init will still not produce shadow/version 
rows because listener registration is still missing. Restore listener 
registration when turning capture back on (or avoid detaching listeners and 
rely on the option gate consistently).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41075&comment_hash=98d363210b9a0d8724b96c1b278c187c73af1a150ff163ae0d8a5c50fd29c061&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41075&comment_hash=98d363210b9a0d8724b96c1b278c187c73af1a150ff163ae0d8a5c50fd29c061&reaction=dislike'>👎</a>



##########
superset/initialization/__init__.py:
##########
@@ -626,6 +626,229 @@ def init_extensions(self) -> None:
                         extension.manifest.id,
                     )
 
+    @staticmethod
+    def _remove_continuum_write_listeners() -> None:
+        """Detach SQLAlchemy-Continuum's own write listeners.
+
+        ``make_versioned()`` runs unconditionally at import of
+        ``superset.extensions`` and registers Continuum's mapper, session,
+        and engine listeners — the ones that write shadow rows and
+        ``version_transaction`` rows on every flush. Skipping only the
+        custom baseline/change-record listeners would leave those running,
+        so with the kill-switch off the shadow tables would silently keep
+        accumulating, contradicting the documented contract.
+
+        This is deliberately a *targeted subset* of
+        ``sqlalchemy_continuum.remove_versioning()``: that helper also
+        calls ``manager.reset()``, which clears ``version_class_map`` —
+        and ``version_class()`` would then silently return the live model
+        class, breaking the read-only ``/versions/`` endpoints this flag
+        promises to keep working.
+
+        Idempotent: guarded on a representative listener so repeated app
+        initializations in one process (test fixtures) don't raise on
+        double-removal.
+        """
+        # pylint: disable=import-outside-toplevel
+        import sqlalchemy as sa
+        from sqlalchemy_continuum import versioning_manager
+
+        if not sa.event.contains(
+            sa.orm.Mapper, "after_insert", versioning_manager.track_inserts
+        ):
+            return  # already detached by a prior init
+        versioning_manager.remove_operations_tracking(sa.orm.Mapper)
+        versioning_manager.remove_session_tracking(sa.orm.session.Session)
+        sa.event.remove(
+            sa.engine.Engine,
+            "before_execute",
+            versioning_manager.track_association_operations,
+        )
+        sa.event.remove(
+            sa.engine.Engine, "rollback", versioning_manager.clear_connection
+        )
+        sa.event.remove(
+            sa.engine.Engine,
+            "set_connection_execution_options",
+            versioning_manager.track_cloned_connections,
+        )
+
+        # Belt-and-suspenders: flip Continuum's master option off as well.
+        # Every write listener checks ``manager.options['versioning']`` before
+        # doing work (manager.py / unit_of_work.py), so if a future Continuum
+        # version registers an additional write listener this detach does not
+        # know to remove, that listener still no-ops. ``version_class()`` reads
+        # from ``version_class_map`` and ignores this option, so the read-only
+        # ``/versions/`` endpoints are unaffected.
+        versioning_manager.options["versioning"] = False
+
+        # Verify the known write listeners are actually gone. A Continuum
+        # upgrade that renamed a handler would make the removals above silently
+        # miss, leaving capture half-on while we report "disabled"; surface
+        # that rather than booting in a contradictory state.
+        if sa.event.contains(
+            sa.orm.Mapper, "after_insert", versioning_manager.track_inserts
+        ):
+            logger.warning(
+                "versioning: Continuum write listeners still attached after "
+                "detach; capture may not be fully disabled. This usually means 
"
+                "the pinned sqlalchemy-continuum version changed how it "
+                "registers listeners."
+            )
+
+    def init_versioning(self) -> None:
+        """Register SQLAlchemy-Continuum baseline and retention listeners.
+
+        Must be called after all versioned model classes have been imported so
+        that VERSIONED_MODELS can be populated and configure_mappers() has run.
+
+        ``ENABLE_VERSIONING_CAPTURE`` (ships default ``False``) gates the two
+        before-flush listener registrations. The flag is operational, not
+        feature: with it off the infrastructure is inert (no save writes
+        shadow rows); flipping it on activates capture. The switch also lets
+        an operator who observes a versioning-induced regression (e.g. a
+        save-path slowdown attributable to the change-record listener)
+        disable capture in ``superset_config.py`` and restart workers — a
+        30-second recovery instead of revert-and-redeploy. Shadow tables
+        already created by the migration stay; they just stop accumulating
+        new rows.
+
+        The fallback here is ``False`` so that any app-factory path that
+        does not load ``superset.config`` (some test factories, embedded
+        use) stays inert by default rather than silently enabling capture.
+        """
+        # Beat-schedule check first: the retention task is independent of
+        # save-path capture and remains useful for ageing-out rows already
+        # written by prior deploys. An operator hitting the kill-switch in
+        # anger may also be running a hand-rolled ``CeleryConfig`` that
+        # silently dropped the prune entry; surfacing both misconfigurations
+        # at the same restart is the cheap, observability-positive shape.
+        self._warn_if_retention_beat_missing()
+
+        if not self.config.get("ENABLE_VERSIONING_CAPTURE", False):
+            logger.warning(
+                "versioning: ENABLE_VERSIONING_CAPTURE is False; "
+                "skipping baseline + change-record listener registration "
+                "and detaching Continuum's write listeners. Save-path "
+                "capture is disabled; existing shadow tables and "
+                "/versions/ endpoints continue to work read-only."
+            )
+            self._remove_continuum_write_listeners()
+            return
+
+        # Symmetric with the OFF branch's ``options['versioning'] = False``:
+        # re-assert it on here so capture is restored even if a prior app
+        # init in the same process (multi-app / test reentrancy) flipped the
+        # process-global Continuum option off. Without this, an OFF app
+        # initialized before an ON app would leave the option False and the
+        # baseline listener — which gates on it — would silently write no
+        # baselines despite capture being "enabled".
+        from sqlalchemy_continuum import versioning_manager
+
+        versioning_manager.options["versioning"] = True
+
+        from sqlalchemy.orm import Session  # noqa: F401
+        from sqlalchemy_continuum import version_class
+
+        from superset.connectors.sqla.models import SqlaTable
+        from superset.models.dashboard import Dashboard
+        from superset.models.slice import Slice
+        from superset.versioning.baseline import (
+            register_baseline_listener,
+            VERSIONED_MODELS,
+        )
+
+        # Note: previously this block called ``configure_mappers()`` before
+        # importing the snapshot modules, believing their Table declarations
+        # needed ``version_transaction`` to exist. That's not actually the
+        # case — the snapshot tables reference ``version_transaction.id``
+        # only at the DB level (via the migration); the SQLAlchemy Table
+        # objects here intentionally declare ``transaction_id`` as a plain
+        # ``BigInteger`` without a FK to avoid the resolution dependency.
+        # Removing the global ``configure_mappers()`` avoids eagerly
+        # resolving relationships in other unrelated models (notably
+        # Flask-AppBuilder's AuditMixin on classes like Tag, whose
+        # ``created_by`` primaryjoin only resolves under specific class
+        # registry states in SQLAlchemy 1.4).
+        from superset.versioning.changes import (  # noqa: E402
+            register_change_record_listener,
+        )
+
+        # All versioned models — Dashboard / Slice / SqlaTable plus their
+        # children (TableColumn / SqlMetric) and the dashboard_slices
+        # M2M — go through Continuum's shadow tables. The JSON-snapshot
+        # path that previously backed dataset / dashboard child diffs
+        # has been removed (full-Continuum spike).
+        for model_cls in (Dashboard, Slice, SqlaTable):
+            try:
+                version_class(model_cls)  # ensure Continuum wired this model
+                # Dedup guard: VERSIONED_MODELS is module-level state, and
+                # test fixtures initialize multiple Superset apps per
+                # process — without the check each re-init appends
+                # duplicate entries.
+                if model_cls not in VERSIONED_MODELS:
+                    VERSIONED_MODELS.append(model_cls)
+            except Exception:  # pylint: disable=broad-except
+                # Continuum failed to wire versioning for this model. We
+                # boot in degraded mode rather than failing startup, but a
+                # silent skip would hide that change capture has stopped for
+                # the model — so surface it at WARNING with the traceback.
+                logger.warning(
+                    "Versioning is not wired for %s; change capture will be "
+                    "skipped for it. This usually means Continuum did not "
+                    "register a version class for the model.",
+                    model_cls.__name__,
+                    exc_info=True,
+                )
+
+        register_baseline_listener()
+        register_change_record_listener()
+
+        # Retention is time-based and runs out-of-band as a Celery beat
+        # task — see ``superset/tasks/version_history_retention.py``
+        # and the ``version_history.prune_old_versions`` entry in
+        # ``CELERYBEAT_SCHEDULE`` (``superset/config.py``). The previous
+        # synchronous after_commit listener was retired so retention
+        # work doesn't add latency to user saves.
+
+    _RETENTION_TASK_NAME = "version_history.prune_old_versions"
+
+    def _warn_if_retention_beat_missing(self) -> None:
+        """WARN at startup when the resolved Celery beat schedule has no
+        ``version_history.prune_old_versions`` entry.
+
+        Operators who redefine ``CeleryConfig`` in ``superset_config.py``
+        — instead of subclassing or merging the default — silently lose
+        the retention task. Capture continues writing rows; the prune
+        never runs; disk grows until paged. The default config carries
+        the entry; this check makes the misconfiguration visible in the
+        deploy log before disk pressure makes it visible at 03:00.
+
+        Handles three shapes of ``CELERY_CONFIG``:
+        * ``None`` — Celery deliberately disabled, no retention either
+          way; return without warning.
+        * a class or module with a ``beat_schedule`` attribute — the
+          default ``CeleryConfig`` shape.
+        * a dict — Celery's documented "config as dict" shape, supported
+          by ``celery_app.config_from_object``.
+        """
+        celery_config = self.config.get("CELERY_CONFIG")
+        if celery_config is None:
+            return  # Celery disabled entirely; no retention task to warn 
about.
+        beat_schedule = (
+            celery_config.get("beat_schedule")
+            if isinstance(celery_config, dict)
+            else getattr(celery_config, "beat_schedule", None)
+        )
+        if not beat_schedule or self._RETENTION_TASK_NAME not in beat_schedule:
+            logger.warning(
+                "versioning: CELERY_CONFIG.beat_schedule is missing the "
+                "%r entry — the retention task will not fire and shadow "
+                "tables will grow unbounded. Either inherit from the "
+                "default CeleryConfig or add the entry to your override.",
+                self._RETENTION_TASK_NAME,
+            )

Review Comment:
   **Suggestion:** This check assumes the beat-schedule key must equal the task 
name, but Celery schedule entry keys are arbitrary labels; only each entry's 
`task` value is authoritative. As written, valid schedules that run the 
retention task under a different key will emit a false warning. Inspect 
schedule values for a matching `task` instead of checking only dictionary keys. 
[logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Startup warns retention missing despite task configured.
   - ⚠️ Operators may chase false Celery beat misconfigurations.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Define a custom CeleryConfig override in superset_config.py (the 
documented extension
   point referenced in superset/config.py) where beat_schedule includes a 
retention entry
   whose key label does not equal "version_history.prune_old_versions", for 
example:
   beat_schedule = {"daily_version_history_retention": {"task":
   "version_history.prune_old_versions", "schedule": crontab(minute=0, 
hour=3)}}.
   
   2. Ensure this CeleryConfig override is wired as CELERY_CONFIG so that
   SupersetAppInitializer.configure_celery uses it 
(superset/initialization/__init__.py:134
   calls celery_app.config_from_object(self.config["CELERY_CONFIG"])).
   
   3. Start Superset; in init_app_in_ctx 
(superset/initialization/__init__.py:273-300), the
   initializer eventually calls init_versioning() at line 299, which invokes
   self._warn_if_retention_beat_missing() at line 147 to validate the beat 
schedule.
   
   4. Inside _warn_if_retention_beat_missing 
(superset/initialization/__init__.py:835-271),
   celery_config is resolved from self.config["CELERY_CONFIG"], beat_schedule 
is pulled
   either from celery_config.beat_schedule or celery_config["beat_schedule"], 
and the check
   at line 843 tests `if not beat_schedule or self._RETENTION_TASK_NAME not in
   beat_schedule:`; because beat_schedule keys are arbitrary labels and the 
retention entry
   key is "daily_version_history_retention" rather than 
"version_history.prune_old_versions",
   this condition is true even though a schedule entry with
   task="version_history.prune_old_versions" exists, causing a misleading 
warning at lines
   845-849 that the retention task "will not fire" when it actually will.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d8167030479c4e50a2adac88b3895f9b&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d8167030479c4e50a2adac88b3895f9b&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/initialization/__init__.py
   **Line:** 843:850
   **Comment:**
        *Logic Error: This check assumes the beat-schedule key must equal the 
task name, but Celery schedule entry keys are arbitrary labels; only each 
entry's `task` value is authoritative. As written, valid schedules that run the 
retention task under a different key will emit a false warning. Inspect 
schedule values for a matching `task` instead of checking only dictionary keys.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41075&comment_hash=2a8a03108019128d6ccbdaa980a38651ca96bba29f29e03c03e1b79c3f0cea5b&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41075&comment_hash=2a8a03108019128d6ccbdaa980a38651ca96bba29f29e03c03e1b79c3f0cea5b&reaction=dislike'>👎</a>



-- 
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