From c666e3ed0933ec6d97c6167b6e536e2e14c17e20 Mon Sep 17 00:00:00 2001 From: Jimmy Briggs Date: Fri, 21 Jun 2024 15:31:49 -0400 Subject: [PATCH 1/2] update --- .Rbuildignore | 19 ++ .github/.gitignore | 1 + .github/workflows/R-CMD-check.yaml | 52 +++ .github/workflows/pkgdown.yaml | 50 +++ .github/workflows/pr-commands.yaml | 81 +++++ .github/workflows/test-coverage.yaml | 61 ++++ .gitignore | 3 + CODE_OF_CONDUCT.md | 126 +++++++ DESCRIPTION | 24 ++ LICENSE | 26 +- LICENSE.md | 21 ++ NAMESPACE | 17 + R/_disable_autoload.R | 3 + R/app_config.R | 61 ++++ R/app_run.R | 27 ++ R/app_server.R | 16 + R/app_ui.R | 55 +++ R/rshinycloudrun-package.R | 6 + R/utils_server.R | 63 ++++ R/utils_ui.R | 405 +++++++++++++++++++++++ README.md | 35 +- app.R | 11 + codecov.yml | 14 + dev/config_attachment.yaml | 12 + dev/pkgdevt.R | 0 inst/.gitignore | 4 + inst/WORDLIST | 11 + man/app_sys.Rd | 26 ++ man/get_app_config.Rd | 34 ++ man/rshinycloudrun-package.Rd | 15 + man/run_app.Rd | 40 +++ rshinycloudrun.Rproj | 22 ++ tests/spelling.R | 3 + tests/testthat.R | 12 + tests/testthat/test-app.R | 3 + tests/testthat/test-golem-recommended.R | 74 +++++ tests/testthat/test-golem_utils_server.R | 54 +++ tests/testthat/test-golem_utils_ui.R | 177 ++++++++++ vignettes/.gitignore | 2 + vignettes/rshinycloudrun.Rmd | 19 ++ 40 files changed, 1659 insertions(+), 26 deletions(-) create mode 100644 .Rbuildignore create mode 100644 .github/.gitignore create mode 100644 .github/workflows/R-CMD-check.yaml create mode 100644 .github/workflows/pkgdown.yaml create mode 100644 .github/workflows/pr-commands.yaml create mode 100644 .github/workflows/test-coverage.yaml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 DESCRIPTION create mode 100644 LICENSE.md create mode 100644 NAMESPACE create mode 100644 R/_disable_autoload.R create mode 100644 R/app_config.R create mode 100644 R/app_run.R create mode 100644 R/app_server.R create mode 100644 R/app_ui.R create mode 100644 R/rshinycloudrun-package.R create mode 100644 R/utils_server.R create mode 100644 R/utils_ui.R create mode 100644 app.R create mode 100644 codecov.yml create mode 100644 dev/config_attachment.yaml create mode 100644 dev/pkgdevt.R create mode 100644 inst/.gitignore create mode 100644 inst/WORDLIST create mode 100644 man/app_sys.Rd create mode 100644 man/get_app_config.Rd create mode 100644 man/rshinycloudrun-package.Rd create mode 100644 man/run_app.Rd create mode 100644 rshinycloudrun.Rproj create mode 100644 tests/spelling.R create mode 100644 tests/testthat.R create mode 100644 tests/testthat/test-app.R create mode 100644 tests/testthat/test-golem-recommended.R create mode 100644 tests/testthat/test-golem_utils_server.R create mode 100644 tests/testthat/test-golem_utils_ui.R create mode 100644 vignettes/.gitignore create mode 100644 vignettes/rshinycloudrun.Rmd diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..9e3b6db --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,19 @@ +^renv$ +^renv\.lock$ +^.*\.Rproj$ +^\.Rproj\.user$ +^data-raw$ +dev_history.R +^dev$ +$run_dev.* +^.here$ +^app\.R$ +^rsconnect$ +^LICENSE\.md$ +^CODE_OF_CONDUCT\.md$ +^codecov\.yml$ +^\.github$ +^app$ +^\.Renviron$ +^\.Rprofile$ +^config\.yml$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..0f2fe08 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,52 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: R-CMD-check + +permissions: read-all + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..c9f0165 --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +permissions: read-all + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml new file mode 100644 index 0000000..d1f7650 --- /dev/null +++ b/.github/workflows/pr-commands.yaml @@ -0,0 +1,81 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + issue_comment: + types: [created] + +name: Commands + +permissions: read-all + +jobs: + document: + if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} + name: document + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::roxygen2 + needs: pr-document + + - name: Document + run: roxygen2::roxygenise() + shell: Rscript {0} + + - name: commit + run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" + git add man/\* NAMESPACE + git commit -m 'Document' + + - uses: r-lib/actions/pr-push@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + style: + if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} + name: style + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: r-lib/actions/setup-r@v2 + + - name: Install dependencies + run: install.packages("styler") + shell: Rscript {0} + + - name: Style + run: styler::style_pkg() + shell: Rscript {0} + + - name: commit + run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" + git add \*.R + git commit -m 'Style' + + - uses: r-lib/actions/pr-push@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..fefc52e --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,61 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: test-coverage + +permissions: read-all + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage + + - name: Test coverage + run: | + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) + shell: Rscript {0} + + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/.gitignore b/.gitignore index e75435c..03aaee2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +config.yml + + # History files .Rhistory .Rapp.history diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3e92cb2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,126 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at No Clocks, LLC. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at . + +[homepage]: https://www.contributor-covenant.org diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..3347cc5 --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,24 @@ +Package: rshinycloudrun +Title: Shiny App on Google Cloud Run +Version: 0.0.0.9000 +Authors@R: + person("Jimmy", "Briggs", , "jimmy.briggs@noclocks.dev", role = c("aut", "cre")) +Description: Deploy a Shiny app to Google Cloud Run. +License: MIT + file LICENSE +Imports: + config (>= 0.3.2), + golem (>= 0.4.1), + pkgload, + shiny (>= 1.8.1.1) +Suggests: + knitr, + rmarkdown, + spelling, + testthat (>= 3.0.0) +VignetteBuilder: + knitr +Config/testthat/edition: 3 +Encoding: UTF-8 +Language: en-US +LazyData: true +RoxygenNote: 7.3.1 diff --git a/LICENSE b/LICENSE index fdddb29..66cb04b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,24 +1,2 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to +YEAR: 2024 +COPYRIGHT HOLDER: No Clocks, LLC diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0d3e995 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2024 No Clocks, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..2c807e8 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,17 @@ +# Generated by roxygen2: do not edit by hand + +export(run_app) +import(shiny) +importFrom(config,get) +importFrom(golem,activate_js) +importFrom(golem,add_resource_path) +importFrom(golem,bundle_resources) +importFrom(golem,favicon) +importFrom(golem,with_golem_options) +importFrom(pkgload,pkg_name) +importFrom(shiny,HTML) +importFrom(shiny,column) +importFrom(shiny,shinyApp) +importFrom(shiny,tagAppendAttributes) +importFrom(shiny,tagList) +importFrom(shiny,tags) diff --git a/R/_disable_autoload.R b/R/_disable_autoload.R new file mode 100644 index 0000000..a8c9436 --- /dev/null +++ b/R/_disable_autoload.R @@ -0,0 +1,3 @@ +# Disabling shiny autoload + +# See ?shiny::loadSupport for more information diff --git a/R/app_config.R b/R/app_config.R new file mode 100644 index 0000000..2504eb3 --- /dev/null +++ b/R/app_config.R @@ -0,0 +1,61 @@ +#' App Sys +#' +#' @description +#' Access files in the current app. +#' +#' @param ... (Optional) Character Vector(s) specifying paths and file(s) +#' within the R package's installation folder. Defaults to nothing which returns +#' the root of the app package folder. +#' +#' @return Character vector of positive length, containing the file paths that +#' matched `...`, or the empty string, `""`, if none matched. +#' +#' @seealso [system.file()] +#' +#' @importFrom pkgload pkg_name +#' +#' @examples +#' index_html <- app_sys("app/www/index.html") +app_sys <- function(...) { + system.file(..., package = pkgload::pkg_name()) +} + + +#' Get App Config +#' +#' @description +#' Retrieves a value from the app package's configuration file, `config.yml`. +#' +#' @param value (Required) Value to retrieve from the config file. +#' @param config (Optional) Configuration environment to use. Defaults to the +#' `APP_CONFIG_ACTIVE`'s environment variable, if available, and if not, uses +#' `R_CONFIG_ACTIVE`'s environment variable. Finally if still unset, +#' defaults to `default`. +#' @param use_parent (Optional) Logical, scan the parent directory for config file. +#' Defaults to `TRUE`. +#' @param file (Optional) Character path to the location of the config file. Defaults +#' to the `config.yml` file in the app package's root installation directory +#' via `app_sys("config.yml")`. +#' +#' @return The value from the config file. +#' +#' @importFrom config get +get_app_config <- function( + value, + config = Sys.getenv( + "APP_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + file = app_sys("config.yml") +) { + config::get( + value = value, + config = config, + file = file, + use_parent = use_parent + ) +} diff --git a/R/app_run.R b/R/app_run.R new file mode 100644 index 0000000..44d4029 --- /dev/null +++ b/R/app_run.R @@ -0,0 +1,27 @@ +#' Run the Shiny Application +#' +#' @param ... arguments to pass to app_opts. +#' @inheritParams shiny::shinyApp +#' +#' @export +#' @importFrom shiny shinyApp +#' @importFrom golem with_golem_options +run_app <- function( + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ... +) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server, + onStart = onStart, + options = options, + enableBookmarking = enableBookmarking, + uiPattern = uiPattern + ), + golem_opts = list(...) + ) +} diff --git a/R/app_server.R b/R/app_server.R new file mode 100644 index 0000000..d912fc0 --- /dev/null +++ b/R/app_server.R @@ -0,0 +1,16 @@ +#' The application server-side +#' +#' @param input,output,session Internal parameters for {shiny}. +#' +#' @import shiny +#' @noRd +app_server <- function(input, output, session) { + output$distPlot <- renderPlot({ + # generate bins based on input$bins from ui.R + x <- faithful[, 2] + bins <- seq(min(x), max(x), length.out = input$bins + 1) + + # draw the histogram with the specified number of bins + hist(x, breaks = bins, col = 'darkgray', border = 'white') + }) +} diff --git a/R/app_ui.R b/R/app_ui.R new file mode 100644 index 0000000..720cfc6 --- /dev/null +++ b/R/app_ui.R @@ -0,0 +1,55 @@ +#' The application User-Interface +#' +#' @param request Internal parameter for `{shiny}`. +#' DO NOT REMOVE. +#' @import shiny +#' @noRd +app_ui <- function(request) { + tagList( + # Leave this function for adding external resources + add_external_resources(), + # Your application UI logic + fluidPage( + titlePanel("Hello World!"), + sidebarLayout( + sidebarPanel( + sliderInput( + "bins", + "Number of Bins:", + min = 1, + max = 50, + value = 30 + ) + ), + mainPanel( + plotOutput("distPlot") + ) + ) + ) + ) +} + +#' Add external Resources to the Application +#' +#' This function is internally used to add external +#' resources inside the Shiny application. +#' +#' @import shiny +#' @importFrom golem add_resource_path activate_js favicon bundle_resources +#' @noRd +add_external_resources <- function() { + shiny::addResourcePath( + "www", + app_sys("app/www") + ) + + tags$head( + golem::favicon("favicon", ext = "png"), + golem::bundle_resources( + path = app_sys("app/www"), + app_title = "Hello World!" + ) + # Add here other external resources + # for example, you can add shinyalert::useShinyalert() + ) +} diff --git a/R/rshinycloudrun-package.R b/R/rshinycloudrun-package.R new file mode 100644 index 0000000..a65cf64 --- /dev/null +++ b/R/rshinycloudrun-package.R @@ -0,0 +1,6 @@ +#' @keywords internal +"_PACKAGE" + +## usethis namespace: start +## usethis namespace: end +NULL diff --git a/R/utils_server.R b/R/utils_server.R new file mode 100644 index 0000000..0099c19 --- /dev/null +++ b/R/utils_server.R @@ -0,0 +1,63 @@ +#' Inverted versions of in, is.null and is.na +#' +#' @noRd +#' +#' @examples +#' 1 %not_in% 1:10 +#' not_null(NULL) +`%not_in%` <- Negate(`%in%`) + +not_null <- Negate(is.null) + +not_na <- Negate(is.na) + +#' Removes the null from a vector +#' +#' @noRd +#' +#' @example +#' drop_nulls(list(1, NULL, 2)) +drop_nulls <- function(x) { + x[!sapply(x, is.null)] +} + +#' If x is `NULL`, return y, otherwise return x +#' +#' @param x,y Two elements to test, one potentially `NULL` +#' +#' @noRd +#' +#' @examples +#' NULL %||% 1 +"%||%" <- function(x, y) { + if (is.null(x)) { + y + } else { + x + } +} + +#' If x is `NA`, return y, otherwise return x +#' +#' @param x,y Two elements to test, one potentially `NA` +#' +#' @noRd +#' +#' @examples +#' NA %|NA|% 1 +"%|NA|%" <- function(x, y) { + if (is.na(x)) { + y + } else { + x + } +} + +#' Typing reactiveValues is too long +#' +#' @inheritParams reactiveValues +#' @inheritParams reactiveValuesToList +#' +#' @noRd +rv <- function(...) shiny::reactiveValues(...) +rvtl <- function(...) shiny::reactiveValuesToList(...) diff --git a/R/utils_ui.R b/R/utils_ui.R new file mode 100644 index 0000000..5a9ee8d --- /dev/null +++ b/R/utils_ui.R @@ -0,0 +1,405 @@ +#' Turn an R list into an HTML list +#' +#' @param list An R list +#' @param class a class for the list +#' +#' @return an HTML list +#' @noRd +#' +#' @examples +#' list_to_li(c("a", "b")) +#' @importFrom shiny tags tagAppendAttributes tagList +list_to_li <- function(list, class = NULL) { + if (is.null(class)) { + tagList( + lapply( + list, + tags$li + ) + ) + } else { + res <- lapply( + list, + tags$li + ) + res <- lapply( + res, + function(x) { + tagAppendAttributes( + x, + class = class + ) + } + ) + tagList(res) + } +} +#' Turn an R list into corresponding HTML paragraph tags +#' +#' @param list an R list +#' @param class a class for the paragraph tags +#' +#' @return An HTML tag +#' @noRd +#' +#' @examples +#' list_to_p(c("This is the first paragraph", "this is the second paragraph")) +#' @importFrom shiny tags tagAppendAttributes tagList +#' +list_to_p <- function(list, class = NULL) { + if (is.null(class)) { + tagList( + lapply( + list, + tags$p + ) + ) + } else { + res <- lapply( + list, + tags$p + ) + res <- lapply( + res, + function(x) { + tagAppendAttributes( + x, + class = class + ) + } + ) + tagList(res) + } +} + +#' @importFrom shiny tags tagAppendAttributes tagList +named_to_li <- function(list, class = NULL) { + if (is.null(class)) { + res <- mapply( + function(x, y) { + tags$li( + HTML( + sprintf("%s: %s", y, x) + ) + ) + }, + list, + names(list), + SIMPLIFY = FALSE + ) + tagList(res) + } else { + res <- mapply( + function(x, y) { + tags$li( + HTML( + sprintf("%s: %s", y, x) + ) + ) + }, + list, + names(list), + SIMPLIFY = FALSE + ) + res <- lapply( + res, + function(x) { + tagAppendAttributes( + x, + class = class + ) + } + ) + tagList(res) + } +} + +#' Remove a tag attribute +#' +#' @param tag the tag +#' @param ... the attributes to remove +#' +#' @return a new tag +#' @noRd +#' +#' @examples +#' a <- shiny::tags$p(src = "plop", "pouet") +#' tagRemoveAttributes(a, "src") +tagRemoveAttributes <- function(tag, ...) { + attrs <- as.character(list(...)) + for (i in seq_along(attrs)) { + tag$attribs[[attrs[i]]] <- NULL + } + tag +} + +#' Hide or display a tag +#' +#' @param tag the tag +#' +#' @return a tag +#' @noRd +#' +#' @examples +#' ## Hide +#' a <- shiny::tags$p(src = "plop", "pouet") +#' undisplay(a) +#' b <- shiny::actionButton("go_filter", "go") +#' undisplay(b) +#' @importFrom shiny tagList +undisplay <- function(tag) { + # if not already hidden + if ( + !is.null(tag$attribs$style) && + !grepl("display:\\s+none", tag$attribs$style) + ) { + tag$attribs$style <- paste( + "display: none;", + tag$attribs$style + ) + } else { + tag$attribs$style <- "display: none;" + } + tag +} + +#' @importFrom shiny tagList +display <- function(tag) { + if ( + !is.null(tag$attribs$style) && + grepl("display:\\s+none", tag$attribs$style) + ) { + tag$attribs$style <- gsub( + "(\\s)*display:(\\s)*none(\\s)*(;)*(\\s)*", + "", + tag$attribs$style + ) + } + tag +} + +#' Hide an elements by calling jquery hide on it +#' +#' @param id the id of the element to hide +#' +#' @noRd +#' +#' @importFrom shiny tags +jq_hide <- function(id) { + tags$script(sprintf("$('#%s').hide()", id)) +} + +#' Add a red star at the end of the text +#' +#' Adds a red star at the end of the text +#' (for example for indicating mandatory fields). +#' +#' @param text the HTLM text to put before the red star +#' +#' @return an html element +#' @noRd +#' +#' @examples +#' with_red_star("Enter your name here") +#' @importFrom shiny tags HTML +with_red_star <- function(text) { + shiny::tags$span( + HTML( + paste0( + text, + shiny::tags$span( + style = "color:red", + "*" + ) + ) + ) + ) +} + + + +#' Repeat tags$br +#' +#' @param times the number of br to return +#' +#' @return the number of br specified in times +#' @noRd +#' +#' @examples +#' rep_br(5) +#' @importFrom shiny HTML +rep_br <- function(times = 1) { + HTML(rep("
", times = times)) +} + +#' Create an url +#' +#' @param url the URL +#' @param text the text to display +#' +#' @return an a tag +#' @noRd +#' +#' @examples +#' enurl("https://www.thinkr.fr", "ThinkR") +#' @importFrom shiny tags +enurl <- function(url, text) { + tags$a(href = url, text) +} + +#' Columns wrappers +#' +#' These are convenient wrappers around +#' `column(12, ...)`, `column(6, ...)`, `column(4, ...)`... +#' +#' @noRd +#' +#' @importFrom shiny column +col_12 <- function(...) { + column(12, ...) +} + +#' @importFrom shiny column +col_10 <- function(...) { + column(10, ...) +} + +#' @importFrom shiny column +col_8 <- function(...) { + column(8, ...) +} + +#' @importFrom shiny column +col_6 <- function(...) { + column(6, ...) +} + + +#' @importFrom shiny column +col_4 <- function(...) { + column(4, ...) +} + + +#' @importFrom shiny column +col_3 <- function(...) { + column(3, ...) +} + + +#' @importFrom shiny column +col_2 <- function(...) { + column(2, ...) +} + + +#' @importFrom shiny column +col_1 <- function(...) { + column(1, ...) +} + + + +#' Make the current tag behave like an action button +#' +#' Only works with compatible tags like button or links +#' +#' @param tag Any compatible tag. +#' @param inputId Unique id. This will host the input value to be used +#' on the server side. +#' +#' @return The modified tag with an extra id and the action button class. +#' @noRd +#' +#' @examples +#' if (interactive()) { +#' library(shiny) +#' +#' link <- a(href = "#", "My super link", style = "color: lightblue;") +#' +#' ui <- fluidPage( +#' make_action_button(link, inputId = "mylink") +#' ) +#' +#' server <- function(input, output, session) { +#' observeEvent(input$mylink, { +#' showNotification("Pouic!") +#' }) +#' } +#' +#' shinyApp(ui, server) +#' } +make_action_button <- function(tag, inputId = NULL) { + # some obvious checks + if (!inherits(tag, "shiny.tag")) stop("Must provide a shiny tag.") + if (!is.null(tag$attribs$class)) { + if (grep("action-button", tag$attribs$class)) { + stop("tag is already an action button") + } + } + if (is.null(inputId) && is.null(tag$attribs$id)) { + stop("tag does not have any id. Please use inputId to be able to + access it on the server side.") + } + + # handle id + if (!is.null(inputId)) { + if (!is.null(tag$attribs$id)) { + warning( + paste( + "tag already has an id. Please use input$", + tag$attribs$id, + "to access it from the server side. inputId will be ignored." + ) + ) + } else { + tag$attribs$id <- inputId + } + } + + # handle class + if (is.null(tag$attribs$class)) { + tag$attribs$class <- "action-button" + } else { + tag$attribs$class <- paste(tag$attribs$class, "action-button") + } + # return tag + tag +} + + +# UNCOMMENT AND USE +# +# attachment::att_amend_desc() +# +# To use this part of the UI +# +#' #' Include Content From a File +#' #' +#' #' Load rendered RMarkdown from a file and turn into HTML. +#' #' +#' #' @rdname includeRMarkdown +#' #' @export +#' #' +#' #' @importFrom rmarkdown render +#' #' @importFrom markdown markdownToHTML +#' #' @importFrom shiny HTML +#' includeRMarkdown <- function(path){ +#' +#' md <- tempfile(fileext = '.md') +#' +#' on.exit(unlink(md),add = TRUE) +#' +#' rmarkdown::render( +#' path, +#' output_format = 'md_document', +#' output_dir = tempdir(), +#' output_file = md,quiet = TRUE +#' ) +#' +#' html <- markdown::markdownToHTML(md, fragment.only = TRUE) +#' +#' Encoding(html) <- "UTF-8" +#' +#' return(HTML(html)) +#' } diff --git a/README.md b/README.md index 3bd3c41..c6eb752 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,33 @@ -# demo-rshiny-cloudrun -Demonstration of CI/CD Docker Deployment to GCP Cloud Run for R Shiny App +# R Shiny App on GCP Cloud Run Demo + + +[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) +[![Codecov test coverage](https://codecov.io/gh/noclocks/demo-rshiny-cloudrun/branch/main/graph/badge.svg)](https://app.codecov.io/gh/noclocks/demo-rshiny-cloudrun?branch=main) +[![R-CMD-check](https://github.com/noclocks/demo-rshiny-cloudrun/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/noclocks/demo-rshiny-cloudrun/actions/workflows/R-CMD-check.yaml) + + + +> [!NOTE] +> This repository houses a demonstration R package that showcases how to develop +> and deploy an R Shiny app to Google Cloud Platform's (GCP) Cloud Run service +> using Docker. + +## Contents + +## Overview + +## Pre-Requisites + +1. [Google Cloud Platform (GCP) Account](https://cloud.google.com/) +2. [Docker Desktop](https://www.docker.com/products/docker-desktop) +3. [R](https://www.r-project.org/) +4. [RStudio](https://www.rstudio.com/products/rstudio/download/) +5. [R Shiny](https://shiny.rstudio.com/) +6. [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) +7. [Google Cloud Run](https://cloud.google.com/run/docs/quickstarts/build-and-deploy) + +## Code of Conduct + +Please note that the `rshinycloudrun` package is released with a +[Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). +By contributing to this project, you agree to abide by its terms. diff --git a/app.R b/app.R new file mode 100644 index 0000000..ba647d0 --- /dev/null +++ b/app.R @@ -0,0 +1,11 @@ +# Launch the ShinyApp (Do not remove this comment) +# To deploy, run: rsconnect::deployApp() +# Or use the blue button on top of this file + +pkgload::load_all( + export_all = FALSE, + helpers = FALSE, + attach_testthat = FALSE +) +options("shiny.app.prod" = TRUE) +rshinycloudrun::run_app() # add parameters here (if any) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..04c5585 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +comment: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true diff --git a/dev/config_attachment.yaml b/dev/config_attachment.yaml new file mode 100644 index 0000000..46e24ec --- /dev/null +++ b/dev/config_attachment.yaml @@ -0,0 +1,12 @@ +path.n: NAMESPACE +path.d: DESCRIPTION +dir.r: R +dir.v: vignettes +dir.t: tests +extra.suggests: ~ +pkg_ignore: ~ +document: yes +normalize: yes +inside_rmd: no +must.exist: yes +check_if_suggests_is_installed: yes diff --git a/dev/pkgdevt.R b/dev/pkgdevt.R new file mode 100644 index 0000000..e69de29 diff --git a/inst/.gitignore b/inst/.gitignore new file mode 100644 index 0000000..3b6213b --- /dev/null +++ b/inst/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!app/ +!WORDLIST diff --git a/inst/WORDLIST b/inst/WORDLIST new file mode 100644 index 0000000..19c9c1e --- /dev/null +++ b/inst/WORDLIST @@ -0,0 +1,11 @@ +CONFIG +Config +GCP +Lifecycle +Pre +RStudio +SDK +Sys +config +sys +yml diff --git a/man/app_sys.Rd b/man/app_sys.Rd new file mode 100644 index 0000000..5179c6b --- /dev/null +++ b/man/app_sys.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_config.R +\name{app_sys} +\alias{app_sys} +\title{App Sys} +\usage{ +app_sys(...) +} +\arguments{ +\item{...}{(Optional) Character Vector(s) specifying paths and file(s) +within the R package's installation folder. Defaults to nothing which returns +the root of the app package folder.} +} +\value{ +Character vector of positive length, containing the file paths that + matched `...`, or the empty string, `""`, if none matched. +} +\description{ +Access files in the current app. +} +\examples{ +index_html <- app_sys("app/www/index.html") +} +\seealso{ +[system.file()] +} diff --git a/man/get_app_config.Rd b/man/get_app_config.Rd new file mode 100644 index 0000000..8e56dd4 --- /dev/null +++ b/man/get_app_config.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_config.R +\name{get_app_config} +\alias{get_app_config} +\title{Get App Config} +\usage{ +get_app_config( + value, + config = Sys.getenv("APP_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + file = app_sys("config.yml") +) +} +\arguments{ +\item{value}{(Required) Value to retrieve from the config file.} + +\item{config}{(Optional) Configuration environment to use. Defaults to the +`APP_CONFIG_ACTIVE`'s environment variable, if available, and if not, uses +`R_CONFIG_ACTIVE`'s environment variable. Finally if still unset, +defaults to `default`.} + +\item{use_parent}{(Optional) Logical, scan the parent directory for config file. +Defaults to `TRUE`.} + +\item{file}{(Optional) Character path to the location of the config file. Defaults +to the `config.yml` file in the app package's root installation directory +via `app_sys("config.yml")`.} +} +\value{ +The value from the config file. +} +\description{ +Retrieves a value from the app package's configuration file, `config.yml`. +} diff --git a/man/rshinycloudrun-package.Rd b/man/rshinycloudrun-package.Rd new file mode 100644 index 0000000..cf1d03a --- /dev/null +++ b/man/rshinycloudrun-package.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rshinycloudrun-package.R +\docType{package} +\name{rshinycloudrun-package} +\alias{rshinycloudrun} +\alias{rshinycloudrun-package} +\title{rshinycloudrun: Shiny App on Google Cloud Run} +\description{ +Deploy a Shiny app to Google Cloud Run. +} +\author{ +\strong{Maintainer}: Jimmy Briggs \email{jimmy.briggs@noclocks.dev} + +} +\keyword{internal} diff --git a/man/run_app.Rd b/man/run_app.Rd new file mode 100644 index 0000000..56d4266 --- /dev/null +++ b/man/run_app.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_run.R +\name{run_app} +\alias{run_app} +\title{Run the Shiny Application} +\usage{ +run_app( + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ... +) +} +\arguments{ +\item{onStart}{A function that will be called before the app is actually run. +This is only needed for \code{shinyAppObj}, since in the \code{shinyAppDir} +case, a \code{global.R} file can be used for this purpose.} + +\item{options}{Named options that should be passed to the \code{runApp} call +(these can be any of the following: "port", "launch.browser", "host", "quiet", +"display.mode" and "test.mode"). You can also specify \code{width} and +\code{height} parameters which provide a hint to the embedding environment +about the ideal height/width for the app.} + +\item{enableBookmarking}{Can be one of \code{"url"}, \code{"server"}, or +\code{"disable"}. The default value, \code{NULL}, will respect the setting from +any previous calls to \code{\link[shiny:enableBookmarking]{enableBookmarking()}}. See \code{\link[shiny:enableBookmarking]{enableBookmarking()}} +for more information on bookmarking your app.} + +\item{uiPattern}{A regular expression that will be applied to each \code{GET} +request to determine whether the \code{ui} should be used to handle the +request. Note that the entire request path must match the regular +expression in order for the match to be considered successful.} + +\item{...}{arguments to pass to app_opts.} +} +\description{ +Run the Shiny Application +} diff --git a/rshinycloudrun.Rproj b/rshinycloudrun.Rproj new file mode 100644 index 0000000..766b3b2 --- /dev/null +++ b/rshinycloudrun.Rproj @@ -0,0 +1,22 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: knitr +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/tests/spelling.R b/tests/spelling.R new file mode 100644 index 0000000..6713838 --- /dev/null +++ b/tests/spelling.R @@ -0,0 +1,3 @@ +if(requireNamespace('spelling', quietly = TRUE)) + spelling::spell_check_test(vignettes = TRUE, error = FALSE, + skip_on_cran = TRUE) diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..877a433 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview +# * https://testthat.r-lib.org/articles/special-files.html + +library(testthat) +library(rshinycloudrun) + +test_check("rshinycloudrun") diff --git a/tests/testthat/test-app.R b/tests/testthat/test-app.R new file mode 100644 index 0000000..8849056 --- /dev/null +++ b/tests/testthat/test-app.R @@ -0,0 +1,3 @@ +test_that("multiplication works", { + expect_equal(2 * 2, 4) +}) diff --git a/tests/testthat/test-golem-recommended.R b/tests/testthat/test-golem-recommended.R new file mode 100644 index 0000000..b314130 --- /dev/null +++ b/tests/testthat/test-golem-recommended.R @@ -0,0 +1,74 @@ +test_that("app ui", { + ui <- app_ui() + golem::expect_shinytaglist(ui) + # Check that formals have not been removed + fmls <- formals(app_ui) + for (i in c("request")) { + expect_true(i %in% names(fmls)) + } +}) + +test_that("app server", { + server <- app_server + expect_type(server, "closure") + # Check that formals have not been removed + fmls <- formals(app_server) + for (i in c("input", "output", "session")) { + expect_true(i %in% names(fmls)) + } +}) + +test_that( + "app_sys works", + { + expect_true( + app_sys("golem-config.yml") != "" + ) + } +) + +test_that( + "golem-config works", + { + config_file <- app_sys("golem-config.yml") + skip_if(config_file == "") + + expect_true( + get_golem_config( + "app_prod", + config = "production", + file = config_file + ) + ) + expect_false( + get_golem_config( + "app_prod", + config = "dev", + file = config_file + ) + ) + } +) + +# Configure this test to fit your need. +# testServer() function makes it possible to test code in server functions and modules, without needing to run the full Shiny application +testServer(app_server, { + + # Set and test an input + session$setInputs(x = 2) + expect_equal(input$x, 2) + + # Example of tests you can do on the server: + # - Checking reactiveValues + # expect_equal(r$lg, 'EN') + # - Checking output + # expect_equal(output$txt, "Text") +}) + +# Configure this test to fit your need +test_that( + "app launches", + { + golem::expect_running(sleep = 5) + } +) diff --git a/tests/testthat/test-golem_utils_server.R b/tests/testthat/test-golem_utils_server.R new file mode 100644 index 0000000..770d387 --- /dev/null +++ b/tests/testthat/test-golem_utils_server.R @@ -0,0 +1,54 @@ +test_that("not_in works", { + expect_true(1 %not_in% 2:10) + expect_false(1 %not_in% 1:10) +}) + +test_that("not_null works", { + expect_true(not_null(1)) + expect_false(not_null(NULL)) +}) + +test_that("not_na works", { + expect_true(not_na(1)) + expect_false(not_na(NA)) +}) + +test_that("drop_nulls works", { + expect_equal( + drop_nulls( + list(x = NULL, y = 2) + ), + list(y = 2) + ) +}) + +test_that("%||% works", { + expect_equal( + NULL %||% 1, + 1 + ) + expect_equal( + 2 %||% 1, + 2 + ) +}) + +test_that("%|NA|% works", { + expect_equal( + NA %|NA|% 1, + 1 + ) + expect_equal( + 2 %|NA|% 1, + 2 + ) +}) + +test_that("rv and rvtl work", { + expect_true( + inherits(rv, "function") + ) + expect_true( + inherits(rvtl, "function") + ) +}) diff --git a/tests/testthat/test-golem_utils_ui.R b/tests/testthat/test-golem_utils_ui.R new file mode 100644 index 0000000..a33935c --- /dev/null +++ b/tests/testthat/test-golem_utils_ui.R @@ -0,0 +1,177 @@ +test_that("Test with_red_star works", { + expect_s3_class(with_red_star("golem"), "shiny.tag") + expect_equal( + as.character(with_red_star("Enter your name here")), + 'Enter your name here*' + ) +}) + +test_that("Test list_to_li works", { + expect_s3_class(list_to_li(c("a", "b")), "shiny.tag.list") + expect_equal( + as.character(list_to_li(c("a", "b"))), + "
  • a
  • \n
  • b
  • " + ) + expect_equal( + as.character(list_to_li(c("a", "b"), class = "my_li")), + '
  • a
  • \n
  • b
  • ' + ) +}) + +test_that("Test list_to_p works", { + expect_s3_class( + list_to_p(c( + "This is the first paragraph", + "this is the second paragraph" + )), + "shiny.tag.list" + ) + expect_equal( + as.character( + list_to_p(c( + "This is the first paragraph", + "this is the second paragraph" + )) + ), + "

    This is the first paragraph

    \n

    this is the second paragraph

    " + ) + expect_equal( + as.character( + list_to_p( + c( + "This is the first paragraph", + "this is the second paragraph" + ), + class = "my_li" + ) + ), + '

    This is the first paragraph

    \n

    this is the second paragraph

    ' + ) +}) + +test_that("Test named_to_li works", { + expect_s3_class(named_to_li(list(a = "a", b = "b")), "shiny.tag.list") + expect_equal( + as.character(named_to_li(list(a = "a", b = "b"))), + "
  • a: a
  • \n
  • b: b
  • " + ) + expect_equal( + as.character(named_to_li(list(a = "a", b = "b"), class = "mylist")), + '
  • a: a
  • \n
  • b: b
  • ' + ) +}) + +test_that("Test tagRemoveAttributes works", { + a_with_tag <- shiny::tags$p(src = "plop", "pouet") + expect_s3_class(a_with_tag, "shiny.tag") + expect_equal( + as.character(a_with_tag), + '

    pouet

    ' + ) + + a_without_tag <- tagRemoveAttributes(a_with_tag, "src") + expect_s3_class(a_without_tag, "shiny.tag") + expect_equal( + as.character(a_without_tag), + "

    pouet

    " + ) +}) + +test_that("Test undisplay works", { + a <- shiny::tags$p(src = "plop", "pouet") + expect_s3_class(a, "shiny.tag") + expect_equal( + as.character(a), + '

    pouet

    ' + ) + a_undisplay <- undisplay(a) + expect_s3_class(a_undisplay, "shiny.tag") + expect_equal( + as.character(a_undisplay), + '

    pouet

    ' + ) + + b <- shiny::actionButton("go_filter", "go") + expect_s3_class(b, "shiny.tag") + expect_equal( + as.character(b), + '' + ) + b_undisplay <- undisplay(b) + expect_s3_class(b, "shiny.tag") + expect_equal( + as.character(b_undisplay), + '' + ) +}) + +test_that("Test display works", { + a_undisplay <- shiny::tags$p(src = "plop", "pouet", style = "display: none;") + expect_s3_class(a_undisplay, "shiny.tag") + expect_equal( + as.character(a_undisplay), + '

    pouet

    ' + ) + a_display <- display(a_undisplay) + expect_s3_class(a_display, "shiny.tag") + expect_equal( + as.character(a_display), + '

    pouet

    ' + ) +}) + +test_that("Test jq_hide works", { + expect_s3_class(jq_hide("golem"), "shiny.tag") + expect_equal( + as.character(jq_hide("golem")), + "" + ) +}) + +test_that("Test rep_br works", { + expect_s3_class(rep_br(5), "html") + expect_equal( + as.character(rep_br(5)), + "




    " + ) +}) + +test_that("Test enurl works", { + expect_s3_class(enurl("https://www.thinkr.fr", "ThinkR"), "shiny.tag") + expect_equal( + as.character(enurl("https://www.thinkr.fr", "ThinkR")), + 'ThinkR' + ) +}) + +test_that("Test columns wrappers works", { + expect_s3_class(col_12(), "shiny.tag") + expect_s3_class(col_10(), "shiny.tag") + expect_s3_class(col_8(), "shiny.tag") + expect_s3_class(col_6(), "shiny.tag") + expect_s3_class(col_4(), "shiny.tag") + expect_s3_class(col_3(), "shiny.tag") + expect_s3_class(col_2(), "shiny.tag") + expect_s3_class(col_1(), "shiny.tag") + + expect_equal(as.character(col_12()), '
    ') + expect_equal(as.character(col_10()), '
    ') + expect_equal(as.character(col_8()), '
    ') + expect_equal(as.character(col_6()), '
    ') + expect_equal(as.character(col_4()), '
    ') + expect_equal(as.character(col_3()), '
    ') + expect_equal(as.character(col_2()), '
    ') + expect_equal(as.character(col_1()), '
    ') +}) + +test_that("Test make_action_button works", { + button <- make_action_button( + a(href = "#", "My super link", style = "color: lightblue;"), + inputId = "mylink" + ) + expect_s3_class(button, "shiny.tag") + expect_equal( + as.character(button), + 'My super link' + ) +}) diff --git a/vignettes/.gitignore b/vignettes/.gitignore new file mode 100644 index 0000000..097b241 --- /dev/null +++ b/vignettes/.gitignore @@ -0,0 +1,2 @@ +*.html +*.R diff --git a/vignettes/rshinycloudrun.Rmd b/vignettes/rshinycloudrun.Rmd new file mode 100644 index 0000000..d413d45 --- /dev/null +++ b/vignettes/rshinycloudrun.Rmd @@ -0,0 +1,19 @@ +--- +title: "rshinycloudrun" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{rshinycloudrun} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(rshinycloudrun) +``` From e4d907891b697b146f7dfe033bb008f85ca6d009 Mon Sep 17 00:00:00 2001 From: Jimmy Briggs Date: Fri, 21 Jun 2024 15:44:35 -0400 Subject: [PATCH 2/2] feat: add deployment and docker --- .Rbuildignore | 3 + .dockerignore | 7 + Dockerfile | 20 ++ build/.gitignore | 8 + build/Dockerfile | 8 + build/Dockerfile_base | 8 + build/renv.lock.prod | 554 ++++++++++++++++++++++++++++++++++++++++++ dev/deployment.R | 46 ++++ dev/run_local.R | 16 ++ 9 files changed, 670 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 build/.gitignore create mode 100644 build/Dockerfile create mode 100644 build/Dockerfile_base create mode 100644 build/renv.lock.prod create mode 100644 dev/deployment.R create mode 100644 dev/run_local.R diff --git a/.Rbuildignore b/.Rbuildignore index 9e3b6db..f916c11 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,3 +17,6 @@ $run_dev.* ^\.Renviron$ ^\.Rprofile$ ^config\.yml$ +^build$ +^Dockerfile$ +^\.dockerignore$ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f52ee68 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.RData +.Rhistory +.git +.gitignore +manifest.json +rsconnect/ +.Rproj.user diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4f1d45c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM rocker/verse:4.4.1 +RUN apt-get update && apt-get install -y && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /usr/local/lib/R/etc/ /usr/lib/R/etc/ +RUN echo "options(repos = c(CRAN = 'https://cran.rstudio.com/'), download.file.method = 'libcurl', Ncpus = 4)" | tee /usr/local/lib/R/etc/Rprofile.site | tee /usr/lib/R/etc/Rprofile.site +RUN R -e 'install.packages("remotes")' +RUN Rscript -e 'remotes::install_version("pkgload",upgrade="never", version = "1.3.4")' +RUN Rscript -e 'remotes::install_version("knitr",upgrade="never", version = "1.47")' +RUN Rscript -e 'remotes::install_version("shiny",upgrade="never", version = "1.8.1.1")' +RUN Rscript -e 'remotes::install_version("config",upgrade="never", version = "0.3.2")' +RUN Rscript -e 'remotes::install_version("testthat",upgrade="never", version = "3.2.1.1")' +RUN Rscript -e 'remotes::install_version("spelling",upgrade="never", version = "2.3.0")' +RUN Rscript -e 'remotes::install_version("rmarkdown",upgrade="never", version = "2.27")' +RUN Rscript -e 'remotes::install_version("golem",upgrade="never", version = "0.4.1")' +RUN mkdir /build_zone +ADD . /build_zone +WORKDIR /build_zone +RUN R -e 'remotes::install_local(upgrade="never")' +RUN rm -rf /build_zone +EXPOSE 80 +CMD R -e "options('shiny.port'=80,shiny.host='0.0.0.0');library(rshinycloudrun);rshinycloudrun::run_app()" diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..b214053 --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,8 @@ +* +!.gitignore +!Dockerfile +!Dockerfile_base +!README.md +!renv.lock.prod +!renv.lock +!compose.yml diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..927a426 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,8 @@ +FROM shinyexample_base +COPY renv.lock.prod renv.lock +RUN R -e 'renv::restore()' +COPY shinyexample_*.tar.gz /app.tar.gz +RUN R -e 'remotes::install_local("/app.tar.gz",upgrade="never")' +RUN rm /app.tar.gz +EXPOSE 80 +CMD R -e "options('shiny.port'=80,shiny.host='0.0.0.0');library(shinyexample);shinyexample::run_app()" diff --git a/build/Dockerfile_base b/build/Dockerfile_base new file mode 100644 index 0000000..3865690 --- /dev/null +++ b/build/Dockerfile_base @@ -0,0 +1,8 @@ +FROM rocker/r-ver:latest:4.4.1 +RUN apt-get update -y && apt-get install -y make zlib1g-dev git && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /usr/local/lib/R/etc/ /usr/lib/R/etc/ +RUN echo "options(renv.config.pak.enabled = FALSE, repos = c(CRAN = 'https://cran.rstudio.com/'), download.file.method = 'libcurl', Ncpus = 4)" | tee /usr/local/lib/R/etc/Rprofile.site | tee /usr/lib/R/etc/Rprofile.site +RUN R -e 'install.packages("remotes")' +RUN R -e 'remotes::install_version("renv", version = "1.0.3")' +COPY renv.lock.prod renv.lock +RUN R -e 'renv::restore()' diff --git a/build/renv.lock.prod b/build/renv.lock.prod new file mode 100644 index 0000000..502f6e4 --- /dev/null +++ b/build/renv.lock.prod @@ -0,0 +1,554 @@ +{ + "R": { + "Version": "4.4.1", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cran.rstudio.com" + } + ] + }, + "Packages": { + "R6": { + "Package": "R6", + "Version": "2.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "470851b6d5d0ac559e9d01bb352b4021" + }, + "Rcpp": { + "Package": "Rcpp", + "Version": "1.0.12", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "methods", + "utils" + ], + "Hash": "5ea2700d21e038ace58269ecdbeb9ec0" + }, + "attempt": { + "Package": "attempt", + "Version": "0.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "rlang" + ], + "Hash": "d7421bb5dfeb2676b9e4a5a60c2fcfd2" + }, + "base64enc": { + "Package": "base64enc", + "Version": "0.1-3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "543776ae6848fde2f48ff3816d0628bc" + }, + "bslib": { + "Package": "bslib", + "Version": "0.7.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "base64enc", + "cachem", + "fastmap", + "grDevices", + "htmltools", + "jquerylib", + "jsonlite", + "lifecycle", + "memoise", + "mime", + "rlang", + "sass" + ], + "Hash": "8644cc53f43828f19133548195d7e59e" + }, + "cachem": { + "Package": "cachem", + "Version": "1.1.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "fastmap", + "rlang" + ], + "Hash": "cd9a672193789068eb5a2aad65a0dedf" + }, + "callr": { + "Package": "callr", + "Version": "3.7.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "processx", + "utils" + ], + "Hash": "d7e13f49c19103ece9e58ad2d83a7354" + }, + "cli": { + "Package": "cli", + "Version": "3.6.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "1216ac65ac55ec0058a6f75d7ca0fd52" + }, + "commonmark": { + "Package": "commonmark", + "Version": "1.9.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5d8225445acb167abf7797de48b2ee3c" + }, + "config": { + "Package": "config", + "Version": "0.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "yaml" + ], + "Hash": "8b7222e9d9eb5178eea545c0c4d33fc2" + }, + "crayon": { + "Package": "crayon", + "Version": "1.5.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "grDevices", + "methods", + "utils" + ], + "Hash": "e8a1e41acf02548751f45c718d55aa6a" + }, + "desc": { + "Package": "desc", + "Version": "1.4.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "cli", + "utils" + ], + "Hash": "99b79fcbd6c4d1ce087f5c5c758b384f" + }, + "digest": { + "Package": "digest", + "Version": "0.6.35", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "698ece7ba5a4fa4559e3d537e7ec3d31" + }, + "fastmap": { + "Package": "fastmap", + "Version": "1.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "aa5e1cd11c2d15497494c5292d7ffcc8" + }, + "fontawesome": { + "Package": "fontawesome", + "Version": "0.5.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "htmltools", + "rlang" + ], + "Hash": "c2efdd5f0bcd1ea861c2d4e2a883a67d" + }, + "fs": { + "Package": "fs", + "Version": "1.6.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "15aeb8c27f5ea5161f9f6a641fafd93a" + }, + "glue": { + "Package": "glue", + "Version": "1.7.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "e0b3a53876554bd45879e596cdb10a52" + }, + "golem": { + "Package": "golem", + "Version": "0.4.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "attempt", + "config", + "here", + "htmltools", + "rlang", + "shiny", + "utils", + "yaml" + ], + "Hash": "dc12172dc35c6c80e18b430dc546fc24" + }, + "here": { + "Package": "here", + "Version": "1.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "rprojroot" + ], + "Hash": "24b224366f9c2e7534d2344d10d59211" + }, + "htmltools": { + "Package": "htmltools", + "Version": "0.5.8.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "base64enc", + "digest", + "fastmap", + "grDevices", + "rlang", + "utils" + ], + "Hash": "81d371a9cc60640e74e4ab6ac46dcedc" + }, + "httpuv": { + "Package": "httpuv", + "Version": "1.6.15", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "Rcpp", + "later", + "promises", + "utils" + ], + "Hash": "d55aa087c47a63ead0f6fc10f8fa1ee0" + }, + "jquerylib": { + "Package": "jquerylib", + "Version": "0.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "htmltools" + ], + "Hash": "5aab57a3bd297eee1c1d862735972182" + }, + "jsonlite": { + "Package": "jsonlite", + "Version": "1.8.8", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "methods" + ], + "Hash": "e1b9c55281c5adc4dd113652d9e26768" + }, + "later": { + "Package": "later", + "Version": "1.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Rcpp", + "rlang" + ], + "Hash": "a3e051d405326b8b0012377434c62b37" + }, + "lifecycle": { + "Package": "lifecycle", + "Version": "1.0.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "glue", + "rlang" + ], + "Hash": "b8552d117e1b808b09a832f589b79035" + }, + "magrittr": { + "Package": "magrittr", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "7ce2733a9826b3aeb1775d56fd305472" + }, + "memoise": { + "Package": "memoise", + "Version": "2.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "cachem", + "rlang" + ], + "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c" + }, + "mime": { + "Package": "mime", + "Version": "0.12", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "tools" + ], + "Hash": "18e9c28c1d3ca1560ce30658b22ce104" + }, + "pkgbuild": { + "Package": "pkgbuild", + "Version": "1.4.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "callr", + "cli", + "desc", + "processx" + ], + "Hash": "a29e8e134a460a01e0ca67a4763c595b" + }, + "pkgload": { + "Package": "pkgload", + "Version": "1.3.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "crayon", + "desc", + "fs", + "glue", + "methods", + "pkgbuild", + "rlang", + "rprojroot", + "utils", + "withr" + ], + "Hash": "876c618df5ae610be84356d5d7a5d124" + }, + "processx": { + "Package": "processx", + "Version": "3.8.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "ps", + "utils" + ], + "Hash": "0c90a7d71988856bad2a2a45dd871bb9" + }, + "promises": { + "Package": "promises", + "Version": "1.3.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R6", + "Rcpp", + "fastmap", + "later", + "magrittr", + "rlang", + "stats" + ], + "Hash": "434cd5388a3979e74be5c219bcd6e77d" + }, + "ps": { + "Package": "ps", + "Version": "1.7.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "dd2b9319ee0656c8acf45c7f40c59de7" + }, + "rappdirs": { + "Package": "rappdirs", + "Version": "0.3.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "5e3c5dc0b071b21fa128676560dbe94d" + }, + "remotes": { + "Package": "remotes", + "Version": "2.5.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods", + "stats", + "tools", + "utils" + ], + "Hash": "3ee025083e66f18db6cf27b56e23e141" + }, + "rlang": { + "Package": "rlang", + "Version": "1.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1" + }, + "rprojroot": { + "Package": "rprojroot", + "Version": "2.0.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "4c8415e0ec1e29f3f4f6fc108bef0144" + }, + "sass": { + "Package": "sass", + "Version": "0.4.9.9000", + "Source": "GitHub", + "RemoteType": "github", + "RemoteHost": "api.github.com", + "RemoteRepo": "sass", + "RemoteUsername": "rstudio", + "RemotePkgRef": "rstudio/sass", + "RemoteRef": "HEAD", + "RemoteSha": "9228fcf39deecfe32b7cb90ed40690338a18acba", + "Requirements": [ + "R6", + "fs", + "htmltools", + "rappdirs", + "rlang" + ], + "Hash": "3eea8b7c0d780b0d04fff9eb99dc7e6d" + }, + "shiny": { + "Package": "shiny", + "Version": "1.8.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "bslib", + "cachem", + "commonmark", + "crayon", + "fastmap", + "fontawesome", + "glue", + "grDevices", + "htmltools", + "httpuv", + "jsonlite", + "later", + "lifecycle", + "methods", + "mime", + "promises", + "rlang", + "sourcetools", + "tools", + "utils", + "withr", + "xtable" + ], + "Hash": "54b26646816af9960a4c64d8ceec75d6" + }, + "sourcetools": { + "Package": "sourcetools", + "Version": "0.1.7-1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "5f5a7629f956619d519205ec475fe647" + }, + "withr": { + "Package": "withr", + "Version": "3.0.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics" + ], + "Hash": "d31b6c62c10dcf11ec530ca6b0dd5d35" + }, + "xtable": { + "Package": "xtable", + "Version": "1.8-4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "stats", + "utils" + ], + "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2" + }, + "yaml": { + "Package": "yaml", + "Version": "2.3.8", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "29240487a071f535f5e5d5a323b7afbd" + } + } +} diff --git a/dev/deployment.R b/dev/deployment.R new file mode 100644 index 0000000..eaf0502 --- /dev/null +++ b/dev/deployment.R @@ -0,0 +1,46 @@ +require(googleAuthR) +require(googleCloudRunner) +require(devtools) +require(golem) +require(dockerfiler) +require(config) + +# Check & Build ----------------------------------------------------------- +devtools::check() +devtools::build() + +# Dockerfile -------------------------------------------------------------- +golem::add_dockerfile() + +# Deployment ------------------------------------------------------------- +library(googleCloudRunner) +options(cli.ignore_unknown_rstudio_theme = TRUE) + +gcp_config <- config::get("gcp", file = "inst/config.yml") + +Sys.setenv( + "GCE_DEFAULT_PROJECT_ID" = gcp_config$project_id, + "GAR_CLIENT_JSON" = gcp_config$client_json, + "GCE_AUTH_FILE" = gcp_config$auth_file, + "GCS_DEFAULT_BUCKET" = gcp_config$bucket, + "CR_REGION" = gcp_config$region, + "CR_BUILD_EMAIL" = gcp_config$build_email +) + +googleCloudRunner::cr_deploy_docker( + local = getwd(), + image_name = pkgload::pkg_name(), + remote = pkgload::pkg_name(), + tag = c("latest", "$BUILD_ID"), + timeout = 600L, + bucket = gcp_config$bucket, + projectId = gcp_config$project_id, + launch_browser = TRUE, + kaniko_cache = TRUE, + predefinedAcl = "bucketOwnerFullControl" +) + +# golem::add_dockerfile_with_renv( +# output_dir = fs::path(getwd(), "build"), +# from = "rocker/r-ver:latest" +# ) diff --git a/dev/run_local.R b/dev/run_local.R new file mode 100644 index 0000000..b0472c6 --- /dev/null +++ b/dev/run_local.R @@ -0,0 +1,16 @@ +# Set options here +options(shiny.app.prod = FALSE) # TRUE = production mode, FALSE = development mode + +# Comment this if you don't want the app to be served on a random port +options(shiny.port = httpuv::randomPort()) + +# Detach all loaded packages and clean your environment +detach_all_attached() +# rm(list=ls(all.names = TRUE)) + +# Document and reload your package +devtools::document() +pkgload::load_all() + +# Run the application +run_app()