From 7e959fbb1309d4dc8d5d5113a00db6d07f87c858 Mon Sep 17 00:00:00 2001 From: omar-rs Date: Thu, 13 Jul 2023 17:27:18 -0400 Subject: [PATCH 1/4] Redeploy to Posit Cloud from a project associates the content with that project --- CHANGELOG.md | 1 + rsconnect/api.py | 23 ++++++++++++++++------- tests/test_api.py | 9 +++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3416c9..44aebf58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Failed deploys to shinyapps.io will now output build logs. Posit Cloud application deploys will also output build logs once supported server-side. +- Redeploy to Posit Cloud from a project now correctly associates the content with that project. ## [1.19.0] - 2023-07-12 diff --git a/rsconnect/api.py b/rsconnect/api.py index 0fd1f8d2..6fe1dd82 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -1319,6 +1319,12 @@ def __init__(self, cloud_client: PositClient, server: CloudServer, project_appli self._server = server self._project_application_id = project_application_id + def _get_current_project_id(self): + if self._project_application_id is not None: + project_application = self._rstudio_client.get_application(self._project_application_id) + return project_application["content_id"] + return None + def prepare_deploy( self, app_id: typing.Optional[typing.Union[str, int]], @@ -1331,11 +1337,10 @@ def prepare_deploy( application_type = "static" if app_mode == AppModes.STATIC else "connect" if app_id is None: - # this is a new deployment. - # get the Posit Cloud project so that we can associate the deployment with it - if self._project_application_id is not None: - project_application = self._rstudio_client.get_application(self._project_application_id) - project_id = project_application["content_id"] + # this is a deployment of a new output + # associate the current Posit Cloud project and space (if any) to the new output + project_id = self._get_current_project_id() + if project_id is not None: project = self._rstudio_client.get_content(project_id) space_id = project["space_id"] else: @@ -1347,7 +1352,7 @@ def prepare_deploy( ) app_id_int = output["source_id"] else: - # this is a redeployment + # this is a redeployment of an existing output if app_store_version is not None: # versioned app store files store content id in app_id output = self._rstudio_client.get_content(app_id) @@ -1359,13 +1364,17 @@ def prepare_deploy( # content_id will appear on static applications as output_id content_id = application.get("content_id") or application.get("output_id") app_id_int = application["id"] - output = self._rstudio_client.get_content(content_id) if application_type == "static": revision = self._rstudio_client.create_revision(content_id) app_id_int = revision["application_id"] + # associate the output with the current Posit Cloud project (if any) + project_id = self._get_current_project_id() + if project_id is not None: + self._rstudio_client.update_output(output["id"], {"project": project_id}) + app_url = output["url"] output_id = output["id"] diff --git a/tests/test_api.py b/tests/test_api.py index e2de0a96..18951579 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,5 +1,5 @@ from unittest import TestCase -from unittest.mock import Mock, patch +from unittest.mock import Mock, patch, call import json import httpretty @@ -332,6 +332,7 @@ def test_prepare_redeploy(self): app_mode = AppModes.PYTHON_SHINY self.cloud_client.get_content.return_value = {"id": 1, "source_id": 10, "url": "https://posit.cloud/content/1"} + self.cloud_client.get_application.return_value = {"id": 10, "content_id": 200} self.cloud_client.create_bundle.return_value = { "id": 100, "presigned_url": "https://presigned.url", @@ -348,6 +349,7 @@ def test_prepare_redeploy(self): ) self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(10, "application/x-tar", bundle_size, bundle_hash) + cloud_client.update_output.assert_called_with(1, {"project": 200}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 10 @@ -364,6 +366,7 @@ def test_prepare_redeploy_static(self): app_mode = AppModes.STATIC self.cloud_client.get_content.return_value = {"id": 1, "source_id": 10, "url": "https://posit.cloud/content/1"} + self.cloud_client.get_application.return_value = {"id": 10, "content_id": 200} self.cloud_client.create_revision.return_value = { "application_id": 11, } @@ -384,6 +387,7 @@ def test_prepare_redeploy_static(self): self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_revision.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(11, "application/x-tar", bundle_size, bundle_hash) + cloud_client.update_output.assert_called_with(1, {"project": 200}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 11 @@ -418,9 +422,10 @@ def test_prepare_redeploy_preversioned_app_store(self): app_mode=app_mode, app_store_version=None, ) - self.cloud_client.get_application.assert_called_with(10) + self.cloud_client.get_application.assert_has_calls([call(app_id), call(project_application_id)]) self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(10, "application/x-tar", bundle_size, bundle_hash) + cloud_client.update_output.assert_called_with(1, {"project": 1}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 10 From 64dec7e551a24875f6d2cb12f1b7ce6d18888ff9 Mon Sep 17 00:00:00 2001 From: omar-rs Date: Fri, 14 Jul 2023 10:04:46 -0400 Subject: [PATCH 2/4] attempt to fix test --- tests/test_main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_main.py b/tests/test_main.py index aff6581e..d7a0b0be 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -350,6 +350,13 @@ def post_output_callback(request, uri, response_headers): body=post_output_callback, ) + httpretty.register_uri( + httpretty.PATCH, + "https://api.posit.cloud/v1/outputs/1", + body=open("tests/testdata/rstudio-responses/create-output.json", "r").read(), + status=200, + ) + def post_bundle_callback(request, uri, response_headers): parsed_request = _load_json(request.body) del parsed_request["checksum"] From 2b4d104ca2c7f965a933000ac69a3635aace7559 Mon Sep 17 00:00:00 2001 From: omar-rs Date: Fri, 14 Jul 2023 13:40:57 -0400 Subject: [PATCH 3/4] move up the call to get the current project --- rsconnect/api.py | 6 +++--- tests/test_api.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index 6fe1dd82..9359e52a 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -1336,10 +1336,10 @@ def prepare_deploy( ) -> PrepareDeployOutputResult: application_type = "static" if app_mode == AppModes.STATIC else "connect" + project_id = self._get_current_project_id() + if app_id is None: # this is a deployment of a new output - # associate the current Posit Cloud project and space (if any) to the new output - project_id = self._get_current_project_id() if project_id is not None: project = self._rstudio_client.get_content(project_id) space_id = project["space_id"] @@ -1347,6 +1347,7 @@ def prepare_deploy( project_id = None space_id = None + # create the new output and associate it with the current Posit Cloud project and space output = self._rstudio_client.create_output( name=app_name, application_type=application_type, project_id=project_id, space_id=space_id ) @@ -1371,7 +1372,6 @@ def prepare_deploy( app_id_int = revision["application_id"] # associate the output with the current Posit Cloud project (if any) - project_id = self._get_current_project_id() if project_id is not None: self._rstudio_client.update_output(output["id"], {"project": project_id}) diff --git a/tests/test_api.py b/tests/test_api.py index 18951579..d2d38022 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -422,10 +422,11 @@ def test_prepare_redeploy_preversioned_app_store(self): app_mode=app_mode, app_store_version=None, ) - self.cloud_client.get_application.assert_has_calls([call(app_id), call(project_application_id)]) + # first call is to get the current project id, second call is to get the application + self.cloud_client.get_application.assert_has_calls([call(project_application_id), call(app_id)]) self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(10, "application/x-tar", bundle_size, bundle_hash) - cloud_client.update_output.assert_called_with(1, {"project": 1}) + self.cloud_client.update_output.assert_called_with(1, {"project": 1}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 10 From 24284b38c8c64f4d06ea7fe4e8805c034f9f7e94 Mon Sep 17 00:00:00 2001 From: omar-rs Date: Tue, 18 Jul 2023 10:52:31 -0400 Subject: [PATCH 4/4] merge --- tests/test_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index d2d38022..5a3b80c8 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -349,7 +349,7 @@ def test_prepare_redeploy(self): ) self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(10, "application/x-tar", bundle_size, bundle_hash) - cloud_client.update_output.assert_called_with(1, {"project": 200}) + self.cloud_client.update_output.assert_called_with(1, {"project": 200}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 10 @@ -387,7 +387,7 @@ def test_prepare_redeploy_static(self): self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_revision.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(11, "application/x-tar", bundle_size, bundle_hash) - cloud_client.update_output.assert_called_with(1, {"project": 200}) + self.cloud_client.update_output.assert_called_with(1, {"project": 200}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 11 @@ -423,7 +423,7 @@ def test_prepare_redeploy_preversioned_app_store(self): app_store_version=None, ) # first call is to get the current project id, second call is to get the application - self.cloud_client.get_application.assert_has_calls([call(project_application_id), call(app_id)]) + self.cloud_client.get_application.assert_has_calls([call(self.project_application_id), call(app_id)]) self.cloud_client.get_content.assert_called_with(1) self.cloud_client.create_bundle.assert_called_with(10, "application/x-tar", bundle_size, bundle_hash) self.cloud_client.update_output.assert_called_with(1, {"project": 1})