EnxDev commented on PR #43232:
URL: https://github.com/apache/superset/pull/43232#issuecomment-5339731051

   ## EnxDev's Review Agent β€” apache/superset#43232 Β· HEAD 1ee21d5
   
   request changes β€” the feature works on the chart/dashboard import path, but 
it adds an unguarded write to an Alpha-only resource, makes export fail hard on 
dangling annotation references, and silently binds legacy bundles' integer 
annotation IDs to unrelated local rows.
   
   ### πŸ”΄ Functional
   
   - **`superset/commands/annotation_layer/importers/v1/utils.py:29`** Β· _High_ 
β€” `import_annotation_layer` is the only importer with no permission gate; 
`import_chart`, `import_dataset` and `import_database` all check 
`security_manager.can_access("can_write", <model>)` and raise 
`ImportFailedError`. `"Annotation Layers"` and `"Annotation"` are in 
`ALPHA_ONLY_VIEW_MENUS`, but Gamma has `can_write on Chart`, so a Gamma user 
can `POST /api/v1/chart/import/` a bundle containing `annotation_layers/*.yaml` 
and create layers plus annotations. `import_dataset` does not block the path: 
for an alive existing dataset it returns early (`if not overwrite or not 
can_write: return existing`), and dataset UUIDs are readable from any chart 
export Gamma can already run. With `overwrite=True` and a layer UUID (also 
readable from an export), `import_from_dict(..., sync=["annotation"])` on line 
55 renames the layer and deletes every annotation not present in the bundle. 
Add the `can_write on Annotati
 on` guard, mirroring `import_dataset`. **regression test:** Gamma imports a 
chart bundle carrying `annotation_layers/` β†’ `ImportFailedError` and no 
`AnnotationLayer` row is created; Alpha succeeds.
   
   - **`superset/commands/chart/importers/v1/utils.py:62`, `:69`** Β· _High_ β€” 
non-FORMULA annotations whose `value` is an `int` are now kept verbatim. Every 
bundle exported before this PR has integer values there (UUID rewriting is new 
in this PR), and master dropped them. Import a pre-43232 chart bundle and the 
annotation binds to whatever local `annotation_layer` / `slice` happens to hold 
that id: the chart renders an unrelated layer's events, or an unrelated chart's 
data as a `line`/`table` overlay. 
`tests/unit_tests/charts/commands/importers/v1/import_test.py:168` now locks 
the behavior in β€” the fixture layer is `{"sourceType": "NATIVE", "value": 2}`, 
commented `"Native layer to be removed on import"`. Drop int values for NATIVE 
and chart-reference sources, or resolve them only when the bundle provably came 
from this instance. **regression test:** import 
`chart_config_with_mixed_annotations` into a DB where `annotation_layer` id 2 
exists and is unrelated; assert the annotati
 on is dropped, not bound to id 2.
   
   - **`superset/commands/chart/export.py:129`, `:134`, `:204`** Β· _High_ β€” a 
chart whose annotation points at a deleted layer or chart can no longer be 
exported at all. Deleting an annotation layer does not clean `slice.params`, so 
dangling references are ordinary. `_replace_annotation_layer_uuids` runs inside 
`_file_content`, on every export path, including `export_related=False`. Chart 
export returns 404 (`superset/charts/api.py:1270`); dashboard export catches 
only `DashboardNotFoundError`; `/api/v1/assets/export/` 
(`superset/importexport/api.py:80-84`) catches nothing, so one bad reference 
anywhere in the instance 500s the whole-instance export. Log and skip the 
unresolvable annotation instead of raising. **regression test:** a chart with 
`{"sourceType": "NATIVE", "value": <deleted id>}` β†’ 
`ExportChartsCommand([id]).run()` completes and the emitted YAML omits that 
layer.
   
   - **`superset/commands/importers/v1/assets.py` (not touched)** Β· _Medium_ β€” 
the assets round trip silently loses every native and chart annotation. Export 
runs with `export_related=False`, so `_file_content` still rewrites ids to 
UUIDs while `_export` never yields `annotation_layers/`. On the way back, 
`ImportAssetsCommand.schemas` has no `annotation_layers/` key, so 
`load_configs` skips those files outright, and it calls `import_chart` without 
`annotation_layer_ids` / `chart_ids` β€” every UUID fails to resolve and is 
dropped. `superset export` β†’ `superset import-directory` therefore drops 
annotations that the chart/dashboard path preserves. **regression test:** 
assets export→import of a chart with a native annotation keeps the reference.
   
   ### 🟑 Should-fix
   
   - 
**`superset/migrations/versions/2026-08-17_18-38_884a2115ebd3_add_uuid_to_annotation_layer_and_annotation.py:67-99`**
 β€” `try: <DDL> except OperationalError: pass` instead of `table_has_column` / 
