diff --git a/NAMESPACE b/NAMESPACE index 5ec57a978e..6b0e0f583e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,6 +28,7 @@ export(trigger_results) export(trigger_tests) export(use_manual_app) export(use_shinyjster) +export(view_image_diffs) export(view_test_images) export(write_sysinfo) importFrom(stats,setNames) diff --git a/R/view-image-diffs.R b/R/view-image-diffs.R new file mode 100644 index 0000000000..48f57b2321 --- /dev/null +++ b/R/view-image-diffs.R @@ -0,0 +1,151 @@ +# TODO +# - Make method to check all images in git status? +# * No. This requires too many permissions. Skipping for now. + + +setup_gha_image_diffs <- function( + ..., + min_diff = 3, + repo_dir = ".", + # location to save images + out_dir = tempfile(), + sha = git_sha(repo_dir) + # # location of repo for comparisons + # vanilla_repo_dir = "../z-shinycoreci.nosync" +) { + ellipsis::check_dots_empty() + repo_dir <- normalizePath(repo_dir) + # vanilla_repo_dir <- normalizePath(vanilla_repo_dir) + force(sha) + withr::local_dir(repo_dir) + + out_dir <- "inst/img-diff-app" + dir.create(out_dir, recursive = TRUE, showWarnings = FALSE) + + + proc_locs <- as.list(Sys.which(c("git", "gm"))) + if (proc_locs$git == "") stop("Please install git") + if (proc_locs$gm == "") stop("Please install graphicsmagick via `brew install graphicsmagick` or `apt-get install graphicsmagick`") + + all_files <- system("git diff --name-only", intern = TRUE) + png_files <- all_files[fs::path_ext(all_files) == "png" & grepl("^inst/apps/", all_files)] + + tmp_img_path <- file.path(out_dir, "tmp_original_image.png") + # Extract original image so that two files can be compared + get_original_file <- function(png_file) { + system(paste0("git show HEAD:", png_file, " > ", tmp_img_path)) + tmp_img_path + } + on.exit(if (file.exists(tmp_img_path)) unlink(tmp_img_path)) + + message("\nFinding images...") + p <- progress::progress_bar$new( + format = "[:current/:total;:eta] :name", + total = length(png_files), + show_after = 0, + clear = FALSE + ) + diffs <- lapply(png_files, function(png_file) { + p$tick(tokens = list(name = png_file)) + # Compare images + shinytest2::screenshot_max_difference(png_file, get_original_file(png_file)) + }) + # diffs <- as.list(seq_along(png_files)) + + diffs <- setNames(diffs, png_files) + # hist(unlist(diffs)) + + diff_folder <- file.path(out_dir, "image_diffs") + if (dir.exists(diff_folder)) unlink(diff_folder, recursive = TRUE) + dir.create(diff_folder, showWarnings = FALSE, recursive = TRUE) + + bad_pngs <- names(diffs[diffs > min_diff]) + bad_diff_count <- unname(unlist(diffs[bad_pngs])) + + message("\nDiff images") + p <- progress::progress_bar$new( + format = "[:current;:total;:eta] :name", + total = length(bad_pngs), + show_after = 0, + clear = FALSE + ) + img_dt <- + lapply(bad_pngs, function(bad_png) { + p$tick(tokens = list(name = bad_png)) + san_path <- fs::path_sanitize(bad_png, "_") + new_png <- fs::file_copy( + bad_png, + file.path(diff_folder, fs::path_ext_set(san_path, ".new.png")), + overwrite = TRUE + ) + orig_png <- fs::file_copy( + get_original_file(bad_png), + file.path(diff_folder, fs::path_ext_set(san_path, ".old.png")), + overwrite = TRUE + ) + diff_png <- file.path(diff_folder, san_path) + system(paste0( + "gm compare ", new_png, " ", orig_png, " -highlight-style assign -file ", diff_png + )) + + dplyr::tibble( + diff_png = diff_png, + orig_png = orig_png, + new_png = new_png + ) + }) %>% + dplyr::bind_rows() + + png_dt <- dplyr::tibble( + file = bad_pngs, + diff_png = img_dt$diff_png, + orig_png = img_dt$orig_png, + new_png = img_dt$new_png, + diff_count = bad_diff_count + ) %>% + dplyr::mutate( + # inst/apps/041-dynamic-ui/tests/testthat/_snaps/linux-4.0/mytest/022.png + base = gsub("^inst/apps/", "", file), + app = gsub("/.*$", "", base), + snap = file.path(basename(dirname(base)), basename(base)), + platform_combo = basename(dirname(dirname(base))), + anchor = fs::path_sanitize(file, "_"), + ) %>% + dplyr::select(-base) %>% + tidyr::separate_wider_delim(cols = "platform_combo", names = c("platform", "Rver"), delim = "-", cols_remove = FALSE) + + data_path <- file.path(out_dir, "data.json") + writeLines( + jsonlite::serializeJSON(png_dt, pretty = TRUE), + data_path, + useBytes = TRUE + ) + + data_path +} + + + +#' @export +view_image_diffs <- function( + ..., + run_fix_snaps = TRUE, + run_setup = TRUE, + open_viewer = TRUE, + repo_dir = ".", + sha = git_sha(repo_dir) +) { + ellipsis::check_dots_empty() + if (run_fix_snaps) { + fix_snaps(ask_apps = FALSE, ask_branches = FALSE, repo_dir = repo_dir, sha = sha) + } + if (run_setup) { + setup_gha_image_diffs(repo_dir = repo_dir, sha = sha) + } + + withr::local_dir(repo_dir) + rmarkdown::render("inst/img-diff-app/diff.Rmd") + if (open_viewer) { + system("open inst/img-diff-app/diff.html") + } +} diff --git a/R/zzz.R b/R/zzz.R index 37650e30b8..0d9d80f6ec 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,5 +1,7 @@ +`%>%` <- NULL .onLoad <- function(...) { + `%>%` <<- dplyr::`%>%` apps_on_load() } diff --git a/README.md b/README.md index 2cb36c37fc..32b8aeaa37 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ - ## Installation @@ -27,36 +26,36 @@ pak::pkg_install("rstudio/shinycoreci") These GitHub packages will be installed to make sure the latest package development is working as expected: - - [r-lib/cachem](http://github.com/r-lib/cachem) - - [r-lib/fastmap](http://github.com/r-lib/fastmap) - - [r-lib/later](http://github.com/r-lib/later) - - [rstudio/bslib](http://github.com/rstudio/bslib) - - [rstudio/bsicons](http://github.com/rstudio/bsicons) - - [ramnathv/htmlwidgets](http://github.com/ramnathv/htmlwidgets) - - [rstudio/crosstalk](http://github.com/rstudio/crosstalk) - - [rstudio/gt](http://github.com/rstudio/gt) - - [rstudio/DT](http://github.com/rstudio/DT) - - [rstudio/dygraphs](http://github.com/rstudio/dygraphs) - - [rstudio/flexdashboard](http://github.com/rstudio/flexdashboard) - - [rstudio/fontawesome](http://github.com/rstudio/fontawesome) - - [rstudio/htmltools](http://github.com/rstudio/htmltools) - - [rstudio/httpuv](http://github.com/rstudio/httpuv) - - [rstudio/leaflet](http://github.com/rstudio/leaflet) - - [rstudio/pool](http://github.com/rstudio/pool) - - [rstudio/promises](http://github.com/rstudio/promises) - - [rstudio/reactlog](http://github.com/rstudio/reactlog) - - [rstudio/sass](http://github.com/rstudio/sass) - - [rstudio/shiny](http://github.com/rstudio/shiny) - - [rstudio/shinymeta](http://github.com/rstudio/shinymeta) - - [rstudio/shinytest](http://github.com/rstudio/shinytest) - - [rstudio/shinytest2](http://github.com/rstudio/shinytest2) - - [rstudio/shinythemes](http://github.com/rstudio/shinythemes) - - [rstudio/shinyvalidate](http://github.com/rstudio/shinyvalidate) - - [rstudio/thematic](http://github.com/rstudio/thematic) - - [rstudio/webdriver](http://github.com/rstudio/webdriver) - - [rstudio/websocket](http://github.com/rstudio/websocket) - - [ropensci/plotly](http://github.com/ropensci/plotly) - - [schloerke/shinyjster](http://github.com/schloerke/shinyjster) +- [r-lib/cachem](http://github.com/r-lib/cachem) +- [r-lib/fastmap](http://github.com/r-lib/fastmap) +- [r-lib/later](http://github.com/r-lib/later) +- [rstudio/bslib](http://github.com/rstudio/bslib) +- [rstudio/bsicons](http://github.com/rstudio/bsicons) +- [ramnathv/htmlwidgets](http://github.com/ramnathv/htmlwidgets) +- [rstudio/crosstalk](http://github.com/rstudio/crosstalk) +- [rstudio/gt](http://github.com/rstudio/gt) +- [rstudio/DT](http://github.com/rstudio/DT) +- [rstudio/dygraphs](http://github.com/rstudio/dygraphs) +- [rstudio/flexdashboard](http://github.com/rstudio/flexdashboard) +- [rstudio/fontawesome](http://github.com/rstudio/fontawesome) +- [rstudio/htmltools](http://github.com/rstudio/htmltools) +- [rstudio/httpuv](http://github.com/rstudio/httpuv) +- [rstudio/leaflet](http://github.com/rstudio/leaflet) +- [rstudio/pool](http://github.com/rstudio/pool) +- [rstudio/promises](http://github.com/rstudio/promises) +- [rstudio/reactlog](http://github.com/rstudio/reactlog) +- [rstudio/sass](http://github.com/rstudio/sass) +- [rstudio/shiny](http://github.com/rstudio/shiny) +- [rstudio/shinymeta](http://github.com/rstudio/shinymeta) +- [rstudio/shinytest](http://github.com/rstudio/shinytest) +- [rstudio/shinytest2](http://github.com/rstudio/shinytest2) +- [rstudio/shinythemes](http://github.com/rstudio/shinythemes) +- [rstudio/shinyvalidate](http://github.com/rstudio/shinyvalidate) +- [rstudio/thematic](http://github.com/rstudio/thematic) +- [rstudio/webdriver](http://github.com/rstudio/webdriver) +- [rstudio/websocket](http://github.com/rstudio/websocket) +- [ropensci/plotly](http://github.com/ropensci/plotly) +- [schloerke/shinyjster](http://github.com/schloerke/shinyjster) Tools for manual and automated testing of shiny apps. @@ -66,17 +65,17 @@ First, install the `{shinycoreci}` repo via {pak} (from instructions above). Bef Commands used to test in different situations: - - [RStudio IDE](https://rstudio.com/products/rstudio/download/#download) - `shinycoreci::test_in_ide()` - - [RStudio Cloud](http://rstudio.cloud) - `shinycoreci::test_in_ide()` - - [RStudio Server Pro](https://colorado.rstudio.com) - `shinycoreci::test_in_ide()` - - R Terminal / R GUI - `shinycoreci::test_in_browser()` - - (Any) Web Browser - `shinycoreci::test_in_browser()` - - [shinyapps.io](http://shinyapps.io) - `shinycoreci::test_in_shinyappsio()` - - [RStudio Connect](http://beta.rstudioconnect.com) - `shinycoreci::test_in_connect()` - - SSO - `shinycoreci::test_in_sso(release = "focal")` - \> Requires `Docker` application to be running - - SSP - `shinycoreci::test_in_ssp(release = "centos7")` - \> Requires `Docker` application to be running +- [RStudio IDE](https://rstudio.com/products/rstudio/download/#download) - `shinycoreci::test_in_ide()` +- [RStudio Cloud](http://rstudio.cloud) - `shinycoreci::test_in_ide()` +- [RStudio Server Pro](https://colorado.rstudio.com) - `shinycoreci::test_in_ide()` +- R Terminal / R GUI - `shinycoreci::test_in_browser()` +- (Any) Web Browser - `shinycoreci::test_in_browser()` +- [shinyapps.io](http://shinyapps.io) - `shinycoreci::test_in_shinyappsio()` +- [RStudio Connect](http://beta.rstudioconnect.com) - `shinycoreci::test_in_connect()` +- SSO - `shinycoreci::test_in_sso(release = "focal")` + \> Requires `Docker` application to be running +- SSP - `shinycoreci::test_in_ssp(release = "centos7")` + \> Requires `Docker` application to be running All testing functions may be run from within the IDE (except for R Terminal / R GUI). @@ -121,10 +120,10 @@ When Windows virtual images update on GitHub Actions, the graphics device may be When contributing a testing app, try to do the following: - - Capture all the functionality with automated tests. - - Also, where possible, write “light-weight” tests (that is, try and avoid **shinytest2** `$expect_screenshot()` where possible since they are prone to false positive differences and thus have a maintenance cost). - - If the app does need manual testing, flag the testing app for manual testing with `shinycoreci::use_manual_app()`. - - Add a description to the app’s UI that makes it clear what the app is testing for. +- Capture all the functionality with automated tests. + - Also, where possible, write “light-weight” tests (that is, try and avoid **shinytest2** `$expect_screenshot()` where possible since they are prone to false positive differences and thus have a maintenance cost). + - If the app does need manual testing, flag the testing app for manual testing with `shinycoreci::use_manual_app()`. +- Add a description to the app’s UI that makes it clear what the app is testing for. Note that **shinycoreci** only supports `{testthat}` testing framework. Call `shinytest2::use_shinytest2(APP_DIR)` to use `{shinytest2}` and `{testthat}` @@ -134,9 +133,7 @@ Note that **shinycoreci** only supports `{testthat}` testing framework. Call `sh 3. **testthat**: primarily useful in combination with `shiny::testServer()` to test server-side reactive logic of the application. - - - - [See here](https://github.com/rstudio/shinycoreci-apps/blob/5691d1f4/apps/001-hello/tests/testthat/tests.R#L4) for an example. +- [See here](https://github.com/rstudio/shinycoreci-apps/blob/5691d1f4/apps/001-hello/tests/testthat/tests.R#L4) for an example. ## Pruning old git branches @@ -150,26 +147,26 @@ git fetch --prune This repo contains several [GitHub Actions](https://github.com/features/actions) workflows: - - [**Test apps:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-test-matrix.yml) Run all automated tests (via `shiny::runTests()`). If on `main` branch, test results will be saved to `_test_results` branch. - - [**Docker:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-docker.yml) Create all SSO and SSP docker images. Docker images are hosted on [`rstudio/shinycoreci` via GitHub Packages](https://github.com/rstudio/shinycoreci/pkgs/container/shinycoreci). - - [**Deploy**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deploy.yml): Deploy all testing apps to [shinyapps.io](shinyapps.io) and [beta.rstudioconnect.com](https://beta.rstudioconnect.com) - - [**Build results website**](https://github.com/rstudio/shinycoreci/actions/workflows/build-results.yml): Builds results for **Test apps** workflow. This workflow is called from within **Test apps**. After all tests have completed, this workflow will process all results in `_test_results` branch into static files, storing the results in `gh-pages` branch. Final website location of results: - - [**Package checks**](https://github.com/rstudio/shinycoreci/actions/workflows/R-CMD-check.yaml): There are three main tasks that this workflow achieves: - 1. Creates the `website` via `{pkgdown}` - 2. Performs `routine` procedures like making sure all documentation and README.md is up to date - 3. Performs `R CMD check` on `{shinycoreci}`, across macOS, Windows, and Ubuntu (multiple R versions). - - [**Update app deps**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deps.yml): Updates known dependencies of all Shiny applications in `./inst/apps`. - - [**Trim old branches**](https://github.com/rstudio/shinycoreci/actions/workflows/trim-old-branches.yml): The current data model of **Test apps** workflow is to create many `gha-**` branches containing the changes of each test run on `main`. `gha-**` branches that have been stale for more than a week are removed. +- [**Test apps:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-test-matrix.yml) Run all automated tests (via `shiny::runTests()`). If on `main` branch, test results will be saved to `_test_results` branch. +- [**Docker:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-docker.yml) Create all SSO and SSP docker images. Docker images are hosted on [`rstudio/shinycoreci` via GitHub Packages](https://github.com/rstudio/shinycoreci/pkgs/container/shinycoreci). +- [**Deploy**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deploy.yml): Deploy all testing apps to [shinyapps.io](shinyapps.io) and [beta.rstudioconnect.com](https://beta.rstudioconnect.com) +- [**Build results website**](https://github.com/rstudio/shinycoreci/actions/workflows/build-results.yml): Builds results for **Test apps** workflow. This workflow is called from within **Test apps**. After all tests have completed, this workflow will process all results in `_test_results` branch into static files, storing the results in `gh-pages` branch. Final website location of results: +- [**Package checks**](https://github.com/rstudio/shinycoreci/actions/workflows/R-CMD-check.yaml): There are three main tasks that this workflow achieves: + 1. Creates the `website` via `{pkgdown}` + 2. Performs `routine` procedures like making sure all documentation and README.md is up to date + 3. Performs `R CMD check` on `{shinycoreci}`, across macOS, Windows, and Ubuntu (multiple R versions). +- [**Update app deps**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deps.yml): Updates known dependencies of all Shiny applications in `./inst/apps`. +- [**Trim old branches**](https://github.com/rstudio/shinycoreci/actions/workflows/trim-old-branches.yml): The current data model of **Test apps** workflow is to create many `gha-**` branches containing the changes of each test run on `main`. `gha-**` branches that have been stale for more than a week are removed. ### Trigger There are a handful of methods that can be called to trigger the GHA actions. - - `shinycoreci::trigger_tests()`: Trigger the **Test apps** workflow. - - `shinycoreci::trigger_docker()`: Trigger the **Docker** workflow. - - `shinycoreci::trigger_deploy()`: Trigger the **Deploy** workflow. - - `shinycoreci::trigger_results()`: Trigger the **Build results website** workflow. - - `shinycoreci::trigger(event_type=)`: Sends a custom event to the GHA workflow. For example, this can be used to trigger **Trim old branches** with `shinycoreci::trigger("trim")`. +- `shinycoreci::trigger_tests()`: Trigger the **Test apps** workflow. +- `shinycoreci::trigger_docker()`: Trigger the **Docker** workflow. +- `shinycoreci::trigger_deploy()`: Trigger the **Deploy** workflow. +- `shinycoreci::trigger_results()`: Trigger the **Build results website** workflow. +- `shinycoreci::trigger(event_type=)`: Sends a custom event to the GHA workflow. For example, this can be used to trigger **Trim old branches** with `shinycoreci::trigger("trim")`. A triggered workflow will run without having to push to the repo. Anyone with repo write access can call this command. @@ -186,11 +183,11 @@ Example schedule where the workflow is run at 2am UTC Monday through Friday: Schedule of `rstudio/shinycoreci` workflows: - - 12am UTC, S-S: **Trim old branches**; \< 1 min - - 12am UTC, M-F: **Update app deps**; \< 5 mins - - 2am UTC, M-F: **Deploy apps**; \~ 2 hrs - - 3am UTC, M-F: **Docker**; \~ 1 hr - - 5am UTC, M-F: **Test apps** (Internally calls **Build results website**); \~ 4 hrs +- 12am UTC, S-S: **Trim old branches**; \< 1 min +- 12am UTC, M-F: **Update app deps**; \< 5 mins +- 2am UTC, M-F: **Deploy apps**; ~ 2 hrs +- 3am UTC, M-F: **Docker**; ~ 1 hr +- 5am UTC, M-F: **Test apps** (Internally calls **Build results website**); ~ 4 hrs ### `build-results.yml` @@ -198,29 +195,27 @@ Breakdown of what happens in the **Build results website** workflow: On completion of `apps-test-matrix.yml`… - - GHA will check out the latest `_test_results` branch into the local folder. - - GHA will check out the latest `gh-pages` branch into the `./_gh-pages` folder. - - GHA will install R and necessary package dependencies. - - Run `./build_site.R` - - Read the *modify times* of each file in `_test_results` and processing files - - Compare *modify times* to *modify times* of output files - - If any input file is newer than the output file, reprocess the documen - - If reprocessing, render `./render-results.Rmd` given proper subset of data - - Save output to \`./\_gh-pages/results/YEAR/MONTH/DAY/index.html - - Update `./_gh-pages/results/index.html` to redirect to the most recent results - - Within the `./_gh-pages` directory - - Add any files that have been altered - - Commit and push back any changes to the `gh-pages` website +- GHA will check out the latest `_test_results` branch into the local folder. +- GHA will check out the latest `gh-pages` branch into the `./_gh-pages` folder. +- GHA will install R and necessary package dependencies. +- Run `./build_site.R` + - Read the *modify times* of each file in `_test_results` and processing files + - Compare *modify times* to *modify times* of output files + - If any input file is newer than the output file, reprocess the documen + - If reprocessing, render `./render-results.Rmd` given proper subset of data + - Save output to \`./\_gh-pages/results/YEAR/MONTH/DAY/index.html + - Update `./_gh-pages/results/index.html` to redirect to the most recent results +- Within the `./_gh-pages` directory + - Add any files that have been altered + - Commit and push back any changes to the `gh-pages` website Final results are available at: # FAQ: - - If you run into an odd `{pak}` installation issue: - - Run `pak::cache_clean()` to clear the cache and try your original command again - - Installing on fresh linux? Run these commands before testing: - - +- If you run into an odd `{pak}` installation issue: + - Run `pak::cache_clean()` to clear the cache and try your original command again +- Installing on fresh linux? Run these commands before testing: ``` r pkgs <- c('base64enc', 'bslib', 'Cairo', 'clipr', 'curl', 'dbplyr', 'DiagrammeR', diff --git a/inst/img-diff-app/.gitignore b/inst/img-diff-app/.gitignore new file mode 100644 index 0000000000..3097b7ff5f --- /dev/null +++ b/inst/img-diff-app/.gitignore @@ -0,0 +1,4 @@ +image_diffs +tmp_original_image.png +data.json +diff.html diff --git a/inst/img-diff-app/app.R b/inst/img-diff-app/app.R new file mode 100644 index 0000000000..8bf9c330f8 --- /dev/null +++ b/inst/img-diff-app/app.R @@ -0,0 +1,87 @@ +library(bslib) +library(shiny) + +print(getwd()) +png_dt <- jsonlite::unserializeJSON(paste0(readLines("data.json"), collapse = "\n")) + +ui <- bslib::page_sidebar( + shiny::uiOutput("title"), + "Diff: ", shiny::verbatimTextOutput("diff_count"), + shiny::imageOutput("diff_image"), + sidebar = bslib::sidebar( + style="white-space: nowrap;", + Map( + seq_len(nrow(png_dt)), + png_dt$app, + png_dt$snap, + png_dt$platform_combo, + f = function(i, app, snap, platform_combo) { + shiny::actionLink( + paste0("link_", i), + # paste0(app, " ", snap, " on ", platform_combo) + paste0(app, "/", platform_combo, "/", snap) + ) + } + ) + ) +) + +server <- function(input, output, session) { + bad_row <- reactiveVal(1) + + lapply(seq_len(nrow(png_dt)), function(i) { + diff_image <- png_dt$diff_file[i] + + observe({ + req(input[[paste0("link_", i)]]) # Force reactivity + bad_row(i) + }) + }) + + output$title <- shiny::renderUI({ + req(bad_row()) + info <- as.list(png_dt[bad_row(), ]) + shiny::tagList( + shiny::h1( + shiny::a( + target="_blank", + href=paste0("https://github.com/rstudio/shinycoreci/tree/main/inst/apps/", info$app), + info$app + ) + ), + shiny::p( + "Snap: ", + shiny::a( + target="_blank", + href=paste0("https://github.com/rstudio/shinycoreci/tree/main/", info$file), + paste0(tail(strsplit(info$file, "/")[[1]], 3), collapse = "/") + ) + ), + shiny::p( + "App: ", + shiny::a( + target="_blank", + href=paste0("https://testing-apps.shinyapps.io/", info$app), + "shinyapps.io" + ) + ), + + shiny::p( + "Diff: ", + shiny::code(info$diff_count) + ), + ) + }) + + output$diff_image <- shiny::renderImage({ + req(bad_row()) + list( + src = file.path("image_diffs", basename(png_dt$diff_file[bad_row()])), + contentType = "image/png", + width = "100%", + style="border: 1px solid black;" + ) + }, deleteFile = FALSE) +} + +shiny::shinyApp(ui, server) diff --git a/inst/img-diff-app/diff.Rmd b/inst/img-diff-app/diff.Rmd new file mode 100644 index 0000000000..1ff76d4a66 --- /dev/null +++ b/inst/img-diff-app/diff.Rmd @@ -0,0 +1,234 @@ +--- +title: Image diffs +output: + html_document: + self_contained: true + style: style.css +--- + +# Latest shinytest2 snapshot diffs + +```{css, echo = FALSE} +/* barret */ +body .main-container { + max-width: 100%; + margin-left: 30px; + margin-right: 30px; +} +``` + + +```{r, include=FALSE} +# library(bslib) +library(shiny) + +png_dt <- jsonlite::unserializeJSON(paste0(readLines("data.json"), collapse = "\n")) +png_dt <- png_dt[order(png_dt$diff_count, decreasing = TRUE), ] +``` + + +```{r, echo = FALSE} +library(gt) +gt_dt <- png_dt +gt_dt$diff_img_w_anchor <- Map( + gt_dt$anchor, + gt_dt$diff_png, + f = function(anchor, diff_png) { + gt::html(as.character(shiny::a( + href=paste0("#", anchor), + shiny::img( + src = file.path("image_diffs", basename(diff_png)), + height = 100 + ) + ))) + } +) +gt_dt$app <- Map( + gt_dt$app, + gt_dt$anchor, + f = function(app, anchor) { + gt::html(as.character(shiny::a( + href=paste0("https://github.com/rstudio/shinycoreci/tree/main/inst/apps/", app), + app + ))) + } +) +gt_dt$snap <- Map( + gt_dt$snap, + gt_dt$anchor, + f = function(snap, anchor) { + gt::html(as.character(shiny::a( + href=paste0("#", anchor), + snap + ))) + } +) +gt_dt %>% + gt() %>% + # gt::tab_header( + # title = "Latest shinytest2 snapshot diffs", + # subtitle = "Ordered by number of diffs" + # ) %>% + gt::cols_label( + app = "App", + snap = "Snap", + platform_combo = "Platform", + diff_count = "Diff count", + diff_img_w_anchor = "Diff file" + ) %>% + gt::tab_spanner( + label = "App", + columns = c(app, snap, platform_combo) + ) %>% + gt::tab_spanner( + label = "Diff", + columns = c(diff_count, diff_img_w_anchor) + ) %>% + gt::tab_options( + table.width = "100%", + column_labels.font.size = 14, + column_labels.font.weight = "bold", + heading.title.font.size = 18, + heading.title.font.weight = "bold", + heading.subtitle.font.size = 16, + heading.subtitle.font.weight = "bold" + ) %>% + # gt::tab_style( + # style = gt::cell_text(weight = "bold"), + # locations = gt::cells_body( + # columns = c(app, snap, platform_combo, diff_count, diff_file) + # ) + # ) %>% + # gt::tab_style( + # style = gt::cell_text(weight = "normal"), + # locations = gt::cells_body( + # columns = c(diff_count, diff_file) + # ) + # ) %>% + # gt::tab_style( + # style = gt::cell_text(weight = "normal"), + # locations = gt::cells_body( + # columns = c(app, snap, platform_combo) + # ) + # ) %>% + # gt::tab_style( + # style = gt::cell_text(weight = "normal"), + # locations = gt::cells_body( + # columns = c(diff_count, diff_img_w_anchor) + # ) + # ) %>% + # text_transform( + # locations = cells_body(c(diff_file)), + # fn = function(x) { + # # loop over the elements of the column + # Map( + # x, + # f = function(.x) { + + # shiny::a( + # href = + # local_image( + # filename = file.path("image_diffs", basename(.x)), + # height = 100 + # ) + # ) + # } + # ) + # } + # ) %>% + gt::cols_hide( + columns = c(anchor, platform, Rver, file, diff_png, orig_png, new_png) + ) %>% + gt::cols_move_to_end( + columns = c(diff_count) + ) +``` + +```{r, results='asis', echo = FALSE} +shiny::tagList(Map( + seq_len(nrow(png_dt)), + png_dt$app, + png_dt$anchor, + png_dt$file, + png_dt$diff_count, + png_dt$diff_png, + png_dt$orig_png, + png_dt$new_png, + f = function(i, app, anchor, file, diff_count, diff_png, orig_png, new_png) { + shiny::tagList( + shiny::h1( + id= anchor, + shiny::a( + target="_blank", + href=paste0("https://github.com/rstudio/shinycoreci/tree/main/inst/apps/", app), + app + ), + shiny::code("/"), + shiny::a( + target="_blank", + href=paste0("https://github.com/rstudio/shinycoreci/tree/main/", file), + paste0(tail(strsplit(file, "/")[[1]], 3), collapse = "/") + ), + shiny::HTML(" — "), + shiny::code(diff_count) + ), + # shiny::p( + # "App: ", + # shiny::a( + # target="_blank", + # href=paste0("https://testing-apps.shinyapps.io/", app), + # "shinyapps.io" + # ) + # ), + # shiny::p( + # "Diff: ", + + # ), + shiny::div( + onclick="change_image(this);", + shiny::div("diff", style="position: absolute; bottom: 3px; right: 3px; color: grey;"), + shiny::img( + src = file.path("image_diffs", basename(diff_png)), + width = "100%", + style="border: 1px solid black;" + ) + ), + shiny::div( + onclick="change_image(this);", + shiny::div("old", style="position: absolute; bottom: 3px; right: 3px; color: grey;"), + shiny::img( + src = file.path("image_diffs", basename(orig_png)), + width = "100%", + style="border: 1px solid black;" + ) + ), + shiny::div( + onclick="change_image(this);", + shiny::div("new", style="position: absolute; bottom: 3px; right: 3px; color: grey;"), + shiny::img( + src = file.path("image_diffs", basename(new_png)), + width = "100%", + style="border: 1px solid black;" + ) + ) + ) + } +), +shiny::tags$script(shiny::HTML( + ' + function change_image(img) { + if (img.src.endsWidth(".new.png")) { + img.src = img.src.replace(".new.png", ".png"); + } else { + img.src = img.src.replace(".png", ".new.png"); + } + if (img.src.includes("diff")) { + img.src = img.src.replace("diff", "snap"); + } else { + img.src = img.src.replace("snap", "diff"); + } + } + ' +) ) +) +```