diff --git a/DESCRIPTION b/DESCRIPTION index 808cbb0..9fe8839 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: multiverse.internals Title: Internal Infrastructure for R-multiverse -Description: R-multiverse requires this internal internal infrastructure - package to automate contribution reviews and populate universes. -Version: 0.2.9 +Description: R-multiverse requires this internal infrastructure package to + automate contribution reviews and populate universes. +Version: 0.2.10 License: MIT + file LICENSE URL: https://github.com/r-multiverse/multiverse.internals BugReports: https://github.com/r-multiverse/multiverse.internals/issues @@ -26,7 +26,7 @@ Authors@R: c( role = "cph" )) Depends: - R (>= 3.5.0) + R (>= 3.6) Imports: gh, igraph, diff --git a/NAMESPACE b/NAMESPACE index caa34ec..aab9063 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,7 @@ export(record_issues) export(record_versions) export(review_pull_request) export(review_pull_requests) +export(staging_is_active) export(try_message) export(update_staging) importFrom(gh,gh) diff --git a/NEWS.md b/NEWS.md index 0976711..3707c97 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# multiverse.internals 0.2.10 + +* Implement freezing mechanism in `update_staging()`. +* Implement `staging_is_active()` to detect when the staging universe should be active. + # multiverse.internals 0.2.9 * Implement community/staging idea from (@jeroen). diff --git a/R/meta_checks.R b/R/meta_checks.R index 98307aa..20228e9 100644 --- a/R/meta_checks.R +++ b/R/meta_checks.R @@ -1,6 +1,6 @@ #' @title List metadata about R-universe package checks #' @export -#' @family list +#' @family meta #' @description List package checks results reported by the #' R-universe package API. #' @return A data frame with one row per package and columns with diff --git a/R/staging_is_active.R b/R/staging_is_active.R new file mode 100644 index 0000000..9d2a4c6 --- /dev/null +++ b/R/staging_is_active.R @@ -0,0 +1,37 @@ +#' @title Check if the stating universe is active. +#' @export +#' @family staging +#' @description Check if the stating universe is active. +#' @return `TRUE` if the staging universe is active, `FALSE` otherwise. +#' @param start Character vector of `"%m-%d"` dates that the +#' staging universe becomes active. Staging will then last for a full +#' calendar month. For example, if you supply a start date of `"01-15"`, +#' then the staging period will include all days from `"01-15"` +#' through `"02-14"` and not include `"02-15"`. +#' @param today Character string with today's date in `"%Y-%m-%d"` format or an +#' object convertible to POSIXlt format. +#' @examples +#' staging_is_active() +staging_is_active <- function( + start = c("01-15", "04-15", "07-15", "10-15"), + today = Sys.Date() +) { + today <- as.POSIXlt(today, tz = "UTC") + start <- strsplit(start, split = "-", fixed = TRUE) + start <- lapply(start, as.integer) + within <- lapply(start, within_staging, today = today) + any(as.logical(within)) +} + +within_staging <- function(start, today) { + month <- today$mon + 1L + day <- today$mday + if (start[1L] > 28L) { + stop( + "a staging start date cannot be later than day 28 of the given month.", + call. = FALSE + ) + } + (month == start[1L] && day >= start[2L]) || + (month == start[1L] + 1L && day < start[2L]) +} diff --git a/R/update_staging.R b/R/update_staging.R index 4b57cc6..38fb34d 100644 --- a/R/update_staging.R +++ b/R/update_staging.R @@ -1,5 +1,6 @@ #' @title Update staging #' @export +#' @family staging #' @description Update the staging universe. #' @details [update_staging()] controls how packages enter and leave #' the staging universe. It updates the staging `packages.json` @@ -32,42 +33,41 @@ update_staging <- function( repo_community = "https://community.r-multiverse.org", mock = NULL ) { - meta_community <- mock$community %||% meta_packages(repo_community) - packages <- promotions(path_community, meta_community) file_staging <- file.path(path_staging, "packages.json") file_community <- file.path(path_community, "packages.json") json_staging <- jsonlite::read_json(file_staging, simplifyVector = TRUE) json_community <- jsonlite::read_json(file_community, simplifyVector = TRUE) - index_promote <- json_community$package %in% packages - promote <- json_community[index_promote,, drop = FALSE] # nolint - meta_community <- meta_community[, c("package", "remotesha")] - promote <- merge(promote, meta_community, all.x = TRUE, all.y = FALSE) - promote$branch <- promote$remotesha - promote$remotesha <- NULL - replace <- !(json_staging$package %in% packages) - json_staging <- json_staging[replace,, drop = FALSE] # nolint - json_staging <- rbind(json_staging, promote) - json_staging <- json_staging[order(json_staging$package),, drop = FALSE ] # nolint - jsonlite::write_json(json_staging, file_staging, pretty = TRUE) - invisible() -} - -promotions <- function(path_community, meta_community) { - promotion_checks <- c( - "descriptions", - "versions" + meta_community <- mock$community %||% meta_packages(repo_community) + issues <- list.files( + file.path(path_staging, "issues"), + all.files = TRUE, + no.. = TRUE ) - issues <- Filter( - x = list.files(file.path(path_community, "issues")), - f = function(package) { - json <- jsonlite::read_json( - path = file.path(path_community, "issues", package) - ) - any(names(json) %in% promotion_checks) - } + freeze <- setdiff(json_staging$package, issues) + update <- setdiff(json_community$package, freeze) + should_freeze <- json_staging$package %in% freeze + json_freeze <- json_staging[should_freeze, ] + json_update <- json_community[json_community$package %in% update, ] + json_freeze$subdir <- json_freeze$subdir %||% + rep(NA_character_, nrow(json_freeze)) + json_update$subdir <- json_update$subdir %||% + rep(NA_character_, nrow(json_update)) + branches <- meta_community[ + meta_community$package %in% update, + c("package", "remotesha") + ] + json_update <- merge( + x = json_update, + y = branches, + by = "package", + all.x = TRUE, + all.y = FALSE ) - file_community <- file.path(path_community, "packages.json") - json <- jsonlite::read_json(file_community, simplifyVector = TRUE) - candidates <- intersect(meta_community$package, json$package) - setdiff(candidates, issues) + json_update <- json_update[!is.na(json_update$remotesha),, drop = FALSE] # nolint + json_update$branch <- json_update$remotesha + json_update$remotesha <- NULL + json_new <- rbind(json_freeze, json_update) + json_new <- json_new[order(json_new$package), ] + jsonlite::write_json(json_new, file_staging, pretty = TRUE) + invisible() } diff --git a/_pkgdown.yml b/_pkgdown.yml index f0eee27..2e56def 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -26,3 +26,4 @@ reference: - title: Staging contents: - update_staging + - staging_is_active diff --git a/inst/WORDLIST b/inst/WORDLIST index 5d59fa5..a60ad31 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -5,3 +5,4 @@ json repo pkgdown pre +POSIXlt diff --git a/inst/mock/community/issues/descriptions b/inst/mock/community/issues/descriptions deleted file mode 100644 index 5368252..0000000 --- a/inst/mock/community/issues/descriptions +++ /dev/null @@ -1,8 +0,0 @@ -{ - "descriptions": { - "remotes": ["owner1/repo1", "owner2/repo2"] - }, - "date": ["2024-06-21"], - "version": ["0.1.1"], - "remote_hash": ["abcdefabcdef1234567890"] -} diff --git a/inst/mock/community/issues/checks b/inst/mock/community/issues/issue similarity index 100% rename from inst/mock/community/issues/checks rename to inst/mock/community/issues/issue diff --git a/inst/mock/community/issues/versions b/inst/mock/community/issues/versions deleted file mode 100644 index f442864..0000000 --- a/inst/mock/community/issues/versions +++ /dev/null @@ -1,11 +0,0 @@ -{ - "versions": { - "version_current": ["0.0.1"], - "hash_current": ["hash_0.0.1"], - "version_highest": ["1.0.0"], - "hash_highest": ["hash_1.0.0"] - }, - "date": ["2024-01-01"], - "version": ["1.0.0"], - "remote_hash": ["abcdefabcdefabcdef123123123"] -} diff --git a/inst/mock/community/packages.json b/inst/mock/community/packages.json index e054a4d..36407e3 100644 --- a/inst/mock/community/packages.json +++ b/inst/mock/community/packages.json @@ -1,32 +1,17 @@ [ { - "package": "promote", - "url": "https://github.com/owner/promote", + "package": "add", + "url": "https://github.com/owner/add", "branch": "*release" }, { - "package": "change", - "url": "https://github.com/owner/change", + "package": "freeze", + "url": "https://github.com/owner/freeze", "branch": "*release" }, { - "package": "keep", - "url": "https://github.com/owner/keep", - "branch": "*release" - }, - { - "package": "checks", - "url": "https://github.com/owner/checks", - "branch": "*release" - }, - { - "package": "descriptions", - "url": "https://github.com/owner/descriptions", - "branch": "*release" - }, - { - "package": "versions", - "url": "https://github.com/owner/versions", + "package": "issue", + "url": "https://github.com/owner/issue", "branch": "*release" } ] diff --git a/inst/mock/staging/issues/checks b/inst/mock/staging/issues/issue similarity index 100% rename from inst/mock/staging/issues/checks rename to inst/mock/staging/issues/issue diff --git a/inst/mock/staging/issues/removed-has-issue b/inst/mock/staging/issues/removed-has-issue new file mode 100644 index 0000000..f07a79c --- /dev/null +++ b/inst/mock/staging/issues/removed-has-issue @@ -0,0 +1,13 @@ +{ + "checks": { + "_linuxdevel": ["success"], + "_macbinary": ["success"], + "_wasmbinary": ["none"], + "_winbinary": ["success"], + "_status": ["success"], + "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/12345"] + }, + "date": ["1980-01-01"], + "version": ["2.0.2"], + "remote_hash": ["abcdef1234567890abcdef"] +} diff --git a/inst/mock/staging/packages.json b/inst/mock/staging/packages.json index cf41733..d47a12f 100644 --- a/inst/mock/staging/packages.json +++ b/inst/mock/staging/packages.json @@ -1,12 +1,22 @@ [ { - "package": "change", - "url": "https://github.com/owner/change", + "package": "freeze", + "url": "https://github.com/owner/freeze", "branch": "original" }, { - "package": "keep", - "url": "https://github.com/owner/keep", - "branch": "sha-keep" + "package": "issue", + "url": "https://github.com/owner/issue", + "branch": "original" + }, + { + "package": "removed-has-issue", + "url": "https://github.com/owner/removed-has-issue", + "branch": "original" + }, + { + "package": "removed-no-issue", + "url": "https://github.com/owner/removed-no-issue", + "branch": "original" } ] diff --git a/man/meta_checks.Rd b/man/meta_checks.Rd index 0fb7489..2776dec 100644 --- a/man/meta_checks.Rd +++ b/man/meta_checks.Rd @@ -21,4 +21,8 @@ R-universe package API. \examples{ meta_checks(repo = "https://wlandau.r-universe.dev") } -\concept{list} +\seealso{ +Other meta: +\code{\link{meta_packages}()} +} +\concept{meta} diff --git a/man/meta_packages.Rd b/man/meta_packages.Rd index bdc6832..f628730 100644 --- a/man/meta_packages.Rd +++ b/man/meta_packages.Rd @@ -20,4 +20,8 @@ List package metadata in an R universe. \examples{ meta_packages(repo = "https://wlandau.r-universe.dev") } +\seealso{ +Other meta: +\code{\link{meta_checks}()} +} \concept{meta} diff --git a/man/staging_is_active.Rd b/man/staging_is_active.Rd new file mode 100644 index 0000000..5f1f8e8 --- /dev/null +++ b/man/staging_is_active.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/staging_is_active.R +\name{staging_is_active} +\alias{staging_is_active} +\title{Check if the stating universe is active.} +\usage{ +staging_is_active( + start = c("01-15", "04-15", "07-15", "10-15"), + today = Sys.Date() +) +} +\arguments{ +\item{start}{Character vector of \code{"\%m-\%d"} dates that the +staging universe becomes active. Staging will then last for a full +calendar month. For example, if you supply a start date of \code{"01-15"}, +then the staging period will include all days from \code{"01-15"} +through \code{"02-14"} and not include \code{"02-15"}.} + +\item{today}{Character string with today's date in \code{"\%Y-\%m-\%d"} format or an +object convertible to POSIXlt format.} +} +\value{ +\code{TRUE} if the staging universe is active, \code{FALSE} otherwise. +} +\description{ +Check if the stating universe is active. +} +\examples{ +staging_is_active() +} +\seealso{ +Other staging: +\code{\link{update_staging}()} +} +\concept{staging} diff --git a/man/update_staging.Rd b/man/update_staging.Rd index 98a14ce..a520f5f 100644 --- a/man/update_staging.Rd +++ b/man/update_staging.Rd @@ -50,3 +50,8 @@ update_staging( ) } } +\seealso{ +Other staging: +\code{\link{staging_is_active}()} +} +\concept{staging} diff --git a/tests/testthat/test-staging_is_active.R b/tests/testthat/test-staging_is_active.R new file mode 100644 index 0000000..5ce0ab8 --- /dev/null +++ b/tests/testthat/test-staging_is_active.R @@ -0,0 +1,43 @@ +test_that("staging_is_active()", { + start <- c("01-15", "04-15", "07-15", "10-15") + active <- c( + "2024-01-15", + "2024-01-16", + "2024-02-13", + "2024-04-15", + "2024-04-25", + "2024-05-14", + "2024-07-15", + "2024-08-12", + "2024-08-13", + "2024-10-15", + "2024-11-01", + "2024-11-13" + ) + for (today in active) { + expect_true( + staging_is_active( + start = start, + today = today + ) + ) + } + inactive <- c( + "2024-01-12", + "2024-02-15", + "2024-04-14", + "2024-05-15", + "2024-07-12", + "2024-08-15", + "2024-10-14", + "2024-11-15" + ) + for (today in inactive) { + expect_false( + staging_is_active( + start = start, + today = today + ) + ) + } +}) diff --git a/tests/testthat/test-update_staging.R b/tests/testthat/test-update_staging.R index 0d6be7e..e02fb5b 100644 --- a/tests/testthat/test-update_staging.R +++ b/tests/testthat/test-update_staging.R @@ -22,32 +22,38 @@ test_that("update_staging()", { to = dir_community, recursive = TRUE ) - json <- jsonlite::read_json(file.path(path_community, "packages.json")) - names <- vapply( - json, + file_staging <- file.path(path_staging, "packages.json") + file_community <- file.path(path_community, "packages.json") + json_staging <- jsonlite::read_json(file_staging) + json_community <- jsonlite::read_json(file_community) + names_community <- vapply( + json_community, function(x) x$package, FUN.VALUE = character(1L) ) meta_community <- data.frame( - package = names, - remotesha = paste0("sha-", names) + package = names_community, + remotesha = paste0("sha-", names_community) ) update_staging( path_staging = path_staging, path_community = path_community, mock = list(community = meta_community) ) - packages <- jsonlite::read_json( - file.path(path_staging, "packages.json"), - simplifyVector = TRUE - ) + packages <- jsonlite::read_json(file_staging, simplifyVector = TRUE) expect_true(is.data.frame(packages)) expect_equal(dim(packages), c(4L, 3L)) - names <- c("change", "checks", "keep", "promote") + names <- c("add", "freeze", "issue", "removed-no-issue") expect_equal(sort(packages$package), sort(names)) expect_equal( packages$url, file.path("https://github.com/owner", packages$package) ) - expect_equal(packages$branch, paste0("sha-", packages$package)) + expect_equal(packages$branch[packages$package == "add"], "sha-add") + expect_equal(packages$branch[packages$package == "freeze"], "original") + expect_equal(packages$branch[packages$package == "issue"], "sha-issue") + expect_equal( + packages$branch[packages$package == "removed-no-issue"], + "original" + ) })