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..9359e52a 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]], @@ -1330,24 +1336,24 @@ 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 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 + if project_id is not None: project = self._rstudio_client.get_content(project_id) space_id = project["space_id"] else: 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 ) 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 +1365,16 @@ 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) + 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..5a3b80c8 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) + 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 @@ -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) + 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 @@ -418,9 +422,11 @@ 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) + # 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(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}) assert prepare_deploy_result.app_id == 1 assert prepare_deploy_result.application_id == 10 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"]