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 diff --git a/labs.yml b/labs.yml index b82ccc5d..e8fcfbc0 100644 --- a/labs.yml +++ b/labs.yml @@ -34,3 +34,21 @@ 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: 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: 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`. diff --git a/src/databricks/labs/lsql/cli.py b/src/databricks/labs/lsql/cli.py index fc50fe56..2015e1aa 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,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""" 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 = {}