diff --git a/sema4ai/src/sema4ai_code/robocorp_language_server.py b/sema4ai/src/sema4ai_code/robocorp_language_server.py index 50067307..3dcfa25b 100644 --- a/sema4ai/src/sema4ai_code/robocorp_language_server.py +++ b/sema4ai/src/sema4ai_code/robocorp_language_server.py @@ -668,72 +668,80 @@ def _cloud_list_workspaces_impl( last_error_result = None - with progress_context( - self._endpoint, "Listing Control Room workspaces", self._dir_cache - ): - ws: IRccWorkspace - ret: list[WorkspaceInfoDict] = [] - result = self._rcc.cloud_list_workspaces() - if not result.success: - # It's an error, so, the data should be None. - return typing.cast( - ActionResultDict[list[WorkspaceInfoDict]], result.as_dict() - ) - - workspaces = result.result - for ws in workspaces or []: - packages: list[PackageInfoDict] = [] + try: + with progress_context( + self._endpoint, "Listing Control Room workspaces", self._dir_cache + ): + ws: IRccWorkspace + ret: list[WorkspaceInfoDict] = [] + result = self._rcc.cloud_list_workspaces() + if not result.success: + # It's an error, so, the data should be None. + return typing.cast( + ActionResultDict[list[WorkspaceInfoDict]], result.as_dict() + ) - activity_package: IRccRobotMetadata - activities_result = self._rcc.cloud_list_workspace_robots( - ws.workspace_id - ) - if not activities_result.success: - # If we can't list the robots of a specific workspace, just skip it - # (the log should still show it but we can proceed to list the - # contents of other workspaces). - last_error_result = activities_result - continue + workspaces = result.result + for ws in workspaces or []: + packages: list[PackageInfoDict] = [] - workspace_activities = activities_result.result - for activity_package in workspace_activities or []: - key = (ws.workspace_id, activity_package.robot_id) - sort_key = "%05d%s" % ( - ws_id_and_pack_id_to_lru_index.get(key, DEFAULT_SORT_KEY), - activity_package.robot_name.lower(), + activity_package: IRccRobotMetadata + activities_result = self._rcc.cloud_list_workspace_robots( + ws.workspace_id ) + if not activities_result.success: + # If we can't list the robots of a specific workspace, just skip it + # (the log should still show it but we can proceed to list the + # contents of other workspaces). + last_error_result = activities_result + continue - package_info = { - "name": activity_package.robot_name, - "id": activity_package.robot_id, - "sortKey": sort_key, + workspace_activities = activities_result.result + for activity_package in workspace_activities or []: + key = (ws.workspace_id, activity_package.robot_id) + sort_key = "%05d%s" % ( + ws_id_and_pack_id_to_lru_index.get(key, DEFAULT_SORT_KEY), + activity_package.robot_name.lower(), + ) + + package_info = { + "name": activity_package.robot_name, + "id": activity_package.robot_id, + "sortKey": sort_key, + "organizationName": ws.organization_name, + "workspaceId": ws.workspace_id, + "workspaceName": ws.workspace_name, + } + packages.append(package_info) + + ws_dict = { "organizationName": ws.organization_name, - "workspaceId": ws.workspace_id, "workspaceName": ws.workspace_name, + "workspaceId": ws.workspace_id, + "packages": packages, } - packages.append(package_info) + ret.append(ws_dict) - ws_dict = { - "organizationName": ws.organization_name, - "workspaceName": ws.workspace_name, - "workspaceId": ws.workspace_id, - "packages": packages, - } - ret.append(ws_dict) - - if not ret and last_error_result is not None: - # It's an error, so, the data should be None. - return typing.cast( - ActionResultDict[list[WorkspaceInfoDict]], last_error_result.as_dict() - ) + if not ret and last_error_result is not None: + # It's an error, so, the data should be None. + return typing.cast( + ActionResultDict[list[WorkspaceInfoDict]], + last_error_result.as_dict(), + ) - if ret: # Only store if we got something. - store: ListWorkspaceCachedInfoDict = { - "ws_info": ret, - "account_cache_key": account_cache_key, + if ret: # Only store if we got something. + store: ListWorkspaceCachedInfoDict = { + "ws_info": ret, + "account_cache_key": account_cache_key, + } + self._dir_cache.store(self.CLOUD_LIST_WORKSPACE_CACHE_KEY, store) + return {"success": True, "message": None, "result": ret} + except Exception as e: + return { + "success": False, + "message": f"Error listing workspaces: {e}", + "result": None, } - self._dir_cache.store(self.CLOUD_LIST_WORKSPACE_CACHE_KEY, store) - return {"success": True, "message": None, "result": ret} @command_dispatcher(commands.SEMA4AI_CREATE_ROBOT_INTERNAL) def _create_robot(self, params: CreateRobotParamsDict) -> ActionResultDict: diff --git a/sema4ai/tests/sema4ai_code_tests/test_language_server_directly.py b/sema4ai/tests/sema4ai_code_tests/test_language_server_directly.py index 272cd3f8..49db8549 100644 --- a/sema4ai/tests/sema4ai_code_tests/test_language_server_directly.py +++ b/sema4ai/tests/sema4ai_code_tests/test_language_server_directly.py @@ -39,15 +39,21 @@ def test_cloud_list_workspaces_cache_invalidate( rcc = language_server._rcc rcc._last_verified_account_info = AccountInfo("default account", "123", "", "") - assert language_server._cloud_list_workspaces({"refresh": False})["success"] + assert language_server._cloud_list_workspaces({"refresh": False})(monitor=NULL)[ + "success" + ] rcc_patch.disallow_calls() - assert language_server._cloud_list_workspaces({"refresh": False})["success"] + assert language_server._cloud_list_workspaces({"refresh": False})(monitor=NULL)[ + "success" + ] rcc._last_verified_account_info = AccountInfo("another account", "123", "", "") # As account changed, the data should be fetched (as we can't due to the patching # the error is expected). with pytest.raises(AssertionError) as e: - assert not language_server._cloud_list_workspaces({"refresh": False})["success"] + assert not language_server._cloud_list_workspaces({"refresh": False})( + monitor=NULL + )["success"] assert "This should not be called at this time (data should be cached)." in str(e) diff --git a/sema4ai/tests/sema4ai_code_tests/test_vscode_integration.py b/sema4ai/tests/sema4ai_code_tests/test_vscode_integration.py index 4de6735f..97a5b7c3 100644 --- a/sema4ai/tests/sema4ai_code_tests/test_vscode_integration.py +++ b/sema4ai/tests/sema4ai_code_tests/test_vscode_integration.py @@ -8,6 +8,8 @@ from unittest import mock import pytest +from sema4ai_code_tests.fixtures import RCC_TEMPLATE_NAMES, RccPatch +from sema4ai_code_tests.protocols import IRobocorpLanguageServerClient from sema4ai_ls_core import uris from sema4ai_ls_core.basic import wait_for_condition from sema4ai_ls_core.callbacks import Callback @@ -31,8 +33,6 @@ LocalPackageMetadataInfoDict, WorkspaceInfoDict, ) -from sema4ai_code_tests.fixtures import RCC_TEMPLATE_NAMES, RccPatch -from sema4ai_code_tests.protocols import IRobocorpLanguageServerClient log = logging.getLogger(__name__) @@ -1080,11 +1080,10 @@ def test_hover_image_integration( ): import base64 + from sema4ai_code_tests.fixtures import IMAGE_IN_BASE64 from sema4ai_ls_core import uris from sema4ai_ls_core.workspace import Document - from sema4ai_code_tests.fixtures import IMAGE_IN_BASE64 - locators_json = tmpdir.join("locators.json") locators_json.write_text("", "utf-8") @@ -1680,11 +1679,10 @@ def test_web_inspector_integrated( This test should be a reference spanning all the APIs that are available for the inspector webview to use. """ - from sema4ai_ls_core import uris - from sema4ai_code_tests.robocode_language_server_client import ( RobocorpLanguageServerClient, ) + from sema4ai_ls_core import uris cases.copy_to("robots", ws_root_path) ls_client: RobocorpLanguageServerClient = language_server_initialized