diff --git a/DESCRIPTION b/DESCRIPTION index d660749..dd50fa5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: dsROCrate -Title: 'DataSHIELD' RO-Crate Wrapper Functions +Title: 'DataSHIELD' RO-Crate Governance Functions Version: 0.0.1 Authors@R: c( person(given = "Roberto", @@ -26,7 +26,6 @@ License: MIT + file LICENSE Suggests: dsBaseClient, DSI, - DSMolgenisArmadillo, DSOpal, knitr, MolgenisArmadillo, @@ -41,6 +40,7 @@ RoxygenNote: 7.3.3 Imports: digest, dplyr, + DSMolgenisArmadillo, jsonlite, methods, purrr, @@ -53,22 +53,24 @@ Imports: yaml Depends: R (>= 4.1.0) +Remotes: + ResearchObject/ro-crate-r VignetteBuilder: knitr Collate: 'ArmadilloCredentials-class.R' - 'audit_cr8tor.R' - 'audit_safe_people.R' - 'audit_safe_project.R' - 'audit_study.R' + 'audit.R' + 'audit_engine.R' 'dsROCrate-package.R' 'dsROCrate.R' - 'rocrate_report.R' + 'print.R' + 'report.R' 'safe_data.R' 'safe_output.R' 'safe_people.R' 'safe_project.R' 'safe_setting.R' 'utils-armadillo.R' + 'utils-audit.R' 'utils-connection.R' 'utils-cr8tor.R' 'utils-date.R' diff --git a/NAMESPACE b/NAMESPACE index 59f7d23..9d8ce92 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,12 +1,13 @@ # Generated by roxygen2: do not edit by hand -S3method(audit_safe_people,default) -S3method(audit_safe_people,opal) -S3method(audit_safe_project,default) -S3method(audit_safe_project,opal) -S3method(audit_study,default) -S3method(audit_study,list) -S3method(audit_study,opal) +S3method(audit,armadillo) +S3method(audit,character) +S3method(audit,cr8tor) +S3method(audit,list) +S3method(audit,opal) +S3method(audit,rocrate) +S3method(audit_engine,cr8tor) +S3method(audit_engine,opal) S3method(extract_safe_data,opal) S3method(extract_safe_data,rocrate) S3method(extract_safe_output,opal) @@ -29,10 +30,11 @@ S3method(flatten_safe_setting,default) S3method(flatten_safe_setting,rocrate) S3method(init,opal) S3method(init,rocrate) -S3method(rocrate_report,character) -S3method(rocrate_report,default) -S3method(rocrate_report,list) -S3method(rocrate_report,rocrate) +S3method(print,cr8tor_bundle) +S3method(report,character) +S3method(report,default) +S3method(report,list) +S3method(report,rocrate) S3method(safe_data,character) S3method(safe_data,default) S3method(safe_data,opal) @@ -55,13 +57,9 @@ S3method(safe_setting,default) S3method(safe_setting,opal) S3method(safe_setting,rocrate) export(armadillo_login) -export(audit_cr8tor) -export(audit_safe_people) -export(audit_safe_project) -export(audit_study) +export(audit) export(init) -export(load_cr8tor_bundle) -export(rocrate_report) +export(report) export(safe_data) export(safe_output) export(safe_people) diff --git a/R/audit.R b/R/audit.R new file mode 100644 index 0000000..820ee27 --- /dev/null +++ b/R/audit.R @@ -0,0 +1,148 @@ +#' Create an audit RO-Crate +#' +#' Create an audit RO-Crate following the 5 Safes Principles. +#' +#' This function handles various audit types, which will be dispatched based on +#' the input object. If the input object is +#' +#' \itemize{ +#' \item a _connection_ to a DataSHIELD server (e.g., OBiBa's Opal): +#'. generates an RO-Crate object with deployment details, including outputs. +#' \item a _path_ pointing to +#' \itemize{ +#' \item **a `cr8tor` archive / governance bundle**: generates an +#' RO-Crate object with pre-deployment governance details. +#' \item **an RO-Crate object**: generates an RO-Crate object with +#' clearly defined 5 Safes elements. +#' } +#' \item an _RO-Crate_ object: generates an RO-Crate object with clearly +#' defined 5 Safes elements. +#' } +#' @param x Object to be audited. This can be +#' \itemize{ +#' \item a _connection_ to a DataSHIELD server (e.g., OBiBa's Opal). +#' \item a _path_ pointing to an RO-Crate OR a `cr8tor` archive / +#'. governance bundle. +#' \item an _RO-Crate_ object. +#' } +#' Alternatively, a list of any of the above. +#' @param ... Additional arguments. +#' @param intent Additional object with governance bundle/specification of the +#' intent of a project. It takes the same types as `x`. +#' @inheritParams audit_engine +#' +#' @returns RO-Crate with audit details. +#' @export +audit <- function(x, ...) { + UseMethod("audit") +} + +#' @rdname audit +#' @export +audit.armadillo <- function(x, ..., intent = NULL) { + # +} + +#' @rdname audit +#' @export +audit.character <- function(x, ..., intent = NULL) { + # verify if the given path exists, if not, return an error message + if (!file.exists(x)) { + stop("The given file does not exist!", call. = FALSE) + } + + # attempt loading a `cr8tor` bundle + x_obj <- tryCatch( + load_cr8tor_bundle(x, ...), + error = function(e) NULL + ) + # alternatively, attempt loading an RO-Crate + if (is.null(x_obj)) { + x_obj <- tryCatch( + rocrateR::load_rocrate(x, ...), + error = function(e) NULL + ) + } + + if (is.null(x_obj)) { + stop( + "The given path does not point to a valid `cr8tor` archive nor an `rocrate", + call. = FALSE + ) + } + + # attempt auditing intent + intent_lst <- audit_intent(intent, ...) + + # call next method + main_audit <- audit(x_obj, intent_lst$main_audit_args) + + # return list + if (is.null(intent_lst$intent_audit)) { + return(main_audit) + } + list(intent = intent_lst$intent_audit, deployment = main_audit) +} + +#' @rdname audit +#' @export +audit.cr8tor <- function(x, ..., intent = NULL) { + # attempt auditing intent + intent_lst <- audit_intent(intent, ..., excluded_args = "path") + + # audit_engine(x, exclude_args(..., "path"), intent = intent) + + # call next method + main_audit <- audit_engine(x, intent_lst$main_audit_args) + + # return list + if (is.null(intent_lst$intent_audit)) { + return(main_audit) + } + list(intent = intent_lst$intent_audit, deployment = main_audit) +} + +#' @rdname audit +#' @export +audit.list <- function(x, ..., intent = NULL) { + purrr::map(x, audit, ..., intent = intent) +} + +#' @rdname audit +#' @export +audit.opal <- function( + x, + ..., + intent = NULL, + project = NULL, + user = NULL, + logs_from = -Inf, + logs_to = Inf, + path = NULL +) { + # attempt auditing intent + intent_lst <- audit_intent(intent, ...) + + # call next method + main_audit <- audit_engine( + x, + project = c(intent_lst$main_audit_args$project, project), + user = c(intent_lst$main_audit_args$user, user), + logs_from = logs_from, + logs_to = logs_to, + path = path, + intent_lst$main_audit_args + ) + + # return list + if (is.null(intent_lst$intent_audit)) { + return(main_audit) + } + list(intent = intent_lst$intent_audit, deployment = main_audit) +} + +#' @rdname audit +#' @export +audit.rocrate <- function(x, ..., intent = NULL) { + # +} diff --git a/R/audit_cr8tor.R b/R/audit_cr8tor.R deleted file mode 100644 index 4bb10aa..0000000 --- a/R/audit_cr8tor.R +++ /dev/null @@ -1,97 +0,0 @@ -#' Audit cr8tor project archive -#' -#' This audit loads a cr8tor project archive and generates an RO-Crate object -#' with pre-deployment governance details. This then can be rendered with -#' [rocrate_report()]. -#' -#' @param path Path to cr8tor archive. -#' @param ... Additional arguments for [rocrateR::load_rocrate]. -#' @return RO-Crate audit object -#' -#' @references https://karectl-crates.github.io/cr8tor-metamodel/ -#' @export -audit_cr8tor <- function(path, ...) { - bundle <- load_cr8tor_bundle(path, ...) - - audit <- list( - metadata = extract_cr8tor_metadata(bundle), - integrity = extract_integrity_cr8tor(bundle), - safe_people = extract_safe_people_cr8tor(bundle), - safe_projects = extract_safe_projects_cr8tor(bundle), - safe_data = extract_safe_data_cr8tor(bundle), - safe_settings = extract_safe_settings_cr8tor(bundle), - safe_outputs = extract_safe_outputs_cr8tor(bundle), - user_projects = extract_user_projects_cr8tor(bundle), - user_groups = extract_user_groups_cr8tor(bundle), - groups = extract_groups_cr8tor(bundle), - permissions = extract_permissions_cr8tor(bundle) #, - # lineage = extract_lineage_cr8tor(bundle), - ) - - as_rocrate_audit(audit) -} - -#' Convert audit result into RO-Crate -#' -#' @param audit list returned by audit_cr8tor() -#' @return rocrate object -#' @noRd -as_rocrate_audit <- function(audit) { - rc <- rocrateR::rocrate_5s() - - # Root dataset describing the audit - rc <- rc |> - rocrateR::add_entity_value( - "./", - "name", - "cr8tor 5 Safes Audit", - overwrite = TRUE - ) |> - rocrateR::add_entity_value( - "./", - "description", - "Audit report generated from cr8tor archive", - overwrite = TRUE - ) - - # ---- Safe People ---- - rc <- rc |> - add_group_entities_cr8tor(audit$groups) |> - add_safe_people_entities_cr8tor( - audit$safe_people$users, - audit$user_projects - ) |> - link_people_to_root(audit$safe_people$users$username) - - # ---- Safe Projects ---- - rc <- rc |> - add_safe_project_entities_cr8tor( - audit$safe_projects, - audit$safe_data$tables - ) - - # ---- Safe Data ---- - rc <- rc |> - add_safe_data_entities_cr8tor(audit$safe_data$tables) - - # ---- Permissions ---- - rc <- rc |> - add_permission_entities_cr8tor( - expand_group_permissions_to_users( - perm_tbl = audit$permissions, - membership_tbl = audit$user_groups, - data_tbl = audit$safe_data$tables - ) |> - dedupe_effective_permissions() - ) - - # ---- Safe Settings ---- - rc <- rc |> - add_safe_setting_entities_cr8tor(audit$safe_settings) - - # ---- Safe Outputs ---- - rc <- rc |> - add_safe_output_entities_cr8tor(audit$safe_outputs) - - rc -} diff --git a/R/audit_engine.R b/R/audit_engine.R new file mode 100644 index 0000000..4bf122a --- /dev/null +++ b/R/audit_engine.R @@ -0,0 +1,166 @@ +#' Audit Engine +#' +#' @param x This can be a connection to a 'DataSHIELD' server (e.g., object with +#' the `opal` class, see [opalr::opal.login()]). +#' @param ... Other optional arguments, see full documentation for details. +#' @param project String with project name(s) from which to extra Safe Project +#' details. +#' @param user String with the user name for which to extract Safe People +#' details. +#' @param logs_from Lower limit timestamp to filter out the outputs generated +#' (default: `-Inf`, everything up to `logs_to`) +#' @param logs_to Upper limit timestamp to filter out the outputs generated +#' (default: `Inf`, everything from `logs_from` onwards). +#' @param path String with path pointing to the root of the RO-Crate. This will +#' be used to store log files. If not provided, logs will be stored within +#' the RO-Crate returned by this function. +#' +#' @returns Audit RO-Crate with 5 Safes Components. +#' @keywords internal +audit_engine <- function(x, ...) { + UseMethod("audit_engine") +} + +#' @rdname audit_engine +#' @export +audit_engine.cr8tor <- function(x, ...) { + audit <- list( + metadata = extract_cr8tor_metadata(x), + integrity = extract_integrity_cr8tor(x), + safe_people = extract_safe_people_cr8tor(x), + safe_projects = extract_safe_projects_cr8tor(x), + safe_data = extract_safe_data_cr8tor(x), + safe_settings = extract_safe_settings_cr8tor(x), + safe_outputs = extract_safe_outputs_cr8tor(x), + user_projects = extract_user_projects_cr8tor(x), + user_groups = extract_user_groups_cr8tor(x), + groups = extract_groups_cr8tor(x), + permissions = extract_permissions_cr8tor(x) #, + # lineage = extract_lineage_cr8tor(x), + ) + + as_rocrate_audit(audit) +} + +#' @rdname audit_engine +#' @export +audit_engine.opal <- function( + x, + ..., + project = NULL, + user = NULL, + logs_from = -Inf, + logs_to = Inf, + path = NULL +) { + # local bindings + name <- principal <- NULL + + # create RO-Create with the 5 safes profile + crate <- rocrateR::rocrate_5s() + + # validate Opal connection + is_opal_admin_con(x) + + # if `project` is missing, then ~extract all project names~ error + if (is.null(project)) { + stop("A `project` name is required!", call. = FALSE) + } + + # extract all data sources to verify `project` contains a valid value. + ds <- opalr::opal.datasources(x) + server_prjs <- ds[, "name"] + idx <- project %in% server_prjs + if (!all(idx)) { + stop( + "The following project", + ifelse(length(idx) == 1, " is ", "s are "), + "not valid: \n", + paste0(" - ", project[!idx], collapse = "\n"), + call. = FALSE + ) + } + + # Safe People ---- + # get users' details + safe_people_tbl <- opalr::opal.get(x, "/system/subject-profiles/") |> + dplyr::bind_rows() |> + dplyr::rename(name = principal) |> + # exclude system administrators from the report + dplyr::filter(!(tolower(name) %in% c("admin", "administrator"))) + + if (!is.null(user)) { + safe_people_tbl <- safe_people_tbl |> + dplyr::filter(tolower(name) %in% user) + + if (nrow(safe_people_tbl) == 0) { + stop( + sprintf( + "No Safe People details were found for the user: %s!", + paste0("'", user, "'", collapse = ", ") + ), + call. = FALSE + ) + } + } + + crate <- safe_people_tbl$name |> + purrr::reduce( + \(crate, u) { + safe_people( + crate, + connection = x, + user = u, + set_author = FALSE, + set_project = FALSE + ) + }, + .init = crate + ) + + # Safe Projects ---- + crate <- project |> + purrr::reduce( + \(crate, p) { + safe_project(crate, connection = x, project = p) + }, + .init = crate + ) + + # Safe Data ---- + crate <- project |> + purrr::reduce( + \(crate, p) { + safe_data(crate, connection = x, project = p) + }, + .init = crate + ) + + # remove permissions associated with admin users + non_admin_user_ids <- safe_people_tbl$name |> + purrr::map_chr(id_hash, prefix = "#person:") + admin_perm_ents <- crate$`@graph` |> + purrr::keep(\(x) grepl("^#perm:", getElement(x, "@id"))) |> + purrr::discard(\(x) getElement(x, "agent")[[1]] %in% non_admin_user_ids) + crate <- admin_perm_ents |> + purrr::reduce(rocrateR::remove_entity, .init = crate) + + # Safe Settings ---- + crate <- safe_setting(x, rocrate = crate) + + # Safe Outputs ---- + crate <- safe_people_tbl$name |> + purrr::reduce( + \(crate, u) { + safe_output( + crate, + connection = x, + path = path, + user = u, + logs_to = logs_to, + logs_from = logs_from + ) + }, + .init = crate + ) +} diff --git a/R/audit_safe_people.R b/R/audit_safe_people.R deleted file mode 100644 index 80b2bb5..0000000 --- a/R/audit_safe_people.R +++ /dev/null @@ -1,174 +0,0 @@ -#' Audit Safe People details -#' -#' Audit Safe People details from a 'DataSHIELD' server, an RO-Crate object or -#' a file path pointing to an RO-Crate. -#' -#' @inheritParams init -#' @param x This can be a connection to a 'DataSHIELD' server (e.g., object with -#' the `opal` class, see [opalr::opal.login()]). -#' @param ... Other optional arguments, see full documentation for details. -#' @param user String with the user name for which to extract Safe People -#' details. -#' @param project String with project name(s) from which to extra Safe People -#' details. -#' @param logs_from Lower limit timestamp to filter out the outputs generated -#' (default: `-Inf`, everything up to `logs_to`) -#' @param logs_to Upper limit timestamp to filter out the outputs generated -#' (default: `Inf`, everything from `logs_from` onwards). -#' -#' @returns Updated RO-Crate object with Safe People information. -#' @export -#' -# @examples -audit_safe_people <- function(x, ...) { - UseMethod("audit_safe_people") -} - -#' @rdname audit_safe_people -#' @export -audit_safe_people.default <- function(x, ...) { - stop( - "Unknown class, please try with a connection object (e.g., OBiBa's Opal)!" - ) -} - -#' @rdname audit_safe_people -#' @export -audit_safe_people.opal <- function( - x, - ..., - user, - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) { - # local bindings - project_tables_all <- subject <- type <- NULL - - # validate Opal connection - is_opal_admin_con(x) - - # if `project` is missing, then extract all project names - if (is.null(project)) { - # extract all data sources - ds <- opalr::opal.datasources(x) - - project <- ds[, "name"] - } - - suppressWarnings({ - project_tables_all <- x |> - get_project_details(project) - }) - - # get permissions for each table in the project - # get table permissions - project_table_permissions_tbl <- seq_len(nrow(project_tables_all)) |> - lapply(function(i) { - get_table_permissions( - x, - project_tables_all[i, "project"][[1]], - project_tables_all[i, "table"][[1]] - ) - }) |> - dplyr::bind_rows() - - # filter out project permissions for the given user - project_table_permissions_tbl_v2 <- project_table_permissions_tbl |> - dplyr::filter(subject %in% user, type == "user") - - # check if any permission records were found for the current project - if (nrow(project_table_permissions_tbl_v2) == 0) { - stop( - "The given `project`, does not have any permissions set for the given `user`!", - call. = FALSE - ) - } - - # create RO-Create with user, projects and datasets they have access to - safe_people_crate <- rocrateR::rocrate_5s() - - ## add Safe Data and Safe Project details - for (p in unique(project_table_permissions_tbl_v2$project)) { - # filter out tables for the current project - project_tables <- project_table_permissions_tbl_v2 |> - dplyr::filter(project == p) - # add tables for the current project - safe_people_crate <- safe_people_crate |> - dsROCrate::safe_data( - project = p, - tables = project_tables$table, - connection = x - ) - # add project details - safe_people_crate <- safe_people_crate |> - dsROCrate::safe_project(project = p, connection = x) - } - - # add Safe People details - for (i in seq_len(length(user))) { - safe_people_crate <- safe_people_crate |> - dsROCrate::safe_people( - user = user[i], - connection = x, - set_author = FALSE, - set_project = FALSE - ) - } - - # extract Dataset entities from the RO-Crate: @id & name - safe_data_entities_tbl <- safe_people_crate |> - flatten_safe_data() |> - dplyr::rename("table_id" = "id") - - # extract Person entities from the RO-Crate: @id & name - safe_people_entities_tbl <- safe_people_crate |> - flatten_safe_people() |> - dplyr::rename("user_id" = "id") - - ## combine the table permissions with Dataset & People entities' @ids - project_table_permissions_tbl_v3 <- project_table_permissions_tbl |> - dplyr::filter(subject %in% user, type == "user") |> - dplyr::left_join(safe_data_entities_tbl, by = c("table" = "name")) |> - dplyr::left_join(safe_people_entities_tbl, by = c("subject" = "name")) |> - dplyr::rename(user = subject) - - ## generate user permission entities and add to the RO-Crate - user_perm_entity_lst <- project_table_permissions_tbl_v3 |> - purrr::pmap(user_perm_entity) |> - purrr::list_c() - - # ignore warnings about existing permission entities - suppressWarnings({ - safe_people_crate <- user_perm_entity_lst |> - purrr::reduce( - rocrateR::add_entity, - overwrite = TRUE, - .init = safe_people_crate - ) - }) - - # add Safe Setting details - safe_people_crate <- x |> - extract_safe_setting(rocrate = safe_people_crate) - - # add Safe Output details - safe_people_crate <- x |> - extract_safe_output( - path = path, - user = safe_people_entities_tbl$name, - logs_to = logs_to, - logs_from = logs_from, - rocrate = safe_people_crate - ) - - # attach input args as attributes to the RO-Crate - attr(safe_people_crate, "audit_type") <- "Safe People" - attr(safe_people_crate, "path") <- path - attr(safe_people_crate, "project") <- project - attr(safe_people_crate, "user") <- user - - # return new RO-Crate - return(safe_people_crate) -} diff --git a/R/audit_safe_project.R b/R/audit_safe_project.R deleted file mode 100644 index af8048c..0000000 --- a/R/audit_safe_project.R +++ /dev/null @@ -1,177 +0,0 @@ -#' Audit Safe Project details -#' -#' Audit Safe Project details from a 'DataSHIELD' server, an RO-Crate object or -#' a file path pointing to an RO-Crate. -#' -#' @inheritParams audit_safe_people -#' @param ... Other optional arguments, see full documentation for details. -#' -#' @returns Updated RO-Crate object with Safe Project information. -#' @export -audit_safe_project <- function(x, ...) { - UseMethod("audit_safe_project") -} - -#' @rdname audit_safe_project -#' @export -audit_safe_project.default <- function(x, ...) { - stop( - "Unknown class, please try with a connection object (e.g., OBiBa's Opal)!" - ) -} - -#' @rdname audit_safe_project -#' @export -audit_safe_project.opal <- function( - x, - ..., - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) { - # local bindings - name <- principal <- project_tables_all <- subject <- table <- type <- NULL - - # validate Opal connection - is_opal_admin_con(x) - # validate_opal_con(x) - - # if `project` is missing, then extract all project names - if (is.null(project)) { - # extract all data sources - ds <- opalr::opal.datasources(x) - - project <- ds[, "name"] - } - - suppressWarnings({ - project_tables_all <- x |> - get_project_details(project) - }) - - # add check to determine if project information was found: - if (nrow(project_tables_all) == 0) { - stop( - paste0( - "No data details were found for given project", - ifelse(length(project) == 1, "", "s"), - "!" - ), - call. = FALSE - ) - } - # get permissions for each table in the project - # get table permissions - project_table_permissions_tbl <- seq_len(nrow(project_tables_all)) |> - lapply(function(i) { - get_table_permissions( - x, - project_tables_all[i, "project"][[1]], - project_tables_all[i, "table"][[1]] - ) - }) |> - dplyr::bind_rows() - - # create RO-Create with projects and datasets, plus information of users that - # have access to them - safe_project_crate <- rocrateR::rocrate_5s() - - ## add Safe Data and Safe Project details - for (p in unique(project_table_permissions_tbl$project)) { - # filter out tables for the current project - project_tables <- project_table_permissions_tbl |> - dplyr::filter(project == p) - # add tables for the current project - safe_project_crate <- safe_project_crate |> - dsROCrate::safe_data( - project = p, - tables = project_tables$table, - connection = x - ) - # add project details - safe_project_crate <- safe_project_crate |> - dsROCrate::safe_project(project = p, connection = x) - } - - # get users' details - safe_people_tbl <- opalr::opal.get(x, "/system/subject-profiles/") |> - dplyr::bind_rows() |> - dplyr::rename(name = principal) |> - # exclude system administrators from the report - dplyr::filter(!(tolower(name) %in% c("admin", "administrator"))) |> - # filter out users that don't have access to the given project(s) - dplyr::filter(name %in% project_table_permissions_tbl$subject) - - # filter out table permissions based on the users found previously: - project_table_permissions_tbl <- project_table_permissions_tbl |> - dplyr::filter(subject %in% safe_people_tbl$name) - - # add Safe People details - for (i in seq_len(nrow(safe_people_tbl))) { - safe_project_crate <- safe_project_crate |> - dsROCrate::safe_people( - user = safe_people_tbl$name[i], - connection = x, - set_author = FALSE, - set_project = FALSE - ) - } - - # extract Dataset entities from the RO-Crate: @id & name - safe_data_entities_tbl <- safe_project_crate |> - flatten_safe_data() |> - dplyr::rename("table_id" = "id") - - # extract Person entities from the RO-Crate: @id & name - safe_people_entities_tbl <- safe_project_crate |> - flatten_safe_people() |> - dplyr::rename("user_id" = "id") - - ## combine the table permissions with Dataset & People entities' @ids - project_table_permissions_tbl_v2 <- project_table_permissions_tbl |> - dplyr::left_join(safe_data_entities_tbl, by = c("table" = "name")) |> - dplyr::left_join(safe_people_entities_tbl, by = c("subject" = "name")) |> - dplyr::rename(user = subject) - - ## generate user permission entities and add to the RO-Crate - user_perm_entity_lst <- project_table_permissions_tbl_v2 |> - purrr::pmap(user_perm_entity) |> - purrr::list_c() - # ignore warnings about existing permission entities - suppressWarnings({ - safe_project_crate <- user_perm_entity_lst |> - purrr::reduce( - rocrateR::add_entity, - overwrite = TRUE, - .init = safe_project_crate - ) - }) - - # add Safe Setting details - safe_project_crate <- x |> - extract_safe_setting(rocrate = safe_project_crate) - - # add Safe Output details - for (u in safe_people_entities_tbl$name) { - # # suppress warnings, as some users might not have logs in the given period - # suppressWarnings({ - safe_project_crate <- x |> - extract_safe_output( - path = path, - user = u, - logs_to = logs_to, - logs_from = logs_from, - rocrate = safe_project_crate, - ) - # }) - } - - # attach input args as attributes to the RO-Crate - attr(safe_project_crate, "audit_type") <- "Safe Project" - attr(safe_project_crate, "path") <- path - attr(safe_project_crate, "project") <- project - - # return new RO-Crate - return(safe_project_crate) -} diff --git a/R/audit_study.R b/R/audit_study.R deleted file mode 100644 index b158602..0000000 --- a/R/audit_study.R +++ /dev/null @@ -1,95 +0,0 @@ -#' Audit Study details -#' -#' Audit Study details from a 'DataSHIELD' server, an RO-Crate object or -#' a file path pointing to an RO-Crate. -#' -#' @inheritParams audit_safe_people -#' @param ... Other optional arguments, see full documentation for details. -#' -#' @returns Updated RO-Crate object with Study information. -#' @export -audit_study <- function(x, ...) { - UseMethod("audit_study") -} - -#' @rdname audit_study -#' @export -audit_study.default <- function(x, ...) { - stop( - "Unknown class, please try a named list of connections (e.g., OBiBa's Opal)", - " or a single connection object!" - ) -} - -#' @rdname audit_study -#' @export -audit_study.list <- function( - x, - ..., - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) { - # local bindings - name <- principal <- project_tables_all <- subject <- table <- type <- NULL - - utils::capture.output( - suppressMessages(suppressWarnings({ - safe_project_reports <- x |> - purrr::map(function(conn) { - audit_study( - conn, - project = project, - logs_from = logs_from, - logs_to = logs_to, - path = path - ) - }) - })), - file = nullfile() - ) - - # attach input args as attributes to the RO-Crate - attr(safe_project_reports, "audit_type") <- "Study" - attr(safe_project_reports, "path") <- path - attr(safe_project_reports, "project") <- project - - # return list with new RO-Crates (one per connection given) - return(safe_project_reports) -} - -#' @rdname audit_study -#' @export -audit_study.opal <- function( - x, - ..., - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) { - # local bindings - name <- principal <- project_tables_all <- subject <- table <- type <- NULL - - utils::capture.output( - suppressMessages(suppressWarnings({ - safe_project_reports <- x |> - audit_safe_project( - project = project, - logs_from = logs_from, - logs_to = logs_to, - path = path - ) - })), - file = nullfile() - ) - - # attach input args as attributes to the RO-Crate - attr(safe_project_reports, "audit_type") <- "Study" - attr(safe_project_reports, "path") <- path - attr(safe_project_reports, "project") <- project - - # return list with new RO-Crates (one per connection given) - return(safe_project_reports) -} diff --git a/R/dsROCrate.R b/R/dsROCrate.R index a3a62f3..0fd90c3 100644 --- a/R/dsROCrate.R +++ b/R/dsROCrate.R @@ -1,4 +1,6 @@ -#' Initialise Five Safes RO-Crate +#' Initialise a Five Safes RO-Crate +#' +#' Creates a new RO-Crate configured for Five Safes auditing. #' #' @param x This can be a connection to a 'DataSHIELD' server (e.g., object with #' the `opal` class, see [opalr::opal.login()]), an RO-Crate @@ -27,7 +29,13 @@ #' the Safe People, it must include `@id` and `name` entries. Alternatively, #' this can be a string with the `name` of the current user. #' -#' @returns Five Safes RO-Crate. +#' @returns Five Safes RO-Crate object. +#' +#' @references +#' Wilkinson, M., Dumontier, M., Aalbersberg, I. et al. (2016) The FAIR Guiding +#' Principles for scientific data management and stewardship. Sci Data 3, +#' 160018. https://doi.org/10.1038/sdata.2016.18 +#' #' @export init <- function(x, ...) { UseMethod("init") diff --git a/R/print.R b/R/print.R new file mode 100644 index 0000000..d963720 --- /dev/null +++ b/R/print.R @@ -0,0 +1,31 @@ +#' @export +print.cr8tor_bundle <- function(x, ...) { + cat("\n") + + is_valid_roc <- function(x) { + inherits(x$rocrate, "rocrate") && + rocrateR::is_rocrate(x$rocrate, error = FALSE) + } + + if (is_valid_roc(x)) { + cat("\U2714 Valid RO-Crate\n") + } else { + cat("\U2716 Invalid RO-Crate\n") + } + + # resources <- unique(basename(x$resources)) + # if (length(resources) > 0) { + # cat("Resources: ", paste0(resources, collapse = ", "), "\n") + # } + # if (length(x$errors)) { + # cat("\nErrors:\n") + # cat(paste0(" - ", x$errors, collapse = "\n"), "\n") + # } + # + # if (length(x$warnings)) { + # cat("\nWarnings:\n") + # cat(paste0(" - ", x$warnings, collapse = "\n"), "\n") + # } + + invisible(x) +} diff --git a/R/rocrate_report.R b/R/report.R similarity index 86% rename from R/rocrate_report.R rename to R/report.R index 965cae9..277ba78 100644 --- a/R/rocrate_report.R +++ b/R/report.R @@ -3,17 +3,17 @@ #' @param x This can be an RO-Crate ([rocrate][rocrateR::rocrate()] class) or a #' string with the path to an RO-Crate. #' @param ... Other optional arguments. See the full documentation, -#' [`?dsROCrate::rocrate_report`][rocrate_report()]. +#' [`?dsROCrate::report`][report()]. #' #' @returns RO-Crate report as markdown (.md) file and/or HTML. #' @export -rocrate_report <- function(x, ...) { - UseMethod("rocrate_report") +report <- function(x, ...) { + UseMethod("report") } -#' @rdname rocrate_report +#' @rdname report #' @export -rocrate_report.character <- function( +report.character <- function( x, ..., title = "DataSHIELD Report", @@ -28,11 +28,11 @@ rocrate_report.character <- function( max_line_length = 200 ) { # attempt loading the RO-Crate - rocrate <- load_rocrate(x) + rocrate <- rocrateR::load_rocrate(x) # call the next generic method rocrate |> - rocrate_report( + report( title = title, filepath = filepath, render = render, @@ -45,9 +45,9 @@ rocrate_report.character <- function( ) } -#' @rdname rocrate_report +#' @rdname report #' @export -rocrate_report.default <- function(x, ...) { +report.default <- function(x, ...) { stop( "Unknown class, please try either a file path or", " an object with `rocrate` class!" @@ -55,9 +55,9 @@ rocrate_report.default <- function(x, ...) { } #' @param study_name String with the study name. -#' @rdname rocrate_report +#' @rdname report #' @export -rocrate_report.list <- function( +report.list <- function( x, ..., study_name, @@ -73,7 +73,7 @@ rocrate_report.list <- function( max_line_length = 200 ) { # local bindings - id <- name <- permission <- project <- server <- user <- NULL + asset <- id <- name <- permission <- project <- server <- user <- NULL # validate that all the objects in the list, `x`, are valid RO-Crates sapply(x, rocrateR::is_rocrate) @@ -81,7 +81,7 @@ rocrate_report.list <- function( # generate individual reports for each RO-Crate report_outputs <- lapply( x, - rocrate_report, + report, title = title, filepath = filepath, render = FALSE, @@ -94,131 +94,40 @@ rocrate_report.list <- function( ) # combine reports ---- - ## Safe People ----- - safe_people_all <- tryCatch( - { - report_outputs |> - # extract each component per server - lapply(getElement, name = "safe_people") |> - # attach the server name as a new column - purrr::imap(~ dplyr::mutate(.x, server = .y)) |> - # combine rows - dplyr::bind_rows() - }, - error = function(e) { - NULL - } - ) - ## Safe Data ---- - safe_data_all <- tryCatch( - { - report_outputs |> - # extract each component per server - lapply(getElement, name = "safe_data") |> - # attach the server name as a new column - purrr::imap(~ dplyr::mutate(.x, server = .y)) |> - # combine rows - dplyr::bind_rows() - }, - error = function(e) { - NULL - } - ) - ## Safe Projects ---- - safe_project_all <- tryCatch( - { - report_outputs |> - # extract each component per server - lapply(getElement, name = "safe_project") |> - # attach the server name as a new column - purrr::imap(~ dplyr::mutate(.x, server = .y)) |> - # combine rows - dplyr::bind_rows() - }, - error = function(e) { - NULL - } - ) - ## Safe Settings ---- - safe_setting_all <- tryCatch( - { - report_outputs |> - # extract each component per server - lapply(getElement, name = "safe_setting") |> - # attach the server name as a new column - purrr::imap(~ dplyr::mutate(.x, server = .y)) |> - # combine rows - dplyr::bind_rows() - }, - error = function(e) { - NULL - } - ) - ## Safe Outputs ---- - safe_output_all <- tryCatch( - { - report_outputs |> - # extract each component per server - lapply(getElement, name = "safe_output") |> - # attach the server name as a new column - purrr::imap(~ dplyr::mutate(.x, server = .y)) |> - # combine rows - dplyr::bind_rows() - }, - error = function(e) { - NULL - } - ) - ## Safe Data permissions (optional) ---- - safe_data_permissions_all <- tryCatch( - { - report_outputs |> - # extract each component per server - lapply(getElement, name = "safe_data_permissions") |> - # attach the server name as a new column - purrr::imap(~ dplyr::mutate(.x, server = .y)) |> - # combine rows - dplyr::bind_rows() - }, - error = function(e) { - NULL - } - ) + ## Safe People + safe_people_all <- .extract_srvr_comp(report_outputs, "safe_people") + ## Safe Data + safe_data_all <- .extract_srvr_comp(report_outputs, "safe_data") + ## Safe Projects + safe_project_all <- .extract_srvr_comp(report_outputs, "safe_project") + ## Safe Settings + safe_setting_all <- .extract_srvr_comp(report_outputs, "safe_setting") + ## Safe Outputs + safe_output_all <- .extract_srvr_comp(report_outputs, "safe_output") + ## Safe Data permissions (optional) + safe_data_permissions_all <- report_outputs |> + .extract_srvr_comp("safe_data_permissions") ## overview table ---- overview_data_all <- tibble::tibble() ## combine aggregated data to generate new overview table - safe_project_data_all <- safe_project_all |> - dplyr::rename(project_id = id) |> - dplyr::left_join( - safe_data_all |> - dplyr::rename(table_id = id), - by = c("table" = "name", "server" = "server") - ) + safe_project_data_all <- safe_project_all #|> + # dplyr::rename(asset = asset_name) + # if data permissions were found, then combine with project-data details and # generate new overview table if (!is.null(safe_data_permissions_all)) { overview_data_all <- safe_data_permissions_all |> - dplyr::left_join( - safe_people_all |> - dplyr::rename(user_id = id), - by = c("user_id", "server") - ) |> - dplyr::left_join(safe_project_data_all, by = c("table_id", "server")) |> - dplyr::select( - server, - project, - table, - permission, - user = name - ) + dplyr::left_join(safe_people_all, by = c("person_id", "server")) |> + dplyr::left_join(safe_project_data_all, by = c("asset_id", "server")) |> + dplyr::select(server, project, asset, permission, user = name) # if any Safe Outputs were found in the inputs, include in the overview if (!is.null(safe_output_all) && nrow(safe_output_all)) { overview_data_all <- overview_data_all |> dplyr::left_join( safe_output_all, - by = c("server", "project", "table", "user") + by = c("server", "project", "asset", "user") ) } } else { @@ -261,7 +170,7 @@ rocrate_report.list <- function( report_contents <- c( .markdown_report_header(title, overview_data_all, overview_lst$diag_path), tidy_overview_tbl |> - # # tidy up duplicated values in `project` and `table` + # # tidy up duplicated values in `project` and `asset` # dplyr::mutate( # Project = unfill_vec(Project), # Data = unfill_vec(Data) @@ -387,9 +296,9 @@ rocrate_report.list <- function( #' overview's diagram (default: `NULL`, estimated based on number of nodes). #' @param max_line_length Integer with the maximum number of characters per line #' in the RO-Crate to be printed in the report. -#' @rdname rocrate_report +#' @rdname report #' @export -rocrate_report.rocrate <- function( +report.rocrate <- function( x, ..., title = "DataSHIELD Report", @@ -404,9 +313,9 @@ rocrate_report.rocrate <- function( max_line_length = 200 ) { # local bindings ---- - actionStatus <- description <- fx <- table <- NULL - encodingFormat <- id <- name <- project <- table_id <- table_name <- NULL - timestamp <- type <- user_id <- user <- NULL + actionStatus <- asset <- asset_id <- description <- fx <- NULL + encodingFormat <- id <- name <- permission <- perm_id <- project <- NULL + table_name <- timestamp <- type <- person_id <- user <- NULL # validate RO-Crate ---- rocrateR::is_rocrate(x) @@ -546,36 +455,31 @@ rocrate_report.rocrate <- function( ### extract (if available) table with user permissions user_perm_tbl <- flatten_user_perm_entity(user_perm_entity_lst) ### extract table with Safe People details - safe_people_tbl <- flatten_safe_people(safe_people_rocrate) |> - dplyr::rename(user_id = id) + safe_people_tbl <- flatten_safe_people(safe_people_rocrate) ### extract table with Safe Project details safe_project_tbl <- flatten_safe_project(safe_project_rocrate) ### extract table with Safe Data details - safe_data_tbl <- flatten_safe_data(safe_data_rocrate) |> - dplyr::rename(table_id = id, table_name = name) + safe_data_tbl <- flatten_safe_data(safe_data_rocrate) if (!is.null(user_perm_tbl) && nrow(user_perm_tbl) > 0) { overview_tbl <- user_perm_tbl |> # combine with Safe People details - dplyr::left_join(safe_people_tbl, by = "user_id") |> - # drop unused columns - dplyr::select(-id, -actionStatus, -description) |> + dplyr::left_join(safe_people_tbl, by = "person_id") |> + # subset columns of interest + dplyr::select(perm_id, person_id, name, asset_id, permission) |> # combine with Safe Data details - dplyr::left_join(safe_data_tbl, by = c("table_id")) |> + dplyr::left_join(safe_data_tbl, by = "asset_id") |> # combine with Safe Project details - dplyr::left_join(safe_project_tbl, by = c("table_name" = "table")) |> - # drop unused columns - dplyr::select(-id, -user_id, -table_id, -type) |> - dplyr::rename(table = table_name) + dplyr::left_join(safe_project_tbl, by = c("asset_id", "asset")) #|> + # # drop unused columns + # dplyr::select(-id, -person_id, -asset_id, -type) |> + # dplyr::rename(asset = asset_name) } else { overview_tbl <- flatten_safe_people(safe_people_rocrate) |> - dplyr::select(-id) |> - dplyr::bind_cols( - flatten_safe_project(safe_project_rocrate) |> - dplyr::select(-id), - flatten_safe_data(safe_data_rocrate) |> - dplyr::select(-id) |> - dplyr::rename(table = name) - ) + purrr::pmap(function(person_id, name, ...) { + tibble::tibble(person_id, name) |> + dplyr::bind_cols(flatten_safe_project(safe_project_rocrate)) + }) |> + purrr::list_c() } # attempt extracting list of functions executed by the users from Safe Outputs @@ -596,15 +500,15 @@ rocrate_report.rocrate <- function( safe_output_tbl_v2 <- safe_output_tbl |> dplyr::mutate( project = gsub("(?=\\.).*$", "", table, perl = TRUE), - table = gsub("^.*(?<=\\.)", "", table, perl = TRUE) + asset = gsub("^.*(?<=\\.)", "", table, perl = TRUE) ) |> - dplyr::distinct(project, table, user, fx, timestamp) + dplyr::distinct(project, asset, user, fx, timestamp) # append the list of functions to the overview table overview_tbl <- overview_tbl |> dplyr::left_join( safe_output_tbl_v2, - by = c("project" = "project", "table" = "table", "name" = "user") + by = c("project" = "project", "asset" = "asset", "name" = "user") ) |> # replace 'NA' in fx & timestamp with empty string dplyr::mutate( @@ -725,10 +629,30 @@ rocrate_report.rocrate <- function( ) } +.extract_srvr_comp <- function(x, component) { + tryCatch( + { + x |> + # extract each component per server + purrr::map(component) |> + # lapply(getElement, name = component) |> + # filter out NULL elements + purrr::keep(\(x) !is.null(x)) |> + # attach the server name as a new column + purrr::imap(~ dplyr::mutate(.x, server = .y)) |> + # combine rows + dplyr::bind_rows() + }, + error = function(e) { + NULL + } + ) +} + #' Create diagram for RO-Crate overview #' #' @param overview_tbl Data frame with overview details for the RO-Crate. -#' @inheritParams rocrate_report +#' @inheritParams report #' #' @returns Diagram object #' @keywords internal @@ -742,11 +666,11 @@ rocrate_report.rocrate <- function( diag_height ) { # local bindings - fx <- name <- permission <- project <- table <- NULL + fx <- name <- permission <- project <- asset <- NULL ## initialise `vars` and `labelvar` - vars <- c("project", "table") - labelvar <- c(project = "Project", table = "Data") + vars <- c("project", "asset") + labelvar <- c(project = "Project", asset = "Data") # check if `overview_tbl` has `permission` field AND include_user_perm = TRUE if ("permission" %in% colnames(overview_tbl) && include_user_perm) { @@ -853,8 +777,8 @@ rocrate_report.rocrate <- function( fx <- permission <- timestamp <- NULL ## initialise `vars` and `varslab` - vars <- c("project", "table") - varslab <- c("Project" = "project", "Data" = "table") + vars <- c("project", "asset") + varslab <- c("Project" = "project", "Data" = "asset") # check if `overview_tbl` has `permission` field AND include_user_perm = TRUE if ("permission" %in% colnames(overview_tbl) && include_user_perm) { @@ -924,7 +848,7 @@ rocrate_report.rocrate <- function( #' Generate Markdown report's header #' #' @inheritParams .overview_diagram -#' @inheritParams rocrate_report +#' @inheritParams report #' #' @returns String with report's header #' @keywords internal diff --git a/R/safe_people.R b/R/safe_people.R index fb19e7d..4426eb5 100644 --- a/R/safe_people.R +++ b/R/safe_people.R @@ -93,8 +93,9 @@ safe_people.opal <- function( validate_opal_con(x) # attempt to retrieve project entity + project_id <- id_hash("#project:", project) safe_project_entity <- rocrate |> - .get_entity(type = "Project") + .get_entity(id = project_id, type = "Project") # initialise empty user entity user_entity <- NULL @@ -108,6 +109,14 @@ safe_people.opal <- function( id = c(getElement(user, "@id"), getElement(user, "id")), type = "Person", name = c(getElement(user, "name"), getElement(user, "username")), + givenName = c( + getElement(user, "givenName"), + getElement(user, "firstname") + ), + familyName = c( + getElement(user, "familyName"), + getElement(user, "surname") + ), affiliation = list(`@id` = c(getElement(user, "affiliation"))) ) } else { diff --git a/R/safe_project.R b/R/safe_project.R index bbd550c..731d277 100644 --- a/R/safe_project.R +++ b/R/safe_project.R @@ -121,34 +121,86 @@ safe_project.opal <- function( # check if the given `project` exists project_exists(x, project = project) + # create project @id + project_id <- id_hash(project_id_suffix, project) + # retrieve details associated to `project` project_details_tbl <- opalr::opal.project(x, project) # filter out asset entities associated with the project based on the # value for `asset_id_suffix`. - project_asset_entities <- rocrate$`@graph` |> + crate_asset_entities <- rocrate$`@graph` |> purrr::keep(\(x) grepl(paste0("^", asset_id_suffix), x$`@id`)) # create project entity timestamps <- getElement(project_details_tbl, "timestamps") project_entity <- rocrateR::entity( - id = id_hash(project_id_suffix, project), + id = project_id, type = "Project", name = getElement(project_details_tbl, "name"), dateCreated = getElement(timestamps, "created"), dateModified = getElement(timestamps, "lastUpdate"), - hasPart = purrr::map(project_asset_entities, ~ list("@id" = .x$`@id`)) + hasPart = if (length(crate_asset_entities) > 0) { + purrr::map(crate_asset_entities, ~ list("@id" = .x$`@id`)) + } else { + NULL + } ) - # if no tables are associated to this project, then drop `hasPart` - if (length(project_asset_entities) == 0) { - project_entity$hasPart <- NULL - } - # add new project entity to the RO-Crate rocrate <- rocrate |> rocrateR::add_entity(project_entity, overwrite = TRUE) + # Opal permissions ---- + perms <- opalr::opal.get(x, "project", project, "permissions/project") + + project_users <- perms |> + purrr::map_chr( + ~ getElement(.x$subject, "principal"), + .default = NA_character_ + ) |> + stats::na.omit() + + # link existing Person entities ---- + people <- .get_entity(rocrate, type = "Person") + + if (!is.null(people)) { + for (p in people) { + user <- p$name + + if (user %in% project_users) { + rocrate <- append_entity_ref( + rocrate, + id = p[["@id"]], + key = "memberOf", + ref_id = project_id + ) + } + } + } + + # link existing asset entities ---- + # extract assets for the given project + project_tbl_assets <- get_project_assets(x, project, "tables") + project_res_assets <- get_project_assets(x, project, "resources") + # combine assets + proj_assets_tbl <- dplyr::bind_rows(project_tbl_assets, project_res_assets) + + # filter crate's assets based on the assets associated to the project + crate_asset_entities <- crate_asset_entities |> + purrr::keep(\(x) getElement(x, "name") %in% proj_assets_tbl$name) + + if (!is.null(crate_asset_entities) && length(crate_asset_entities) > 0) { + for (ass in crate_asset_entities) { + rocrate <- append_entity_ref( + rocrate, + id = ass[["@id"]], + key = "isPartOf", + ref_id = project_id + ) + } + } + # attach input arguments as attributes attr(rocrate, "connection") <- x attr(rocrate, "path") <- path diff --git a/R/safe_setting.R b/R/safe_setting.R index 7272781..c0d5aca 100644 --- a/R/safe_setting.R +++ b/R/safe_setting.R @@ -204,6 +204,7 @@ safe_setting.opal <- function( name = Package, version = Version, description = Description |> + gsub(pattern = "[[:space:]]+", replacement = " ") |> trimws() ) }) diff --git a/R/utils-audit.R b/R/utils-audit.R new file mode 100644 index 0000000..5be4ee8 --- /dev/null +++ b/R/utils-audit.R @@ -0,0 +1,38 @@ +audit_intent <- function(intent, excluded_args = c("project", "user"), ...) { + # if `intent` is NOT NULL, audit this object + intent_audit <- if (!is.null(intent)) { + audit(intent, ...) + } else { + NULL + } + + # list of additional args + main_audit_args <- list(...) + + # check if `intent_audit` is not NULL + if (!is.null(intent_audit)) { + # extract Project(s) and People from the `intent` audit crate + safe_project_tbl <- intent_audit |> + flatten_safe_project() + safe_people_tbl <- intent_audit |> + flatten_safe_people() + + main_audit_args <- exclude_args(main_audit_args, excluded = excluded_args) + + main_audit_args <- c( + main_audit_args, + project = safe_project_tbl$project, + user = safe_people_tbl$name + ) + } + + list(intent_audit = intent_audit, main_audit_args = main_audit_args) +} + +exclude_args <- function(..., excluded) { + # capture additional args + args <- list(...) + arg_names <- names(args) + # exclude args that shouldn't be passed to the next function + args[!(arg_names %in% excluded)] +} diff --git a/R/utils-connection.R b/R/utils-connection.R index 8273ab6..95dacce 100644 --- a/R/utils-connection.R +++ b/R/utils-connection.R @@ -86,10 +86,9 @@ project_exists <- function(x, ...) { project_exists.opal <- function(x, ..., project) { if (!opalr::opal.project_exists(x, project)) { stop( - paste0( - "The given `project = '", - project, - "'` was not found in the given Opal connection!" + sprintf( + "The `project = '%s'` was not found in the given Opal connection!", + project ), call. = FALSE ) @@ -109,10 +108,9 @@ setMethod( ) { if (!(project %in% MolgenisArmadillo::armadillo.list_projects())) { stop( - paste0( - "The given `project = '", - project, - "'` was not found in the given Armadillo connection!" + sprintf( + "The `project = '%s'` was not found in the given Armadillo connection!", + project ), call. = FALSE ) diff --git a/R/utils-cr8tor.R b/R/utils-cr8tor.R index 5045db5..7444268 100644 --- a/R/utils-cr8tor.R +++ b/R/utils-cr8tor.R @@ -17,7 +17,7 @@ add_safe_people_entities_cr8tor <- function(rc, people_tbl, membership_tbl) { for (i in seq_len(nrow(people_tbl))) { p <- people_tbl[i, ] - person_id <- paste0("#person:", digest::digest(p$username)) + person_id <- id_hash("#person:", p$username) # projects user belongs to projs <- membership_tbl |> @@ -26,20 +26,17 @@ add_safe_people_entities_cr8tor <- function(rc, people_tbl, membership_tbl) { unique() memberOf <- purrr::map(projs, \(pr) { - list(`@id` = paste0("#project:", pr)) + list(`@id` = id_hash("#project:", pr)) }) - display_name <- paste(p$given_name, p$family_name) - if (is.na(display_name) || trimws(display_name) == "") { - display_name <- p$username - } - rc <- rc |> rocrateR::add_entity( rocrateR::entity( id = person_id, type = "Person", - name = display_name, + name = p$username, + givenName = p$given_name, + familyName = p$family_name, email = p$email, affiliation = p$affiliation, memberOf = memberOf @@ -58,35 +55,34 @@ add_safe_people_entities_cr8tor <- function(rc, people_tbl, membership_tbl) { #' @param proj_tbl Tibble with project metadata. #' Columns: id, name. #' @param data_tbl Tibble with dataset metadata. -#' Columns: project, table. +#' Columns: project, asset. #' #' @return Updated RO-Crate #' @noRd add_safe_project_entities_cr8tor <- function(rc, proj_tbl, data_tbl) { # local bindings - project <- NULL + asset <- project <- NULL now <- format(Sys.time(), "%Y-%m-%dT%H:%M:%OS3Z", tz = "UTC") for (i in seq_len(nrow(proj_tbl))) { - project_id <- proj_tbl$id[i] - project_name <- proj_tbl$name[i] - project_eid <- paste0("#project:", project_id) + project_id <- proj_tbl$project_id[i] + project <- proj_tbl$name[i] ds_ids <- data_tbl |> dplyr::filter(project == project_id) |> - dplyr::pull(table) |> + dplyr::pull(asset) |> unique() |> - (\(x) paste0("#dataset:", x))() + (\(x) id_hash("#asset:", paste0(project, x)))() has_part <- purrr::map(ds_ids, \(x) list(`@id` = x)) rc <- rc |> rocrateR::add_entity( rocrateR::entity( - id = project_eid, + id = id_hash("#project:", project), type = "Project", - name = project_name, + name = project, dateCreated = now, dateModified = now, hasPart = has_part @@ -104,21 +100,21 @@ add_safe_project_entities_cr8tor <- function(rc, proj_tbl, data_tbl) { #' #' @param rc RO-Crate object, see [rocrateR::rocrate]. #' @param tbl Tibble with dataset metadata. -#' Columns: project, table. +#' Columns: project, asset. #' #' @return Updated RO-Crate #' @noRd add_safe_data_entities_cr8tor <- function(rc, tbl) { for (i in seq_len(nrow(tbl))) { - dataset_id <- paste0("#dataset:", tbl$table[i]) - project_id <- paste0("#project:", tbl$project[i]) + asset_id <- id_hash("#asset:", paste0(tbl$project[i], tbl$asset[i])) + project_id <- id_hash("#project:", tbl$project[i]) rc <- rc |> rocrateR::add_entity( rocrateR::entity( - id = dataset_id, + id = asset_id, type = "Dataset", - name = tbl$table[i], + name = tbl$asset[i], isPartOf = list(`@id` = project_id) ) ) @@ -140,7 +136,7 @@ add_safe_data_entities_cr8tor <- function(rc, tbl) { add_group_entities_cr8tor <- function(rc, groups_tbl) { for (i in seq_len(nrow(groups_tbl))) { g <- groups_tbl[i, ] - gid <- paste0("#group:", g$group_id) + gid <- id_hash("#group:", g$group_id) rc <- rc |> rocrateR::add_entity( @@ -149,7 +145,7 @@ add_group_entities_cr8tor <- function(rc, groups_tbl) { type = "Organization", name = g$group_id, description = g$description, - parentOrganization = list(`@id` = paste0("#project:", g$project)) + parentOrganization = list(`@id` = id_hash("#project:", g$project)) ) ) } @@ -159,12 +155,12 @@ add_group_entities_cr8tor <- function(rc, groups_tbl) { #' Add Permission entities (Opal-level) #' -#' Expands user-table permission matrix into RO-Crate Action entities. +#' Expands user-asset permission matrix into RO-Crate Action entities. #' Uses `user_perm_entity()` to generate Read/Write/Control actions. #' #' @param rc RO-Crate object, see [rocrateR::rocrate]. #' @param perm_expanded_tbl Tibble with user permissions expanded. -#' Columns: username, table, permission. +#' Columns: username, asset, permission. #' #' @return Updated RO-Crate #' @noRd @@ -172,14 +168,14 @@ add_permission_entities_cr8tor <- function(rc, perm_expanded_tbl) { for (i in seq_len(nrow(perm_expanded_tbl))) { row <- perm_expanded_tbl[i, ] - user_id <- paste0("#person:", digest::digest(row$username)) - table_id <- paste0("#dataset:", row$table) + person_id <- id_hash("#person:", row$username) + asset_id <- id_hash("#asset:", paste0(row$project, row$asset)) ents <- user_perm_entity( - user = row$username, - user_id = user_id, - table = row$table, - table_id = table_id, + person = row$username, + person_id = person_id, + asset = row$asset, + asset_id = asset_id, permission = row$permission ) @@ -200,7 +196,7 @@ add_safe_setting_entities_cr8tor <- function(rc, tbl) { rc <- rc |> rocrateR::add_entity( rocrateR::entity( - id = paste0("#setting:", nm), + id = id_hash("#setting:", nm), type = "PropertyValue", name = nm, value = as.character(tbl[[i]]) @@ -227,14 +223,84 @@ add_safe_output_entities_cr8tor <- function(rc, tbl) { ) } -#' Keep strongest permission per user-table pair +#' Convert audit result into RO-Crate +#' +#' @param audit list returned by audit.cr8tor() +#' @return rocrate object +#' @noRd +as_rocrate_audit <- function(audit) { + rc <- rocrateR::rocrate_5s() + + # Root dataset describing the audit + rc <- rc |> + rocrateR::add_entity_value( + "./", + "name", + "cr8tor 5 Safes Audit", + overwrite = TRUE + ) |> + rocrateR::add_entity_value( + "./", + "description", + "Audit report generated from cr8tor archive", + overwrite = TRUE + ) + + # ---- Safe People ---- + rc <- rc |> + add_group_entities_cr8tor(audit$groups) |> + add_safe_people_entities_cr8tor( + audit$safe_people$users, + audit$user_projects + ) |> + link_people_to_root(audit$safe_people$users$username) + + # ---- Safe Projects ---- + rc <- rc |> + add_safe_project_entities_cr8tor( + audit$safe_projects, + audit$safe_data$assets + ) + + # ---- Safe Data ---- + rc <- rc |> + add_safe_data_entities_cr8tor(audit$safe_data$assets) + + # ---- Permissions ---- + rc <- rc |> + add_permission_entities_cr8tor( + expand_group_permissions_to_users( + perm_tbl = audit$permissions |> + dplyr::left_join( + audit$safe_projects, + by = c("project" = "project_id") + ), + membership_tbl = audit$user_groups, + data_tbl = audit$safe_data$assets + ) |> + dedupe_effective_permissions() + ) + + # ---- Safe Settings ---- + rc <- rc |> + add_safe_setting_entities_cr8tor(audit$safe_settings) + + # ---- Safe Outputs ---- + rc <- rc |> + add_safe_output_entities_cr8tor(audit$safe_outputs) + + rc +} + + +#' Keep strongest permission per user-asset pair #' -#' @param perm_tbl Tibble with username, table, permission. +#' @param perm_tbl Tibble with username, asset, permission. #' @return Deduplicated tibble #' @noRd dedupe_effective_permissions <- function(perm_tbl) { # local bindings - permission <- strength <- username <- NULL + asset <- permission <- strength <- username <- NULL strength_order <- c( "view", "view-values", @@ -247,13 +313,13 @@ dedupe_effective_permissions <- function(perm_tbl) { dplyr::mutate( strength = match(permission, strength_order) ) |> - dplyr::group_by(username, table) |> + dplyr::group_by(username, asset) |> dplyr::slice_max(strength, n = 1, with_ties = FALSE) |> dplyr::ungroup() |> dplyr::select(-strength) } -#' Expand cr8tor group permissions to user-table permissions +#' Expand cr8tor group permissions to user-asset permissions #' #' Converts: #' Group → Project permissions @@ -264,7 +330,7 @@ dedupe_effective_permissions <- function(perm_tbl) { #' @param membership_tbl Tibble from extract_user_groups_cr8tor(). #' @param data_tbl Tibble from extract_safe_data_cr8tor()$tables. #' -#' @return Tibble with username, project, table, permission +#' @return Tibble with username, project, asset, permission #' @noRd expand_group_permissions_to_users <- function( perm_tbl, @@ -272,7 +338,7 @@ expand_group_permissions_to_users <- function( data_tbl ) { # local bindings - project <- role <- user <- username <- NULL + asset <- name <- project <- role <- user <- username <- NULL # map cr8tor role to Opal permission role_to_permission <- function(role) { @@ -305,10 +371,11 @@ expand_group_permissions_to_users <- function( # 3. Map roles to permissions perm_tables |> + dplyr::mutate(project = name) |> dplyr::transmute( username, project, - table, + asset, permission = role_to_permission(role) ) |> dplyr::distinct() @@ -363,9 +430,9 @@ extract_integrity_cr8tor <- function(bundle) { extract_lineage_cr8tor <- function(bundle) { users <- extract_safe_people_cr8tor(bundle)$users$username proj <- extract_safe_projects_cr8tor(bundle)$name - tables <- extract_safe_data_cr8tor(bundle)$tables$table + assets <- extract_safe_data_cr8tor(bundle)$assets$asset - expand.grid(user = users, project = proj, table = tables) + expand.grid(user = users, project = proj, asset = assets) } #' Extract Safe Data (datasets & tables) @@ -383,9 +450,11 @@ extract_safe_data_cr8tor <- function(bundle) { } ing_yaml <- yaml::yaml.load(paste(ing$content[[1]], collapse = "\n")) + # extract groups and projects + group_tbl <- extract_groups_cr8tor(bundle) proj_tbl <- extract_safe_projects_cr8tor(bundle) - tables <- ing_yaml$datasets |> + assets <- ing_yaml$datasets |> purrr::map(function(ds) { # project name from locations yaml_project <- ds$locations[[1]]$opal_project_name %||% NA_character_ @@ -396,7 +465,7 @@ extract_safe_data_cr8tor <- function(bundle) { tibble::tibble( project = project_id, dataset = ds$name, - table = tbl$name, + asset = tbl$name, n_cols = length(tbl$columns) ) }) |> @@ -405,8 +474,8 @@ extract_safe_data_cr8tor <- function(bundle) { purrr::list_c() tibble::tibble( - tables = tables, - n_tables = nrow(tables) + assets = assets, + n_assets = nrow(assets) ) } @@ -488,6 +557,13 @@ extract_user_groups_cr8tor <- function(bundle) { } extract_user_projects_cr8tor <- function(bundle) { + # local bindings + description <- group_id <- NULL + # extract group details + grp_tbl <- extract_groups_cr8tor(bundle) + # extract project details + prj_tbl <- extract_projects_cr8tor(bundle) + # extract user docs user_docs <- bundle$resources[ grepl("user-.*\\.ya?ml$", names(bundle$resources)) ] @@ -499,16 +575,20 @@ extract_user_projects_cr8tor <- function(bundle) { if (length(groups) == 0) { return(NULL) } - purrr::map(groups, function(g) { - grp <- g$value - project <- sub("-(admin|analyst)$", "", grp) + usr_grp_tbl <- purrr::map(groups, function(g) { tibble::tibble( username = username, - project = project + group_id = g$value ) }) |> purrr::list_c() + + # combine user x groups x projects + usr_grp_tbl |> + dplyr::left_join(grp_tbl, by = "group_id") |> + dplyr::select(-description, -group_id) |> + dplyr::left_join(prj_tbl, by = "project") }) |> purrr::list_c() |> dplyr::distinct() @@ -589,6 +669,20 @@ extract_permissions_cr8tor <- function(bundle) { purrr::list_c() } +extract_projects_cr8tor <- function(bundle) { + prj_docs <- bundle$resources[ + grepl("project-.*\\.ya?ml$", names(bundle$resources)) + ] + + purrr::map(prj_docs, function(doc) { + tibble::tibble( + project = doc$metadata$name, + description = doc$spec$description %||% NA_character_ + ) + }) |> + purrr::list_c() +} + #' Extract Safe Projects #' #' @param bundle cr8tor_bundle @@ -600,12 +694,16 @@ extract_safe_projects_cr8tor <- function(bundle) { # deployment spec proj_docs <- bundle$resources[ - grepl("project-.*\\.ya?ml$", names(bundle$resources)) + grepl( + sprintf("project-%s.*\\.ya?ml$", proj$identifier), + names(bundle$resources) + ) ] tibble::tibble( - id = proj$identifier %||% proj$name %||% NA_character_, - name = proj$name %||% NA_character_, + project_id = proj$identifier, + name = proj$identifier %||% NA_character_, + description = proj$name %||% NA_character_, uuid = proj$`@id` %||% NA_character_, deployment_defined = length(proj_docs) > 0 ) @@ -655,7 +753,7 @@ find_bagit_root <- function(path) { link_people_to_root <- function(rc, usernames) { authors <- lapply(usernames, \(u) { - list(`@id` = paste0("#person:", digest::digest(u))) + list(`@id` = id_hash("#person:", u)) }) rocrateR::add_entity_value( @@ -674,20 +772,20 @@ link_people_to_root <- function(rc, usernames) { #' * resources/ → deployment & governance YAML specs. #' * config.toml → platform configuration. #' -#' @param path Path to cr8tor ZIP archive. +#' @param x Path to cr8tor ZIP archive. #' @param ... Unused. #' #' @return Object of class `cr8tor`. -#' @export -load_cr8tor_bundle <- function(path, ...) { +#' @keywords internal +load_cr8tor_bundle <- function(x, ...) { tmp <- tempfile("cr8tor_") dir.create(tmp, recursive = TRUE, showWarnings = FALSE) - utils::unzip(path, exdir = tmp) + utils::unzip(x, exdir = tmp) # load RO-Crate layer rocrate <- rocrateR::load_rocrate( - path, #file.path(tmp, "bagit"), + x, #file.path(tmp, "bagit"), load_content = TRUE, ... ) @@ -719,11 +817,14 @@ load_cr8tor_bundle <- function(path, ...) { config = config, root = tmp_root ), - class = "cr8tor" + class = c("cr8tor", "cr8tor_bundle", "list") ) } -map_project_name_to_id <- function(project_name, proj_tbl) { - idx <- which(proj_tbl$name == project_name) - if (length(idx)) proj_tbl$id[idx[1]] else project_name +map_project_name_to_id <- function(project, proj_tbl) { + idx <- which(proj_tbl$description == project) + if (length(idx) == 0) { + idx <- which(proj_tbl$name == project) + } + if (length(idx)) proj_tbl$project_id[idx[1]] else project } diff --git a/R/utils-opal.R b/R/utils-opal.R index 64f26fc..cce520f 100644 --- a/R/utils-opal.R +++ b/R/utils-opal.R @@ -1,3 +1,15 @@ +#' Add Asset Permissions to RO-Crate +#' +#' Creates a Permission entity describing dataset-level access for a person +#' within a project and links it to the relevant assets. +#' +#' @param rocrate RO-Crate object, see [rocrateR::rocrate]. +#' @param project Project identifier. +#' @param person User identifier. +#' @param assets Vector of strings with asset identifiers. +#' +#' @return Updated RO-Crate. +#' #' @noRd add_asset_permissions_to_crate <- function( rocrate, @@ -12,41 +24,37 @@ add_asset_permissions_to_crate <- function( asset_id <- id_lookup[[asset$name]] asset_type <- asset$asset_type - perms <- get_asset_permissions( - x, - project, - asset_type, - asset$name - ) + # retrieve asset permissions + perms <- get_asset_permissions(x, project, asset_type, asset$name) if (is.null(perms)) { next } + # iterate through person permissions for an asset for (j in seq_len(nrow(perms))) { - user <- perms$user[j] + person <- perms$person[j] permission <- perms$permission[j] - user_id <- id_hash("#user:", user) + person_id <- id_hash("#person:", person) # create permission entities perm_ents <- user_asset_perm_entities( - user = user, - user_id = user_id, - asset_name = asset$name, + person = person, + person_id = person_id, + asset = asset$name, asset_id = asset_id, permission = permission, asset_type = asset_type ) # add permission entities - for (ent in perm_ents) { - rocrate <- rocrateR::add_entity( - rocrate, - ent, - overwrite = TRUE - ) - } + rocrate <- purrr::reduce( + perm_ents, + rocrateR::add_entity, + overwrite = TRUE, + .init = rocrate + ) } } @@ -126,6 +134,27 @@ get_asset_permissions <- function(x, project, asset_type, name) { perms <- opalr::opal.table_perm(x, project, name) } else { perms <- opalr::opal.resource_perm(x, project, name) + if (is.null(perms) || nrow(perms) == 0) { + # if the previous call returns NULL, then attempt a different approach + perms_lst <- x |> + opalr::opal.get("project", project, "permissions/resources") + # derive permissions at Project level + perms <- tryCatch( + perms_lst |> + purrr::map(function(p) { + tibble::tibble( + subject = p$subject$principal, + resource = p$resource, + permission = purrr::list_c(p$actions) |> + gsub(pattern = "RESOURCES_", replacement = "") |> + gsub(pattern = "ALL", replacement = "administrate") |> + tolower() + ) + }), + error = function(e) NULL + ) |> + purrr::list_c() + } } if (is.null(perms) || length(perms$subject) == 0) { @@ -133,7 +162,7 @@ get_asset_permissions <- function(x, project, asset_type, name) { } tibble::tibble( - user = perms$subject, + person = perms$subject, permission = perms$permission ) } @@ -344,7 +373,7 @@ get_table_permissions <- function(x, project, tables) { #' @keywords internal flatten_user_perm_entity <- function(x) { # local bindings - agent <- object <- table_id <- user_id <- NULL + agent <- object <- asset_id <- person_id <- NULL # flatten list of entities x_tbl <- x |> @@ -357,14 +386,14 @@ flatten_user_perm_entity <- function(x) { # edit tibble with entities x_tbl |> dplyr::rename( - id = "@id", + perm_id = "@id", type = "@type", - user_id = agent, - table_id = object + person_id = agent, + asset_id = object ) |> dplyr::mutate( - user_id = unlist(user_id), - table_id = unlist(table_id), + person_id = unlist(person_id), + asset_id = unlist(asset_id), ) |> # add permission field, based on `type` dplyr::mutate( @@ -563,9 +592,9 @@ update_project_datasets <- function(rocrate, project, ds_ids) { #' @noRd user_asset_perm_entities <- function( - user, - user_id, - asset_name, + person, + person_id, + asset, asset_id, permission, asset_type = c("table", "resource") @@ -574,35 +603,42 @@ user_asset_perm_entities <- function( # reuse existing function by aliasing "table" args user_perm_entity( - user = user, - user_id = user_id, - table = asset_name, - table_id = asset_id, + person = person, + person_id = person_id, + asset = asset, + asset_id = asset_id, permission = permission ) } -#' Create user permission entities +#' Create user/person permission entities #' -#' @param user String with user name. -#' @param user_id String with user `@id`. -#' @param table String with dataset/table name. -#' @param table_id String with dataset/table `@id`. +#' @param person String with person name/username. +#' @param person_id String with person `@id`. +#' @param asset String with dataset/table/resource name. +#' @param asset_id String with dataset/table `@id`. #' @param permission String with permission ('view', 'view-values', 'edit', #' 'edit-values' OR 'administrate'). #' @param ... Other additional values. #' #' @returns List of [rocrateR::entity] objects #' @keywords internal -user_perm_entity <- function(user, user_id, table, table_id, permission, ...) { +user_perm_entity <- function( + person, + person_id, + asset, + asset_id, + permission, + ... +) { # set local bindings description <- type <- NULL action_status <- "PotentialActionStatus" - agent <- list(list(`@id` = user_id)) - object <- list(list(`@id` = table_id)) + agent <- list(list(`@id` = person_id)) + object <- list(list(`@id` = asset_id)) # create combined @id - comb_id <- paste0("#perm:", digest::digest(paste0(user, "-", table))) + comb_id <- id_hash("#perm:", paste0(person, "-", asset)) # update combine @id, @type and description based on permission if (permission == "view") { diff --git a/R/utils-rocrate.R b/R/utils-rocrate.R index a151a47..845aff3 100644 --- a/R/utils-rocrate.R +++ b/R/utils-rocrate.R @@ -42,72 +42,3 @@ load_content <- function(rocrate, roc_path) { return(rocrate) } - -#' Load RO-Crate from file -#' -#' @param x String with path to RO-Crate -#' -#' @returns RO-Crate object. -#' @keywords internal -load_rocrate <- function(x) { - # check if the given file, `x`, exists - if (!file.exists(x)) { - stop("The given file:\n `", x, "`\nis not a valid path!", call. = FALSE) - } - - # initialise local variables - roc_path <- rocrate <- NULL - - # check if the given path points to an RO-Crate bag (zip file) - if (grepl("zip$", tolower(x))) { - # create temp directory to extract contents of RO-Crate - tempdir_name <- file.path(tempdir(), digest::digest(Sys.time())) - dir.create(tempdir_name, recursive = TRUE) - on.exit(unlink(tempdir_name, force = TRUE, recursive = TRUE)) - - # unbag RO-Crate - roc_path <- tryCatch( - { - rocrateR::unbag_rocrate(x, output = tempdir_name, quiet = TRUE) - }, - error = function(e) { - x - } - ) - } else { - roc_path <- x - } - - # load RO-Crate - rocrate <- tryCatch( - { - # list JSON files, if roc_path points to a directory - if (dir.exists(roc_path)) { - roc_path_files <- list.files( - roc_path, - pattern = "[json|JSON]$", - recursive = TRUE, - full.names = TRUE - ) - roc_path_files <- roc_path_files[1] - roc_path <- dirname(roc_path_files) - } else { - roc_path_files <- roc_path - roc_path <- dirname(roc_path) - } - rocrateR::read_rocrate(roc_path_files) - }, - error = function(e) { - NULL - } - ) - - # check if the RO-Crate was loaded correctly - if (is.null(rocrate)) { - stop("Unable to load an RO-Crate from the given file:\n `", x, "`") - } - - # check if any of the entities with `@type = 'File'` have empty `content` - rocrate <- rocrate |> - load_content(roc_path = roc_path) -} diff --git a/R/utils-rocrateR.R b/R/utils-rocrateR.R index f819722..7cd0e7c 100644 --- a/R/utils-rocrateR.R +++ b/R/utils-rocrateR.R @@ -1,3 +1,33 @@ +#' Append entity reference +#' +#' Adds a reference to an existing property while +#' preserving existing values and avoiding duplicates. +#' +#' @keywords internal +append_entity_ref <- function(rocrate, id, key, ref_id) { + entity <- rocrateR::get_entity(rocrate, id = id)[[1]] + + current <- entity[[key]] + + existing_ids <- if (!is.null(current)) { + getElement(current, "@id") %||% purrr::map_chr(current, "@id") + } else { + character() + } + + ids <- unique(c(existing_ids, ref_id)) + + value <- purrr::map(ids, ~ list("@id" = .x)) + + rocrateR::add_entity_value( + rocrate, + id = id, + key = key, + value = value, + overwrite = TRUE + ) +} + #' Wrapper for [rocrateR::get_entity()] #' #' This wrapper is used to suppress warning messages like diff --git a/R/utils-safe_data.R b/R/utils-safe_data.R index 553c759..5b3765d 100644 --- a/R/utils-safe_data.R +++ b/R/utils-safe_data.R @@ -17,26 +17,17 @@ extract_safe_data.opal <- function(x, ..., rocrate = rocrateR::rocrate_5s()) { # extract all data sources ds <- opalr::opal.datasources(x) - # cycle through the data source (x) and data project details - for (i in seq_len(nrow(ds))) { - project_name <- ds[i, "name"] - project_tables <- get_project_tables(x, project_name) - if ( - !is.na(project_name) && - !is.null(project_name) && - length(project_tables) > 0 - ) { - rocrate <- rocrate |> - safe_data( - project = project_name, - tables = project_tables, - connection = x - ) - } - } + # extract project names and ignore NAs + projects <- ds$name[!is.na(ds$name) & !is.null(ds$name)] - # return RO-Crate with Safe Data details - return(rocrate) + # create RO-Crate with Safe Data details for the projects found on the server + purrr::reduce( + projects, + \(crate, p) { + safe_data(crate, connection = x, project = p) + }, + .init = rocrate + ) } #' @param id (Optional) Vector with `@id` strings for Safe Data entity(ies) @@ -47,46 +38,39 @@ extract_safe_data.rocrate <- function( x, ..., id = NULL, + asset_id_suffix = "#asset:", rocrate = rocrateR::rocrate_5s() ) { # validate RO-Crate rocrateR::is_rocrate(x) - # extract Dataset entities - entities_lst <- .get_entity(x, type = "Dataset") + # filter out asset entities associated with the project based on the + # value for `asset_id_suffix`. + entities_lst <- x$`@graph` |> + purrr::keep(\(x) grepl(paste0("^", asset_id_suffix), x$`@id`)) # if `id` was provided, then filter out only those entities if (!is.null(id)) { - idx <- entities_lst |> - sapply(\(x) getElement(x, "@id") %in% id) - entities_lst <- entities_lst[idx] + entities_lst <- entities_lst |> + purrr::keep(\(x) getElement(x, "@id") %in% id) } - # remove root entity, ./ - idx <- entities_lst |> - sapply(\(x) getElement(x, "@id") == "./") - entities_lst[idx] <- NULL - # check if any entities were found if (length(entities_lst) == 0) { stop("No matching entities were found!", call. = FALSE) } else { message( length(entities_lst), - " 'Dataset' entit", + " asset entit", ifelse(length(entities_lst) == 1, "y was", "ies were"), " found!" ) } - # add Dataset entities to the RO-Crate + # add entities to the RO-Crate suppressWarnings({ - rocrate <- rocrate |> - rocrateR::add_entity(entities_lst, verbose = FALSE) + purrr::reduce(entities_lst, rocrateR::add_entity, .init = rocrate) }) - - # return RO-Crate with the Safe Data details - return(rocrate) } #' Flatten object with Safe Data details @@ -96,7 +80,7 @@ extract_safe_data.rocrate <- function( #' @param id Vector of strings with the `@id`s for the datasets to be extracted. #' If not provided, extract all entities with `@type = 'Dataset'`. #' -#' @returns Data frame with safe data details. +#' @returns Data frame with Safe Data details. #' @rdname flatten_safe_data #' @keywords internal flatten_safe_data <- function(x, ...) { @@ -111,28 +95,36 @@ flatten_safe_data.default <- function(x, ...) { #' @rdname flatten_safe_data #' @export -flatten_safe_data.rocrate <- function(x, ..., id = NULL) { +flatten_safe_data.rocrate <- function( + x, + ..., + id = NULL, + asset_id_suffix = "#asset:" +) { + # local bindings + asset_id <- person_id <- NULL + tryCatch( { - # extract Dataset entities - entities_tbl <- x |> - .get_entity(type = "Dataset") |> + # extract asset entities + entities_tbl <- x$`@graph` |> + purrr::keep(\(x) grepl(paste0("^", asset_id_suffix), x$`@id`)) |> # extract @id and name for each entity lapply(function(ent) { tibble::tibble( - id = getElement(ent, "@id"), - name = getElement(ent, "name") + asset_id = getElement(ent, "@id"), + asset = getElement(ent, "name") ) }) |> # combine all rows dplyr::bind_rows() |> # filter out root (./) entity - dplyr::filter(id != "./") + dplyr::filter(asset_id != "./") # if `id` is provided, then only keep those entities if (!is.null(id)) { entities_tbl <- entities_tbl |> - dplyr::filter(id %in% !!id) + dplyr::filter(asset_id %in% !!id) } # return dataset entities diff --git a/R/utils-safe_people.R b/R/utils-safe_people.R index da607cf..98494d1 100644 --- a/R/utils-safe_people.R +++ b/R/utils-safe_people.R @@ -125,6 +125,8 @@ flatten_safe_people.default <- function(x, ...) { #' @rdname flatten_safe_people #' @export flatten_safe_people.rocrate <- function(x, ..., id = NULL) { + # local bindings + person_id <- NULL tryCatch( { # extract Person entities @@ -133,7 +135,7 @@ flatten_safe_people.rocrate <- function(x, ..., id = NULL) { # extract @id and name for each entity lapply(function(ent) { tibble::tibble( - id = getElement(ent, "@id"), + person_id = getElement(ent, "@id"), name = getElement(ent, "name"), given_name = c( getElement(ent, "givenName"), @@ -152,7 +154,7 @@ flatten_safe_people.rocrate <- function(x, ..., id = NULL) { # if `id` is provided, then only keep those entities if (!is.null(id)) { entities_tbl <- entities_tbl |> - dplyr::filter(id %in% !!id) + dplyr::filter(person_id %in% !!id) } # return dataset entities diff --git a/R/utils-safe_project.R b/R/utils-safe_project.R index 12c025b..1437d1e 100644 --- a/R/utils-safe_project.R +++ b/R/utils-safe_project.R @@ -24,11 +24,11 @@ extract_safe_project.opal <- function( # cycle through the data source (x) and extract project details for (i in seq_len(nrow(ds))) { - project_name <- ds[i, "name"] - if (!is.na(project_name) && !is.null(project_name)) { + project <- ds[i, "name"] + if (!is.na(project) && !is.null(project)) { suppressWarnings({ rocrate <- rocrate |> - safe_project(project = project_name, connection = x) + safe_project(project = project, connection = x) }) } } @@ -135,26 +135,27 @@ flatten_safe_project.rocrate <- function(x, ..., y = x) { has_part <- getElement(ent, "hasPart") |> unlist() project_id <- getElement(ent, "@id") - project_name <- getElement(ent, "name") + project <- getElement(ent, "name") - # if the current Project entity has any Datasets, extract them + # if the current Project entity has any assets, extract them if (!is.null(has_part)) { - # extract datasets by @id - table_names <- y |> - flatten_safe_data(id = has_part) |> - # extract names - getElement("name") - - return(tibble::tibble( - id = project_id, - project = project_name, - table = table_names - )) + # extract assets by @id + assets_tbl <- y |> + flatten_safe_data(id = has_part) + + proj_assets_tbl <- tibble::tibble( + project_id = project_id, + project = project + ) |> + dplyr::bind_cols(assets_tbl) + + return(proj_assets_tbl) } return(tibble::tibble( - id = project_id, - project = project_name, - table = NA + project_id = project_id, + project = project, + asset_id = NA, + asset = NA )) }) |> dplyr::bind_rows() diff --git a/README.Rmd b/README.Rmd index 15f4b8c..2b8f087 100644 --- a/README.Rmd +++ b/README.Rmd @@ -67,12 +67,12 @@ rocrate_txt <- function(rocrate) { } ``` -# dsROCrate: 'DataSHIELD' RO-Crate Wrapper Functions logo +# dsROCrate: 'DataSHIELD' RO-Crate Governance Functions logo [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) [![CRAN status](https://www.r-pkg.org/badges/version/dsROCrate)](https://CRAN.R-project.org/package=dsROCrate) -[![Codecov test coverage](https://codecov.io/gh/DataSHIELD-5S/dsROCrate/graph/badge.svg)](https://app.codecov.io/gh/DataSHIELD-5S/dsROCrate) +[![Codecov test coverage](https://codecov.io/gh/FederatedMethods/dsROCrate/graph/badge.svg)](https://app.codecov.io/gh/FederatedMethods/dsROCrate) The goal of dsROCrate is to provide functions to wrap elements from a @@ -80,11 +80,11 @@ The goal of dsROCrate is to provide functions to wrap elements from a ## 1. Installation -You can install the development version of dsROCrate from [GitHub](https://github.com/DataSHIELD-5S/dsROCrate/) with: +You can install the development version of dsROCrate from [GitHub](https://github.com/FederatedMethods/dsROCrate/) with: ``` r # install.packages("pak") -pak::pak("DataSHIELD-5S/dsROCrate") +pak::pak("FederatedMethods/dsROCrate") ``` ## 2. Creating your first RO-Crate @@ -394,7 +394,7 @@ safe_people_crate_v1 <- opalr::opal.login( password = USERPASS, url = SERVER ) |> - dsROCrate::audit_safe_people(user = "dsuser", project = "CNSIM") + dsROCrate::audit(user = "dsuser", project = "CNSIM") print(safe_people_crate_v1) ``` @@ -402,7 +402,7 @@ print(safe_people_crate_v1) ###### Markdown report A markdown report can be created with an overview and details for an RO-Crate, -using the `dsROCrate::rocrate_report`: +using the `dsROCrate::report`: **Only generate .Rmd file** @@ -410,7 +410,7 @@ using the `dsROCrate::rocrate_report`: safe_people_crate_v1_rmd <- tempfile(fileext = ".Rmd") # temporary file safe_people_crate_contents <- safe_people_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_people_crate_v1_rmd, render = FALSE) + dsROCrate::report(filepath = safe_people_crate_v1_rmd, render = FALSE) # display Overview diagram safe_people_crate_contents$overview_diagram @@ -424,34 +424,12 @@ safe_people_crate_contents$overview_data |> ```{r, eval = FALSE} safe_people_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_people_crate_v1_rmd, + dsROCrate::report(filepath = safe_people_crate_v1_rmd, title = "DataSHIELD Safe People - Audit Report", render = TRUE, overwrite = TRUE) ``` -##### List all accessible projects & tables for an user -```{r safe_people_crate_audit_v2, warning=FALSE} -safe_people_crate_v2 <- opalr::opal.login( - username = USERNAME, - password = USERPASS, - url = SERVER -) |> - dsROCrate::audit_safe_people(user = "dsuser") - -safe_people_crate_v2_rmd <- tempfile(fileext = ".Rmd") # temporary file - -safe_people_crate_contents_v2 <- safe_people_crate_v2 |> - dsROCrate::rocrate_report(filepath = safe_people_crate_v2_rmd, render = FALSE) - -# display Overview diagram -safe_people_crate_contents_v2$overview_diagram - -# display Overview data (Safe People, Safe Projects and Safe Data) -safe_people_crate_contents_v2$overview_data |> - knitr::kable() -``` - ### 3.2. Audit Project ##### List users and dataset/table level permissions within a project @@ -461,7 +439,7 @@ safe_project_crate_v1 <- opalr::opal.login( password = USERPASS, url = SERVER ) |> - dsROCrate::audit_safe_project(project = "CNSIM") + dsROCrate::audit(project = "CNSIM") print(safe_project_crate_v1) ``` @@ -469,7 +447,7 @@ print(safe_project_crate_v1) ###### Markdown report A markdown report can be created with an overview and details for an RO-Crate, -using the `dsROCrate::rocrate_report`: +using the `dsROCrate::report`: **Only generate .Rmd file** @@ -477,7 +455,7 @@ using the `dsROCrate::rocrate_report`: safe_project_crate_v1_rmd <- tempfile(fileext = ".Rmd") # temporary file safe_project_crate_contents <- safe_project_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_project_crate_v1_rmd, render = FALSE) + dsROCrate::report(filepath = safe_project_crate_v1_rmd, render = FALSE) # display Overview diagram safe_project_crate_contents$overview_diagram @@ -491,7 +469,7 @@ safe_project_crate_contents$overview_data |> ```{r, eval = FALSE} safe_project_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_project_crate_v1_rmd, + dsROCrate::report(filepath = safe_project_crate_v1_rmd, title = "DataSHIELD Safe Project - Audit Report", render = TRUE, overwrite = TRUE) @@ -516,7 +494,7 @@ study_crate_v1 <- url = "https://opal-demo.obiba.org" ) ) |> - dsROCrate::audit_study(project = "CNSIM") + dsROCrate::audit(project = "CNSIM") print(study_crate_v1) ``` @@ -524,7 +502,7 @@ print(study_crate_v1) ###### Markdown report A markdown report can be created with an overview and details for an RO-Crate, -using the `dsROCrate::rocrate_report`: +using the `dsROCrate::report`: **Only generate .Rmd file** @@ -532,7 +510,7 @@ using the `dsROCrate::rocrate_report`: study_crate_v1_rmd <- tempfile(fileext = ".Rmd") # temporary file safe_project_crate_contents <- study_crate_v1 |> - dsROCrate::rocrate_report(filepath = study_crate_v1_rmd, render = FALSE) + dsROCrate::report(filepath = study_crate_v1_rmd, render = FALSE) # display Overview diagram safe_project_crate_contents$overview_diagram @@ -546,7 +524,7 @@ safe_project_crate_contents$overview_data |> ```{r, eval = FALSE} study_crate_v1 |> - dsROCrate::rocrate_report(filepath = study_crate_v1_rmd, + dsROCrate::report(filepath = study_crate_v1_rmd, title = "DataSHIELD Study audit", render = TRUE, overwrite = TRUE) @@ -564,7 +542,6 @@ You are welcome to use any of the following hex codes when referencing `{dsROCra ```{r, echo = FALSE, message = FALSE, error = FALSE} unlink(safe_people_crate_v1_rmd, TRUE, TRUE) -unlink(safe_people_crate_v2_rmd, TRUE, TRUE) unlink(safe_project_crate_v1_rmd, TRUE, TRUE) unlink(study_crate_v1_rmd, TRUE, TRUE) ``` diff --git a/README.md b/README.md index 789614e..d08c1c8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# dsROCrate: ‘DataSHIELD’ RO-Crate Wrapper Functions logo +# dsROCrate: ‘DataSHIELD’ RO-Crate Governance Functions logo @@ -10,7 +10,7 @@ experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](h [![CRAN status](https://www.r-pkg.org/badges/version/dsROCrate)](https://CRAN.R-project.org/package=dsROCrate) [![Codecov test -coverage](https://codecov.io/gh/DataSHIELD-5S/dsROCrate/graph/badge.svg)](https://app.codecov.io/gh/DataSHIELD-5S/dsROCrate) +coverage](https://codecov.io/gh/FederatedMethods/dsROCrate/graph/badge.svg)](https://app.codecov.io/gh/FederatedMethods/dsROCrate) The goal of dsROCrate is to provide functions to wrap elements from a @@ -19,11 +19,11 @@ The goal of dsROCrate is to provide functions to wrap elements from a ## 1. Installation You can install the development version of dsROCrate from -[GitHub](https://github.com/DataSHIELD-5S/dsROCrate/) with: +[GitHub](https://github.com/FederatedMethods/dsROCrate/) with: ``` r # install.packages("pak") -pak::pak("DataSHIELD-5S/dsROCrate") +pak::pak("FederatedMethods/dsROCrate") ``` ## 2. Creating your first RO-Crate @@ -86,7 +86,7 @@ o <- opalr::opal.login( print(o) #> url: https://opal-demo.obiba.org #> name: opal-demo.obiba.org -#> version: 5.5.2 +#> version: 5.6.1 #> username: administrator ``` @@ -130,7 +130,7 @@ print(basic_rocrate) #> "@type": "Dataset", #> "name": "", #> "description": "", -#> "datePublished": "2026-02-27", +#> "datePublished": "2026-03-27", #> "license": { #> "@id": "http://spdx.org/licenses/CC-BY-4.0" #> }, @@ -162,12 +162,52 @@ basic_rocrate <- o |> print(basic_rocrate) # note that the output will be truncated ... #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241", +#> "@id": "#perm:9bf7f75b6c5b07d02830b95652cd39a0-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" +#> }, +#> "object": { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#perm:1f09051d217d17c3e9b5ed92819ded26-admin-table", +#> "@type": "ControlAction", +#> "agent": { +#> "@id": "#person:a3bc19cc9c1269320cf2847c63a66a92" +#> }, +#> "object": { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User has full administrative rights: view/edit dictionary and view/edit individual values." +#> }, +#> { +#> "@id": "#perm:4d2673da68a58c3bce23a61d97b6df51-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:cb809df1c2fb30b154f60b843e62b3d0" +#> }, +#> "object": { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae", #> "@type": "Dataset", #> "name": "CNSIM1", -#> "dateCreated": "2026-02-27T06:29:50.731Z", -#> "dateModified": "2026-02-27T06:29:51.865Z", -#> "path": "/datasource/CNSIM/table/CNSIM1" +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM1", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> } #> ] #> } @@ -185,15 +225,17 @@ basic_rocrate <- o |> print(basic_rocrate) # note that the output will be truncated ... +#> ] +#> }, #> { #> "@id": "#project:7ba189863f9f641196596cb28e04aa14", #> "@type": "Project", #> "name": "CNSIM", -#> "dateCreated": "2026-02-27T06:29:49.235Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", +#> "dateCreated": "2026-03-27T06:29:56.149Z", +#> "dateModified": "2026-03-27T06:30:01.340Z", #> "hasPart": [ #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> } #> ] #> } @@ -215,12 +257,7 @@ print(basic_rocrate) # note that the output will be truncated #> { #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", #> "@type": "Person", -#> "name": "dsuser", -#> "memberOf": [ -#> { -#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" -#> } -#> ] +#> "name": "dsuser" #> } #> ] #> } @@ -257,91 +294,229 @@ basic_rocrate <- o |> print(basic_rocrate) # note that the output will be truncated ... #> { -#> "@id": "_:localid:datashield.privacyLevel:5", +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78", #> "@type": "PropertyValue", #> "name": "datashield.privacyLevel", #> "value": "5" #> }, #> { -#> "@id": "_:localid:default.datashield.privacyControlLevel:banana", +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b", #> "@type": "PropertyValue", #> "name": "default.datashield.privacyControlLevel", #> "value": "banana" #> }, #> { -#> "@id": "_:localid:default.nfilter.glm:0.33", +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae", #> "@type": "PropertyValue", #> "name": "default.nfilter.glm", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.kNN:3", +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7", #> "@type": "PropertyValue", #> "name": "default.nfilter.kNN", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.density:0.33", +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.density", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.max:40", +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.max", #> "value": "40" #> }, #> { -#> "@id": "_:localid:default.nfilter.noise:0.25", +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5", #> "@type": "PropertyValue", #> "name": "default.nfilter.noise", #> "value": "0.25" #> }, #> { -#> "@id": "_:localid:default.nfilter.string:80", +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54", #> "@type": "PropertyValue", #> "name": "default.nfilter.string", #> "value": "80" #> }, #> { -#> "@id": "_:localid:default.nfilter.stringShort:20", +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5", #> "@type": "PropertyValue", #> "name": "default.nfilter.stringShort", #> "value": "20" #> }, #> { -#> "@id": "_:localid:default.nfilter.subset:3", +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985", #> "@type": "PropertyValue", #> "name": "default.nfilter.subset", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.tab:3", +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0", #> "@type": "PropertyValue", #> "name": "default.nfilter.tab", #> "value": "3" #> }, #> { -#> "@id": "_:localid:3aaeab3631b4c9bbe7f44d60805b8f9c", +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8", +#> "@type": "CreativeWork", +#> "name": "Disclosure Control Environment", +#> "description": "Disclosure control settings extract from the OBiBa Opal server connection provided, using the profile: 'default'.", +#> "hasPart": [ +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78" +#> }, +#> { +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b" +#> }, +#> { +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae" +#> }, +#> { +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7" +#> }, +#> { +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79" +#> }, +#> { +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f" +#> }, +#> { +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5" +#> }, +#> { +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54" +#> }, +#> { +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5" +#> }, +#> { +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985" +#> }, +#> { +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0" +#> } +#> ] +#> }, +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27", #> "@type": "SoftwareApplication", #> "name": "dsBase", #> "version": "6.3.5", -#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows\n you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have\n been designed to only share non disclosive summary statistics, with built in automated output\n checking based on statistical disclosure control. With data sites setting the threshold values for\n the automated output checks. For more details, see 'citation(\"dsBase\")'." +#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have been designed to only share non disclosive summary statistics, with built in automated output checking based on statistical disclosure control. With data sites setting the threshold values for the automated output checks. For more details, see 'citation(\"dsBase\")'." #> }, #> { -#> "@id": "_:localid:e2f7c43973c40d7a6a6731da5a0aa564", +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44", #> "@type": "SoftwareApplication", #> "name": "dsTidyverse", #> "version": "1.1.0", #> "description": "Implementation of selected 'Tidyverse' functions within 'DataSHIELD', an open-source federated analysis solution in R. Currently, DataSHIELD contains very limited tools for data manipulation, so the aim of this package is to improve the researcher experience by implementing essential functions for data manipulation, including subsetting, filtering, grouping, and renaming variables. This is the serverside package which should be installed on the server holding the data, and is used in conjuncture with the clientside package 'dsTidyverseClient' which is installed in the local R environment of the analyst. For more information, see and ." #> }, #> { -#> "@id": "_:localid:411453e2513e6d909fe2cb8273b034dc", +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b", #> "@type": "SoftwareApplication", #> "name": "resourcer", #> "version": "1.5.0", -#> "description": "A resource represents some data or a computation unit. It is \n described by a URL and credentials. This package proposes a Resource model\n with \"resolver\" and \"client\" classes to facilitate the access and the usage of the \n resources." +#> "description": "A resource represents some data or a computation unit. It is described by a URL and credentials. This package proposes a Resource model with \"resolver\" and \"client\" classes to facilitate the access and the usage of the resources." +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc", +#> "@type": "SoftwareApplication", +#> "name": "Opal", +#> "version": "5.6.1", +#> "description": "Opal is OBiBa's (https://www.obiba.org/) core database application for epidemiological studies. Participant data, collected by questionnaires, medical instruments, sensors, administrative databases etc. can be integrated and stored in a central data repository under a uniform model." +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1", +#> "@type": "CreativeWork", +#> "name": "Approved Analytical Software Environment", +#> "description": "Software packages installed in the controlled Opal/DataSHIELD environment used for federated analysis.", +#> "hasPart": [ +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27" +#> }, +#> { +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44" +#> }, +#> { +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b" +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc" +#> } +#> ] +#> }, +#> { +#> "@id": "#control:output-checking", +#> "@type": "CreativeWork", +#> "name": "Statistical Disclosure Output Checking", +#> "description": "Automated disclosure control prevents release of small-cell counts and disclosive statistics." +#> }, +#> { +#> "@id": "#control:server-side-analysis", +#> "@type": "CreativeWork", +#> "name": "Server-Side Analysis Enforcement", +#> "description": "Raw data never leaves the secure server; analysis occurs via vetted aggregate functions." +#> }, +#> { +#> "@id": "#control:session-logging", +#> "@type": "CreativeWork", +#> "name": "Comprehensive Session Logging", +#> "description": "All analytical actions are logged and auditable." +#> }, +#> { +#> "@id": "#control:secure-facility", +#> "@type": "CreativeWork", +#> "name": "Secure Data Facility", +#> "description": "Access restricted to approved secure premises." +#> }, +#> { +#> "@id": "#control:access-governance", +#> "@type": "CreativeWork", +#> "name": "Access Governance Process", +#> "description": "Data access committee review and approval required." +#> }, +#> { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5", +#> "@type": "CreativeWork", +#> "name": "Safe Setting Controls (Opal)", +#> "description": "Technical, physical and organisational safeguards applied to minimise disclosure risk.", +#> "hasPart": [ +#> { +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8" +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1" +#> }, +#> { +#> "@id": "#control:output-checking" +#> }, +#> { +#> "@id": "#control:server-side-analysis" +#> }, +#> { +#> "@id": "#control:session-logging" +#> }, +#> { +#> "@id": "#control:secure-facility" +#> }, +#> { +#> "@id": "#control:access-governance" +#> } +#> ] +#> }, +#> { +#> "@id": "#link:b16fbdedcc33e826878020dcd5fad3d3", +#> "@type": "CreativeWork", +#> "name": "Safe Settings x Safe Project Link", +#> "about": { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5" +#> }, +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> } #> ] #> } @@ -454,7 +629,7 @@ basic_rocrate <- o |> logs_from = Sys.time() - 60, # capture the last minute logs_to = Sys.time()) #> opening file input connection. -#> Found 83 records... Imported 83 records. Simplifying... +#> Found 55 records... Imported 55 records. Simplifying... #> closing file input connection. #> Warning: A `path` wasn't provided! The logs will be included in the RO-Crate #> object, under the `content` tag! @@ -463,114 +638,114 @@ basic_rocrate <- o |> ``` r print(basic_rocrate) # note that the output will be truncated ... -#> "@type": "SoftwareApplication", -#> "name": "resourcer", -#> "version": "1.5.0", -#> "description": "A resource represents some data or a computation unit. It is \n described by a URL and credentials. This package proposes a Resource model\n with \"resolver\" and \"client\" classes to facilitate the access and the usage of the \n resources." +#> }, +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> }, #> { -#> "@id": "20260227T152405-dslogs-dsuser.log", +#> "@id": "20260327T120002-dslogs-dsuser.log", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:05", -#> "name": "20260227T152405-dslogs-dsuser.log", -#> "description": "This file contains the raw logs for the user: `dsuser` , between: 2026-02-27 15:23:05 and 2026-02-27 15:24:05", +#> "dateModified": "2026-03-27 12:00:02", +#> "name": "20260327T120002-dslogs-dsuser.log", +#> "description": "This file contains the raw logs for the user: `dsuser` , between: 2026-03-27 11:59:01 and 2026-03-27 12:00:01", #> "encodingFormat": "text/plain", #> "content": [ -#> ["[INFO][2026-02-27T15:24:02][OPEN] created a datashield session fc258313-6846-4a8e-93d9-4809cd794fbe", "[INFO][2026-02-27T15:24:02][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:24:03][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:05][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] +#> ["[INFO][2026-03-27T11:59:57][OPEN] created a datashield session 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", "[INFO][2026-03-27T11:59:59][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-03-27T12:00:00][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] #> ] #> }, #> { -#> "@id": "20260227T152405-dslogs-dsuser_mappings.csv", +#> "@id": "20260327T120002-dslogs-dsuser_mappings.csv", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:05", -#> "name": "20260227T152405-dslogs-dsuser_mappings.csv", +#> "dateModified": "2026-03-27 12:00:02", +#> "name": "20260327T120002-dslogs-dsuser_mappings.csv", #> "description": "This file contains mappings and evaluated functions", #> "encodingFormat": "text/csv", #> "content": [ #> [ #> { -#> "timestamp": "2026-02-27T15:24:02", +#> "timestamp": "2026-03-27T11:59:57", #> "action": "OPEN", #> "user": "dsuser", -#> "r_cmd": "Open session: fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "r_cmd": "Open session: 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "fx": "DSI::datashield.login", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:02", +#> "timestamp": "2026-03-27T11:59:59", #> "action": "ASSIGN", #> "user": "dsuser", #> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", #> "fx": "base::assign", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", #> "fx": "dsBase::lsDS", #> "symbol": "search.filter = NULL, 1L", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "base::exists(\"dsROCrate_test\")", #> "fx": "base::exists", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::classDS(\"dsROCrate_test\")", #> "fx": "dsBase::classDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::isValidDS(dsROCrate_test)", #> "fx": "dsBase::isValidDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::dimDS(\"dsROCrate_test\")", #> "fx": "dsBase::dimDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:05", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::colnamesDS(\"dsROCrate_test\")", #> "fx": "dsBase::colnamesDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> } #> ] @@ -631,7 +806,7 @@ basic_rocrate <- basic_rocrate |> path_to_rocrate_bag <- basic_rocrate |> rocrateR::bag_rocrate(path = "./rocrates", overwrite = TRUE) #> RO-Crate successfully 'bagged'! -#> For details, see: ./rocrates/rocrate-200ecad114285e36b84c12cfa3be6526.zip +#> For details, see: ./rocrates/rocrate-168d1073270b8b5ddb29de5fb1e8f745.zip ``` We can explore the contents with the following commands: @@ -643,11 +818,12 @@ path_to_rocrate_bag |> rocrateR::unbag_rocrate(output = "./rocrates/ROC", quiet = TRUE) |> # create tree with the files fs::dir_tree() -#> ./rocrates/ROC/ +#> /Users/robertovillegas-diaz/Documents/dsROCrate/rocrates/ROC +#> ├── bag-info.txt #> ├── bagit.txt #> ├── data -#> │ ├── 20260227T152405-dslogs-dsuser.log -#> │ ├── 20260227T152405-dslogs-dsuser_mappings.csv +#> │ ├── 20260327T120002-dslogs-dsuser.log +#> │ ├── 20260327T120002-dslogs-dsuser_mappings.csv #> │ └── ro-crate-metadata.json #> ├── manifest-sha512.txt #> └── tagmanifest-sha512.txt @@ -673,9 +849,9 @@ safe_people_crate_v1 <- opalr::opal.login( password = USERPASS, url = SERVER ) |> - dsROCrate::audit_safe_people(user = "dsuser", project = "CNSIM") + dsROCrate::audit(user = "dsuser", project = "CNSIM") #> opening file input connection. -#> Found 83 records... Imported 83 records. Simplifying... +#> Found 55 records... Imported 55 records. Simplifying... #> closing file input connection. print(safe_people_crate_v1) @@ -697,7 +873,7 @@ print(safe_people_crate_v1) #> "@type": "Dataset", #> "name": "", #> "description": "", -#> "datePublished": "2026-02-27", +#> "datePublished": "2026-03-27", #> "license": { #> "@id": "http://spdx.org/licenses/CC-BY-4.0" #> }, @@ -706,10 +882,10 @@ print(safe_people_crate_v1) #> }, #> "hasPart": [ #> { -#> "@id": "20260227T152406-dslogs-dsuser.log" +#> "@id": "20260327T120003-dslogs-dsuser.log" #> }, #> { -#> "@id": "20260227T152406-dslogs-dsuser_mappings.csv" +#> "@id": "20260327T120003-dslogs-dsuser_mappings.csv" #> } #> ] #> }, @@ -719,60 +895,36 @@ print(safe_people_crate_v1) #> "name": "Five Safes RO-Crate profile" #> }, #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241", -#> "@type": "Dataset", -#> "name": "CNSIM1", -#> "dateCreated": "2026-02-27T06:29:50.731Z", -#> "dateModified": "2026-02-27T06:29:51.865Z", -#> "path": "/datasource/CNSIM/table/CNSIM1" -#> }, -#> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9", -#> "@type": "Dataset", -#> "name": "CNSIM2", -#> "dateCreated": "2026-02-27T06:29:51.872Z", -#> "dateModified": "2026-02-27T06:29:53.057Z", -#> "path": "/datasource/CNSIM/table/CNSIM2" -#> }, -#> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492", -#> "@type": "Dataset", -#> "name": "CNSIM3", -#> "dateCreated": "2026-02-27T06:29:53.064Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", -#> "path": "/datasource/CNSIM/table/CNSIM3" +#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", +#> "@type": "Person", +#> "name": "dsuser" #> }, #> { #> "@id": "#project:7ba189863f9f641196596cb28e04aa14", #> "@type": "Project", #> "name": "CNSIM", -#> "dateCreated": "2026-02-27T06:29:49.235Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", +#> "dateCreated": "2026-03-27T06:29:56.149Z", +#> "dateModified": "2026-03-27T06:30:01.340Z", #> "hasPart": [ #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> } #> ] #> }, #> { -#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", -#> "@type": "Person", -#> "name": "dsuser" -#> }, -#> { #> "@id": "#perm:9bf7f75b6c5b07d02830b95652cd39a0-dict-summary-read", #> "@type": "ReadAction", #> "agent": { #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." @@ -784,7 +936,7 @@ print(safe_people_crate_v1) #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." @@ -796,411 +948,375 @@ print(safe_people_crate_v1) #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." #> }, #> { -#> "@id": "_:localid:datashield.privacyLevel:5", +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae", +#> "@type": "Dataset", +#> "name": "CNSIM1", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM1", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#asset:b6721026564c746f604df7ba785931fa", +#> "@type": "Dataset", +#> "name": "CNSIM2", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM2", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d", +#> "@type": "Dataset", +#> "name": "CNSIM3", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM3", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78", #> "@type": "PropertyValue", #> "name": "datashield.privacyLevel", #> "value": "5" #> }, #> { -#> "@id": "_:localid:default.datashield.privacyControlLevel:banana", +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b", #> "@type": "PropertyValue", #> "name": "default.datashield.privacyControlLevel", #> "value": "banana" #> }, #> { -#> "@id": "_:localid:default.nfilter.glm:0.33", +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae", #> "@type": "PropertyValue", #> "name": "default.nfilter.glm", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.kNN:3", +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7", #> "@type": "PropertyValue", #> "name": "default.nfilter.kNN", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.density:0.33", +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.density", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.max:40", +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.max", #> "value": "40" #> }, #> { -#> "@id": "_:localid:default.nfilter.noise:0.25", +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5", #> "@type": "PropertyValue", #> "name": "default.nfilter.noise", #> "value": "0.25" #> }, #> { -#> "@id": "_:localid:default.nfilter.string:80", +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54", #> "@type": "PropertyValue", #> "name": "default.nfilter.string", #> "value": "80" #> }, #> { -#> "@id": "_:localid:default.nfilter.stringShort:20", +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5", #> "@type": "PropertyValue", #> "name": "default.nfilter.stringShort", #> "value": "20" #> }, #> { -#> "@id": "_:localid:default.nfilter.subset:3", +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985", #> "@type": "PropertyValue", #> "name": "default.nfilter.subset", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.tab:3", +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0", #> "@type": "PropertyValue", #> "name": "default.nfilter.tab", #> "value": "3" #> }, #> { -#> "@id": "_:localid:3aaeab3631b4c9bbe7f44d60805b8f9c", +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8", +#> "@type": "CreativeWork", +#> "name": "Disclosure Control Environment", +#> "description": "Disclosure control settings extract from the OBiBa Opal server connection provided, using the profile: 'default'.", +#> "hasPart": [ +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78" +#> }, +#> { +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b" +#> }, +#> { +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae" +#> }, +#> { +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7" +#> }, +#> { +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79" +#> }, +#> { +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f" +#> }, +#> { +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5" +#> }, +#> { +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54" +#> }, +#> { +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5" +#> }, +#> { +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985" +#> }, +#> { +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0" +#> } +#> ] +#> }, +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27", #> "@type": "SoftwareApplication", #> "name": "dsBase", #> "version": "6.3.5", -#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows\n you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have\n been designed to only share non disclosive summary statistics, with built in automated output\n checking based on statistical disclosure control. With data sites setting the threshold values for\n the automated output checks. For more details, see 'citation(\"dsBase\")'." +#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have been designed to only share non disclosive summary statistics, with built in automated output checking based on statistical disclosure control. With data sites setting the threshold values for the automated output checks. For more details, see 'citation(\"dsBase\")'." #> }, #> { -#> "@id": "_:localid:e2f7c43973c40d7a6a6731da5a0aa564", +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44", #> "@type": "SoftwareApplication", #> "name": "dsTidyverse", #> "version": "1.1.0", #> "description": "Implementation of selected 'Tidyverse' functions within 'DataSHIELD', an open-source federated analysis solution in R. Currently, DataSHIELD contains very limited tools for data manipulation, so the aim of this package is to improve the researcher experience by implementing essential functions for data manipulation, including subsetting, filtering, grouping, and renaming variables. This is the serverside package which should be installed on the server holding the data, and is used in conjuncture with the clientside package 'dsTidyverseClient' which is installed in the local R environment of the analyst. For more information, see and ." #> }, #> { -#> "@id": "_:localid:411453e2513e6d909fe2cb8273b034dc", +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b", #> "@type": "SoftwareApplication", #> "name": "resourcer", #> "version": "1.5.0", -#> "description": "A resource represents some data or a computation unit. It is \n described by a URL and credentials. This package proposes a Resource model\n with \"resolver\" and \"client\" classes to facilitate the access and the usage of the \n resources." +#> "description": "A resource represents some data or a computation unit. It is described by a URL and credentials. This package proposes a Resource model with \"resolver\" and \"client\" classes to facilitate the access and the usage of the resources." +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc", +#> "@type": "SoftwareApplication", +#> "name": "Opal", +#> "version": "5.6.1", +#> "description": "Opal is OBiBa's (https://www.obiba.org/) core database application for epidemiological studies. Participant data, collected by questionnaires, medical instruments, sensors, administrative databases etc. can be integrated and stored in a central data repository under a uniform model." +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1", +#> "@type": "CreativeWork", +#> "name": "Approved Analytical Software Environment", +#> "description": "Software packages installed in the controlled Opal/DataSHIELD environment used for federated analysis.", +#> "hasPart": [ +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27" +#> }, +#> { +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44" +#> }, +#> { +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b" +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc" +#> } +#> ] +#> }, +#> { +#> "@id": "#control:output-checking", +#> "@type": "CreativeWork", +#> "name": "Statistical Disclosure Output Checking", +#> "description": "Automated disclosure control prevents release of small-cell counts and disclosive statistics." +#> }, +#> { +#> "@id": "#control:server-side-analysis", +#> "@type": "CreativeWork", +#> "name": "Server-Side Analysis Enforcement", +#> "description": "Raw data never leaves the secure server; analysis occurs via vetted aggregate functions." +#> }, +#> { +#> "@id": "#control:session-logging", +#> "@type": "CreativeWork", +#> "name": "Comprehensive Session Logging", +#> "description": "All analytical actions are logged and auditable." +#> }, +#> { +#> "@id": "#control:secure-facility", +#> "@type": "CreativeWork", +#> "name": "Secure Data Facility", +#> "description": "Access restricted to approved secure premises." +#> }, +#> { +#> "@id": "#control:access-governance", +#> "@type": "CreativeWork", +#> "name": "Access Governance Process", +#> "description": "Data access committee review and approval required." +#> }, +#> { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5", +#> "@type": "CreativeWork", +#> "name": "Safe Setting Controls (Opal)", +#> "description": "Technical, physical and organisational safeguards applied to minimise disclosure risk.", +#> "hasPart": [ +#> { +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8" +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1" +#> }, +#> { +#> "@id": "#control:output-checking" +#> }, +#> { +#> "@id": "#control:server-side-analysis" +#> }, +#> { +#> "@id": "#control:session-logging" +#> }, +#> { +#> "@id": "#control:secure-facility" +#> }, +#> { +#> "@id": "#control:access-governance" +#> } +#> ] +#> }, +#> { +#> "@id": "#link:b16fbdedcc33e826878020dcd5fad3d3", +#> "@type": "CreativeWork", +#> "name": "Safe Settings x Safe Project Link", +#> "about": { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5" +#> }, +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> }, #> { -#> "@id": "20260227T152406-dslogs-dsuser.log", +#> "@id": "20260327T120003-dslogs-dsuser.log", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:06", -#> "name": "20260227T152406-dslogs-dsuser.log", +#> "dateModified": "2026-03-27 12:00:03", +#> "name": "20260327T120003-dslogs-dsuser.log", #> "description": "This file contains the raw logs for the user: `dsuser` , between: ALL and ALL", #> "encodingFormat": "text/plain", #> "content": [ -#> ["[INFO][2026-02-27T08:56:24][OPEN] created a datashield session 622f2c74-9242-48e2-82d9-bc3250f8aa4b", "[INFO][2026-02-27T08:56:26][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T08:56:26][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T08:56:27][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:00:37][OPEN] created a datashield session 7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", "[INFO][2026-02-27T09:00:37][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T09:00:38][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:00:38][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:20:47][OPEN] created a datashield session 08413766-4fa8-4eec-9392-ec30581fb48c", "[INFO][2026-02-27T09:20:48][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T09:20:48][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:20:49][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:08:39][OPEN] created a datashield session 88a48432-61c8-4444-bb6f-1d0174d4f177", "[INFO][2026-02-27T14:08:39][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T14:08:39][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:08:40][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:18:40][OPEN] created a datashield session e45b3573-21cc-445d-8d22-b7fc289d279a", "[INFO][2026-02-27T14:18:40][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T14:18:40][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:18:41][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:04:13][OPEN] created a datashield session 026b4b18-c40c-4f08-8a19-75716fee1c75", "[INFO][2026-02-27T15:04:13][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:04:14][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:04:14][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:13:01][OPEN] created a datashield session a3a6f257-79ef-443c-a110-393b1ff196f0", "[INFO][2026-02-27T15:13:01][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:13:02][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:13:03][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:02][OPEN] created a datashield session fc258313-6846-4a8e-93d9-4809cd794fbe", "[INFO][2026-02-27T15:24:02][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:24:03][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:05][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] +#> ["[INFO][2026-03-27T11:59:57][OPEN] created a datashield session 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", "[INFO][2026-03-27T11:59:59][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-03-27T12:00:00][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] #> ] #> }, #> { -#> "@id": "20260227T152406-dslogs-dsuser_mappings.csv", +#> "@id": "20260327T120003-dslogs-dsuser_mappings.csv", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:06", -#> "name": "20260227T152406-dslogs-dsuser_mappings.csv", +#> "dateModified": "2026-03-27 12:00:03", +#> "name": "20260327T120003-dslogs-dsuser_mappings.csv", #> "description": "This file contains mappings and evaluated functions", #> "encodingFormat": "text/csv", #> "content": [ #> [ #> { -#> "timestamp": "2026-02-27T08:56:24", +#> "timestamp": "2026-03-27T11:59:57", #> "action": "OPEN", #> "user": "dsuser", -#> "r_cmd": "Open session: 622f2c74-9242-48e2-82d9-bc3250f8aa4b", +#> "r_cmd": "Open session: 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "fx": "DSI::datashield.login", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T08:56:26", +#> "timestamp": "2026-03-27T11:59:59", #> "action": "ASSIGN", #> "user": "dsuser", #> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", #> "fx": "base::assign", #> "table": "CNSIM.CNSIM1", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T08:56:27", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", #> "fx": "dsBase::lsDS", #> "symbol": "search.filter = NULL, 1L", #> "table": "CNSIM.CNSIM1", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T09:00:37", -#> "action": "OPEN", +#> "timestamp": "2026-03-27T12:00:01", +#> "action": "AGGREGATE", #> "user": "dsuser", -#> "r_cmd": "Open session: 7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "fx": "DSI::datashield.login", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", +#> "r_cmd": "base::exists(\"dsROCrate_test\")", +#> "fx": "base::exists", +#> "symbol": "dsROCrate_test", +#> "table": "CNSIM.CNSIM1", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T09:00:37", -#> "action": "ASSIGN", +#> "timestamp": "2026-03-27T12:00:01", +#> "action": "AGGREGATE", #> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", +#> "r_cmd": "dsBase::classDS(\"dsROCrate_test\")", +#> "fx": "dsBase::classDS", +#> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T09:00:38", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:47", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 08413766-4fa8-4eec-9392-ec30581fb48c", -#> "fx": "DSI::datashield.login", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:48", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:49", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:39", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "fx": "DSI::datashield.login", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:39", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:40", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:40", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "fx": "DSI::datashield.login", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:40", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:41", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:13", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "fx": "DSI::datashield.login", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:13", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:14", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:01", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "fx": "DSI::datashield.login", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:01", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:03", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:02", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: fc258313-6846-4a8e-93d9-4809cd794fbe", -#> "fx": "DSI::datashield.login", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:02", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:04", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:04", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "base::exists(\"dsROCrate_test\")", -#> "fx": "base::exists", -#> "symbol": "dsROCrate_test", -#> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:04", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::classDS(\"dsROCrate_test\")", -#> "fx": "dsBase::classDS", -#> "symbol": "dsROCrate_test", -#> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::isValidDS(dsROCrate_test)", #> "fx": "dsBase::isValidDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::dimDS(\"dsROCrate_test\")", #> "fx": "dsBase::dimDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:05", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::colnamesDS(\"dsROCrate_test\")", #> "fx": "dsBase::colnamesDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> } #> ] @@ -1213,7 +1329,7 @@ print(safe_people_crate_v1) ###### Markdown report A markdown report can be created with an overview and details for an -RO-Crate, using the `dsROCrate::rocrate_report`: +RO-Crate, using the `dsROCrate::report`: **Only generate .Rmd file** @@ -1221,13 +1337,11 @@ RO-Crate, using the `dsROCrate::rocrate_report`: safe_people_crate_v1_rmd <- tempfile(fileext = ".Rmd") # temporary file safe_people_crate_contents <- safe_people_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_people_crate_v1_rmd, render = FALSE) + dsROCrate::report(filepath = safe_people_crate_v1_rmd, render = FALSE) #> 1 'Author' entity was found! -#> 3 'Dataset' entities were found! -#> Warning: No entities were found with @type = 'WriteAction'! -#> Warning: No entities were found with @type = 'ControlAction'! +#> 3 asset entities were found! #> 1 'Project' entity was found! -#> 14 'PropertyValue' OR 'SoftwareApplication' entities were found! +#> 22 'CreativeWork', 'PropertyValue' OR 'SoftwareApplication' entities were found! #> 2 'File' entities were found! # display Overview diagram @@ -1245,27 +1359,13 @@ safe_people_crate_contents$overview_data |> | Project | Data | Access Level | People | Function | Timestamp | |:--------|:-------|:-------------|:-------|:-------------------|:--------------------| -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T08:56:26 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T08:56:27 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:00:37 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:00:38 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:20:48 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:20:49 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:08:39 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:08:40 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:18:40 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:18:41 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:04:13 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:04:14 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:13:01 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:13:03 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:24:02 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-02-27T15:24:05 | +| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-03-27T11:59:59 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-03-27T12:00:01 | | CNSIM | CNSIM2 | read | dsuser | | | | CNSIM | CNSIM3 | read | dsuser | | | @@ -1273,101 +1373,12 @@ safe_people_crate_contents$overview_data |> ``` r safe_people_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_people_crate_v1_rmd, + dsROCrate::report(filepath = safe_people_crate_v1_rmd, title = "DataSHIELD Safe People - Audit Report", render = TRUE, overwrite = TRUE) ``` -##### List all accessible projects & tables for an user - -``` r -safe_people_crate_v2 <- opalr::opal.login( - username = USERNAME, - password = USERPASS, - url = SERVER -) |> - dsROCrate::audit_safe_people(user = "dsuser") -#> opening file input connection. -#> Found 83 records... Imported 83 records. Simplifying... -#> closing file input connection. - -safe_people_crate_v2_rmd <- tempfile(fileext = ".Rmd") # temporary file - -safe_people_crate_contents_v2 <- safe_people_crate_v2 |> - dsROCrate::rocrate_report(filepath = safe_people_crate_v2_rmd, render = FALSE) -#> 1 'Author' entity was found! -#> 30 'Dataset' entities were found! -#> 11 'Project' entities were found! -#> 14 'PropertyValue' OR 'SoftwareApplication' entities were found! -#> 2 'File' entities were found! - -# display Overview diagram -safe_people_crate_contents_v2$overview_diagram -``` - - - -``` r - -# display Overview data (Safe People, Safe Projects and Safe Data) -safe_people_crate_contents_v2$overview_data |> - knitr::kable() -``` - -| Project | Data | Access Level | People | Function | Timestamp | -|:---|:---|:---|:---|:---|:---| -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T08:56:26 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T08:56:27 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:00:37 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:00:38 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:20:48 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:20:49 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:08:39 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:08:40 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:18:40 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:18:41 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:04:13 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:04:14 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:13:01 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:13:03 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:24:02 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-02-27T15:24:05 | -| CNSIM | CNSIM2 | read | dsuser | | | -| CNSIM | CNSIM3 | read | dsuser | | | -| DASIM | DASIM1 | read | dsuser | | | -| DASIM | DASIM2 | read | dsuser | | | -| DASIM | DASIM3 | read | dsuser | | | -| DISCORDANT | DISCORDANT_STUDY1 | read | dsuser | | | -| DISCORDANT | DISCORDANT_STUDY2 | read | dsuser | | | -| DISCORDANT | DISCORDANT_STUDY3 | read | dsuser | | | -| GREENSPACE | Cohort1_exposome | read | dsuser | | | -| GREENSPACE | Cohort2_exposome | read | dsuser | | | -| GREENSPACE | Cohort3_exposome | read | dsuser | | | -| GWAS | ega_phenotypes | read | dsuser | | | -| GWAS | ega_phenotypes_1 | read | dsuser | | | -| GWAS | ega_phenotypes_2 | read | dsuser | | | -| GWAS | ega_phenotypes_3 | read | dsuser | | | -| MEDIATION | UPBdata1 | read | dsuser | | | -| MEDIATION | UPBdata2 | read | dsuser | | | -| MEDIATION | UPBdata3 | read | dsuser | | | -| SURVIVAL | EXPAND_WITH_MISSING1 | read | dsuser | | | -| SURVIVAL | EXPAND_WITH_MISSING2 | read | dsuser | | | -| SURVIVAL | EXPAND_WITH_MISSING3 | read | dsuser | | | -| TESTING | TESTING1 | read | dsuser | | | -| TESTING | TESTING2 | read | dsuser | | | -| TESTING | TESTING3 | read | dsuser | | | -| TITANIC_NEWCOMERS_WORKSHOP | titanic_server_1 | read | dsuser | | | -| TITANIC_NEWCOMERS_WORKSHOP | titanic_server_2 | read | dsuser | | | -| depression | growth_1 | read | dsuser | | | -| depression | growth_2 | read | dsuser | | | -| serverDataKey | myKey | read | dsuser | | | - ### 3.2. Audit Project ##### List users and dataset/table level permissions within a project @@ -1378,9 +1389,18 @@ safe_project_crate_v1 <- opalr::opal.login( password = USERPASS, url = SERVER ) |> - dsROCrate::audit_safe_project(project = "CNSIM") + dsROCrate::audit(project = "CNSIM") +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. #> opening file input connection. -#> Found 83 records... Imported 83 records. Simplifying... +#> Found 55 records... Imported 55 records. Simplifying... #> closing file input connection. print(safe_project_crate_v1) @@ -1402,7 +1422,7 @@ print(safe_project_crate_v1) #> "@type": "Dataset", #> "name": "", #> "description": "", -#> "datePublished": "2026-02-27", +#> "datePublished": "2026-03-27", #> "license": { #> "@id": "http://spdx.org/licenses/CC-BY-4.0" #> }, @@ -1411,10 +1431,10 @@ print(safe_project_crate_v1) #> }, #> "hasPart": [ #> { -#> "@id": "20260227T152439-dslogs-dsuser.log" +#> "@id": "20260327T120011-dslogs-dsuser.log" #> }, #> { -#> "@id": "20260227T152439-dslogs-dsuser_mappings.csv" +#> "@id": "20260327T120011-dslogs-dsuser_mappings.csv" #> } #> ] #> }, @@ -1424,60 +1444,63 @@ print(safe_project_crate_v1) #> "name": "Five Safes RO-Crate profile" #> }, #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241", -#> "@type": "Dataset", -#> "name": "CNSIM1", -#> "dateCreated": "2026-02-27T06:29:50.731Z", -#> "dateModified": "2026-02-27T06:29:51.865Z", -#> "path": "/datasource/CNSIM/table/CNSIM1" +#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", +#> "@type": "Person", +#> "name": "dsuser" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9", -#> "@type": "Dataset", -#> "name": "CNSIM2", -#> "dateCreated": "2026-02-27T06:29:51.872Z", -#> "dateModified": "2026-02-27T06:29:53.057Z", -#> "path": "/datasource/CNSIM/table/CNSIM2" +#> "@id": "#person:cb809df1c2fb30b154f60b843e62b3d0", +#> "@type": "Person", +#> "name": "dsuser1" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492", -#> "@type": "Dataset", -#> "name": "CNSIM3", -#> "dateCreated": "2026-02-27T06:29:53.064Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", -#> "path": "/datasource/CNSIM/table/CNSIM3" +#> "@id": "#person:a3cd7ce7818436c83b1eadaa5ba47411", +#> "@type": "Person", +#> "name": "dsuser2" +#> }, +#> { +#> "@id": "#person:5657241505661473308ae9aa9a378293", +#> "@type": "Person", +#> "name": "dsuser3" #> }, #> { #> "@id": "#project:7ba189863f9f641196596cb28e04aa14", #> "@type": "Project", #> "name": "CNSIM", -#> "dateCreated": "2026-02-27T06:29:49.235Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", +#> "dateCreated": "2026-03-27T06:29:56.149Z", +#> "dateModified": "2026-03-27T06:30:01.340Z", #> "hasPart": [ #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> } #> ] #> }, #> { -#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", -#> "@type": "Person", -#> "name": "dsuser" -#> }, -#> { #> "@id": "#perm:9bf7f75b6c5b07d02830b95652cd39a0-dict-summary-read", #> "@type": "ReadAction", #> "agent": { #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#perm:4d2673da68a58c3bce23a61d97b6df51-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:cb809df1c2fb30b154f60b843e62b3d0" +#> }, +#> "object": { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." @@ -1489,7 +1512,19 @@ print(safe_project_crate_v1) #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#perm:802d140a064e6ebf3a784f759af1b640-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:a3cd7ce7818436c83b1eadaa5ba47411" +#> }, +#> "object": { +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." @@ -1501,411 +1536,387 @@ print(safe_project_crate_v1) #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." #> }, #> { -#> "@id": "_:localid:datashield.privacyLevel:5", -#> "@type": "PropertyValue", -#> "name": "datashield.privacyLevel", -#> "value": "5" +#> "@id": "#perm:04c3f293c7a360fe0a1b7c29c8363540-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:5657241505661473308ae9aa9a378293" +#> }, +#> "object": { +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." #> }, #> { -#> "@id": "_:localid:default.datashield.privacyControlLevel:banana", -#> "@type": "PropertyValue", -#> "name": "default.datashield.privacyControlLevel", -#> "value": "banana" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae", +#> "@type": "Dataset", +#> "name": "CNSIM1", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM1", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> }, #> { -#> "@id": "_:localid:default.nfilter.glm:0.33", -#> "@type": "PropertyValue", -#> "name": "default.nfilter.glm", -#> "value": "0.33" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa", +#> "@type": "Dataset", +#> "name": "CNSIM2", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM2", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> }, #> { -#> "@id": "_:localid:default.nfilter.kNN:3", +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d", +#> "@type": "Dataset", +#> "name": "CNSIM3", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM3", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78", +#> "@type": "PropertyValue", +#> "name": "datashield.privacyLevel", +#> "value": "5" +#> }, +#> { +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b", +#> "@type": "PropertyValue", +#> "name": "default.datashield.privacyControlLevel", +#> "value": "banana" +#> }, +#> { +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae", +#> "@type": "PropertyValue", +#> "name": "default.nfilter.glm", +#> "value": "0.33" +#> }, +#> { +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7", #> "@type": "PropertyValue", #> "name": "default.nfilter.kNN", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.density:0.33", +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.density", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.max:40", +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.max", #> "value": "40" #> }, #> { -#> "@id": "_:localid:default.nfilter.noise:0.25", +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5", #> "@type": "PropertyValue", #> "name": "default.nfilter.noise", #> "value": "0.25" #> }, #> { -#> "@id": "_:localid:default.nfilter.string:80", +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54", #> "@type": "PropertyValue", #> "name": "default.nfilter.string", #> "value": "80" #> }, #> { -#> "@id": "_:localid:default.nfilter.stringShort:20", +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5", #> "@type": "PropertyValue", #> "name": "default.nfilter.stringShort", #> "value": "20" #> }, #> { -#> "@id": "_:localid:default.nfilter.subset:3", +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985", #> "@type": "PropertyValue", #> "name": "default.nfilter.subset", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.tab:3", +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0", #> "@type": "PropertyValue", #> "name": "default.nfilter.tab", #> "value": "3" #> }, #> { -#> "@id": "_:localid:3aaeab3631b4c9bbe7f44d60805b8f9c", +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8", +#> "@type": "CreativeWork", +#> "name": "Disclosure Control Environment", +#> "description": "Disclosure control settings extract from the OBiBa Opal server connection provided, using the profile: 'default'.", +#> "hasPart": [ +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78" +#> }, +#> { +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b" +#> }, +#> { +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae" +#> }, +#> { +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7" +#> }, +#> { +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79" +#> }, +#> { +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f" +#> }, +#> { +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5" +#> }, +#> { +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54" +#> }, +#> { +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5" +#> }, +#> { +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985" +#> }, +#> { +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0" +#> } +#> ] +#> }, +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27", #> "@type": "SoftwareApplication", #> "name": "dsBase", #> "version": "6.3.5", -#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows\n you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have\n been designed to only share non disclosive summary statistics, with built in automated output\n checking based on statistical disclosure control. With data sites setting the threshold values for\n the automated output checks. For more details, see 'citation(\"dsBase\")'." +#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have been designed to only share non disclosive summary statistics, with built in automated output checking based on statistical disclosure control. With data sites setting the threshold values for the automated output checks. For more details, see 'citation(\"dsBase\")'." #> }, #> { -#> "@id": "_:localid:e2f7c43973c40d7a6a6731da5a0aa564", +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44", #> "@type": "SoftwareApplication", #> "name": "dsTidyverse", #> "version": "1.1.0", #> "description": "Implementation of selected 'Tidyverse' functions within 'DataSHIELD', an open-source federated analysis solution in R. Currently, DataSHIELD contains very limited tools for data manipulation, so the aim of this package is to improve the researcher experience by implementing essential functions for data manipulation, including subsetting, filtering, grouping, and renaming variables. This is the serverside package which should be installed on the server holding the data, and is used in conjuncture with the clientside package 'dsTidyverseClient' which is installed in the local R environment of the analyst. For more information, see and ." #> }, #> { -#> "@id": "_:localid:411453e2513e6d909fe2cb8273b034dc", +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b", #> "@type": "SoftwareApplication", #> "name": "resourcer", #> "version": "1.5.0", -#> "description": "A resource represents some data or a computation unit. It is \n described by a URL and credentials. This package proposes a Resource model\n with \"resolver\" and \"client\" classes to facilitate the access and the usage of the \n resources." +#> "description": "A resource represents some data or a computation unit. It is described by a URL and credentials. This package proposes a Resource model with \"resolver\" and \"client\" classes to facilitate the access and the usage of the resources." +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc", +#> "@type": "SoftwareApplication", +#> "name": "Opal", +#> "version": "5.6.1", +#> "description": "Opal is OBiBa's (https://www.obiba.org/) core database application for epidemiological studies. Participant data, collected by questionnaires, medical instruments, sensors, administrative databases etc. can be integrated and stored in a central data repository under a uniform model." +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1", +#> "@type": "CreativeWork", +#> "name": "Approved Analytical Software Environment", +#> "description": "Software packages installed in the controlled Opal/DataSHIELD environment used for federated analysis.", +#> "hasPart": [ +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27" +#> }, +#> { +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44" +#> }, +#> { +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b" +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc" +#> } +#> ] +#> }, +#> { +#> "@id": "#control:output-checking", +#> "@type": "CreativeWork", +#> "name": "Statistical Disclosure Output Checking", +#> "description": "Automated disclosure control prevents release of small-cell counts and disclosive statistics." +#> }, +#> { +#> "@id": "#control:server-side-analysis", +#> "@type": "CreativeWork", +#> "name": "Server-Side Analysis Enforcement", +#> "description": "Raw data never leaves the secure server; analysis occurs via vetted aggregate functions." +#> }, +#> { +#> "@id": "#control:session-logging", +#> "@type": "CreativeWork", +#> "name": "Comprehensive Session Logging", +#> "description": "All analytical actions are logged and auditable." +#> }, +#> { +#> "@id": "#control:secure-facility", +#> "@type": "CreativeWork", +#> "name": "Secure Data Facility", +#> "description": "Access restricted to approved secure premises." +#> }, +#> { +#> "@id": "#control:access-governance", +#> "@type": "CreativeWork", +#> "name": "Access Governance Process", +#> "description": "Data access committee review and approval required." #> }, #> { -#> "@id": "20260227T152439-dslogs-dsuser.log", +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5", +#> "@type": "CreativeWork", +#> "name": "Safe Setting Controls (Opal)", +#> "description": "Technical, physical and organisational safeguards applied to minimise disclosure risk.", +#> "hasPart": [ +#> { +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8" +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1" +#> }, +#> { +#> "@id": "#control:output-checking" +#> }, +#> { +#> "@id": "#control:server-side-analysis" +#> }, +#> { +#> "@id": "#control:session-logging" +#> }, +#> { +#> "@id": "#control:secure-facility" +#> }, +#> { +#> "@id": "#control:access-governance" +#> } +#> ] +#> }, +#> { +#> "@id": "#link:b16fbdedcc33e826878020dcd5fad3d3", +#> "@type": "CreativeWork", +#> "name": "Safe Settings x Safe Project Link", +#> "about": { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5" +#> }, +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "20260327T120011-dslogs-dsuser.log", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:39", -#> "name": "20260227T152439-dslogs-dsuser.log", +#> "dateModified": "2026-03-27 12:00:11", +#> "name": "20260327T120011-dslogs-dsuser.log", #> "description": "This file contains the raw logs for the user: `dsuser` , between: ALL and ALL", #> "encodingFormat": "text/plain", #> "content": [ -#> ["[INFO][2026-02-27T08:56:24][OPEN] created a datashield session 622f2c74-9242-48e2-82d9-bc3250f8aa4b", "[INFO][2026-02-27T08:56:26][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T08:56:26][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T08:56:27][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:00:37][OPEN] created a datashield session 7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", "[INFO][2026-02-27T09:00:37][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T09:00:38][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:00:38][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:20:47][OPEN] created a datashield session 08413766-4fa8-4eec-9392-ec30581fb48c", "[INFO][2026-02-27T09:20:48][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T09:20:48][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:20:49][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:08:39][OPEN] created a datashield session 88a48432-61c8-4444-bb6f-1d0174d4f177", "[INFO][2026-02-27T14:08:39][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T14:08:39][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:08:40][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:18:40][OPEN] created a datashield session e45b3573-21cc-445d-8d22-b7fc289d279a", "[INFO][2026-02-27T14:18:40][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T14:18:40][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:18:41][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:04:13][OPEN] created a datashield session 026b4b18-c40c-4f08-8a19-75716fee1c75", "[INFO][2026-02-27T15:04:13][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:04:14][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:04:14][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:13:01][OPEN] created a datashield session a3a6f257-79ef-443c-a110-393b1ff196f0", "[INFO][2026-02-27T15:13:01][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:13:02][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:13:03][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:02][OPEN] created a datashield session fc258313-6846-4a8e-93d9-4809cd794fbe", "[INFO][2026-02-27T15:24:02][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:24:03][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:05][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] +#> ["[INFO][2026-03-27T11:59:57][OPEN] created a datashield session 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", "[INFO][2026-03-27T11:59:59][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-03-27T12:00:00][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] #> ] #> }, #> { -#> "@id": "20260227T152439-dslogs-dsuser_mappings.csv", +#> "@id": "20260327T120011-dslogs-dsuser_mappings.csv", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:39", -#> "name": "20260227T152439-dslogs-dsuser_mappings.csv", +#> "dateModified": "2026-03-27 12:00:11", +#> "name": "20260327T120011-dslogs-dsuser_mappings.csv", #> "description": "This file contains mappings and evaluated functions", #> "encodingFormat": "text/csv", #> "content": [ #> [ #> { -#> "timestamp": "2026-02-27T08:56:24", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "fx": "DSI::datashield.login", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T08:56:26", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T08:56:27", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:00:37", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "fx": "DSI::datashield.login", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:00:37", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:00:38", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:47", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 08413766-4fa8-4eec-9392-ec30581fb48c", -#> "fx": "DSI::datashield.login", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:48", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:49", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:39", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "fx": "DSI::datashield.login", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:39", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:40", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:40", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "fx": "DSI::datashield.login", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:40", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:41", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:13", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "fx": "DSI::datashield.login", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:13", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:14", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:01", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "fx": "DSI::datashield.login", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:01", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:03", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:02", +#> "timestamp": "2026-03-27T11:59:57", #> "action": "OPEN", #> "user": "dsuser", -#> "r_cmd": "Open session: fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "r_cmd": "Open session: 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "fx": "DSI::datashield.login", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:02", +#> "timestamp": "2026-03-27T11:59:59", #> "action": "ASSIGN", #> "user": "dsuser", #> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", #> "fx": "base::assign", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", #> "fx": "dsBase::lsDS", #> "symbol": "search.filter = NULL, 1L", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "base::exists(\"dsROCrate_test\")", #> "fx": "base::exists", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::classDS(\"dsROCrate_test\")", #> "fx": "dsBase::classDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::isValidDS(dsROCrate_test)", #> "fx": "dsBase::isValidDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::dimDS(\"dsROCrate_test\")", #> "fx": "dsBase::dimDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:05", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::colnamesDS(\"dsROCrate_test\")", #> "fx": "dsBase::colnamesDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> } #> ] @@ -1918,7 +1929,7 @@ print(safe_project_crate_v1) ###### Markdown report A markdown report can be created with an overview and details for an -RO-Crate, using the `dsROCrate::rocrate_report`: +RO-Crate, using the `dsROCrate::report`: **Only generate .Rmd file** @@ -1926,13 +1937,11 @@ RO-Crate, using the `dsROCrate::rocrate_report`: safe_project_crate_v1_rmd <- tempfile(fileext = ".Rmd") # temporary file safe_project_crate_contents <- safe_project_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_project_crate_v1_rmd, render = FALSE) -#> 1 'Author' entity was found! -#> 3 'Dataset' entities were found! -#> Warning: No entities were found with @type = 'WriteAction'! -#> Warning: No entities were found with @type = 'ControlAction'! + dsROCrate::report(filepath = safe_project_crate_v1_rmd, render = FALSE) +#> 4 'Author' entities were found! +#> 3 asset entities were found! #> 1 'Project' entity was found! -#> 14 'PropertyValue' OR 'SoftwareApplication' entities were found! +#> 22 'CreativeWork', 'PropertyValue' OR 'SoftwareApplication' entities were found! #> 2 'File' entities were found! # display Overview diagram @@ -1948,37 +1957,26 @@ safe_project_crate_contents$overview_data |> knitr::kable() ``` -| Project | Data | Access Level | People | Function | Timestamp | -|:--------|:-------|:-------------|:-------|:-------------------|:--------------------| -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T08:56:26 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T08:56:27 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:00:37 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:00:38 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:20:48 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:20:49 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:08:39 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:08:40 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:18:40 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:18:41 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:04:13 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:04:14 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:13:01 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:13:03 | -| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:24:02 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-02-27T15:24:04 | -| CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-02-27T15:24:05 | -| CNSIM | CNSIM2 | read | dsuser | | | -| CNSIM | CNSIM3 | read | dsuser | | | +| Project | Data | Access Level | People | Function | Timestamp | +|:--------|:-------|:-------------|:--------|:-------------------|:--------------------| +| CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-03-27T11:59:59 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-03-27T12:00:01 | +| CNSIM | CNSIM1 | read | dsuser1 | | | +| CNSIM | CNSIM2 | read | dsuser | | | +| CNSIM | CNSIM2 | read | dsuser2 | | | +| CNSIM | CNSIM3 | read | dsuser | | | +| CNSIM | CNSIM3 | read | dsuser3 | | | **Render and display report (HTML)** ``` r safe_project_crate_v1 |> - dsROCrate::rocrate_report(filepath = safe_project_crate_v1_rmd, + dsROCrate::report(filepath = safe_project_crate_v1_rmd, title = "DataSHIELD Safe Project - Audit Report", render = TRUE, overwrite = TRUE) @@ -2004,7 +2002,37 @@ study_crate_v1 <- url = "https://opal-demo.obiba.org" ) ) |> - dsROCrate::audit_study(project = "CNSIM") + dsROCrate::audit(project = "CNSIM") +#> opening file input connection. +#> Found 6 records... Imported 6 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 6 records... Imported 6 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 6 records... Imported 6 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 6 records... Imported 6 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 6 records... Imported 6 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 6 records... Imported 6 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. +#> opening file input connection. +#> Found 55 records... Imported 55 records. Simplifying... +#> closing file input connection. print(study_crate_v1) #> $opal_test @@ -2026,7 +2054,7 @@ print(study_crate_v1) #> "@type": "Dataset", #> "name": "", #> "description": "", -#> "datePublished": "2026-02-27", +#> "datePublished": "2026-03-27", #> "license": { #> "@id": "http://spdx.org/licenses/CC-BY-4.0" #> }, @@ -2040,28 +2068,53 @@ print(study_crate_v1) #> "name": "Five Safes RO-Crate profile" #> }, #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241", -#> "@type": "Dataset", -#> "name": "CNSIM1", -#> "dateCreated": "2026-01-17T10:53:48.925Z", -#> "dateModified": "2026-01-17T10:53:50.189Z", -#> "path": "/datasource/CNSIM/table/CNSIM1" +#> "@id": "#person:8ab380609ec94312fa958741d1f0f0b1", +#> "@type": "Person", +#> "name": "user1" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9", -#> "@type": "Dataset", -#> "name": "CNSIM2", -#> "dateCreated": "2026-01-17T10:53:50.194Z", -#> "dateModified": "2026-01-17T10:53:51.417Z", -#> "path": "/datasource/CNSIM/table/CNSIM2" +#> "@id": "#person:89bba9a8875a3a16196372b4c087edbd", +#> "@type": "Person", +#> "name": "ds" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492", -#> "@type": "Dataset", -#> "name": "CNSIM3", -#> "dateCreated": "2026-01-17T10:53:51.422Z", -#> "dateModified": "2026-01-17T10:53:52.663Z", -#> "path": "/datasource/CNSIM/table/CNSIM3" +#> "@id": "#person:ab761662ca15f3f7658a0b3adeaae564", +#> "@type": "Person", +#> "name": "bthillo@gmail.com", +#> "sub": "109004362127439404576", +#> "email_verified": true, +#> "given_name": "Roberto", +#> "family_name": "Villegas-Diaz", +#> "picture": "https://lh3.googleusercontent.com/a/ACg8ocK8GIJLuuDRjfevJjXSZ8Ymw_Y67r8_bsud8eLGClA92MS-GLhd8Q=s96-c", +#> "email": "bthillo@gmail.com" +#> }, +#> { +#> "@id": "#person:f53ed7aa4ab05429c9d20f360d451a98", +#> "@type": "Person", +#> "name": "i.w.farr@googlemail.com", +#> "sub": "106174335072326132292", +#> "email_verified": true, +#> "given_name": "ian", +#> "family_name": "farr", +#> "picture": "https://lh3.googleusercontent.com/a/ACg8ocKBagSKWdPGazh5CWkffgXleyPaSqn66IlAOMm0voLm-79S1A=s96-c", +#> "email": "i.w.farr@googlemail.com" +#> }, +#> { +#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", +#> "@type": "Person", +#> "name": "dsuser" +#> }, +#> { +#> "@id": "#person:315ba97bcf520312d32e7f1e4f5e8575", +#> "@type": "Person", +#> "name": "yannick.marcon@obiba.org", +#> "sub": "112183318969537221630", +#> "email_verified": true, +#> "given_name": "Yannick", +#> "hd": "obiba.org", +#> "family_name": "Marcon", +#> "picture": "https://lh3.googleusercontent.com/a/ACg8ocJFG4mQ2lz80itm91vEUX3jnj12IRv1tF_OaVBDf2Ear6pUsA=s96-c", +#> "email": "yannick.marcon@obiba.org" #> }, #> { #> "@id": "#project:7ba189863f9f641196596cb28e04aa14", @@ -2071,119 +2124,288 @@ print(study_crate_v1) #> "dateModified": "2026-01-17T10:53:52.663Z", #> "hasPart": [ #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> } #> ] #> }, #> { -#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", -#> "@type": "Person", -#> "name": "dsuser" -#> }, -#> { #> "@id": "#perm:9bf7f75b6c5b07d02830b95652cd39a0-dict-summary-read", #> "@type": "ReadAction", #> "agent": { #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." #> }, #> { -#> "@id": "_:localid:datashield.privacyLevel:5", +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae", +#> "@type": "Dataset", +#> "name": "CNSIM1", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM1", +#> "dateCreated": "2026-01-17T00:00:00Z", +#> "dateModified": "2026-01-17T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#asset:b6721026564c746f604df7ba785931fa", +#> "@type": "Dataset", +#> "name": "CNSIM2", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM2", +#> "dateCreated": "2026-01-17T00:00:00Z", +#> "dateModified": "2026-01-17T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d", +#> "@type": "Dataset", +#> "name": "CNSIM3", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM3", +#> "dateCreated": "2026-01-17T00:00:00Z", +#> "dateModified": "2026-01-17T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78", #> "@type": "PropertyValue", #> "name": "datashield.privacyLevel", #> "value": "5" #> }, #> { -#> "@id": "_:localid:default.datashield.privacyControlLevel:banana", +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b", #> "@type": "PropertyValue", #> "name": "default.datashield.privacyControlLevel", #> "value": "banana" #> }, #> { -#> "@id": "_:localid:default.nfilter.glm:0.33", +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae", #> "@type": "PropertyValue", #> "name": "default.nfilter.glm", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.kNN:3", +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7", #> "@type": "PropertyValue", #> "name": "default.nfilter.kNN", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.density:0.33", +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.density", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.max:40", +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.max", #> "value": "40" #> }, #> { -#> "@id": "_:localid:default.nfilter.noise:0.25", +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5", #> "@type": "PropertyValue", #> "name": "default.nfilter.noise", #> "value": "0.25" #> }, #> { -#> "@id": "_:localid:default.nfilter.string:80", +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54", #> "@type": "PropertyValue", #> "name": "default.nfilter.string", #> "value": "80" #> }, #> { -#> "@id": "_:localid:default.nfilter.stringShort:20", +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5", #> "@type": "PropertyValue", #> "name": "default.nfilter.stringShort", #> "value": "20" #> }, #> { -#> "@id": "_:localid:default.nfilter.subset:3", +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985", #> "@type": "PropertyValue", #> "name": "default.nfilter.subset", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.tab:3", +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0", #> "@type": "PropertyValue", #> "name": "default.nfilter.tab", #> "value": "3" #> }, #> { -#> "@id": "_:localid:3aaeab3631b4c9bbe7f44d60805b8f9c", +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8", +#> "@type": "CreativeWork", +#> "name": "Disclosure Control Environment", +#> "description": "Disclosure control settings extract from the OBiBa Opal server connection provided, using the profile: 'default'.", +#> "hasPart": [ +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78" +#> }, +#> { +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b" +#> }, +#> { +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae" +#> }, +#> { +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7" +#> }, +#> { +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79" +#> }, +#> { +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f" +#> }, +#> { +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5" +#> }, +#> { +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54" +#> }, +#> { +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5" +#> }, +#> { +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985" +#> }, +#> { +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0" +#> } +#> ] +#> }, +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27", #> "@type": "SoftwareApplication", #> "name": "dsBase", #> "version": "6.3.5", -#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows\n you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have\n been designed to only share non disclosive summary statistics, with built in automated output\n checking based on statistical disclosure control. With data sites setting the threshold values for\n the automated output checks. For more details, see 'citation(\"dsBase\")'." +#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have been designed to only share non disclosive summary statistics, with built in automated output checking based on statistical disclosure control. With data sites setting the threshold values for the automated output checks. For more details, see 'citation(\"dsBase\")'." #> }, #> { -#> "@id": "_:localid:e2f7c43973c40d7a6a6731da5a0aa564", +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44", #> "@type": "SoftwareApplication", #> "name": "dsTidyverse", #> "version": "1.1.0", #> "description": "Implementation of selected 'Tidyverse' functions within 'DataSHIELD', an open-source federated analysis solution in R. Currently, DataSHIELD contains very limited tools for data manipulation, so the aim of this package is to improve the researcher experience by implementing essential functions for data manipulation, including subsetting, filtering, grouping, and renaming variables. This is the serverside package which should be installed on the server holding the data, and is used in conjuncture with the clientside package 'dsTidyverseClient' which is installed in the local R environment of the analyst. For more information, see and ." #> }, #> { -#> "@id": "_:localid:411453e2513e6d909fe2cb8273b034dc", +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b", #> "@type": "SoftwareApplication", #> "name": "resourcer", #> "version": "1.5.0", -#> "description": "A resource represents some data or a computation unit. It is \n described by a URL and credentials. This package proposes a Resource model\n with \"resolver\" and \"client\" classes to facilitate the access and the usage of the \n resources." +#> "description": "A resource represents some data or a computation unit. It is described by a URL and credentials. This package proposes a Resource model with \"resolver\" and \"client\" classes to facilitate the access and the usage of the resources." +#> }, +#> { +#> "@id": "#software:0cd42af0c314700303b0c4b14b16b9ce", +#> "@type": "SoftwareApplication", +#> "name": "Opal", +#> "version": "5.6.0-SNAPSHOT", +#> "description": "Opal is OBiBa's (https://www.obiba.org/) core database application for epidemiological studies. Participant data, collected by questionnaires, medical instruments, sensors, administrative databases etc. can be integrated and stored in a central data repository under a uniform model." +#> }, +#> { +#> "@id": "#env:software_stack:55187df92eed3fae2198151d49e29389", +#> "@type": "CreativeWork", +#> "name": "Approved Analytical Software Environment", +#> "description": "Software packages installed in the controlled Opal/DataSHIELD environment used for federated analysis.", +#> "hasPart": [ +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27" +#> }, +#> { +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44" +#> }, +#> { +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b" +#> }, +#> { +#> "@id": "#software:0cd42af0c314700303b0c4b14b16b9ce" +#> } +#> ] +#> }, +#> { +#> "@id": "#control:output-checking", +#> "@type": "CreativeWork", +#> "name": "Statistical Disclosure Output Checking", +#> "description": "Automated disclosure control prevents release of small-cell counts and disclosive statistics." +#> }, +#> { +#> "@id": "#control:server-side-analysis", +#> "@type": "CreativeWork", +#> "name": "Server-Side Analysis Enforcement", +#> "description": "Raw data never leaves the secure server; analysis occurs via vetted aggregate functions." +#> }, +#> { +#> "@id": "#control:session-logging", +#> "@type": "CreativeWork", +#> "name": "Comprehensive Session Logging", +#> "description": "All analytical actions are logged and auditable." +#> }, +#> { +#> "@id": "#control:secure-facility", +#> "@type": "CreativeWork", +#> "name": "Secure Data Facility", +#> "description": "Access restricted to approved secure premises." +#> }, +#> { +#> "@id": "#control:access-governance", +#> "@type": "CreativeWork", +#> "name": "Access Governance Process", +#> "description": "Data access committee review and approval required." +#> }, +#> { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5", +#> "@type": "CreativeWork", +#> "name": "Safe Setting Controls (Opal)", +#> "description": "Technical, physical and organisational safeguards applied to minimise disclosure risk.", +#> "hasPart": [ +#> { +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8" +#> }, +#> { +#> "@id": "#env:software_stack:55187df92eed3fae2198151d49e29389" +#> }, +#> { +#> "@id": "#control:output-checking" +#> }, +#> { +#> "@id": "#control:server-side-analysis" +#> }, +#> { +#> "@id": "#control:session-logging" +#> }, +#> { +#> "@id": "#control:secure-facility" +#> }, +#> { +#> "@id": "#control:access-governance" +#> } +#> ] +#> }, +#> { +#> "@id": "#link:b16fbdedcc33e826878020dcd5fad3d3", +#> "@type": "CreativeWork", +#> "name": "Safe Settings x Safe Project Link", +#> "about": { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5" +#> }, +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } #> } #> ] #> } @@ -2207,7 +2429,7 @@ print(study_crate_v1) #> "@type": "Dataset", #> "name": "", #> "description": "", -#> "datePublished": "2026-02-27", +#> "datePublished": "2026-03-27", #> "license": { #> "@id": "http://spdx.org/licenses/CC-BY-4.0" #> }, @@ -2216,10 +2438,10 @@ print(study_crate_v1) #> }, #> "hasPart": [ #> { -#> "@id": "20260227T152444-dslogs-dsuser.log" +#> "@id": "20260327T120017-dslogs-dsuser.log" #> }, #> { -#> "@id": "20260227T152444-dslogs-dsuser_mappings.csv" +#> "@id": "20260327T120017-dslogs-dsuser_mappings.csv" #> } #> ] #> }, @@ -2229,60 +2451,63 @@ print(study_crate_v1) #> "name": "Five Safes RO-Crate profile" #> }, #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241", -#> "@type": "Dataset", -#> "name": "CNSIM1", -#> "dateCreated": "2026-02-27T06:29:50.731Z", -#> "dateModified": "2026-02-27T06:29:51.865Z", -#> "path": "/datasource/CNSIM/table/CNSIM1" +#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", +#> "@type": "Person", +#> "name": "dsuser" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9", -#> "@type": "Dataset", -#> "name": "CNSIM2", -#> "dateCreated": "2026-02-27T06:29:51.872Z", -#> "dateModified": "2026-02-27T06:29:53.057Z", -#> "path": "/datasource/CNSIM/table/CNSIM2" +#> "@id": "#person:cb809df1c2fb30b154f60b843e62b3d0", +#> "@type": "Person", +#> "name": "dsuser1" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492", -#> "@type": "Dataset", -#> "name": "CNSIM3", -#> "dateCreated": "2026-02-27T06:29:53.064Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", -#> "path": "/datasource/CNSIM/table/CNSIM3" +#> "@id": "#person:a3cd7ce7818436c83b1eadaa5ba47411", +#> "@type": "Person", +#> "name": "dsuser2" +#> }, +#> { +#> "@id": "#person:5657241505661473308ae9aa9a378293", +#> "@type": "Person", +#> "name": "dsuser3" #> }, #> { #> "@id": "#project:7ba189863f9f641196596cb28e04aa14", #> "@type": "Project", #> "name": "CNSIM", -#> "dateCreated": "2026-02-27T06:29:49.235Z", -#> "dateModified": "2026-02-27T06:29:54.200Z", +#> "dateCreated": "2026-03-27T06:29:56.149Z", +#> "dateModified": "2026-03-27T06:30:01.340Z", #> "hasPart": [ #> { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> } #> ] #> }, #> { -#> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2", -#> "@type": "Person", -#> "name": "dsuser" -#> }, -#> { #> "@id": "#perm:9bf7f75b6c5b07d02830b95652cd39a0-dict-summary-read", #> "@type": "ReadAction", #> "agent": { #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:67adf2d8e106aca9b11de773758bd241" +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#perm:4d2673da68a58c3bce23a61d97b6df51-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:cb809df1c2fb30b154f60b843e62b3d0" +#> }, +#> "object": { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." @@ -2294,7 +2519,19 @@ print(study_crate_v1) #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:ffb1b1adcafc024743be1b0c252787c9" +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#perm:802d140a064e6ebf3a784f759af1b640-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:a3cd7ce7818436c83b1eadaa5ba47411" +#> }, +#> "object": { +#> "@id": "#asset:b6721026564c746f604df7ba785931fa" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." @@ -2306,411 +2543,387 @@ print(study_crate_v1) #> "@id": "#person:a0af2a94926db1b49ad7a812eef509d2" #> }, #> "object": { -#> "@id": "#dataset:cc3061aef69ce457358815fb9d8c6492" +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" #> }, #> "actionStatus": "PotentialActionStatus", #> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." #> }, #> { -#> "@id": "_:localid:datashield.privacyLevel:5", +#> "@id": "#perm:04c3f293c7a360fe0a1b7c29c8363540-dict-summary-read", +#> "@type": "ReadAction", +#> "agent": { +#> "@id": "#person:5657241505661473308ae9aa9a378293" +#> }, +#> "object": { +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d" +#> }, +#> "actionStatus": "PotentialActionStatus", +#> "description": "User may view table dictionary and summary statistics only; access to individual values is restricted." +#> }, +#> { +#> "@id": "#asset:fad6faf661584d53e58f9730b14c5aae", +#> "@type": "Dataset", +#> "name": "CNSIM1", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM1", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#asset:b6721026564c746f604df7ba785931fa", +#> "@type": "Dataset", +#> "name": "CNSIM2", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM2", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#asset:14fbb8de0021e6d237a2ed7779f9625d", +#> "@type": "Dataset", +#> "name": "CNSIM3", +#> "description": "", +#> "url": "/datasource/CNSIM/table/CNSIM3", +#> "dateCreated": "2026-03-27T00:00:00Z", +#> "dateModified": "2026-03-27T00:00:00Z", +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78", #> "@type": "PropertyValue", #> "name": "datashield.privacyLevel", #> "value": "5" #> }, #> { -#> "@id": "_:localid:default.datashield.privacyControlLevel:banana", +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b", #> "@type": "PropertyValue", #> "name": "default.datashield.privacyControlLevel", #> "value": "banana" #> }, #> { -#> "@id": "_:localid:default.nfilter.glm:0.33", +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae", #> "@type": "PropertyValue", #> "name": "default.nfilter.glm", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.kNN:3", +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7", #> "@type": "PropertyValue", #> "name": "default.nfilter.kNN", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.density:0.33", +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.density", #> "value": "0.33" #> }, #> { -#> "@id": "_:localid:default.nfilter.levels.max:40", +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f", #> "@type": "PropertyValue", #> "name": "default.nfilter.levels.max", #> "value": "40" #> }, #> { -#> "@id": "_:localid:default.nfilter.noise:0.25", +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5", #> "@type": "PropertyValue", #> "name": "default.nfilter.noise", #> "value": "0.25" #> }, #> { -#> "@id": "_:localid:default.nfilter.string:80", +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54", #> "@type": "PropertyValue", #> "name": "default.nfilter.string", #> "value": "80" #> }, #> { -#> "@id": "_:localid:default.nfilter.stringShort:20", +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5", #> "@type": "PropertyValue", #> "name": "default.nfilter.stringShort", #> "value": "20" #> }, #> { -#> "@id": "_:localid:default.nfilter.subset:3", +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985", #> "@type": "PropertyValue", #> "name": "default.nfilter.subset", #> "value": "3" #> }, #> { -#> "@id": "_:localid:default.nfilter.tab:3", +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0", #> "@type": "PropertyValue", #> "name": "default.nfilter.tab", #> "value": "3" #> }, #> { -#> "@id": "_:localid:3aaeab3631b4c9bbe7f44d60805b8f9c", +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8", +#> "@type": "CreativeWork", +#> "name": "Disclosure Control Environment", +#> "description": "Disclosure control settings extract from the OBiBa Opal server connection provided, using the profile: 'default'.", +#> "hasPart": [ +#> { +#> "@id": "#disc:27d8c1d2233ecb654a24b635fd4dbd78" +#> }, +#> { +#> "@id": "#disc:46b65707a6b998f3b1364bc10e6d9b4b" +#> }, +#> { +#> "@id": "#disc:7769282e7b0a1cb8887f60886c7b56ae" +#> }, +#> { +#> "@id": "#disc:49d822f52075aafbec1d1b2545aa46b7" +#> }, +#> { +#> "@id": "#disc:9a59210a743557bbd61cb21c3f1e0a79" +#> }, +#> { +#> "@id": "#disc:a00b1e31a448142ee66bbb013f990e1f" +#> }, +#> { +#> "@id": "#disc:29dd5e27a9f1c66d81a7037c236e7dd5" +#> }, +#> { +#> "@id": "#disc:1c12e549b91e2cc0856f56657988ce54" +#> }, +#> { +#> "@id": "#disc:786bc0ffcdd3054925e431240caecea5" +#> }, +#> { +#> "@id": "#disc:fd9bea5ef311d5f14b28d237ecb6e985" +#> }, +#> { +#> "@id": "#disc:4e93a50cbd8cfea8f0a6adc50ee7aac0" +#> } +#> ] +#> }, +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27", #> "@type": "SoftwareApplication", #> "name": "dsBase", #> "version": "6.3.5", -#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows\n you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have\n been designed to only share non disclosive summary statistics, with built in automated output\n checking based on statistical disclosure control. With data sites setting the threshold values for\n the automated output checks. For more details, see 'citation(\"dsBase\")'." +#> "description": "Base 'DataSHIELD' functions for the server side. 'DataSHIELD' is a software package which allows you to do non-disclosive federated analysis on sensitive data. 'DataSHIELD' analytic functions have been designed to only share non disclosive summary statistics, with built in automated output checking based on statistical disclosure control. With data sites setting the threshold values for the automated output checks. For more details, see 'citation(\"dsBase\")'." #> }, #> { -#> "@id": "_:localid:e2f7c43973c40d7a6a6731da5a0aa564", +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44", #> "@type": "SoftwareApplication", #> "name": "dsTidyverse", #> "version": "1.1.0", #> "description": "Implementation of selected 'Tidyverse' functions within 'DataSHIELD', an open-source federated analysis solution in R. Currently, DataSHIELD contains very limited tools for data manipulation, so the aim of this package is to improve the researcher experience by implementing essential functions for data manipulation, including subsetting, filtering, grouping, and renaming variables. This is the serverside package which should be installed on the server holding the data, and is used in conjuncture with the clientside package 'dsTidyverseClient' which is installed in the local R environment of the analyst. For more information, see and ." #> }, #> { -#> "@id": "_:localid:411453e2513e6d909fe2cb8273b034dc", +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b", #> "@type": "SoftwareApplication", #> "name": "resourcer", #> "version": "1.5.0", -#> "description": "A resource represents some data or a computation unit. It is \n described by a URL and credentials. This package proposes a Resource model\n with \"resolver\" and \"client\" classes to facilitate the access and the usage of the \n resources." +#> "description": "A resource represents some data or a computation unit. It is described by a URL and credentials. This package proposes a Resource model with \"resolver\" and \"client\" classes to facilitate the access and the usage of the resources." +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc", +#> "@type": "SoftwareApplication", +#> "name": "Opal", +#> "version": "5.6.1", +#> "description": "Opal is OBiBa's (https://www.obiba.org/) core database application for epidemiological studies. Participant data, collected by questionnaires, medical instruments, sensors, administrative databases etc. can be integrated and stored in a central data repository under a uniform model." +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1", +#> "@type": "CreativeWork", +#> "name": "Approved Analytical Software Environment", +#> "description": "Software packages installed in the controlled Opal/DataSHIELD environment used for federated analysis.", +#> "hasPart": [ +#> { +#> "@id": "#software:f8784d80bad08f840fba23fa9c41ec27" +#> }, +#> { +#> "@id": "#software:afa897ee58de14b27570462c97a9dd44" +#> }, +#> { +#> "@id": "#software:d2133fef4ca6df6312f205e51aee541b" +#> }, +#> { +#> "@id": "#software:77514b155cfdb0c8c535fbe54daf67fc" +#> } +#> ] +#> }, +#> { +#> "@id": "#control:output-checking", +#> "@type": "CreativeWork", +#> "name": "Statistical Disclosure Output Checking", +#> "description": "Automated disclosure control prevents release of small-cell counts and disclosive statistics." +#> }, +#> { +#> "@id": "#control:server-side-analysis", +#> "@type": "CreativeWork", +#> "name": "Server-Side Analysis Enforcement", +#> "description": "Raw data never leaves the secure server; analysis occurs via vetted aggregate functions." +#> }, +#> { +#> "@id": "#control:session-logging", +#> "@type": "CreativeWork", +#> "name": "Comprehensive Session Logging", +#> "description": "All analytical actions are logged and auditable." +#> }, +#> { +#> "@id": "#control:secure-facility", +#> "@type": "CreativeWork", +#> "name": "Secure Data Facility", +#> "description": "Access restricted to approved secure premises." #> }, #> { -#> "@id": "20260227T152444-dslogs-dsuser.log", +#> "@id": "#control:access-governance", +#> "@type": "CreativeWork", +#> "name": "Access Governance Process", +#> "description": "Data access committee review and approval required." +#> }, +#> { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5", +#> "@type": "CreativeWork", +#> "name": "Safe Setting Controls (Opal)", +#> "description": "Technical, physical and organisational safeguards applied to minimise disclosure risk.", +#> "hasPart": [ +#> { +#> "@id": "#env:disclosure_settings:7d746edc2fb36ee671241a333742b3a8" +#> }, +#> { +#> "@id": "#env:software_stack:239d65ed3c0c0c3932e23cebb34be7e1" +#> }, +#> { +#> "@id": "#control:output-checking" +#> }, +#> { +#> "@id": "#control:server-side-analysis" +#> }, +#> { +#> "@id": "#control:session-logging" +#> }, +#> { +#> "@id": "#control:secure-facility" +#> }, +#> { +#> "@id": "#control:access-governance" +#> } +#> ] +#> }, +#> { +#> "@id": "#link:b16fbdedcc33e826878020dcd5fad3d3", +#> "@type": "CreativeWork", +#> "name": "Safe Settings x Safe Project Link", +#> "about": { +#> "@id": "#safesetting:8489860e7540c5dfb95b5d8ddab232c5" +#> }, +#> "isPartOf": { +#> "@id": "#project:7ba189863f9f641196596cb28e04aa14" +#> } +#> }, +#> { +#> "@id": "20260327T120017-dslogs-dsuser.log", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:44", -#> "name": "20260227T152444-dslogs-dsuser.log", +#> "dateModified": "2026-03-27 12:00:17", +#> "name": "20260327T120017-dslogs-dsuser.log", #> "description": "This file contains the raw logs for the user: `dsuser` , between: ALL and ALL", #> "encodingFormat": "text/plain", #> "content": [ -#> ["[INFO][2026-02-27T08:56:24][OPEN] created a datashield session 622f2c74-9242-48e2-82d9-bc3250f8aa4b", "[INFO][2026-02-27T08:56:26][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T08:56:26][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T08:56:27][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:00:37][OPEN] created a datashield session 7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", "[INFO][2026-02-27T09:00:37][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T09:00:38][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:00:38][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:20:47][OPEN] created a datashield session 08413766-4fa8-4eec-9392-ec30581fb48c", "[INFO][2026-02-27T09:20:48][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T09:20:48][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T09:20:49][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:08:39][OPEN] created a datashield session 88a48432-61c8-4444-bb6f-1d0174d4f177", "[INFO][2026-02-27T14:08:39][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T14:08:39][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:08:40][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:18:40][OPEN] created a datashield session e45b3573-21cc-445d-8d22-b7fc289d279a", "[INFO][2026-02-27T14:18:40][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T14:18:40][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T14:18:41][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:04:13][OPEN] created a datashield session 026b4b18-c40c-4f08-8a19-75716fee1c75", "[INFO][2026-02-27T15:04:13][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:04:14][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:04:14][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:13:01][OPEN] created a datashield session a3a6f257-79ef-443c-a110-393b1ff196f0", "[INFO][2026-02-27T15:13:01][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:13:02][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:13:03][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:02][OPEN] created a datashield session fc258313-6846-4a8e-93d9-4809cd794fbe", "[INFO][2026-02-27T15:24:02][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-02-27T15:24:03][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:04][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-02-27T15:24:05][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] +#> ["[INFO][2026-03-27T11:59:57][OPEN] created a datashield session 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", "[INFO][2026-03-27T11:59:59][ASSIGN] created symbol 'dsROCrate_test' from: 'dsROCrate_test <- opal[CNSIM.CNSIM1]'", "[INFO][2026-03-27T12:00:00][PARSE] parsed 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::lsDS(search.filter = NULL, 1L)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'base::exists(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::classDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::isValidDS(dsROCrate_test)'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::dimDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][PARSE] parsed 'dsBase::colnamesDS(\"dsROCrate_test\")'", "[INFO][2026-03-27T12:00:01][AGGREGATE] evaluated 'dsBase::colnamesDS(\"dsROCrate_test\")'"] #> ] #> }, #> { -#> "@id": "20260227T152444-dslogs-dsuser_mappings.csv", +#> "@id": "20260327T120017-dslogs-dsuser_mappings.csv", #> "@type": "File", -#> "dateModified": "2026-02-27 15:24:44", -#> "name": "20260227T152444-dslogs-dsuser_mappings.csv", +#> "dateModified": "2026-03-27 12:00:17", +#> "name": "20260327T120017-dslogs-dsuser_mappings.csv", #> "description": "This file contains mappings and evaluated functions", #> "encodingFormat": "text/csv", #> "content": [ #> [ #> { -#> "timestamp": "2026-02-27T08:56:24", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "fx": "DSI::datashield.login", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T08:56:26", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T08:56:27", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "622f2c74-9242-48e2-82d9-bc3250f8aa4b", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:00:37", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "fx": "DSI::datashield.login", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:00:37", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:00:38", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "7925f5e8-f7b6-416e-b05b-5ac2c19f00c1", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:47", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 08413766-4fa8-4eec-9392-ec30581fb48c", -#> "fx": "DSI::datashield.login", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:48", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T09:20:49", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "08413766-4fa8-4eec-9392-ec30581fb48c", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:39", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "fx": "DSI::datashield.login", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:39", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:08:40", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "88a48432-61c8-4444-bb6f-1d0174d4f177", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:40", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "fx": "DSI::datashield.login", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:40", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T14:18:41", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "e45b3573-21cc-445d-8d22-b7fc289d279a", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:13", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: 026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "fx": "DSI::datashield.login", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:13", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:04:14", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "026b4b18-c40c-4f08-8a19-75716fee1c75", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:01", -#> "action": "OPEN", -#> "user": "dsuser", -#> "r_cmd": "Open session: a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "fx": "DSI::datashield.login", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:01", -#> "action": "ASSIGN", -#> "user": "dsuser", -#> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", -#> "fx": "base::assign", -#> "table": "CNSIM.CNSIM1", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:13:03", -#> "action": "AGGREGATE", -#> "user": "dsuser", -#> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", -#> "fx": "dsBase::lsDS", -#> "symbol": "search.filter = NULL, 1L", -#> "table": "CNSIM.CNSIM1", -#> "session": "a3a6f257-79ef-443c-a110-393b1ff196f0", -#> "backend": "OBiBa's Opal" -#> }, -#> { -#> "timestamp": "2026-02-27T15:24:02", +#> "timestamp": "2026-03-27T11:59:57", #> "action": "OPEN", #> "user": "dsuser", -#> "r_cmd": "Open session: fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "r_cmd": "Open session: 84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "fx": "DSI::datashield.login", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:02", +#> "timestamp": "2026-03-27T11:59:59", #> "action": "ASSIGN", #> "user": "dsuser", #> "r_cmd": "dsROCrate_test <- opal[CNSIM.CNSIM1]", #> "fx": "base::assign", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::lsDS(search.filter = NULL, 1L)", #> "fx": "dsBase::lsDS", #> "symbol": "search.filter = NULL, 1L", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "base::exists(\"dsROCrate_test\")", #> "fx": "base::exists", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::classDS(\"dsROCrate_test\")", #> "fx": "dsBase::classDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::isValidDS(dsROCrate_test)", #> "fx": "dsBase::isValidDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:04", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::dimDS(\"dsROCrate_test\")", #> "fx": "dsBase::dimDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> }, #> { -#> "timestamp": "2026-02-27T15:24:05", +#> "timestamp": "2026-03-27T12:00:01", #> "action": "AGGREGATE", #> "user": "dsuser", #> "r_cmd": "dsBase::colnamesDS(\"dsROCrate_test\")", #> "fx": "dsBase::colnamesDS", #> "symbol": "dsROCrate_test", #> "table": "CNSIM.CNSIM1", -#> "session": "fc258313-6846-4a8e-93d9-4809cd794fbe", +#> "session": "84b50a64-e51e-4d54-a155-bd3ebbdbcdf8", #> "backend": "OBiBa's Opal" #> } #> ] @@ -2718,17 +2931,12 @@ print(study_crate_v1) #> } #> ] #> } -#> -#> attr(,"audit_type") -#> [1] "Study" -#> attr(,"project") -#> [1] "CNSIM" ``` ###### Markdown report A markdown report can be created with an overview and details for an -RO-Crate, using the `dsROCrate::rocrate_report`: +RO-Crate, using the `dsROCrate::report`: **Only generate .Rmd file** @@ -2736,20 +2944,15 @@ RO-Crate, using the `dsROCrate::rocrate_report`: study_crate_v1_rmd <- tempfile(fileext = ".Rmd") # temporary file safe_project_crate_contents <- study_crate_v1 |> - dsROCrate::rocrate_report(filepath = study_crate_v1_rmd, render = FALSE) -#> 1 'Author' entity was found! -#> 3 'Dataset' entities were found! -#> Warning: No entities were found with @type = 'WriteAction'! -#> Warning: No entities were found with @type = 'ControlAction'! + dsROCrate::report(filepath = study_crate_v1_rmd, render = FALSE) +#> 6 'Author' entities were found! +#> 3 asset entities were found! #> 1 'Project' entity was found! -#> 14 'PropertyValue' OR 'SoftwareApplication' entities were found! -#> Warning: No entities were found with @type = 'File'! -#> 1 'Author' entity was found! -#> 3 'Dataset' entities were found! -#> Warning: No entities were found with @type = 'WriteAction'! -#> Warning: No entities were found with @type = 'ControlAction'! +#> 22 'CreativeWork', 'PropertyValue' OR 'SoftwareApplication' entities were found! +#> 4 'Author' entities were found! +#> 3 asset entities were found! #> 1 'Project' entity was found! -#> 14 'PropertyValue' OR 'SoftwareApplication' entities were found! +#> 22 'CreativeWork', 'PropertyValue' OR 'SoftwareApplication' entities were found! #> 2 'File' entities were found! # display Overview diagram @@ -2767,36 +2970,25 @@ safe_project_crate_contents$overview_data |> | Server | Project | Data | Access Level | People | Function | Timestamp | |:---|:---|:---|:---|:---|:---|:---| -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T08:56:26 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T08:56:27 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:00:37 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:00:38 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T09:20:48 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T09:20:49 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:08:39 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:08:40 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T14:18:40 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T14:18:41 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:04:13 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:04:14 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:13:01 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:13:03 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-02-27T15:24:02 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-02-27T15:24:04 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-02-27T15:24:04 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-02-27T15:24:04 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-02-27T15:24:04 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-02-27T15:24:04 | -| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-02-27T15:24:05 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::assign | 2026-03-27T11:59:59 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::lsDS | 2026-03-27T12:00:01 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | base::exists | 2026-03-27T12:00:01 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::classDS | 2026-03-27T12:00:01 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::isValidDS | 2026-03-27T12:00:01 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::dimDS | 2026-03-27T12:00:01 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser | dsBase::colnamesDS | 2026-03-27T12:00:01 | +| opal_demo | CNSIM | CNSIM1 | read | dsuser1 | | | | opal_demo | CNSIM | CNSIM2 | read | dsuser | | | +| opal_demo | CNSIM | CNSIM2 | read | dsuser2 | | | | opal_demo | CNSIM | CNSIM3 | read | dsuser | | | +| opal_demo | CNSIM | CNSIM3 | read | dsuser3 | | | | opal_test | CNSIM | CNSIM1 | read | dsuser | | | **Render and display report (HTML)** ``` r study_crate_v1 |> - dsROCrate::rocrate_report(filepath = study_crate_v1_rmd, + dsROCrate::report(filepath = study_crate_v1_rmd, title = "DataSHIELD Study audit", render = TRUE, overwrite = TRUE) diff --git a/man/append_entity_ref.Rd b/man/append_entity_ref.Rd new file mode 100644 index 0000000..2d7807b --- /dev/null +++ b/man/append_entity_ref.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-rocrateR.R +\name{append_entity_ref} +\alias{append_entity_ref} +\title{Append entity reference} +\usage{ +append_entity_ref(rocrate, id, key, ref_id) +} +\description{ +Adds a reference to an existing property while +preserving existing values and avoiding duplicates. +} +\keyword{internal} diff --git a/man/audit.Rd b/man/audit.Rd new file mode 100644 index 0000000..c92afb4 --- /dev/null +++ b/man/audit.Rd @@ -0,0 +1,90 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/audit.R +\name{audit} +\alias{audit} +\alias{audit.armadillo} +\alias{audit.character} +\alias{audit.cr8tor} +\alias{audit.list} +\alias{audit.opal} +\alias{audit.rocrate} +\title{Create an audit RO-Crate} +\usage{ +audit(x, ...) + +\method{audit}{armadillo}(x, ..., intent = NULL) + +\method{audit}{character}(x, ..., intent = NULL) + +\method{audit}{cr8tor}(x, ..., intent = NULL) + +\method{audit}{list}(x, ..., intent = NULL) + +\method{audit}{opal}( + x, + ..., + intent = NULL, + project = NULL, + user = NULL, + logs_from = -Inf, + logs_to = Inf, + path = NULL +) + +\method{audit}{rocrate}(x, ..., intent = NULL) +} +\arguments{ +\item{x}{Object to be audited. This can be +\itemize{ +\item a \emph{connection} to a DataSHIELD server (e.g., OBiBa's Opal). +\item a \emph{path} pointing to an RO-Crate OR a \code{cr8tor} archive / +. governance bundle. +\item an \emph{RO-Crate} object. +} +Alternatively, a list of any of the above.} + +\item{...}{Additional arguments.} + +\item{intent}{Additional object with governance bundle/specification of the +intent of a project. It takes the same types as \code{x}.} + +\item{project}{String with project name(s) from which to extra Safe Project +details.} + +\item{user}{String with the user name for which to extract Safe People +details.} + +\item{logs_from}{Lower limit timestamp to filter out the outputs generated +(default: \code{-Inf}, everything up to \code{logs_to})} + +\item{logs_to}{Upper limit timestamp to filter out the outputs generated +(default: \code{Inf}, everything from \code{logs_from} onwards).} + +\item{path}{String with path pointing to the root of the RO-Crate. This will +be used to store log files. If not provided, logs will be stored within +the RO-Crate returned by this function.} +} +\value{ +RO-Crate with audit details. +} +\description{ +Create an audit RO-Crate following the 5 Safes Principles. +} +\details{ +This function handles various audit types, which will be dispatched based on +the input object. If the input object is + +\itemize{ +\item a \emph{connection} to a DataSHIELD server (e.g., OBiBa's Opal): +. generates an RO-Crate object with deployment details, including outputs. +\item a \emph{path} pointing to +\itemize{ +\item \strong{a \code{cr8tor} archive / governance bundle}: generates an +RO-Crate object with pre-deployment governance details. +\item \strong{an RO-Crate object}: generates an RO-Crate object with +clearly defined 5 Safes elements. +} +\item an \emph{RO-Crate} object: generates an RO-Crate object with clearly +defined 5 Safes elements. +} +} diff --git a/man/audit_cr8tor.Rd b/man/audit_cr8tor.Rd deleted file mode 100644 index 911dd71..0000000 --- a/man/audit_cr8tor.Rd +++ /dev/null @@ -1,24 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/audit_cr8tor.R -\name{audit_cr8tor} -\alias{audit_cr8tor} -\title{Audit cr8tor project archive} -\usage{ -audit_cr8tor(path, ...) -} -\arguments{ -\item{path}{Path to cr8tor archive.} - -\item{...}{Additional arguments for \link[rocrateR:load_rocrate]{rocrateR::load_rocrate}.} -} -\value{ -RO-Crate audit object -} -\description{ -This audit loads a cr8tor project archive and generates an RO-Crate object -with pre-deployment governance details. This then can be rendered with -\code{\link[=rocrate_report]{rocrate_report()}}. -} -\references{ -https://karectl-crates.github.io/cr8tor-metamodel/ -} diff --git a/man/audit_safe_people.Rd b/man/audit_engine.Rd similarity index 67% rename from man/audit_safe_people.Rd rename to man/audit_engine.Rd index 2ec7bda..5e134b0 100644 --- a/man/audit_safe_people.Rd +++ b/man/audit_engine.Rd @@ -1,20 +1,20 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/audit_safe_people.R -\name{audit_safe_people} -\alias{audit_safe_people} -\alias{audit_safe_people.default} -\alias{audit_safe_people.opal} -\title{Audit Safe People details} +% Please edit documentation in R/audit_engine.R +\name{audit_engine} +\alias{audit_engine} +\alias{audit_engine.cr8tor} +\alias{audit_engine.opal} +\title{Audit Engine} \usage{ -audit_safe_people(x, ...) +audit_engine(x, ...) -\method{audit_safe_people}{default}(x, ...) +\method{audit_engine}{cr8tor}(x, ...) -\method{audit_safe_people}{opal}( +\method{audit_engine}{opal}( x, ..., - user, project = NULL, + user = NULL, logs_from = -Inf, logs_to = Inf, path = NULL @@ -26,10 +26,10 @@ the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}). \item{...}{Other optional arguments, see full documentation for details.} -\item{user}{String with the user name for which to extract Safe People +\item{project}{String with project name(s) from which to extra Safe Project details.} -\item{project}{String with project name(s) from which to extra Safe People +\item{user}{String with the user name for which to extract Safe People details.} \item{logs_from}{Lower limit timestamp to filter out the outputs generated @@ -43,9 +43,9 @@ be used to store log files. If not provided, logs will be stored within the RO-Crate returned by this function.} } \value{ -Updated RO-Crate object with Safe People information. +Audit RO-Crate with 5 Safes Components. } \description{ -Audit Safe People details from a 'DataSHIELD' server, an RO-Crate object or -a file path pointing to an RO-Crate. +Audit Engine } +\keyword{internal} diff --git a/man/audit_safe_project.Rd b/man/audit_safe_project.Rd deleted file mode 100644 index 6b26c6c..0000000 --- a/man/audit_safe_project.Rd +++ /dev/null @@ -1,47 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/audit_safe_project.R -\name{audit_safe_project} -\alias{audit_safe_project} -\alias{audit_safe_project.default} -\alias{audit_safe_project.opal} -\title{Audit Safe Project details} -\usage{ -audit_safe_project(x, ...) - -\method{audit_safe_project}{default}(x, ...) - -\method{audit_safe_project}{opal}( - x, - ..., - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} - -\item{...}{Other optional arguments, see full documentation for details.} - -\item{project}{String with project name(s) from which to extra Safe People -details.} - -\item{logs_from}{Lower limit timestamp to filter out the outputs generated -(default: \code{-Inf}, everything up to \code{logs_to})} - -\item{logs_to}{Upper limit timestamp to filter out the outputs generated -(default: \code{Inf}, everything from \code{logs_from} onwards).} - -\item{path}{String with path pointing to the root of the RO-Crate. This will -be used to store log files. If not provided, logs will be stored within -the RO-Crate returned by this function.} -} -\value{ -Updated RO-Crate object with Safe Project information. -} -\description{ -Audit Safe Project details from a 'DataSHIELD' server, an RO-Crate object or -a file path pointing to an RO-Crate. -} diff --git a/man/audit_study.Rd b/man/audit_study.Rd deleted file mode 100644 index b6efab8..0000000 --- a/man/audit_study.Rd +++ /dev/null @@ -1,57 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/audit_study.R -\name{audit_study} -\alias{audit_study} -\alias{audit_study.default} -\alias{audit_study.list} -\alias{audit_study.opal} -\title{Audit Study details} -\usage{ -audit_study(x, ...) - -\method{audit_study}{default}(x, ...) - -\method{audit_study}{list}( - x, - ..., - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) - -\method{audit_study}{opal}( - x, - ..., - project = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} - -\item{...}{Other optional arguments, see full documentation for details.} - -\item{project}{String with project name(s) from which to extra Safe People -details.} - -\item{logs_from}{Lower limit timestamp to filter out the outputs generated -(default: \code{-Inf}, everything up to \code{logs_to})} - -\item{logs_to}{Upper limit timestamp to filter out the outputs generated -(default: \code{Inf}, everything from \code{logs_from} onwards).} - -\item{path}{String with path pointing to the root of the RO-Crate. This will -be used to store log files. If not provided, logs will be stored within -the RO-Crate returned by this function.} -} -\value{ -Updated RO-Crate object with Study information. -} -\description{ -Audit Study details from a 'DataSHIELD' server, an RO-Crate object or -a file path pointing to an RO-Crate. -} diff --git a/man/dot-break_tibble.Rd b/man/dot-break_tibble.Rd index 984612d..63f913c 100644 --- a/man/dot-break_tibble.Rd +++ b/man/dot-break_tibble.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R +% Please edit documentation in R/report.R \name{.break_tibble} \alias{.break_tibble} \title{Break tibble by group, \code{varname}} diff --git a/man/dot-markdown_report_body.Rd b/man/dot-markdown_report_body.Rd index fb89bdc..b7882df 100644 --- a/man/dot-markdown_report_body.Rd +++ b/man/dot-markdown_report_body.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R +% Please edit documentation in R/report.R \name{.markdown_report_body} \alias{.markdown_report_body} \title{Generate Markdown report's body} diff --git a/man/dot-markdown_report_header.Rd b/man/dot-markdown_report_header.Rd index 5d340c9..cf0bf99 100644 --- a/man/dot-markdown_report_header.Rd +++ b/man/dot-markdown_report_header.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R +% Please edit documentation in R/report.R \name{.markdown_report_header} \alias{.markdown_report_header} \title{Generate Markdown report's header} diff --git a/man/dot-markdown_report_rocrate.Rd b/man/dot-markdown_report_rocrate.Rd index 545990c..13276ea 100644 --- a/man/dot-markdown_report_rocrate.Rd +++ b/man/dot-markdown_report_rocrate.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R +% Please edit documentation in R/report.R \name{.markdown_report_rocrate} \alias{.markdown_report_rocrate} \title{Embed RO-Crate in Markdown report} diff --git a/man/dot-overview_diagram.Rd b/man/dot-overview_diagram.Rd index b579631..26306e4 100644 --- a/man/dot-overview_diagram.Rd +++ b/man/dot-overview_diagram.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R +% Please edit documentation in R/report.R \name{.overview_diagram} \alias{.overview_diagram} \title{Create diagram for RO-Crate overview} diff --git a/man/dot-tidy_overview.Rd b/man/dot-tidy_overview.Rd index 405b0ad..ca35a57 100644 --- a/man/dot-tidy_overview.Rd +++ b/man/dot-tidy_overview.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R +% Please edit documentation in R/report.R \name{.tidy_overview} \alias{.tidy_overview} \title{Create tidy version of the overview table} diff --git a/man/dsROCrate-package.Rd b/man/dsROCrate-package.Rd index f504919..6aa5977 100644 --- a/man/dsROCrate-package.Rd +++ b/man/dsROCrate-package.Rd @@ -4,7 +4,7 @@ \name{dsROCrate-package} \alias{dsROCrate} \alias{dsROCrate-package} -\title{dsROCrate: 'DataSHIELD' RO-Crate Wrapper Functions} +\title{dsROCrate: 'DataSHIELD' RO-Crate Governance Functions} \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} diff --git a/man/extract_safe_data.Rd b/man/extract_safe_data.Rd index 9674262..32853fb 100644 --- a/man/extract_safe_data.Rd +++ b/man/extract_safe_data.Rd @@ -10,7 +10,13 @@ extract_safe_data(x, ...) \method{extract_safe_data}{opal}(x, ..., rocrate = rocrateR::rocrate_5s()) -\method{extract_safe_data}{rocrate}(x, ..., id = NULL, rocrate = rocrateR::rocrate_5s()) +\method{extract_safe_data}{rocrate}( + x, + ..., + id = NULL, + asset_id_suffix = "#asset:", + rocrate = rocrateR::rocrate_5s() +) } \arguments{ \item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with @@ -24,6 +30,9 @@ RO-Crate.} \item{id}{(Optional) Vector with \verb{@id} strings for Safe Data entity(ies) to be extracted from the given RO-Crate, \code{x}.} + +\item{asset_id_suffix}{String with ID suffix for the tables/datasets +entities in the RO-Crate (default: \code{"#asset:"}).} } \value{ RO-Crate with Safe Data entity(ies). diff --git a/man/figures/README-safe_project_crate_audit_v1-1.png b/man/figures/README-safe_project_crate_audit_v1-1.png index 758c424..27440e4 100644 Binary files a/man/figures/README-safe_project_crate_audit_v1-1.png and b/man/figures/README-safe_project_crate_audit_v1-1.png differ diff --git a/man/figures/README-study_crate_audit_v1-1.png b/man/figures/README-study_crate_audit_v1-1.png index 4c6a33b..b39bbb4 100644 Binary files a/man/figures/README-study_crate_audit_v1-1.png and b/man/figures/README-study_crate_audit_v1-1.png differ diff --git a/man/figures/logo.png b/man/figures/logo.png index 2aedf15..3952ea5 100644 Binary files a/man/figures/logo.png and b/man/figures/logo.png differ diff --git a/man/figures/logo_black.png b/man/figures/logo_black.png index bab0360..c115445 100644 Binary files a/man/figures/logo_black.png and b/man/figures/logo_black.png differ diff --git a/man/figures/logo_white.png b/man/figures/logo_white.png index ccde747..f706d72 100644 Binary files a/man/figures/logo_white.png and b/man/figures/logo_white.png differ diff --git a/man/flatten_safe_data.Rd b/man/flatten_safe_data.Rd index 1dff1fe..1ae4c16 100644 --- a/man/flatten_safe_data.Rd +++ b/man/flatten_safe_data.Rd @@ -10,7 +10,7 @@ flatten_safe_data(x, ...) \method{flatten_safe_data}{default}(x, ...) -\method{flatten_safe_data}{rocrate}(x, ..., id = NULL) +\method{flatten_safe_data}{rocrate}(x, ..., id = NULL, asset_id_suffix = "#asset:") } \arguments{ \item{x}{Object (e.g., RO-Crate) with Safe Data details. This can be @@ -20,7 +20,7 @@ generated with the \code{\link[=extract_safe_data]{extract_safe_data()}} functio If not provided, extract all entities with \verb{@type = 'Dataset'}.} } \value{ -Data frame with safe data details. +Data frame with Safe Data details. } \description{ Flatten object with Safe Data details diff --git a/man/init.Rd b/man/init.Rd index 6151dc8..646685f 100644 --- a/man/init.Rd +++ b/man/init.Rd @@ -4,7 +4,7 @@ \alias{init} \alias{init.opal} \alias{init.rocrate} -\title{Initialise Five Safes RO-Crate} +\title{Initialise a Five Safes RO-Crate} \usage{ init(x, ...) @@ -70,8 +70,13 @@ values will be extracted from (e.g., OBiBa's Opal). Optional, if \code{x} is set to a connection object. If so, then \code{rocrate} is required.} } \value{ -Five Safes RO-Crate. +Five Safes RO-Crate object. } \description{ -Initialise Five Safes RO-Crate +Creates a new RO-Crate configured for Five Safes auditing. +} +\references{ +Wilkinson, M., Dumontier, M., Aalbersberg, I. et al. (2016) The FAIR Guiding +Principles for scientific data management and stewardship. Sci Data 3, +160018. https://doi.org/10.1038/sdata.2016.18 } diff --git a/man/load_cr8tor_bundle.Rd b/man/load_cr8tor_bundle.Rd index 80953e6..8378d49 100644 --- a/man/load_cr8tor_bundle.Rd +++ b/man/load_cr8tor_bundle.Rd @@ -4,10 +4,10 @@ \alias{load_cr8tor_bundle} \title{Load cr8tor governance bundle / project archive} \usage{ -load_cr8tor_bundle(path, ...) +load_cr8tor_bundle(x, ...) } \arguments{ -\item{path}{Path to cr8tor ZIP archive.} +\item{x}{Path to cr8tor ZIP archive.} \item{...}{Unused.} } @@ -22,3 +22,4 @@ A cr8tor project archive contains: \item config.toml → platform configuration. } } +\keyword{internal} diff --git a/man/load_rocrate.Rd b/man/load_rocrate.Rd deleted file mode 100644 index 1d2f9c8..0000000 --- a/man/load_rocrate.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-rocrate.R -\name{load_rocrate} -\alias{load_rocrate} -\title{Load RO-Crate from file} -\usage{ -load_rocrate(x) -} -\arguments{ -\item{x}{String with path to RO-Crate} -} -\value{ -RO-Crate object. -} -\description{ -Load RO-Crate from file -} -\keyword{internal} diff --git a/man/rocrate_report.Rd b/man/report.Rd similarity index 84% rename from man/rocrate_report.Rd rename to man/report.Rd index 5fb57da..fcfa194 100644 --- a/man/rocrate_report.Rd +++ b/man/report.Rd @@ -1,16 +1,16 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rocrate_report.R -\name{rocrate_report} -\alias{rocrate_report} -\alias{rocrate_report.character} -\alias{rocrate_report.default} -\alias{rocrate_report.list} -\alias{rocrate_report.rocrate} +% Please edit documentation in R/report.R +\name{report} +\alias{report} +\alias{report.character} +\alias{report.default} +\alias{report.list} +\alias{report.rocrate} \title{Create an RO-Crate report} \usage{ -rocrate_report(x, ...) +report(x, ...) -\method{rocrate_report}{character}( +\method{report}{character}( x, ..., title = "DataSHIELD Report", @@ -25,9 +25,9 @@ rocrate_report(x, ...) max_line_length = 200 ) -\method{rocrate_report}{default}(x, ...) +\method{report}{default}(x, ...) -\method{rocrate_report}{list}( +\method{report}{list}( x, ..., study_name, @@ -43,7 +43,7 @@ rocrate_report(x, ...) max_line_length = 200 ) -\method{rocrate_report}{rocrate}( +\method{report}{rocrate}( x, ..., title = "DataSHIELD Report", @@ -63,7 +63,7 @@ rocrate_report(x, ...) string with the path to an RO-Crate.} \item{...}{Other optional arguments. See the full documentation, -\code{\link[=rocrate_report]{?dsROCrate::rocrate_report}}.} +\code{\link[=report]{?dsROCrate::report}}.} \item{title}{String with title for the report (default: 'DataSHIELD Report').} diff --git a/man/user_perm_entity.Rd b/man/user_perm_entity.Rd index 87d6e04..fac1c4c 100644 --- a/man/user_perm_entity.Rd +++ b/man/user_perm_entity.Rd @@ -2,18 +2,18 @@ % Please edit documentation in R/utils-opal.R \name{user_perm_entity} \alias{user_perm_entity} -\title{Create user permission entities} +\title{Create user/person permission entities} \usage{ -user_perm_entity(user, user_id, table, table_id, permission, ...) +user_perm_entity(person, person_id, asset, asset_id, permission, ...) } \arguments{ -\item{user}{String with user name.} +\item{person}{String with person name/username.} -\item{user_id}{String with user \verb{@id}.} +\item{person_id}{String with person \verb{@id}.} -\item{table}{String with dataset/table name.} +\item{asset}{String with dataset/table/resource name.} -\item{table_id}{String with dataset/table \verb{@id}.} +\item{asset_id}{String with dataset/table \verb{@id}.} \item{permission}{String with permission ('view', 'view-values', 'edit', 'edit-values' OR 'administrate').} @@ -24,6 +24,6 @@ user_perm_entity(user, user_id, table, table_id, permission, ...) List of \link[rocrateR:entity]{rocrateR::entity} objects } \description{ -Create user permission entities +Create user/person permission entities } \keyword{internal} diff --git a/tests/testthat/test-audit_safe_people.R b/tests/testthat/test-audit_safe_people.R deleted file mode 100644 index 07baf89..0000000 --- a/tests/testthat/test-audit_safe_people.R +++ /dev/null @@ -1,88 +0,0 @@ -test_that("default method errors for unsupported classes", { - expect_error( - audit_safe_people(123), - "Unknown class" - ) -}) - -test_that("rocrate method fails for invalid object", { - expect_error( - audit_safe_people(list()), - class = "error" - ) -}) - -test_that("opal method returns RO-Crate with expected attributes", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_safe_people(opal_con, user = attr(opal_con, "PEOPLE")) - ) - - expect_s3_class(crate, "rocrate") - - expect_equal(attr(crate, "audit_type"), "Safe People") - expect_true("project" %in% names(attributes(crate))) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("opal method works with specific project", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_safe_people( - opal_con, - project = attr(opal_con, "PROJECT"), - user = attr(opal_con, "PEOPLE") - ) - ) - - expect_s3_class(crate, "rocrate") - expect_equal(attr(crate, "project"), attr(opal_con, "PROJECT")) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("opal method errors for unknown project", { - # setup - opal_con <- opal_demo_con() - - expect_error( - audit_safe_people( - opal_con, - project = "NON_EXISTENT_PROJECT", - user = attr(opal_con, "PEOPLE") - ), - "The given `project`, does not have any permissions set for the given `user`!", - fixed = TRUE - ) -}) - -test_that("path argument is stored as attribute", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_safe_people( - opal_con, - path = tempdir(), - user = attr(opal_con, "PEOPLE") - ) - ) - - expect_equal(attr(crate, "path"), tempdir()) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) diff --git a/tests/testthat/test-audit_safe_project.R b/tests/testthat/test-audit_safe_project.R deleted file mode 100644 index a7b868b..0000000 --- a/tests/testthat/test-audit_safe_project.R +++ /dev/null @@ -1,82 +0,0 @@ -test_that("default method errors for unsupported classes", { - expect_error( - audit_safe_project(123), - "Unknown class" - ) -}) - -test_that("rocrate method fails for invalid object", { - expect_error( - audit_safe_project(list()), - class = "error" - ) -}) - -test_that("opal method returns RO-Crate with expected attributes", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_safe_project(opal_con) - ) - - expect_s3_class(crate, "rocrate") - - expect_equal(attr(crate, "audit_type"), "Safe Project") - expect_true("project" %in% names(attributes(crate))) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("opal method works with specific project", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_safe_project( - opal_con, - project = attr(opal_con, "PROJECT") - ) - ) - - expect_s3_class(crate, "rocrate") - expect_equal(attr(crate, "project"), attr(opal_con, "PROJECT")) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("opal method errors for unknown project", { - # setup - opal_con <- opal_demo_con() - - expect_error( - audit_safe_project(opal_con, project = "NON_EXISTENT_PROJECT"), - "No data details were found", - fixed = TRUE - ) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("path argument is stored as attribute", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_safe_project( - opal_con, - path = tempdir() - ) - ) - - expect_equal(attr(crate, "path"), tempdir()) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) diff --git a/tests/testthat/test-audit_study.R b/tests/testthat/test-audit_study.R deleted file mode 100644 index 24cac16..0000000 --- a/tests/testthat/test-audit_study.R +++ /dev/null @@ -1,89 +0,0 @@ -test_that("default method errors for unsupported classes", { - expect_error( - audit_study(123), - "Unknown class" - ) -}) - -test_that("list method validates a list object with", { - expect_no_error( - audit_study(list()) - ) -}) - -test_that("list method fails for invalid object", { - # setup - crate <- rocrateR::rocrate_5s() - - expect_error( - audit_study(crate), - class = "error" - ) -}) - -test_that("list method returns list with expected attributes", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_study(list(demo = opal_con)) - ) - - expect_equal(attr(crate, "audit_type"), "Study") - expect_true("demo" %in% names(crate)) - expect_true(length(crate) == 1) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("list method works with specific project", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_study( - list(demo = opal_con), - project = attr(opal_con, "PROJECT") - ) - ) - - expect_equal(attr(crate, "project"), attr(opal_con, "PROJECT")) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("list method errors for unknown project", { - # setup - opal_con <- opal_demo_con() - - expect_error( - audit_study(list(demo = opal_con), project = "NON_EXISTENT_PROJECT"), - "No data details were found", - fixed = TRUE - ) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("path argument is stored as attribute", { - # setup - opal_con <- opal_demo_con() - - # ignore warning about empty logs - suppressWarnings( - crate <- audit_study( - list(demo = opal_con), - path = tempdir() - ) - ) - - expect_equal(attr(crate, "path"), tempdir()) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) diff --git a/tests/testthat/test-rocrate_report.R b/tests/testthat/test-report.R similarity index 75% rename from tests/testthat/test-rocrate_report.R rename to tests/testthat/test-report.R index afdcd0c..551277f 100644 --- a/tests/testthat/test-rocrate_report.R +++ b/tests/testthat/test-report.R @@ -1,18 +1,18 @@ -test_that("rocrate_report.default errors", { +test_that("report.default errors", { expect_error( - rocrate_report(123), + report(123), "Unknown class" ) }) -test_that("rocrate_report.character errors for invalid path", { +test_that("report.character errors for invalid path", { expect_error( - rocrate_report("nonexistent_path", render = FALSE), - "is not a valid path" + report("nonexistent_path", render = FALSE), + "The provided path does not exist" ) }) -test_that("rocrate_report.character errors if read_rocrate fails", { +test_that("report.character errors if read_rocrate fails", { # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") tmp_file <- tempfile(tmpdir = tmp_dir) @@ -24,15 +24,15 @@ test_that("rocrate_report.character errors if read_rocrate fails", { read_rocrate = function(...) stop("fail"), code = { expect_error( - rocrate_report(tmp_file, render = FALSE), - "Unable to load an RO-Crate" + report(tmp_file, render = FALSE), + "Could not determine how to load RO-Crate from provided input" ) }, .package = "rocrateR" ) }) -test_that("rocrate_report.character errors if input is invalid .zip", { +test_that("report.character errors if input is invalid .zip", { # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") tmp_file <- tempfile(tmpdir = tmp_dir, fileext = ".zip") @@ -40,10 +40,10 @@ test_that("rocrate_report.character errors if input is invalid .zip", { on.exit(unlink(tmp_dir, recursive = TRUE, force = TRUE)) file.create(tmp_file) - expect_error(rocrate_report(tmp_file, render = FALSE)) + expect_error(report(tmp_file, render = FALSE)) }) -test_that("rocrate_report.character dispatches to rocrate method", { +test_that("report.character dispatches to rocrate method", { # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") dir.create(tmp_dir, recursive = TRUE) @@ -59,16 +59,16 @@ test_that("rocrate_report.character dispatches to rocrate method", { ) testthat::with_mocked_bindings( load_content = function(x, ...) x, - rocrate_report.rocrate = function(x, ...) "OK", + report.character = function(x, ...) "OK", code = { - result <- rocrate_report(tmp_dir, render = FALSE) + result <- report(tmp_dir, render = FALSE) expect_equal(result, "OK") }, .package = "dsROCrate" ) }) -test_that("rocrate_report.rocrate errors if required entities missing", { +test_that("report.rocrate errors if required entities missing", { fake_rocrate <- structure(list(), class = "rocrate") testthat::local_mocked_bindings( @@ -82,7 +82,7 @@ test_that("rocrate_report.rocrate errors if required entities missing", { extract_safe_data = function(...) NULL, code = { expect_error( - rocrate_report(fake_rocrate, render = FALSE), + report(fake_rocrate, render = FALSE), "missing" ) }, @@ -90,7 +90,7 @@ test_that("rocrate_report.rocrate errors if required entities missing", { ) }) -test_that("rocrate_report.rocrate returns expected structure", { +test_that("report.rocrate returns expected structure", { fake_rocrate <- structure(list(), class = "rocrate") safe_people <- list() @@ -109,12 +109,19 @@ test_that("rocrate_report.rocrate returns expected structure", { extract_safe_setting = function(...) NULL, extract_safe_output = function(...) NULL, flatten_safe_people = function(...) { - tibble::tibble(id = "u1", name = "dsuser", project = "P1") + tibble::tibble(person_id = "u1", name = "dsuser", project = "P1") }, flatten_safe_project = function(...) { - tibble::tibble(id = "p1", project = "P1", table = "T1") + tibble::tibble( + project_id = "p1", + project = "P1", + asset_id = "t1", + asset = "T1" + ) + }, + flatten_safe_data = function(...) { + tibble::tibble(asset_id = "t1", asset = "T1") }, - flatten_safe_data = function(...) tibble::tibble(id = "t1", name = "T1"), flatten_safe_output = function(...) tibble::tibble(), flatten_user_perm_entity = function(...) NULL, .overview_diagram = function(...) list(diag_lst = "diag", diag_path = NULL), @@ -123,7 +130,7 @@ test_that("rocrate_report.rocrate returns expected structure", { .markdown_report_body = function(...) "BODY", .markdown_report_rocrate = function(...) "ROCRATE", code = { - result <- rocrate_report( + result <- report( fake_rocrate, render = FALSE, filepath = tempfile(fileext = ".md") @@ -148,7 +155,7 @@ test_that("rocrate_report.rocrate returns expected structure", { ) }) -test_that("rocrate_report.rocrate prevents overwrite", { +test_that("report.rocrate prevents overwrite", { fake_rocrate <- structure(list(), class = "rocrate") # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") @@ -168,7 +175,7 @@ test_that("rocrate_report.rocrate prevents overwrite", { extract_safe_data = function(...) list(), code = { expect_error( - rocrate_report(fake_rocrate, filepath = tmp_file, overwrite = FALSE), + report(fake_rocrate, filepath = tmp_file, overwrite = FALSE), "existing file" ) }, @@ -176,20 +183,25 @@ test_that("rocrate_report.rocrate prevents overwrite", { ) }) -test_that("rocrate_report.list aggregates multiple servers", { +test_that("report.list aggregates multiple servers", { fake_rocrate <- structure(list(), class = "rocrate") server_output <- list( - safe_people = tibble::tibble(id = "u1", name = "dsuser"), - safe_project = tibble::tibble(id = "p1", table = "T1"), - safe_data = tibble::tibble(id = "t1", name = "T1"), + safe_people = tibble::tibble(id_person = "u1", name = "dsuser"), + safe_project = tibble::tibble( + id_project = "p1", + project = "P1", + asset_id = "t1", + asset = "T1" + ), + safe_data = tibble::tibble(asset_id = "t1", asset = "T1"), safe_data_permissions = NULL, safe_setting = tibble::tibble(), safe_output = tibble::tibble(), overview_data = tibble::tibble( project = "P1", - table = "T1", - user = "dsuser" + asset = "T1", + person = "dsuser" ) ) @@ -199,14 +211,14 @@ test_that("rocrate_report.list aggregates multiple servers", { ) testthat::with_mocked_bindings( - rocrate_report = function(...) server_output, + report = function(...) server_output, .overview_diagram = function(...) list(diag_lst = "diag", diag_path = NULL), .tidy_overview = function(...) tibble::tibble(Project = "P1"), .markdown_report_header = function(...) "HEADER", .markdown_report_body = function(...) "BODY", .markdown_report_rocrate = function(...) "ROCRATE", code = { - result <- rocrate_report( + result <- report( list(server1 = fake_rocrate, server2 = fake_rocrate), study_name = "StudyX", render = FALSE, @@ -223,7 +235,7 @@ test_that("rocrate_report.list aggregates multiple servers", { test_that(".tidy_overview collapses permissions correctly", { df <- tibble::tibble( project = "P1", - table = "T1", + asset = "T1", name = "dsuser", permission = c("read", "write") ) @@ -254,13 +266,13 @@ test_that(".break_tibble returns input table when varname = NULL", { expect_equal(knitr::kable(df), .break_tibble(df, NULL)) }) -test_that("rocrate_report fails if the given path points to a non-existing directory", { +test_that("report fails if the given path points to a non-existing directory", { expect_error( - rocrate_report(rocrateR::rocrate_5s(), filepath = "path/to/dir") + report(rocrateR::rocrate_5s(), filepath = "path/to/dir") ) }) -test_that("rocrate_report works end-to-end with real dsROCrate audit outputs", { +test_that("report works end-to-end with real dsROCrate audit outputs", { # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") dir.create(tmp_dir, recursive = TRUE) @@ -270,17 +282,20 @@ test_that("rocrate_report works end-to-end with real dsROCrate audit outputs", { opal_con <- opal_demo_con() # generate audit RO-Crate for a Safe Project - roc <- audit_safe_project( - opal_con, - project = "CNSIM", - path = tmp_dir - ) + # suppress warnings about missing logs + suppressWarnings({ + roc <- audit( + opal_con, + project = "CNSIM", + path = tmp_dir + ) + }) out_file <- file.path(tmp_dir, "report.md") # ignore warnings about missing permission entities (e.g., @type = 'ControlAction') suppressWarnings( - result <- rocrate_report( + result <- report( roc, filepath = out_file, render = FALSE, @@ -300,7 +315,7 @@ test_that("rocrate_report works end-to-end with real dsROCrate audit outputs", { opalr::opal.logout(opal_con) }) -test_that("rocrate_report.character loads crate from disk", { +test_that("report.character loads crate from disk", { # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") dir.create(tmp_dir, recursive = TRUE) @@ -310,11 +325,14 @@ test_that("rocrate_report.character loads crate from disk", { opal_con <- opal_demo_con() # generate audit RO-Crate for a Safe Project - roc <- audit_safe_project( - opal_con, - project = "CNSIM", - path = tmp_dir - ) + # suppress warnings about missing logs + suppressWarnings({ + roc <- audit( + opal_con, + project = "CNSIM", + path = tmp_dir + ) + }) # write crate to disk (real metadata file) path_to_rocrate_bag <- rocrateR::bag_rocrate(roc, path = tmp_dir) @@ -323,7 +341,7 @@ test_that("rocrate_report.character loads crate from disk", { # ignore warnings about missing permission entities (e.g., @type = 'ControlAction') suppressWarnings( - result <- rocrate_report( + result <- report( path_to_rocrate_bag, filepath = out_file, render = FALSE, @@ -339,7 +357,7 @@ test_that("rocrate_report.character loads crate from disk", { opalr::opal.logout(opal_con) }) -test_that("rocrate_report.list aggregates outputs from a study audit", { +test_that("report.list aggregates outputs from a study audit", { # create temporary file tmp_dir <- file.path(tempdir(), "dsROCRate_tests") dir.create(tmp_dir, recursive = TRUE) @@ -349,17 +367,20 @@ test_that("rocrate_report.list aggregates outputs from a study audit", { opal_con <- opal_demo_con() # generate audit RO-Crate for a study - roc <- audit_study( - list(demo = opal_con), - project = "CNSIM", - path = tmp_dir + ## ignore warnings about missing logs + suppressWarnings( + roc <- audit( + list(demo = opal_con), + project = "CNSIM", + path = tmp_dir + ) ) out_file <- file.path(tmp_dir, "report.md") # ignore warnings about missing permission entities (e.g., @type = 'ControlAction') suppressWarnings( - result <- rocrate_report( + result <- report( roc, filepath = out_file, study_name = "StudyX", @@ -376,11 +397,11 @@ test_that("rocrate_report.list aggregates outputs from a study audit", { opalr::opal.logout(opal_con) }) -test_that("rocrate_report.list handles missing outputs from a study audit", { +test_that("report.list handles missing outputs from a study audit", { testthat::with_mocked_bindings( - rocrate_report = function(...) list(), + report = function(...) list(), code = { - result <- rocrate_report( + result <- report( list(server1 = rocrateR::rocrate_5s()), study_name = "StudyX", render = FALSE, diff --git a/tests/testthat/test-safe_output.R b/tests/testthat/test-safe_output.R index 3fa72b4..8d6f04d 100644 --- a/tests/testthat/test-safe_output.R +++ b/tests/testthat/test-safe_output.R @@ -183,14 +183,17 @@ test_that("safe_output works", { tempdir_name <- tempdir() on.exit(unlink(tempdir_name, force = TRUE, recursive = TRUE)) expect_true(dir.exists(tempdir_name)) - basic_rocrate_8 <- basic_rocrate_3 |> - dsROCrate::safe_output( - connection = opal_con, - logs_from = Sys.time() - 60, # capture the last min - logs_to = Sys.time(), - user = "dsuser", - path = tempdir_name - ) + ## ignore warnings about missing logs + suppressWarnings( + basic_rocrate_8 <- basic_rocrate_3 |> + dsROCrate::safe_output( + connection = opal_con, + logs_from = Sys.time() - 60, # capture the last min + logs_to = Sys.time(), + user = "dsuser", + path = tempdir_name + ) + ) unlink(tempdir_name, force = TRUE, recursive = TRUE) expect_false(dir.exists(tempdir_name)) diff --git a/tests/testthat/test-utils-connection.R b/tests/testthat/test-utils-connection.R index 0f72e69..ba197ad 100644 --- a/tests/testthat/test-utils-connection.R +++ b/tests/testthat/test-utils-connection.R @@ -110,7 +110,7 @@ test_that("parse_user_profiles.opal() handles missing userInfo column", { test_that("parse_user_profiles.opal() parses JSON userInfo and NA correctly", { opal_con <- structure(list(), class = "opal") - json <- '{"firstName":"John","lastName":"Doe"}' + json <- '{"givenName":"John","familyName":"Doe"}' local_mocked_bindings( `opal.get` = function(...) { diff --git a/tests/testthat/test-utils-opal.R b/tests/testthat/test-utils-opal.R index 63899f5..dcc6835 100644 --- a/tests/testthat/test-utils-opal.R +++ b/tests/testthat/test-utils-opal.R @@ -154,10 +154,10 @@ test_that("update_project_datasets updates project entities of an RO-Crate", { test_that("user_perm_entity works for all values of 'permission'", { input_tbl <- tibble::tibble( - user = "dsuser", - user_id = "dsuser", - table = "tab1", - table_id = "tab1", + person = "dsuser", + person_id = "dsuser", + asset = "tab1", + asset_id = "tab1", permission = c( "view", "view-values", @@ -177,10 +177,10 @@ test_that("user_perm_entity works for all values of 'permission'", { test_that("user_perm_entity returns NULL for unknown 'permission'", { input_tbl <- tibble::tibble( - user = "dsuser", - user_id = "dsuser", - table = "tab1", - table_id = "tab1", + person = "dsuser", + person_id = "dsuser", + asset = "tab1", + asset_id = "tab1", permission = "INVALID" ) diff --git a/tests/testthat/test-utils-safe_data.R b/tests/testthat/test-utils-safe_data.R index 047408a..c9036db 100644 --- a/tests/testthat/test-utils-safe_data.R +++ b/tests/testthat/test-utils-safe_data.R @@ -68,7 +68,7 @@ test_that("extract_safe_data.rocrate copies Dataset entities", { src <- rocrateR::add_entity( src, entity = rocrateR::entity( - id = "#ds1", + id = "#asset:1", type = "Dataset", name = "Dataset 1" ) @@ -77,7 +77,7 @@ test_that("extract_safe_data.rocrate copies Dataset entities", { src <- rocrateR::add_entity( src, entity = rocrateR::entity( - id = "#ds2", + id = "#asset:2", type = "Dataset", name = "Dataset 2" ) @@ -102,7 +102,7 @@ test_that("extract_safe_data.rocrate filters Dataset entities by id", { src <- rocrateR::add_entity( src, entity = rocrateR::entity( - id = "#keep_me", + id = "#asset:keep_me", type = "Dataset", name = "Keep" ) @@ -111,7 +111,7 @@ test_that("extract_safe_data.rocrate filters Dataset entities by id", { src <- rocrateR::add_entity( src, entity = rocrateR::entity( - id = "#drop_me", + id = "#asset:drop_me", type = "Dataset", name = "Drop" ) @@ -119,13 +119,13 @@ test_that("extract_safe_data.rocrate filters Dataset entities by id", { # ignore warning of project not having tables associated suppressWarnings( - new_roc <- extract_safe_data(src, id = "#keep_me") + new_roc <- extract_safe_data(src, id = "#asset:keep_me") ) ents <- rocrateR::get_entity(new_roc, type = "Dataset") ids <- vapply(ents, function(e) e[["@id"]], character(1)) - expect_equal(ids, c("./", "#keep_me")) + expect_equal(ids, c("./", "#asset:keep_me")) }) test_that("extract_safe_data.rocrate errors when no Dataset entities exist", { @@ -151,7 +151,7 @@ test_that("flatten_safe_data.rocrate extracts id and name correctly", { roc <- rocrateR::add_entity( roc, entity = rocrateR::entity( - id = "#ds1", + id = "#asset:1", type = "Dataset", name = "Dataset One" ) @@ -160,7 +160,7 @@ test_that("flatten_safe_data.rocrate extracts id and name correctly", { roc <- rocrateR::add_entity( roc, entity = rocrateR::entity( - id = "#ds2", + id = "#asset:2", type = "Dataset", name = "Dataset Two" ) @@ -169,7 +169,7 @@ test_that("flatten_safe_data.rocrate extracts id and name correctly", { res <- flatten_safe_data(roc) expect_s3_class(res, "data.frame") - expect_true(all(c("id", "name") %in% names(res))) + expect_true(all(c("asset_id", "asset") %in% names(res))) expect_equal(nrow(res), 2) }) @@ -179,7 +179,7 @@ test_that("flatten_safe_data.rocrate filters by id argument", { roc <- rocrateR::add_entity( roc, entity = rocrateR::entity( - id = "#dsA", + id = "#asset:A", type = "Dataset", name = "A" ) @@ -188,16 +188,16 @@ test_that("flatten_safe_data.rocrate filters by id argument", { roc <- rocrateR::add_entity( roc, entity = rocrateR::entity( - id = "#dsB", + id = "#asset:B", type = "Dataset", name = "B" ) ) - res <- flatten_safe_data(roc, id = "#dsA") + res <- flatten_safe_data(roc, id = "#asset:A") expect_equal(nrow(res), 1) - expect_equal(res$id, "#dsA") + expect_equal(res$asset_id, "#asset:A") }) test_that("flatten_safe_data.rocrate returns empty tibble on error", { diff --git a/tests/testthat/test-utils-safe_people.R b/tests/testthat/test-utils-safe_people.R index 31bc897..e34ef11 100644 --- a/tests/testthat/test-utils-safe_people.R +++ b/tests/testthat/test-utils-safe_people.R @@ -200,9 +200,10 @@ test_that("flatten_safe_people.rocrate extracts person metadata correctly", { expect_s3_class(res, "data.frame") expect_true(all( - c("id", "name", "given_name", "family_name", "organisation") %in% names(res) + c("person_id", "name", "given_name", "family_name", "organisation") %in% + names(res) )) - expect_equal(res$id, "#p1") + expect_equal(res$person_id, "#p1") expect_equal(res$name, "Jane Doe") }) @@ -222,7 +223,7 @@ test_that("flatten_safe_people.rocrate filters by id argument", { res <- flatten_safe_people(roc, id = "#p2") expect_equal(nrow(res), 1) - expect_equal(res$id, "#p2") + expect_equal(res$person_id, "#p2") }) test_that("flatten_safe_people.rocrate returns empty tibble on malformed input", { diff --git a/tests/testthat/test-utils-safe_project.R b/tests/testthat/test-utils-safe_project.R index 7482061..7e51e7c 100644 --- a/tests/testthat/test-utils-safe_project.R +++ b/tests/testthat/test-utils-safe_project.R @@ -215,8 +215,8 @@ test_that("flatten_safe_project.rocrate returns tibble", { # minimal stub for flatten_safe_data used internally flatten_safe_data <- function(x, ..., id = NULL) { tibble::tibble( - id = id, - name = c("table1", "table2") + asset_id = id, + asset = c("table1", "table2") ) } @@ -225,7 +225,10 @@ test_that("flatten_safe_project.rocrate returns tibble", { out <- flatten_safe_project(rocrate) expect_s3_class(out, "tbl_df") - expect_true(all(c("id", "project", "table") %in% names(out))) + print(names(out)) + expect_true(all( + c("project_id", "project", "asset_id", "asset") %in% names(out) + )) }) test_that("flatten_safe_project.rocrate extracts project metadata correctly", { @@ -258,8 +261,8 @@ test_that("flatten_safe_project.rocrate extracts project metadata correctly", { flatten_safe_data <- function(x, ..., id = NULL) { tibble::tibble( - id = id, - name = paste0("name_", id) + asset_id = id, + asset = paste0("name_", id) ) } @@ -268,7 +271,7 @@ test_that("flatten_safe_project.rocrate extracts project metadata correctly", { out <- flatten_safe_project(rocrate) expect_true("Project 1" %in% out$project) - expect_true(any(grepl("name_", out$table))) + expect_true(any(grepl("name_", out$asset))) }) test_that("flatten_safe_project.rocrate handles project without hasPart", { @@ -286,7 +289,7 @@ test_that("flatten_safe_project.rocrate handles project without hasPart", { out <- flatten_safe_project(rocrate) expect_equal(nrow(out), 1) - expect_true(is.na(out$table)) + expect_true(is.na(out$asset)) }) test_that("flatten_safe_project.rocrate returns empty tibble on error", {