You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by kg...@apache.org on 2026/05/29 16:26:09 UTC

(superset) 02/23: refactor(activity-view): enforce schema contracts (review polish)

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

kgabryje pushed a commit to branch sc-107283-versioning-activity-view
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 47b804a7dd3431c9bf8e17ac56ee1c9606779d87
Author: Mike Bridge <mi...@ext.preset.io>
AuthorDate: Tue May 26 14:48:06 2026 -0600

    refactor(activity-view): enforce schema contracts (review polish)
    
    Apply clean-code review polish on top of the Phase 1 scaffold before
    Phase 2 builds on this surface:
    
    * Replace magic-string field descriptions with ``validate.OneOf`` on
      ``entity_kind``, ``source``, ``entity_deletion_state``, and ``kind``.
      Canonical lists hoisted to module-level tuples (``ACTIVITY_*``).
    * Type ``path`` as ``fields.List(fields.String())`` and ``impact`` as
      a new ``ActivityImpactSchema`` — replaces two ``fields.Raw`` ``Any``s
      whose shape was previously documented only in prose.
    * Resolve the ``ActivityChangedBySchema`` docstring contradiction —
      the schema deliberately omits ``username`` so it is no longer
      honest to call it "identical shape to VersionChangedBySchema".
    * Fix the ``activity.py`` module docstring: one polymorphic
      ``get_activity`` function, not three entry points.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <no...@anthropic.com>
---
 superset/versioning/activity.py |  3 +-
 superset/versioning/schemas.py  | 88 +++++++++++++++++++++++++++++++----------
 2 files changed, 69 insertions(+), 22 deletions(-)

diff --git a/superset/versioning/activity.py b/superset/versioning/activity.py
index a53c37f115f..c340f544b67 100644
--- a/superset/versioning/activity.py
+++ b/superset/versioning/activity.py
@@ -23,7 +23,8 @@ chain — a dashboard's activity stream includes edits to charts that
 were attached to it AND edits to datasets those charts pointed at,
 each time-bounded by when the relationship was active.
 
-Three public entry points correspond to the three endpoint families:
+One public entry point — ``get_activity(model_cls, entity_uuid, ...)`` —
+dispatches on the first argument to serve all three endpoint families:
 
 * ``get_activity(Dashboard, dashboard_uuid, ...)`` — own edits + charts
   attached during their dashboard window + datasets those charts used
diff --git a/superset/versioning/schemas.py b/superset/versioning/schemas.py
index b1909b10bee..e1d7ce7719e 100644
--- a/superset/versioning/schemas.py
+++ b/superset/versioning/schemas.py
@@ -23,7 +23,7 @@ avoid triplicated definitions.
 
 from __future__ import annotations
 
-from marshmallow import fields, Schema
+from marshmallow import fields, Schema, validate
 
 
 class VersionChangedBySchema(Schema):
@@ -130,15 +130,42 @@ class VersionListResponseSchema(Schema):
 
 # ---- Cross-entity activity view (sc-107283) -------------------------------
 
+#: Allowed values for ``ActivityRecordSchema.entity_kind``. Mirrors the model
+#: classes registered with Continuum versioning (sc-103156).
+ACTIVITY_ENTITY_KINDS: tuple[str, ...] = ("Dashboard", "Slice", "SqlaTable")
+
+#: Allowed values for ``ActivityRecordSchema.source`` (spec AV-013).
+ACTIVITY_SOURCES: tuple[str, ...] = ("self", "related")
+
+#: Allowed values for ``ActivityRecordSchema.entity_deletion_state``.
+#: Hard-delete is communicated separately via ``entity_deleted=true``;
+#: the remaining state is the soft-delete sentinel (sc-103157).
+ACTIVITY_DELETION_STATES: tuple[str, ...] = ("soft_deleted",)
+
+#: Allowed values for ``ActivityRecordSchema.kind`` — mirrors the
+#: change-record taxonomy from sc-103156 FR-016. ``"field"`` is the
+#: fallback for scalar changes without a more specific category.
+ACTIVITY_CHANGE_KINDS: tuple[str, ...] = (
+    "filter",
+    "metric",
+    "dimension",
+    "column",
+    "chart",
+    "time_range",
+    "color_palette",
+    "restore",
+    "field",
+)
+
 
 class ActivityChangedBySchema(Schema):
