From 6c3165e8765213e39ed2a8f9c39b509edd731f6a Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 27 Sep 2024 19:09:15 +0200 Subject: [PATCH 1/4] Added `update-dashboard` command --- labs.yml | 10 ++++++++++ src/databricks/labs/lsql/cli.py | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/labs.yml b/labs.yml index b82ccc5d..38d08446 100644 --- a/labs.yml +++ b/labs.yml @@ -34,3 +34,13 @@ commands: description: Publish the dashboard after creating by setting to `yes` or `y`. - name: open-browser description: Open the dashboard in the browser after creating by setting to `yes` or `y`. + + - name: update-dashboard + description: Update published dashboard from code. + flags: + - name: folder + description: The folder with dashboard files. By default, the current working directory. + - name: dashboard-id + description: The dashboard ID to update. By default, it'll try to pickup dashboard from your workspace home folder. + - name: open-browser + description: Open the dashboard in the browser after creating by setting to `yes` or `y`. \ No newline at end of file diff --git a/src/databricks/labs/lsql/cli.py b/src/databricks/labs/lsql/cli.py index fc50fe56..f8a41f8d 100644 --- a/src/databricks/labs/lsql/cli.py +++ b/src/databricks/labs/lsql/cli.py @@ -43,6 +43,30 @@ def create_dashboard( print(sdk_dashboard.dashboard_id) +@lsql.command +def update_dashboard( + w: WorkspaceClient, + dashboard_id: str | None = None, + folder: Path = Path.cwd(), + open_browser: str = "false", +): + """Update a dashboard from queries""" + should_open_browser = open_browser.lower() in STRING_AFFIRMATIVES + lakeview_dashboards = Dashboards(w) + folder = Path(folder) + dashboard_metadata = DashboardMetadata.from_path(folder) + if not dashboard_id: + me = w.current_user.me() + workspace_path = f"/Users/{me.user_name}/{dashboard_metadata.display_name}.lvdash.json" + workspace_object = w.workspace.get_status(workspace_path) + dashboard_id = workspace_object.resource_id + sdk_dashboard = lakeview_dashboards.create_dashboard(dashboard_metadata, dashboard_id=dashboard_id) + if should_open_browser: + assert sdk_dashboard.dashboard_id is not None + dashboard_url = lakeview_dashboards.get_url(dashboard_id) + webbrowser.open(dashboard_url) + + @lsql.command(is_unauthenticated=True) def fmt(folder: Path = Path.cwd(), *, normalize_case: str = "true", exclude: Iterable[str] = ()): """Format SQL files in a folder""" From 26cc8c37a92fe5387ec9004e1eb08bb1f52c54c9 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 27 Sep 2024 19:09:21 +0200 Subject: [PATCH 2/4] Added `update-dashboard` command --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 174f3dc4..e13561ee 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -*.out \ No newline at end of file +*.out +/.databricks-login.json From 1d8b01ad653b595524fc6cacaf7e6d834e8a5d83 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 27 Sep 2024 19:21:56 +0200 Subject: [PATCH 3/4] Added `update-dashboard` command --- labs.yml | 18 +++++++++++---- src/databricks/labs/lsql/cli.py | 25 +------------------- src/databricks/labs/lsql/dashboards.py | 32 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/labs.yml b/labs.yml index 38d08446..e8fcfbc0 100644 --- a/labs.yml +++ b/labs.yml @@ -35,12 +35,20 @@ commands: - name: open-browser description: Open the dashboard in the browser after creating by setting to `yes` or `y`. - - name: update-dashboard - description: Update published dashboard from code. + - name: deploy-dashboard + description: Create or update dashboard from code. flags: - name: folder description: The folder with dashboard files. By default, the current working directory. - - name: dashboard-id - description: The dashboard ID to update. By default, it'll try to pickup dashboard from your workspace home folder. + - name: catalog + description: | + Overwrite the catalog in the queries' `FROM` clauses with given value. + Useful when developing with separate catalogs, for example, for production and development. + - name: database + description: | + Overwrite the database in the queries' `FROM` clauses with given value. + Useful when developing with separate databases, for example, for production and development. + - name: publish + description: Publish the dashboard after creating by setting to `yes` or `y`. - name: open-browser - description: Open the dashboard in the browser after creating by setting to `yes` or `y`. \ No newline at end of file + description: Open the dashboard in the browser after creating by setting to `yes` or `y`. diff --git a/src/databricks/labs/lsql/cli.py b/src/databricks/labs/lsql/cli.py index f8a41f8d..e1071ae4 100644 --- a/src/databricks/labs/lsql/cli.py +++ b/src/databricks/labs/lsql/cli.py @@ -35,6 +35,7 @@ def create_dashboard( catalog=catalog or None, database=database or None, ) + # TODO: rename this method and command to `deploy_dashboard` sdk_dashboard = lakeview_dashboards.create_dashboard(dashboard_metadata, publish=should_publish) if should_open_browser: assert sdk_dashboard.dashboard_id is not None @@ -43,30 +44,6 @@ def create_dashboard( print(sdk_dashboard.dashboard_id) -@lsql.command -def update_dashboard( - w: WorkspaceClient, - dashboard_id: str | None = None, - folder: Path = Path.cwd(), - open_browser: str = "false", -): - """Update a dashboard from queries""" - should_open_browser = open_browser.lower() in STRING_AFFIRMATIVES - lakeview_dashboards = Dashboards(w) - folder = Path(folder) - dashboard_metadata = DashboardMetadata.from_path(folder) - if not dashboard_id: - me = w.current_user.me() - workspace_path = f"/Users/{me.user_name}/{dashboard_metadata.display_name}.lvdash.json" - workspace_object = w.workspace.get_status(workspace_path) - dashboard_id = workspace_object.resource_id - sdk_dashboard = lakeview_dashboards.create_dashboard(dashboard_metadata, dashboard_id=dashboard_id) - if should_open_browser: - assert sdk_dashboard.dashboard_id is not None - dashboard_url = lakeview_dashboards.get_url(dashboard_id) - webbrowser.open(dashboard_url) - - @lsql.command(is_unauthenticated=True) def fmt(folder: Path = Path.cwd(), *, normalize_case: str = "true", exclude: Iterable[str] = ()): """Format SQL files in a folder""" diff --git a/src/databricks/labs/lsql/dashboards.py b/src/databricks/labs/lsql/dashboards.py index c31754c9..b48280e0 100644 --- a/src/databricks/labs/lsql/dashboards.py +++ b/src/databricks/labs/lsql/dashboards.py @@ -20,6 +20,7 @@ import sqlglot import yaml from databricks.sdk import WorkspaceClient +from databricks.sdk.errors import NotFound from databricks.sdk.service.dashboards import Dashboard as SDKDashboard from databricks.sdk.service.workspace import ExportFormat @@ -1108,6 +1109,24 @@ def create_dashboard( dashboard_id: str | None = None, warehouse_id: str | None = None, publish: bool = False, + ) -> SDKDashboard: + # TODO: remove this method once downstreams are updated + return self.deploy_dashboard( + dashboard_metadata, + parent_path=parent_path, + dashboard_id=dashboard_id, + warehouse_id=warehouse_id, + publish=publish, + ) + + def deploy_dashboard( + self, + dashboard_metadata: DashboardMetadata, + *, + parent_path: str | None = None, + dashboard_id: str | None = None, + warehouse_id: str | None = None, + publish: bool = False, ) -> SDKDashboard: """Create a Lakeview dashboard. @@ -1125,6 +1144,11 @@ def create_dashboard( """ dashboard_metadata.validate() serialized_dashboard = json.dumps(dashboard_metadata.as_lakeview().as_dict()) + me = self._ws.current_user.me() + if not parent_path: + parent_path = f"/Users/{me.user_name}" + if not dashboard_id: + dashboard_id = self._maybe_discover_dashboard_id(dashboard_metadata, parent_path) if dashboard_id is not None: sdk_dashboard = self._ws.lakeview.update( dashboard_id, @@ -1144,6 +1168,14 @@ def create_dashboard( self._ws.lakeview.publish(sdk_dashboard.dashboard_id, warehouse_id=warehouse_id) return sdk_dashboard + def _maybe_discover_dashboard_id(self, dashboard_metadata: DashboardMetadata, parent_path: str) -> str | None: + try: + file_path = f"{parent_path}/{dashboard_metadata.display_name}.lvdash.json" + workspace_object = self._ws.workspace.get_status(file_path) + return workspace_object.resource_id + except NotFound: + return None + def _with_better_names(self, dashboard: Dashboard) -> Dashboard: """Replace names with human-readable names.""" better_names = {} From 9eefa40275f8a8a1bc08553e79049b2f1382a54e Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 27 Sep 2024 19:23:48 +0200 Subject: [PATCH 4/4] Added `update-dashboard` command --- src/databricks/labs/lsql/cli.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/databricks/labs/lsql/cli.py b/src/databricks/labs/lsql/cli.py index e1071ae4..2015e1aa 100644 --- a/src/databricks/labs/lsql/cli.py +++ b/src/databricks/labs/lsql/cli.py @@ -44,6 +44,20 @@ def create_dashboard( print(sdk_dashboard.dashboard_id) +@lsql.command +def deploy_dashboard( + w: WorkspaceClient, + folder: Path = Path.cwd(), + *, + catalog: str = "", + database: str = "", + publish: str = "false", + open_browser: str = "false", +): + """Create a dashboard from queries""" + create_dashboard(w, folder, catalog=catalog, database=database, publish=publish, open_browser=open_browser) + + @lsql.command(is_unauthenticated=True) def fmt(folder: Path = Path.cwd(), *, normalize_case: str = "true", exclude: Iterable[str] = ()): """Format SQL files in a folder"""