rstudio / rsconnect

Publish Shiny Applications, RMarkdown Documents, Jupyter Notebooks, Plumber APIs, and more

Home Page:http://rstudio.github.io/rsconnect/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

This content is 'api', but the bundle is identified as 'shiny'

slodge-work opened this issue · comments

As part of my mass upgrade of lots of projects and our CI/CD system to R4.3, I've also been upgrading rsconnect too. Similar to #998 one of our projects has upgraded rsconnect from 0.8.29 to 1.1.0

On deployment we now see an error of:

List of 3 $ code   : int 48 $ error  : chr "you cannot change the type of content once deployed. This content is 'api', but the bundle is identified as 'sh"| __truncated__ $ payload: NULL

This is possibly linked to #942 (but the reverse direction)

Our repo structure is:

/
  /package # contains DESCRIPTION, R, etc - shared code between shiny and plumber
  /plumber # contains a plumber.R file
  /renv # renv is used at the top level of the repo
  /shiny # contains a shiny app

Our CI/CD system has code like:

library(R.InHouseDeploymentHelpers)

write_manifest("shiny/")
deploy_app(
  "name_for_shiny_content",
  "shiny/"
)

write_manifest("plumber/", "plumber.R")
deploy_app(
  "name_for_plumber_content",
  "plumber/"
)

This previously has worked well for us - we've got repos with several Markdown reports, several shiny apps and a plumber api all deployed from the same CD script.

I see appMode is available now... I'll give it a try...

I just tried the following to mirror what you described, but the manifests appear correct.

# Create a new project and launch a separate RStudio session.
usethis::create_project("~/Desktop/mixed-content")

# In that new project ...
unlink("R", recursive = TRUE)
dir.create("shiny")
writeLines(c(
    "shinyApp('single file Shiny deploy', function(input, output) {})"
), "shiny/app.R")

dir.create("plumber")
writeLines(c(
      "#* @get /",
      "root <- function() { 'initial Plumber deploy' }"
), "plumber/plumber.R")

# Using the R library ...

rsconnect::writeManifest("shiny")
rsconnect::writeManifest("plumber")
jsonlite::fromJSON("shiny/manifest.json")$metadata$appmode
#> shiny
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> plumber

# Using renv ...

renv::init()
renv::install(c("rsconnect","shiny","plumber"), prompt = FALSE)

rsconnect::writeManifest("shiny")
rsconnect::writeManifest("plumber")
jsonlite::fromJSON("shiny/manifest.json")$metadata$appmode
#> shiny
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> plumber

Could you tell us more about the shiny/ and plumber/ directories? Could you adjust my example to recreate your scenario?

Thank you Aron.

Awesome to have a repro to play with.

It looks like the problem isn't to do with having 2 apps - it looks like this is to do with us supplying a primary doc parameter for the plumber app.

Here's what reproduces the error for me:

dir.create("shiny")
writeLines(c(
  "library('shiny')",
  "shinyApp('single file Shiny deploy', function(input, output) {})"
), "shiny/app.R")

dir.create("plumber")
writeLines(c(
  "library('plumber')",
  "#* @get /",
  "root <- function() { 'initial Plumber deploy' }"
), "plumber/plumber.R")

install.packages(c("rsconnect", "shiny", "plumber"))

rsconnect::writeManifest("shiny")
rsconnect::writeManifest("plumber",appPrimaryDoc = "plumber.R")


jsonlite::fromJSON("shiny/manifest.json")$metadata$appmode
#> shiny
jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> shiny

This code used to work fine under rsconnect 0.x ... but I'm OK removing the parameter if it's an expected change 👍

Just to confirm no shiny needed to see this:

dir.create("plumber")
writeLines(c(
  "library('plumber')",
  "#* @get /",
  "root <- function() { 'initial Plumber deploy' }"
), "plumber/plumber.R")

install.packages(c("rsconnect", "shiny", "plumber"))

rsconnect::writeManifest("plumber",appPrimaryDoc = "plumber.R")

jsonlite::fromJSON("plumber/manifest.json")$metadata$appmode
#> shiny

The appPrimaryDoc is not needed for Plumber APIs. That information is necessary when you are deploying alpha.Rmd, beta.Rmd, and primary.Rmd together. Maybe the alpha.Rmd and beta.Rmd files are included by code into primary.Rmd. In that case, you want to use appPrimaryDoc = "primary.Rmd" to indicate that is the document that should be rendered.

The primary document is also handy for static content, as it indicates the document to serve when a browser visits the content URL (without a filename). Loading /my-content/ would redirect to /my-content/primary.html, for example.

In your example:

rsconect::writeManifest(appPrimaryDoc = "plumber.R")

the check for API content previously happened before consideration of appPrimaryDoc:

https://github.com/rstudio/rsconnect/blame/5e0189d2e52c6708af942a600b115530c0094b1f/R/appMetadata.R#L76-L93

The refactor to appMetadata and inferAppMode moved the primary doc inference ahead of the API type check. rsconnect-0.8.29 ignored appPrimaryDoc for Plumber APIs, but I'm inclined to keep the current behavior for now.

Long story short -- you should not specify appPrimaryDoc for Plumber APIs.

Please reopen this issue if you are unable to remove the appPrimaryDoc argument or if you uncover other reasons for us to revisit how we infer the content type.

Adding a few more breadcrumbs for future-us:

The appPrimaryDoc is sometimes used for Shiny applications when folks use a nonstandard filename dashboard.R for their Shiny application.

In addition to forcing the content type, that file is renamed to app.R during bundling:

rsconnect/R/bundle.R

Lines 21 to 27 in f3efe89

# if deploying a single-file Shiny application, name it "app.R" so it can
# be run as an ordinary Shiny application
if (is.character(appPrimaryDoc) &&
tolower(tools::file_ext(appPrimaryDoc)) == "r" &&
file == appPrimaryDoc) {
to <- file.path(bundleDir, "app.R")
}

Breadcrumb also for my future what-if...

I think the reason why we had this plumber.R (which previously worked) was because we previously were hosting a shiny and an app in the same folder - especially so they could share a common /R folder. We since changed that so they shared a package instead (but that did require some CI/CD tooling to auto-update package versions on our side)

@slodge-work - shifting to separate directories makes many things more straightforward, but does complicate code sharing.

If you find yourself needing to handle flat directories in the future, you could also build your own appFiles enumeration and discard the Shiny app.R or Plumber plumber.R as appropriate. The appMode option is also a way to avoid content type inference.