Breaking change to the (still-unmerged) activity-view JSON contract:
``entity_kind`` now emits ``"dashboard"`` / ``"chart"`` / ``"dataset"``
instead of the Python class names ``"Dashboard"`` / ``"Slice"`` /
``"SqlaTable"``. The class names are developer-facing artifacts that
leaked the model layer (e.g. ``"Slice"`` predates the UI rename to
"chart"; ``"SqlaTable"`` predates "dataset"). User-facing JSON should
speak user language.
Implementation: a new ``_USER_FACING_KIND`` map translates at JSON
serialization time only (in ``_decorate_records``). Internal code keeps
the Python class-name form (``model_cls.__name__``) for dispatch — the
existing ``_NAME_COLUMN``, ``_NOT_FOUND_EXC``, ``_API_KIND_LABEL``,
``_can_read``, ``_compute_impact``, etc. all key off class names and
are unchanged. The translation happens at the single ``record["entity_kind"]
= ...`` assignment.
Schema validator ``ACTIVITY_ENTITY_KINDS`` updated to the new tuple.
Integration tests' response-shape assertions renamed via bulk sed; unit
tests testing internal helpers are unchanged (they operate on internal
api_kind / class names).
UPDATING.md example payload updated. Spec updates (spec.md, data-model.md,
contracts/activity-view.yaml) committed separately to the spec repo.
Full suite: 66 unit + 35 integration + 1 xfailed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* T042 test_activity_marks_hard_deleted_chart_with_tombstone (D-15)
Edit a chart, hard-delete it via db.session.delete(chart) + commit,
hit dashboard activity. Asserts tombstoned record has
entity_deleted=True, entity_uuid=None, entity_name preserved from
the last shadow row. The fixture's _cleanup tolerates missing slices
(uses Slice.id.in_(slice_ids) which silently skips).
* T044 test_activity_surfaces_dashboard_restore_event (AV-015) — xfail
AV-015 requires sc-103156's restore code to emit a synthetic
kind="restore" change record with to_value carrying the source
version_uuid + label. sc-103156's restore_version() currently does
not — the diff capture surfaces the field changes as kind="field".
The activity layer correctly passes through whatever kind sc-103156
emits; the test will start passing once the upstream emission lands.
Marked strict=True so the day sc-103156 emits, this fails-to-fail
and we know to revisit.
* T051 test_activity_excludes_records_after_retention_prune (AV-010)
Edit a chart (creates a new version_transaction), backdate that
transaction's issued_at past the 30-day retention cutoff, run
_prune_old_versions_impl(retention_days=30). Assert the prune
removed ≥1 transaction AND the activity endpoint's filtered count
decreased.
* T039 test_activity_pagination_is_deterministic_and_disjoint
(SC-AV-002 pragmatic interpretation). The spec's stricter "no
skip/duplicate under concurrent writes" is unprovable with offset
pagination — new top-inserted records shift every later page by
one. Cursor pagination would solve this (deferred per plan §D-10).
Under offset, the testable guarantees are: (a) same request fired
twice produces identical pages; (b) page N and page N+1 are
disjoint under one request round. Both come from the
(issued_at DESC, transaction_id DESC, sequence DESC) sort.
Full activity-view suite: 35 passed, 1 xfailed in 46s. The xfail is
T044 with the documented sc-103156 dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three of the Phase 6 polish tasks landed together:
* T040 test_activity_ordering_is_stable_by_issued_at_then_transaction_id
— AV-006 deterministic-ordering contract. Iterates adjacent pairs in
the response and asserts (issued_at, transaction_id) is monotonically
non-increasing. Catches any sort-stability regression — random
ordering would fail the pairwise check almost certainly.
* T041 test_activity_page_size_caps_returned_records_at_200 — pairs
with the existing pagination_clamps test. The former confirms
?page_size=500 doesn't 400; this one confirms the response is
bounded to ≤200 records as the OpenAPI contract guarantees.
* T053 verification: every fixture-mutating test in
activity_view_tests.py was already following the
try/finally + rollback convention (sweep verified 30 tests; zero
non-conformant). No remediation needed; documenting the
sweep result in the commit message.
Full activity-view suite: 32/32 integration + 65/65 unit tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two doc-shaped tasks landed together because they're both about making
the activity-view endpoints discoverable by external consumers.
T047 — UPDATING.md: new section under the existing entity-version-history
entry documenting the three activity-view endpoints (dashboard / chart /
dataset), their query params (since / until / include / page / page_size),
the response shape with all DTO fields, the silent permission filter
(AV-008), tombstone behaviour (D-15), and the no-feature-flag / no-
new-tables impact statement. Mirrors the depth of the sc-103156
versioning section above it.
T048 satisfied by the UPDATING.md entry: the activity-view feature
adds no new config keys (no SUPERSET_* env vars, no feature flag), and
the per-endpoint API reference is auto-generated from the YAML
docstrings via FAB's OpenAPI integration. The `/swagger/v1` page picks
up the activity endpoints automatically — verified by the new tests
below. sc-103156 followed the same pattern (UPDATING.md only, no
standalone config doc).
T049 — Three new tests in TestActivityOpenApiSpec verify FAB's OpenAPI
generation includes the activity endpoints with the right shape:
* test_three_activity_paths_appear_in_openapi — the three
/<uuid_str>/activity/ paths are surfaced in /api/v1/_openapi.
* test_activity_endpoints_document_query_params — since / until /
include / page / page_size are all declared, and include's enum is
exactly {"self", "related", "all"}.
* test_activity_endpoints_declare_200_response — 200 + 400/401/403/404
are all declared response codes.
base_api_tests.py::TestOpenApiSpec::test_open_api_spec already
validates the full spec's structural correctness on every CI run, so
malformed YAML in the activity-view docstrings would have been caught
upstream. The new tests add activity-specific assertions about the
endpoints' presence and parameter shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the dataset activity stream. Per AV-004 datasets have no
transitive layer in V2: dataset activity = dataset's own edits only,
regardless of whether charts use the dataset. The chart/dashboard
endpoints surface dataset edits in their *related* streams (already
shipped); the dataset endpoint stays narrowly self-only.
* T033 DatasetRestApi.activity — third application of the shared
resolve_endpoint_path_entity + parse_activity_query_params pattern.
Endpoint body is ~10 lines of real logic, same shape as T016 and
T028 but with SqlaTable as the path entity. The activity orchestrator
already short-circuits the related-entity resolution for datasets
(_resolve_scope returns [] for related when path_kind="SqlaTable"),
so the dataset endpoint inherits the V2 semantics for free.
* T034 TestDatasetActivityView class added to activity_view_tests.py.
* T035 test_dataset_activity_excludes_chart_edits — AS-1 of US3 /
AV-004 verbatim. Edit a chart that uses the dataset; assert the
chart edit does NOT appear in the dataset's activity stream
(datasets are read-only upstream of charts in V2).
* T036 test_dataset_activity_includes_dataset_self_edits — confirms
the positive path: editing the dataset's own description surfaces
a SqlaTable/self record.
* New test_dataset_activity_related_only_returns_empty — AV-004 in
contract form: ?include=related on a dataset returns an empty
result list and count=0 because there are no related entities to
draw from.
Plus the standard boundary tests (404 for unknown UUID, 400 for
malformed UUID / invalid include, 403 for non-owner, 200 envelope
shape).
Full activity-view suite: 27/27 integration tests + 57/57 unit tests
green. All three endpoint families live.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the chart cross-entity activity stream (US2). Builds on the
resolve_endpoint_path_entity helper from the prior tidy commit
(1d60513079), so the new endpoint is a small composition of: resolve
path entity → parse params → get_activity(Slice, ...) → serialize.
* T028 ChartRestApi.activity — @expose("/<uuid_str>/activity/") on the
chart blueprint. Decorator stack mirrors list_versions; registered
in include_route_methods. The MODEL_API_RW_METHOD_PERMISSION_MAP
entry "activity": "write" added in Phase 3 already covers this
endpoint family (the map is class-permission-agnostic).
* T029 TestChartActivityView class added to activity_view_tests.py,
following the same patterns as TestDashboardActivityView.
* T030 test_chart_activity_includes_dataset_edit_as_related — Given
a chart pointing at the birth_names dataset, When the dataset's
description is edited, Then the chart activity stream surfaces a
SqlaTable/related record (AS-1 of US2).
* T032 test_chart_activity_excludes_sibling_dashboards — Given the
chart is on a dashboard, When the dashboard's title is edited,
Then NO Dashboard records appear in the chart's activity. Per the
spec's Relationship Traversal section: charts don't see sideways
to dashboards they happen to be on.
* T031 (datasource-switch attribution) deferred — needs a second
dataset fixture which the birth_names environment doesn't provide
cleanly. Will land with a multi-dataset test harness in a follow-up.
Additional coverage: 404 for unknown UUID, 400 for malformed UUID,
400 for invalid include, 403 for non-owner, 200 envelope smoke,
chart-self-edit appears as source=self, include=self filter test.
Full suite: 19/19 integration tests + 57/57 unit tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five structural changes — no behaviour change — applied as one commit
per the tidy-first discipline. All from the clean-code review of
e4070a4716.
* Tidy 1 — Fix _fetch_change_records sort key. Was calling .timestamp()
on issued_at with an `else 0` fallback; the fallback was dead defense
(column is non-null per sc-103156 schema) and .timestamp() introduces
non-determinism on tz-naive datetimes. Sort on the datetimes directly.
* Tidy 2 — parse_activity_query_params now raises ActivityParamsError
(subclass of ValueError) instead of returning (Optional[dict],
Optional[str]). The tuple was forcing every caller into a defensive
`if error or params is None: return self.response_400(message=error
or "Invalid query parameters")`. The new shape — `try: params =
parse(...); except ActivityParamsError as exc: return
response_400(str(exc))` — is shorter, type-safe, and the contract
is enforced at the boundary.
* Tidy 3 — Test helper now uses Flask client's query_string= parameter
instead of f-string concatenation. Handles URL-encoding correctly
for the day a test passes a value containing & / = / + / etc.
* Tidy 4 — get_activity pipeline collapses to a single rolling
`records` variable instead of the mid-stream `raw / visible_raw /
enriched / visible` naming. Each function call's name documents
what the step does; no intermediate variable names needed.
* Tidy 5 — Extracted four per-parameter parsers: _parse_optional_iso,
_parse_include, _parse_page, _parse_page_size. The "parse one
parameter" concept now has a name. Cost: four small helpers (each
10-15 lines, one job). Benefit: parse_activity_query_params is a
10-line table-driven dispatcher.
Test changes: parser unit tests now use pytest.raises(ActivityParamsError)
instead of unpacking the (params, error) tuple. Added one test
confirming ActivityParamsError subclasses ValueError so the standard
library exception hierarchy still catches it. Total unit tests: 57.
Integration tests still 10/10 green.
Deferred per the review: the UUID-parse + entity-find + ownership-
check dance is duplicated between activity / list_versions / get_version
and will grow to T028 / T033. Refactor when T028 lands — three real
callers > one prospective one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the dashboard cross-entity activity stream (US1 MVP).
Closes T015-T018, T023-T024, T026 from sc-107283 tasks.md. T019-T022
(complex fixture choreography) and T025 (RBAC fixture for restricted
chart access) deferred to a follow-up; T025's logical coverage is
provided by the new unit tests for _can_read kind dispatch.
* T015 Rename _dashboard_related_scope → _resolve_dashboard_scope and
_chart_related_scope → _resolve_chart_scope, parallel to _resolve_scope
/ _resolve_path_entity. Aligns the codebase with the task spec names.
* T016 New activity(uuid_str) method on DashboardRestApi:
@expose("/<uuid_str>/activity/"), @protect + @safe + @statsd_metrics
+ @event_logger. Same row-level ownership check pattern as
/versions/. Registered in include_route_methods and mapped to "write"
in MODEL_API_RW_METHOD_PERMISSION_MAP so the can_write Dashboard
permission gates access (Alpha-non-owner gets 403 from
raise_for_ownership, not 404 from missing permission).
* New parse_activity_query_params helper in activity.py — shared parser
for since/until/include/page/page_size used by all three endpoint
families. Tolerates the Z suffix Python <3.11 fromisoformat rejects.
Silently clamps page_size to the contract max instead of rejecting.
Two correctness/scale bugs found and fixed under integration-test load:
* SQLite SQLITE_MAX_EXPR_DEPTH (1000) was tripping on dashboards with
many slices × many historical attachment windows. _fetch_change_records
used to emit one OR-clause per (entity_kind, entity_id, window) tuple;
now it issues one SELECT per kind with entity_id IN (...) and filters
by exact windows in Python via _row_within_any_window. SQL shape is
proportional to the number of kinds (≤3); the per-entity window
precision is preserved.
* _merge_entity_windows now unions overlapping/touching windows within
each entity via _union_windows. Sequential fixture loads create many
redundant Continuum shadow rows; without merging, the unfiltered
windows still produce many OR branches downstream.
* Pipeline-ordering fix in get_activity: visibility filter runs BEFORE
decoration. Decoration strips entity_id (not in API contract) and the
filter needs it — and dropping invisible records early also avoids
paying for name lookup + tombstone probes on records the requester
can't see (AV-008's silent-filter contract).
Tests:
* tests/integration_tests/versioning/activity_view_tests.py — 10 tests
for TestDashboardActivityView covering: 404 for unknown UUID, 400 for
malformed UUID / invalid include / invalid since, 403 for non-owner,
200 envelope smoke, chart-edit-appears-as-related, include=self
filter, include=related filter, page_size clamping.
* tests/unit_tests/versioning/test_activity.py — grew from 30 to 56
tests. New coverage: parse_activity_query_params (7 cases), _can_read
per-kind dispatch (4 cases — covers T025 at unit scope), _union_windows
(9 parametrized cases), _merge_entity_windows window-union case,
_row_within_any_window (6 cases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* T001 — new ``superset/versioning/activity.py`` module with ASF
header and docstring describing the cross-entity activity-view query
layer (companion to ``queries.py``). Empty otherwise; Phase 2 fills
it with the shared query helpers.
* T002 — empty test file
``tests/integration_tests/versioning/activity_view_tests.py`` with
ASF header, module docstring naming the three test classes that will
follow, and a ``SupersetTestCase`` import. Phase 3+ fills it with
test classes.
* T003 — three Marshmallow schemas added to
``superset/versioning/schemas.py``:
* ``ActivityChangedBySchema`` — User subset on each record.
* ``ActivityRecordSchema`` — one change record (per data-model.md).
* ``ActivityResponseSchema`` — envelope ``{result, count}``.
Fields mirror data-model.md §"``ActivityRecord`` DTO" line-for-line;
metadata descriptions are FAB/Swagger-ready.
No new endpoints, no listeners, no DB writes — pure scaffolding. The
three schemas are unused until Phase 3 (T016) wires the first endpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>