`add_columns` from `superset.migrations.shared.utils`. On Postgres a duplicate 
column raises `ProgrammingError`, which is not caught, and any failed DDL 
poisons the alembic transaction; swallowing the exception can also leave the 
column added while the unique constraint is silently skipped. Guard with 
`table_has_column` and let real errors surface. Same pattern in `downgrade()` 
at `:105-125`.
   - **`…884a2115ebd3….py:55`** β€” `_assign_missing_uuids` loads the whole table 
into memory in one query. Use `paginated_update` from 
`superset.migrations.shared.utils`.
   - **PR description** β€” SIP-59 migration on two data-bearing tables with 
"Runtime estimates and downtime expectations provided" unchecked.
   - **`superset/commands/chart/importers/v1/__init__.py:104`** β€” 
`referenced_chart_uuids` is built only from `params.annotation_layers`, while 
`topological_sort_charts._annotation_dependencies` and 
`_resolve_query_context_annotations` also read `query_context`. A chart 
referenced only from `query_context` lands in `dependent_chart_configs`, which 
is never sorted, so it can be imported after its dependent and the reference is 
dropped. Reuse the `query_context` scan for discovery.
   - **Docs** β€” export bundles now carry `annotation_layers/` and store 
annotation `value` as UUID strings. No `UPDATING.md` entry and no `docs/` 
change for a format change plus a migration.
   - **Coverage** β€” codecov patch coverage is 86.0%; 
`chart/importers/v1/utils.py` 77.1%, `chart/export.py` 78.9%. The gaps are the 
new failure branches (`topological_sort_charts` circular-dependency warning, 
`_resolve_uuid_to_id` exception path). Both are pure functions β€” cheap unit 
tests, no integration fixtures needed.
   
   ### πŸ”΅ Nits
   
   - `superset/commands/annotation_layer/importers/dispatcher.py` β€” no caller. 
`annotation_layers/api.py` gains no import/export routes, so it is reachable 
only from tests. Wire the endpoints or drop the dispatcher.
   - `superset/commands/annotation_layer/importers/v1/utils.py:40` β€” 
`config["id"] = existing.id` is dead; `import_from_dict` strips any key outside 
`export_fields | {"uuid"}`.
   - `tests/integration_tests/charts/commands_tests.py` β€” 
`test_export_chart_missing_native_annotation_reference_preserves_value` asserts 
`pytest.raises(AnnotationLayerNotFoundError)`; the name says the opposite.
   - `superset/commands/chart/export.py:116` β€” `list[dict]  # type: 
ignore[type-arg]`; write `list[dict[str, Any]]` instead of suppressing.
   - `superset/commands/chart/importers/v1/__init__.py:22` β€” `from 
superset_core.common.models import Subject  # noqa: F401`; `Subject` is used in 
`_import_chart_with_tags`, so drop the noqa.
   
   ### πŸ™Œ Praise
   
   - `superset/commands/importers/v1/utils.py:146`, `:249` β€” `config: Any = 
None` fixes a real `NameError` when `load_yaml` raises before assignment, and 
the `if file_name not in exc.messages` guard stops the `{file: {file: …}}` 
double-wrap of `load_yaml`'s own `ValidationError`.
   
   ### On the existing bot comments
   
   Checked each against the diff at this HEAD rather than taking the replies at 
face value:
   
   - **CI** β€” `test-mysql` is red on 
`tests/integration_tests/sql_lab/api_tests.py::TestSqlLabApi::test_execute_custom_templated`
 (`Duplicate entry '200' for key 'dbs.PRIMARY'`). Unrelated flake, not this PR.
   - "Test expects `ChartImportError` for `not-a-uuid`" (bito, codeant) β€” fixed 
at this HEAD; the test now asserts the drop and the resulting chart contents.
   - "`load_configs` does not convert ISO timestamps" (codeant) β€” handled: 
`import_annotation_layer` deserializes `start_dttm`/`end_dttm` before 
`import_from_dict`. `load_configs` storing the raw config is by design, and 
every other importer compensates the same way.
   - "`assign_uuids` assigns the `uuid4` function on SQLite" (codeant) β€” not 
applicable; the migration uses its own `_assign_missing_uuids`, which calls 
`uuid4()`.
   - "Annotation layers always imported with `overwrite=False` in the dashboard 
importer" (codeant, bito) β€” incorrect. It passes `overwrite_assets`, the same 
flag charts, datasets and databases use in that importer.
   - "`_resolve_annotation_list` deletes query_context formula annotations" 
(codeant) β€” incorrect. The `annotationType == FORMULA` branch keeps them before 
`sourceType` is consulted.
   - "Missing chart/layer refs emit a non-portable integer on export" (codeant) 
β€” the export side raises instead, which is its own problem; see the third πŸ”΄ 
above. The integer-passthrough risk is real but on the import side, at 
`chart/importers/v1/utils.py:62`.
   
   <!-- enxdev-review-agent:1ee21d5 -->
   _Reviewed by EnxDev's Review Agent β€” @EnxDev Β· HEAD 1ee21d5._
   


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