feat: UI for semantic layers

This commit is contained in:
Beto Dealmeida
2026-02-11 16:02:37 -05:00
parent 1bb51d13f5
commit 4dc5b0a07d
13 changed files with 8399 additions and 3534 deletions

View File

@@ -51,7 +51,8 @@ def test_create_semantic_layer_success(mocker: MockerFixture) -> None:
result = CreateSemanticLayerCommand(data).run()
assert result == new_model
dao.create.assert_called_once_with(attributes=data)
expected = {**data, "configuration": '{"account": "test"}'}
dao.create.assert_called_once_with(attributes=expected)
mock_cls.from_configuration.assert_called_once_with({"account": "test"})

View File

@@ -221,6 +221,116 @@ def test_update_semantic_layer_copies_data(mocker: MockerFixture) -> None:
assert original_data == {"description": "Original"}
# =============================================================================
# UpdateSemanticLayerCommand tests
# =============================================================================
def test_update_semantic_layer_success(mocker: MockerFixture) -> None:
"""Test successful update of a semantic layer."""
mock_model = MagicMock()
mock_model.type = "snowflake"
dao = mocker.patch(
"superset.commands.semantic_layer.update.SemanticLayerDAO",
)
dao.find_by_uuid.return_value = mock_model
dao.update.return_value = mock_model
data = {"name": "Updated", "description": "New desc"}
result = UpdateSemanticLayerCommand("some-uuid", data).run()
assert result == mock_model
dao.find_by_uuid.assert_called_once_with("some-uuid")
dao.update.assert_called_once_with(mock_model, attributes=data)
def test_update_semantic_layer_not_found(mocker: MockerFixture) -> None:
"""Test that SemanticLayerNotFoundError is raised when model is missing."""
dao = mocker.patch(
"superset.commands.semantic_layer.update.SemanticLayerDAO",
)
dao.find_by_uuid.return_value = None
with pytest.raises(SemanticLayerNotFoundError):
UpdateSemanticLayerCommand("missing-uuid", {"name": "test"}).run()
def test_update_semantic_layer_duplicate_name(mocker: MockerFixture) -> None:
"""Test that SemanticLayerInvalidError is raised for duplicate names."""
mock_model = MagicMock()
mock_model.type = "snowflake"
dao = mocker.patch(
"superset.commands.semantic_layer.update.SemanticLayerDAO",
)
dao.find_by_uuid.return_value = mock_model
dao.validate_update_uniqueness.return_value = False
with pytest.raises(SemanticLayerInvalidError):
UpdateSemanticLayerCommand("some-uuid", {"name": "Duplicate"}).run()
def test_update_semantic_layer_validates_configuration(
mocker: MockerFixture,
) -> None:
"""Test that configuration is validated against the plugin."""
mock_model = MagicMock()
mock_model.type = "snowflake"
dao = mocker.patch(
"superset.commands.semantic_layer.update.SemanticLayerDAO",
)
dao.find_by_uuid.return_value = mock_model
dao.update.return_value = mock_model
mock_cls = MagicMock()
mocker.patch.dict(
"superset.commands.semantic_layer.update.registry",
{"snowflake": mock_cls},
)
config = {"account": "test"}
UpdateSemanticLayerCommand("some-uuid", {"configuration": config}).run()
mock_cls.from_configuration.assert_called_once_with(config)
def test_update_semantic_layer_skips_name_check_when_no_name(
mocker: MockerFixture,
) -> None:
"""Test that name uniqueness is not checked when name is not provided."""
mock_model = MagicMock()
mock_model.type = "snowflake"
dao = mocker.patch(
"superset.commands.semantic_layer.update.SemanticLayerDAO",
)
dao.find_by_uuid.return_value = mock_model
dao.update.return_value = mock_model
UpdateSemanticLayerCommand("some-uuid", {"description": "Updated"}).run()
dao.validate_update_uniqueness.assert_not_called()
def test_update_semantic_layer_copies_data(mocker: MockerFixture) -> None:
"""Test that the command copies input data and does not mutate it."""
mock_model = MagicMock()
mock_model.type = "snowflake"
dao = mocker.patch(
"superset.commands.semantic_layer.update.SemanticLayerDAO",
)
dao.find_by_uuid.return_value = mock_model
dao.update.return_value = mock_model
original_data = {"description": "Original"}
UpdateSemanticLayerCommand("some-uuid", original_data).run()
assert original_data == {"description": "Original"}
def _make_view_model(
uuid: str = "view-uuid-1",
name: str = "my_view",