From 442963e5841a45aa7ee2b120a7583b4da8798570 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 16:22:41 +0200 Subject: [PATCH 1/7] return preview_url from deploy --- rsconnect/api.py | 6 +++++- rsconnect/http_support.py | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index 1e621e88..4dae1153 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -335,6 +335,7 @@ class RSConnectClientDeployResult(TypedDict): app_id: str app_guid: str app_url: str + preview_url: str title: str | None @@ -569,11 +570,14 @@ def deploy( task = self.content_deploy(app_guid, app_bundle["id"], activate=activate) + preview_url = app["url"].replace("/content/", "/preview/") + f"/{app_bundle['id']}" + return { - "task_id": task["task_id"], + "task_id": task["id"], "app_id": app_id, "app_guid": app["guid"], "app_url": app["url"], + "preview_url": preview_url, "title": app["title"], } diff --git a/rsconnect/http_support.py b/rsconnect/http_support.py index 29f04540..27e5900c 100644 --- a/rsconnect/http_support.py +++ b/rsconnect/http_support.py @@ -405,7 +405,15 @@ def _do_request( for key, value in response.getheaders(): logger.debug("--> %s: %s" % (key, value)) logger.debug("Body:") - logger.debug("--> %s" % response_body) + if response.getheader("Content-Type", "").startswith("application/json"): + # Only print JSON responses. + # Otherwise we end up dumping entire web pages to the log. + try: + logger.debug("--> %s" % response_body) + except json.JSONDecodeError: + logger.debug("--> ") + else: + logger.debug("--> ") finally: if local_connection: self.__exit__() From 9af8bfcfb24ad729a45ffdab42b3999edd5bf91a Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 16:24:25 +0200 Subject: [PATCH 2/7] Explain why replace --- rsconnect/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rsconnect/api.py b/rsconnect/api.py index 4dae1153..1c5d4578 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -570,6 +570,9 @@ def deploy( task = self.content_deploy(app_guid, app_bundle["id"], activate=activate) + # http://ADDRESS/preview/APP_GUID/BUNDLE_ID + # Using replace makes this a bit more robust to changes in the URL structure + # like connect being served on a subpath. preview_url = app["url"].replace("/content/", "/preview/") + f"/{app_bundle['id']}" return { From 953272a076c5f8b64a341c0af54c5fe0589ca920 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 16:41:22 +0200 Subject: [PATCH 3/7] output the preview_url as the Direct content URL --- rsconnect/api.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index 1c5d4578..38f1b3d2 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -331,11 +331,11 @@ def __init__(self, url: str): class RSConnectClientDeployResult(TypedDict): - task_id: NotRequired[str] + task_id: str | None app_id: str - app_guid: str + app_guid: str | None app_url: str - preview_url: str + preview_url: str | None title: str | None @@ -576,11 +576,11 @@ def deploy( preview_url = app["url"].replace("/content/", "/preview/") + f"/{app_bundle['id']}" return { - "task_id": task["id"], + "task_id": task["task_id"], "app_id": app_id, "app_guid": app["guid"], "app_url": app["url"], - "preview_url": preview_url, + "preview_url": preview_url if not activate else None, "title": app["title"], } @@ -1086,12 +1086,14 @@ def deploy_bundle(self, activate: bool = True): print("Application successfully deployed to {}".format(prepare_deploy_result.app_url)) webbrowser.open_new(prepare_deploy_result.app_url) - self.deployed_info = { - "app_url": prepare_deploy_result.app_url, - "app_id": prepare_deploy_result.app_id, - "app_guid": None, - "title": self.title, - } + self.deployed_info = RSConnectClientDeployResult( + app_url=prepare_deploy_result.app_url, + app_id=str(prepare_deploy_result.app_id), + app_guid=None, + task_id=None, + preview_url=None, + title=self.title, + ) return self def emit_task_log( @@ -1134,7 +1136,7 @@ def emit_task_log( app_dashboard_url = app_config.get("config_url") log_callback.info("Deployment completed successfully.") log_callback.info("\t Dashboard content URL: %s", app_dashboard_url) - log_callback.info("\t Direct content URL: %s", self.deployed_info["app_url"]) + log_callback.info("\t Direct content URL: %s", self.deployed_info["preview_url"] or self.deployed_info["app_url"]) return self From e2b1bc460222cc41e5d8f79eccffcd0621771eed Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 16:50:30 +0200 Subject: [PATCH 4/7] Test for the right URL being emitted --- tests/test_main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 6cb36407..9f08876b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -113,7 +113,7 @@ def test_deploy(self): ], ) @httpretty.activate(verbose=True, allow_net_connect=False) - def test_deploy_draft(self, command, target, expected_activate): + def test_deploy_draft(self, command, target, expected_activate, caplog): original_api_key_value = os.environ.pop("CONNECT_API_KEY", None) original_server_value = os.environ.pop("CONNECT_SERVER", None) @@ -241,10 +241,15 @@ def post_application_deploy_callback(request, uri, response_headers): # Do not validate app mode, so that the "target" content doesn't matter. "rsconnect.api.RSConnectExecutor.validate_app_mode", new=lambda self_, *args, **kwargs: self_, - ): + ), caplog.at_level("INFO"): result = runner.invoke(cli, args) assert result.exit_code == 0, result.output assert deploy_api_invoked == [True] + assert "Deployment completed successfully." in caplog.text + if expected_activate: + assert "Direct content URL: http://fake_server/apps/1234-5678-9012-3456" in caplog.text + else: + assert "Direct content URL: http://fake_server/preview/1234-5678-9012-3456/FAKE_BUNDLE_ID" in caplog.text finally: if original_api_key_value: os.environ["CONNECT_API_KEY"] = original_api_key_value From c3501ea271783984c95184bbc6e1cad20d9b0b54 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 18:34:53 +0200 Subject: [PATCH 5/7] fix rebase --- tests/test_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 9f08876b..c68fa79e 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -146,7 +146,7 @@ def test_deploy_draft(self, command, target, expected_activate, caplog): "id": "1234-5678-9012-3456", "guid": "1234-5678-9012-3456", "title": "app5", - "url": "http://fake_server/apps/1234-5678-9012-3456", + "url": "http://fake_server/content/1234-5678-9012-3456", } ), adding_headers={"Content-Type": "application/json"}, @@ -247,7 +247,7 @@ def post_application_deploy_callback(request, uri, response_headers): assert deploy_api_invoked == [True] assert "Deployment completed successfully." in caplog.text if expected_activate: - assert "Direct content URL: http://fake_server/apps/1234-5678-9012-3456" in caplog.text + assert "Direct content URL: http://fake_server/content/1234-5678-9012-3456" in caplog.text else: assert "Direct content URL: http://fake_server/preview/1234-5678-9012-3456/FAKE_BUNDLE_ID" in caplog.text finally: From 225ebfe0385f0e775322c8e335943f3b2c46c3f8 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 18:40:45 +0200 Subject: [PATCH 6/7] format --- rsconnect/api.py | 4 +++- tests/test_main.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index 38f1b3d2..fa46656f 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -1136,7 +1136,9 @@ def emit_task_log( app_dashboard_url = app_config.get("config_url") log_callback.info("Deployment completed successfully.") log_callback.info("\t Dashboard content URL: %s", app_dashboard_url) - log_callback.info("\t Direct content URL: %s", self.deployed_info["preview_url"] or self.deployed_info["app_url"]) + log_callback.info( + "\t Direct content URL: %s", self.deployed_info["preview_url"] or self.deployed_info["app_url"] + ) return self diff --git a/tests/test_main.py b/tests/test_main.py index c68fa79e..0b89d001 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -241,7 +241,9 @@ def post_application_deploy_callback(request, uri, response_headers): # Do not validate app mode, so that the "target" content doesn't matter. "rsconnect.api.RSConnectExecutor.validate_app_mode", new=lambda self_, *args, **kwargs: self_, - ), caplog.at_level("INFO"): + ), caplog.at_level( + "INFO" + ): result = runner.invoke(cli, args) assert result.exit_code == 0, result.output assert deploy_api_invoked == [True] @@ -249,7 +251,9 @@ def post_application_deploy_callback(request, uri, response_headers): if expected_activate: assert "Direct content URL: http://fake_server/content/1234-5678-9012-3456" in caplog.text else: - assert "Direct content URL: http://fake_server/preview/1234-5678-9012-3456/FAKE_BUNDLE_ID" in caplog.text + assert ( + "Direct content URL: http://fake_server/preview/1234-5678-9012-3456/FAKE_BUNDLE_ID" in caplog.text + ) finally: if original_api_key_value: os.environ["CONNECT_API_KEY"] = original_api_key_value From 2b26f9c91a75913ef43890b40447c1c54e70e564 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Wed, 11 Jun 2025 18:44:45 +0200 Subject: [PATCH 7/7] remove unused import --- rsconnect/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index fa46656f..e7611a57 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -46,9 +46,9 @@ # they should both come from the same typing module. # https://peps.python.org/pep-0655/#usage-in-python-3-11 if sys.version_info >= (3, 11): - from typing import NotRequired, TypedDict + from typing import TypedDict else: - from typing_extensions import NotRequired, TypedDict + from typing_extensions import TypedDict from . import validation from .bundle import _default_title