From d7ae1fe446aea437f7ced79a2bf4f2aaf65b370b Mon Sep 17 00:00:00 2001 From: Cor Date: Wed, 19 Jun 2024 08:41:46 +0200 Subject: [PATCH] Support setting widget title and description (#166) Resolves #165 Stacked on #161 --- src/databricks/labs/lsql/dashboards.py | 15 +++++++- tests/integration/test_dashboards.py | 14 +++++++ tests/unit/test_dashboards.py | 53 +++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/databricks/labs/lsql/dashboards.py b/src/databricks/labs/lsql/dashboards.py index 87e24635..5b1132b5 100644 --- a/src/databricks/labs/lsql/dashboards.py +++ b/src/databricks/labs/lsql/dashboards.py @@ -33,6 +33,7 @@ TableEncodingMap, TableV2Spec, Widget, + WidgetFrameSpec, WidgetSpec, ) @@ -120,6 +121,8 @@ def _get_arguments_parser() -> ArgumentParser: parser.add_argument("-o", "--order", type=int) parser.add_argument("-w", "--width", type=int) parser.add_argument("-h", "--height", type=int) + parser.add_argument("-t", "--title", type=str) + parser.add_argument("-d", "--description", type=str) return parser def _parse_header(self, header: str) -> dict[str, str]: @@ -181,12 +184,16 @@ def __init__( width: int = 0, height: int = 0, _id: str = "", + title: str = "", + description: str = "", ): self._path = path self.order = order self.width = width self.height = height self.id = _id or path.stem + self.title = title + self.description = description size = self._size self.width = self.width or size[0] @@ -236,9 +243,13 @@ def from_dict(cls, *, path: str | Path, **optionals) -> "WidgetMetadata": return cls(path, **optionals) def as_dict(self) -> dict[str, str]: + exclude_attributes = { + "handler", # Handler is inferred from file extension + "path", # Path is set explicitly below + } body = {"path": self._path.as_posix()} - for attribute in "order", "width", "height", "id": - if attribute in body: + for attribute in dir(self): + if attribute.startswith("_") or callable(getattr(self, attribute)) or attribute in exclude_attributes: continue value = getattr(self, attribute) if value is not None: diff --git a/tests/integration/test_dashboards.py b/tests/integration/test_dashboards.py index a65333cf..65b2e6df 100644 --- a/tests/integration/test_dashboards.py +++ b/tests/integration/test_dashboards.py @@ -206,3 +206,17 @@ def test_dashboards_deploys_dashboard_with_markdown_header(ws, make_dashboard, t sdk_dashboard = dashboards.deploy_dashboard(lakeview_dashboard, dashboard_id=sdk_dashboard.dashboard_id) assert ws.lakeview.get(sdk_dashboard.dashboard_id) + + +def test_dashboards_deploys_dashboard_with_widget_title_and_description(ws, make_dashboard, tmp_path): + sdk_dashboard = make_dashboard() + + description = "-- --title 'Counting' --description 'The answer to life'\nSELECT 42" + (tmp_path / "counter.sql").write_text(description) + + dashboards = Dashboards(ws) + lakeview_dashboard = dashboards.create_dashboard(tmp_path) + + sdk_dashboard = dashboards.deploy_dashboard(lakeview_dashboard, dashboard_id=sdk_dashboard.dashboard_id) + + assert ws.lakeview.get(sdk_dashboard.dashboard_id) diff --git a/tests/unit/test_dashboards.py b/tests/unit/test_dashboards.py index ad3e99c5..011a9d5a 100644 --- a/tests/unit/test_dashboards.py +++ b/tests/unit/test_dashboards.py @@ -160,7 +160,7 @@ def test_query_handler_ignores_non_header_comment(tmp_path, query): assert all(value is None for value in header.values()) -@pytest.mark.parametrize("attribute", ["id", "order", "height", "width"]) +@pytest.mark.parametrize("attribute", ["id", "order", "height", "width", "title", "description"]) def test_query_handler_parses_attribute_from_header(tmp_path, attribute): path = tmp_path / "query.sql" path.write_text(f"-- --{attribute} 10\nSELECT 1") @@ -260,11 +260,11 @@ def test_widget_metadata_replaces_width_and_height(tmp_path): assert updated_metadata.height == 10 -@pytest.mark.parametrize("attribute", ["id", "order", "width", "height"]) +@pytest.mark.parametrize("attribute", ["id", "order", "width", "height", "title", "description"]) def test_widget_metadata_replaces_attribute(tmp_path, attribute: str): path = tmp_path / "test.sql" path.write_text("SELECT 1") - widget_metadata = WidgetMetadata(path, 1, 1, 1) + widget_metadata = WidgetMetadata(path, 1, 1, 1, "1", "1", "1") updated_metadata = widget_metadata.from_dict(**{"path": path, attribute: "10"}) assert str(getattr(updated_metadata, attribute)) == "10" @@ -272,8 +272,23 @@ def test_widget_metadata_replaces_attribute(tmp_path, attribute: str): def test_widget_metadata_as_dict(tmp_path): path = tmp_path / "test.sql" path.write_text("SELECT 1") - raw = {"path": path.as_posix(), "id": "test", "order": "10", "width": "10", "height": "10"} - widget_metadata = WidgetMetadata(path, 10, 10, 10) + raw = { + "path": path.as_posix(), + "id": "test", + "order": "-1", + "width": "3", + "height": "6", + "title": "Test widget", + "description": "Longer explanation", + } + widget_metadata = WidgetMetadata( + path, + order=-1, + width=3, + height=6, + title="Test widget", + description="Longer explanation", + ) assert widget_metadata.as_dict() == raw @@ -695,6 +710,34 @@ def test_dashboard_creates_dashboard_with_custom_sized_widget(tmp_path, header): ws.assert_not_called() +def test_dashboard_creates_dashboard_with_title(tmp_path): + ws = create_autospec(WorkspaceClient) + + query = "-- --title 'Count me in'\nSELECT 2918" + (tmp_path / "counter.sql").write_text(query) + + lakeview_dashboard = Dashboards(ws).create_dashboard(tmp_path) + + frame = lakeview_dashboard.pages[0].layout[0].widget.spec.frame + assert frame.title == "Count me in" + assert frame.show_title + ws.assert_not_called() + + +def test_dashboard_creates_dashboard_with_description(tmp_path): + ws = create_autospec(WorkspaceClient) + + query = "-- --description 'Only when it counts'\nSELECT 2918" + (tmp_path / "counter.sql").write_text(query) + + lakeview_dashboard = Dashboards(ws).create_dashboard(tmp_path) + + frame = lakeview_dashboard.pages[0].layout[0].widget.spec.frame + assert frame.description == "Only when it counts" + assert frame.show_description + ws.assert_not_called() + + def test_dashboard_handles_incorrect_query_header(tmp_path, caplog): ws = create_autospec(WorkspaceClient)