diff --git a/R/test_dependencies.R b/R/test_dependencies.R index de2ea85..7dce5ff 100644 --- a/R/test_dependencies.R +++ b/R/test_dependencies.R @@ -123,7 +123,7 @@ shinyloadtest_report(df, "R/report1.html") library(connectapi) client <- connect( - server = paste0("https://", Sys.getenv("CONNECT_SERVER")), + server = Sys.getenv("CONNECT_SERVER"), api_key = Sys.getenv("CONNECT_API_KEY") ) diff --git a/_freeze/materials/d1-04-deploy-admin/index/execute-results/html.json b/_freeze/materials/d1-04-deploy-admin/index/execute-results/html.json new file mode 100644 index 0000000..50d6b5a --- /dev/null +++ b/_freeze/materials/d1-04-deploy-admin/index/execute-results/html.json @@ -0,0 +1,20 @@ +{ + "hash": "3e56d1f02a91584fae41636bfdd25373", + "result": { + "markdown": "---\ntitle: \"Deployment & Administration\"\nsubtitle: \"posit::conf(2023)
Shiny in Production: Tools & Techniques\"\nfooter: \"[{{< var workshop_short_url >}}]({{< var workshop_full_url >}})\"\nformat: \n revealjs:\n theme: [default, ../slides.scss] # moon= teal bg | dark\n scrollable: true\n incremental: false\n slide-number: c/t # c/t | c | h/v | h.v\n slide-tone: false #true\n code-line-numbers: true\n history: false\nrevealjs-plugins:\n - codewindow\n---\n\n::: {.cell}\n\n:::\n\n\n## Choose your Adventure\n\n::: columns\n::: {.column width=\"50%\"}\n### Posit Connect\n:::\n\n::: {.column width=\"50%\"}\n### Containers\n:::\n:::\n\n::: notes\nTODO: Find pictures demonstrating the \"App & Packages Only\" (Posit Connect, Shinyapps.io) versus \"The whole environment\" (Containers)\n:::\n\n## Deployment Checklist\n\nāœ… Create `app.R` in directory\n\nāœ… Take note of any custom environment variables\n\nāœ… Ensure `DESCRIPTION` is up-to-date with required packages\n\nāœ… Remove outdated functions / scripts\n\n\n\n## {.center-xy}\n\n::: mono\n::: {.codewindow .r}\n03_deploy.R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n## RStudio ----\n## If you want to deploy on RStudio related platforms\ngolem::add_rstudioconnect_file()\ngolem::add_shinyappsio_file()\ngolem::add_shinyserver_file()\n```\n:::\n\n:::\n:::\n\n# ``{=html} Code-Along {background-color=\"#17395c\"}\n\nPush-Button Deployment to Posit Connect\n\n---\n\n::: mono\n::: {.codewindow .r width=\"1000px\"}\n03_deploy.R\n\n::: {.cell}\n\n```{.r .cell-code}\nrsconnect::deployApp(\n appName = \"legobricksapp\",\n appTitle = desc::desc_get_field(\"Package\"),\n appFiles = c(\n \"R/\",\n \"inst/\",\n \"data/\",\n \"NAMESPACE\",\n \"DESCRIPTION\",\n \"app.R\"\n ),\n appId = rsconnect::deployments(\".\")$appId,\n lint = FALSE,\n forceUpdate = TRUE\n)\n```\n:::\n\n:::\n:::\n\n## Balancing Act {.centerheading}\n\n:::: {.columns}\n\n::: {.column width=\"33%\"}\n\nšŸ”¼ Max Processes\n\n* More resources dedicated to particular application\n* Potential for server overload\n\n:::\n\n::: {.column width=\"33%\"}\n\n![](assets/img/teeter_totter.png)\n\n:::\n\n::: {.column width=\"33%\"}\n\nšŸ”¼ Min Processes\n\n* Resources dedicated even when app is idle\n* Potentially wasteful if app is not used concurrently often\n\n:::\n\n::::\n\n## Business Talk {background-image=\"assets/img/lego_boss_office.jpg\" background-size=\"cover\" background-color=\"black\"}\n\n## You Might be Asked ...\n\n::: {.fragment}\n\n\"How many people used your app last year?\"\n\n:::\n\n::: {.fragment}\n\n\"How long are users on the app at a given time?\"\n\n:::\n\n::: {.fragment}\n::: {.fragment .strike}\n\"What is the return on investment (ROI) this app is bringing the company?\"\n:::\n:::\n\n::: {.fragment}\n\n:::: {.columns}\n\n::: {.column width=\"55%\"}\n![](assets/img/daniel-bryan-no.gif){width=90%}\n:::\n\n::: {.column width=\"30%\"}\n### NOT TODAY!\n:::\n\n::::\n\n:::\n\n## šŸ“¦ Introducing [`{connectapi}`](https://pkgs.rstudio.com/connectapi/)\n\n> R client for the [Posit Connect Server API](https://docs.posit.co/connect/api/) as well as helpful functions that utilize the client\n\nChecklist:\n\nāœ… Create API key for your Posit Connect account\n\nāœ… Create `.Renviron` with following variables: `CONNECT_SERVER`, `CONNECT_API_KEY`\n\n## Introduce Yourself\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(connectapi)\n\nclient <- connect(\n server = Sys.getenv(\"CONNECT_SERVER\"),\n api_key = Sys.getenv(\"CONNECT_API_KEY\")\n)\n\nclient\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPosit Connect API Client: \n Posit Connect Server: https://rsc.training.rstudio.com\n Posit Connect API Key: ***********FWxW\n```\n\n\n:::\n:::\n\n\n* `client` is an R6 object representing the API client\n* Required parameter for many convenience functions\n\n## Know Yourself\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_guid <- user_guid_from_username(client, \"rpodcast\")\n\nmy_guid\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"47e1338f-54a8-4b0e-9b3c-e4380611b30f\"\n```\n\n\n:::\n:::\n\n\n* All content/users have a **unique indentifier (guid)**\n\n## Obtain (Your) Shiny Apps\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(dplyr)\n\nmeta_df <- get_content(client) |>\n filter(owner_guid == my_guid) |>\n select(guid, name, title)\n\nmeta_df\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 27 Ɨ 3\n guid name title \n \n 1 40dd2969-5d67-4940-89c4-08fe31fd1c74 brickapp brickapp \n 2 fba6896e-49e4-45c6-856f-03a3bc4f498f legobricksapp legobricks.app \n 3 626d6bb1-f36d-4c9b-bc9e-5cb44f46e698 hellogolem22 hellogolem2 \n 4 6f8116dc-eabf-4a64-aacb-4aeffbb4eaa4 golembrochure2 golembrochure \n 5 a4e6809f-4858-4888-a848-94925d3df4a0 golembrochure golembrochure \n 6 f268d424-a286-41b9-91f1-ae458797e64a golemhellolocal golemhellolocal\n 7 4d9124a0-158b-4bd9-9b65-07b877db0906 test test \n 8 c60de16e-0b29-4267-a2c5-0eb6815d7999 hellogolem3 hellogolem \n 9 2aec4c5b-144f-4a6c-8abf-7d07a17d78b2 hellogolem hellogolem \n10 344b9f1b-ddbe-4145-a1f0-eb6608662f08 hellogolem2 hellogolem \n# ā„¹ 17 more rows\n```\n\n\n:::\n:::\n\n\n## Usage Metrics\n\nSession durations for the `legobricksapp`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\napp_guid <- \"fba6896e-49e4-45c6-856f-03a3bc4f498f\"\n\nget_usage_shiny(\n client,\n content_guid = app_guid,\n limit = 5\n ) |>\n filter(!is.na(ended)) |>\n mutate(session_duration = ended - started) |>\n select(user_guid, session_duration)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 5 Ɨ 2\n user_guid session_duration\n \n1 47e1338f-54a8-4b0e-9b3c-e4380611b30f 10.933333 mins \n2 47e1338f-54a8-4b0e-9b3c-e4380611b30f 1.216667 mins \n3 47e1338f-54a8-4b0e-9b3c-e4380611b30f 4.733333 mins \n4 47e1338f-54a8-4b0e-9b3c-e4380611b30f 6.766667 mins \n5 47e1338f-54a8-4b0e-9b3c-e4380611b30f 1.183333 mins \n```\n\n\n:::\n:::\n\n\n## Usage Metrics\n\nHow many users visited `legobricksapp` in last 28 days?\n\n\n::: {.cell}\n\n```{.r .cell-code}\napp_guid <- \"fba6896e-49e4-45c6-856f-03a3bc4f498f\"\ntime_start <- Sys.Date() - lubridate::days(28)\nget_usage_shiny(\n client,\n content_guid = app_guid,\n limit = 100,\n from = time_start\n ) |>\n pull(user_guid) |>\n unique() |>\n length()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4\n```\n\n\n:::\n:::", + "supporting": [ + "index_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-after-body": [ + "\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/materials/d1-04-deploy-admin/assets/img/daniel-bryan-no.gif b/materials/d1-04-deploy-admin/assets/img/daniel-bryan-no.gif new file mode 100644 index 0000000..c803688 Binary files /dev/null and b/materials/d1-04-deploy-admin/assets/img/daniel-bryan-no.gif differ diff --git a/materials/d1-04-deploy-admin/assets/img/lego_boss_office.jpg b/materials/d1-04-deploy-admin/assets/img/lego_boss_office.jpg new file mode 100644 index 0000000..9ac60e1 Binary files /dev/null and b/materials/d1-04-deploy-admin/assets/img/lego_boss_office.jpg differ diff --git a/materials/d1-04-deploy-admin/assets/img/optimized.gif b/materials/d1-04-deploy-admin/assets/img/optimized.gif new file mode 100644 index 0000000..5da89e9 Binary files /dev/null and b/materials/d1-04-deploy-admin/assets/img/optimized.gif differ diff --git a/materials/d1-04-deploy-admin/assets/img/teeter_totter.png b/materials/d1-04-deploy-admin/assets/img/teeter_totter.png new file mode 100644 index 0000000..0c2e1e0 Binary files /dev/null and b/materials/d1-04-deploy-admin/assets/img/teeter_totter.png differ diff --git a/materials/d1-04-deploy-admin/index.qmd b/materials/d1-04-deploy-admin/index.qmd index 2f4c1aa..f9c20d9 100644 --- a/materials/d1-04-deploy-admin/index.qmd +++ b/materials/d1-04-deploy-admin/index.qmd @@ -1,17 +1,269 @@ --- title: "Deployment & Administration" subtitle: "posit::conf(2023)
Shiny in Production: Tools & Techniques" -author: "TBD" footer: "[{{< var workshop_short_url >}}]({{< var workshop_full_url >}})" format: revealjs: theme: [default, ../slides.scss] # moon= teal bg | dark scrollable: true - incremental: true + incremental: false slide-number: c/t # c/t | c | h/v | h.v slide-tone: false #true - code-line-numbers: false + code-line-numbers: true history: false +revealjs-plugins: + - codewindow --- -## More to come +```{css} +.centerheading h2 { + text-align: center; +} + +.mono { + font-family: monospace; + font-size: 1.4em; +} + +.center-xy { + margin: 0; + position: absolute; + top: 25%; + left: 0%; + -ms-transform: translateY(-50%), translateX(-50%); + transform: translateY(-50%), translateX(-50%); +} +``` + +## Choose your Adventure + +::: columns +::: {.column width="50%"} +### Posit Connect +::: + +::: {.column width="50%"} +### Containers +::: +::: + +::: notes +TODO: Find pictures demonstrating the "App & Packages Only" (Posit Connect, Shinyapps.io) versus "The whole environment" (Containers) +::: + +## Deployment Checklist + +āœ… Create `app.R` in directory + +āœ… Take note of any custom environment variables + +āœ… Ensure `DESCRIPTION` is up-to-date with required packages + +āœ… Remove outdated functions / scripts + + + +## {.center-xy} + +::: mono +::: {.codewindow .r} +03_deploy.R + +```{r} +#| eval: false +#| echo: true +## RStudio ---- +## If you want to deploy on RStudio related platforms +golem::add_rstudioconnect_file() +golem::add_shinyappsio_file() +golem::add_shinyserver_file() +``` +::: +::: + +# `r fontawesome::fa("people-carry", "white")` Code-Along {background-color="#17395c"} + +Push-Button Deployment to Posit Connect + +--- + +::: mono +::: {.codewindow .r width="1000px"} +03_deploy.R +```{r} +#| eval: false +#| echo: true +rsconnect::deployApp( + appName = "legobricksapp", + appTitle = desc::desc_get_field("Package"), + appFiles = c( + "R/", + "inst/", + "data/", + "NAMESPACE", + "DESCRIPTION", + "app.R" + ), + appId = rsconnect::deployments(".")$appId, + lint = FALSE, + forceUpdate = TRUE +) +``` +::: +::: + +## Balancing Act {.centerheading} + +:::: {.columns} + +::: {.column width="33%"} + +šŸ”¼ Max Processes + +* More resources dedicated to particular application +* Potential for server overload + +::: + +::: {.column width="33%"} + +![](assets/img/teeter_totter.png) + +::: + +::: {.column width="33%"} + +šŸ”¼ Min Processes + +* Resources dedicated even when app is idle +* Potentially wasteful if app is not used concurrently often + +::: + +:::: + +## Business Talk {background-image="assets/img/lego_boss_office.jpg" background-size="cover" background-color="black"} + +## You Might be Asked ... + +::: {.fragment} + +"How many people used your app last year?" + +::: + +::: {.fragment} + +"How long are users on the app at a given time?" + +::: + +::: {.fragment} +::: {.fragment .strike} +"What is the return on investment (ROI) this app is bringing the company?" +::: +::: + +::: {.fragment} + +:::: {.columns} + +::: {.column width="55%"} +![](assets/img/daniel-bryan-no.gif){width=90%} +::: + +::: {.column width="30%"} +### NOT TODAY! +::: + +:::: + +::: + +## šŸ“¦ Introducing [`{connectapi}`](https://pkgs.rstudio.com/connectapi/) + +> R client for the [Posit Connect Server API](https://docs.posit.co/connect/api/) as well as helpful functions that utilize the client + +Checklist: + +āœ… Create API key for your Posit Connect account + +āœ… Create `.Renviron` with following variables: `CONNECT_SERVER`, `CONNECT_API_KEY` + +## Introduce Yourself + +```{r} +#| echo: true +library(connectapi) + +client <- connect( + server = Sys.getenv("CONNECT_SERVER"), + api_key = Sys.getenv("CONNECT_API_KEY") +) + +client +``` + +* `client` is an R6 object representing the API client +* Required parameter for many convenience functions + +## Know Yourself + +```{r} +#| echo: true +my_guid <- user_guid_from_username(client, "rpodcast") + +my_guid +``` + +* All content/users have a **unique indentifier (guid)** + +## Obtain (Your) Shiny Apps + +```{r} +#| echo: true +library(dplyr) + +meta_df <- get_content(client) |> + filter(owner_guid == my_guid) |> + select(guid, name, title) + +meta_df +``` + +## Usage Metrics + +Session durations for the `legobricksapp`: + +```{r} +#| echo: true +app_guid <- "fba6896e-49e4-45c6-856f-03a3bc4f498f" + +get_usage_shiny( + client, + content_guid = app_guid, + limit = 5 + ) |> + filter(!is.na(ended)) |> + mutate(session_duration = ended - started) |> + select(user_guid, session_duration) +``` + +## Usage Metrics + +How many users visited `legobricksapp` in last 28 days? + +```{r} +#| echo: true +app_guid <- "fba6896e-49e4-45c6-856f-03a3bc4f498f" +time_start <- Sys.Date() - lubridate::days(28) +get_usage_shiny( + client, + content_guid = app_guid, + limit = 100, + from = time_start + ) |> + pull(user_guid) |> + unique() |> + length() +``` \ No newline at end of file