-    """Subset of the User model included in each activity record.
+    """User attribution for an activity record.
 
-    Identical shape to :class:`VersionChangedBySchema` — kept as a separate
-    class so the activity-view contract can evolve independently of the
-    version-history contract if the two diverge later. ``null`` when the
-    saving user has been deleted from ``ab_user`` (sc-103156 §Session
-    2026-05-18 clarification).
+    The activity-view payload exposes only the display fields
+    (``id`` + given/family name); ``username`` is omitted by design (see
+    data-model.md §"ActivityRecord DTO"). ``null`` when the saving user
+    has been deleted from ``ab_user`` (sc-103156 §Session 2026-05-18
+    clarification).
     """
 
     id = fields.Integer()
@@ -146,6 +173,25 @@ class ActivityChangedBySchema(Schema):
     last_name = fields.String()
 
 
+class ActivityImpactSchema(Schema):
+    """Dependent-count summary attached to ``source='related'`` records.
+
+    Synthesized server-side at the time of the activity query — it counts
+    siblings affected by the same upstream change at the same transaction
+    (e.g., how many charts on the requested dashboard pointed at the
+    dataset whose edit this record represents).
+    """
+
+    charts = fields.Integer(
+        metadata={
+            "description": (
+                "Number of sibling charts on the path entity affected by "
+                "the same related-record change at this transaction."
+            )
+        },
+    )
+
+
 class ActivityRecordSchema(Schema):
     """One change record in the activity stream.
 
@@ -165,12 +211,8 @@ class ActivityRecordSchema(Schema):
         },
     )
     entity_kind = fields.String(
-        metadata={
-            "description": (
-                "Model class of the source entity: "
-                '``"Dashboard"`` | ``"Slice"`` | ``"SqlaTable"``.'
-            )
-        },
+        validate=validate.OneOf(ACTIVITY_ENTITY_KINDS),
+        metadata={"description": "Model class of the source entity."},
     )
     entity_uuid = fields.String(
         allow_none=True,
@@ -202,15 +244,16 @@ class ActivityRecordSchema(Schema):
     )
     entity_deletion_state = fields.String(
         allow_none=True,
+        validate=validate.OneOf(ACTIVITY_DELETION_STATES),
         metadata={
             "description": (
-                'Present + ``"soft_deleted"`` when the source entity has '
-                "non-null ``deleted_at`` (sc-103157). Absent or ``null`` "
-                "otherwise."
+                "Present when the source entity has non-null ``deleted_at`` "
+                "(sc-103157). Absent or ``null`` otherwise."
             )
         },
     )
     source = fields.String(
+        validate=validate.OneOf(ACTIVITY_SOURCES),
         metadata={
             "description": (
                 '``"self"`` if ``(entity_kind, entity_id)`` matches the '
@@ -236,15 +279,17 @@ class ActivityRecordSchema(Schema):
         },
     )
     kind = fields.String(
+        validate=validate.OneOf(ACTIVITY_CHANGE_KINDS),
         metadata={
             "description": (
-                "Change-record taxonomy enum from sc-103156 FR-016 "
-                "(``filter`` / ``metric`` / ``time_range`` / ``field`` / "
-                "``column`` / ``chart`` / ``restore`` / ...)."
+                "Change-record taxonomy enum from sc-103156 FR-016. "
+                "``field`` is the fallback for scalar changes without a "
+                "more specific category."
             )
         },
     )
-    path = fields.Raw(
+    path = fields.List(
+        fields.String(),
         metadata={
             "description": (
                 "JSON-pointer-style segment array (sc-103156 FR-019). "
@@ -269,7 +314,8 @@ class ActivityRecordSchema(Schema):
             )
         },
     )
-    impact = fields.Raw(
+    impact = fields.Nested(
+        ActivityImpactSchema,
         allow_none=True,
         metadata={
             "description": (