From 5cf79fceda295ceefd6bc9f8d41b7d593272e66e Mon Sep 17 00:00:00 2001
From: Anthony Sena
Date: Wed, 22 Mar 2023 17:21:32 -0400
Subject: [PATCH 1/5] Bump version to v0.0.4
---
DESCRIPTION | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/DESCRIPTION b/DESCRIPTION
index 1f4ff941..e39c225f 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
Package: Strategus
Type: Package
Title: Coordinating and Executing Analytics Using HADES Modules
-Version: 0.0.3
+Version: 0.0.4
Date: 2023-03-10
Authors@R: c(
person("Martijn", "Schuemie", email = "schuemie@ohdsi.org", role = c("aut", "cre")),
From dff8db6069849c5292f6dcc429af1eff4fbcf23e Mon Sep 17 00:00:00 2001
From: Anthony Sena
Date: Fri, 24 Mar 2023 15:42:46 -0400
Subject: [PATCH 2/5] Add CDM table error handling - fixes #54
---
R/DatabaseMetaData.R | 29 +++++++
tests/testSqlite.R | 3 +
tests/testthat.R | 13 ----
tests/testthat/setup.R | 101 +++++++++++++++++++++++++
tests/testthat/test-DatabaseMetaData.R | 81 ++++++++++++++++++++
tests/testthat/test-Strategus.R | 21 ++---
6 files changed, 225 insertions(+), 23 deletions(-)
create mode 100644 tests/testSqlite.R
delete mode 100644 tests/testthat.R
create mode 100644 tests/testthat/setup.R
create mode 100644 tests/testthat/test-DatabaseMetaData.R
diff --git a/R/DatabaseMetaData.R b/R/DatabaseMetaData.R
index 05a26db8..84696562 100644
--- a/R/DatabaseMetaData.R
+++ b/R/DatabaseMetaData.R
@@ -34,6 +34,19 @@ createDatabaseMetaData <- function(executionSettings, keyringName = NULL) {
connection <- DatabaseConnector::connect(connectionDetails)
on.exit(DatabaseConnector::disconnect(connection))
+ # Verify the CDM tables required by this function exist prior to
+ # querying. Then we can stop the processing and provide an informative
+ # message to the user.
+ requiredTables <- c("cdm_source", "vocabulary", "observation_period")
+ cdmTableList <- DatabaseConnector::getTableNames(connection = connection,
+ databaseSchema = executionSettings$cdmDatabaseSchema)
+
+
+ if (!length(cdmTableList[which(x = cdmTableList %in% requiredTables)]) == length(requiredTables)) {
+ missingCdmTables <- requiredTables[!(requiredTables %in% cdmTableList)]
+ stop(sprintf("FATAL ERROR: Your OMOP CDM is missing the following required tables: %s", paste(missingCdmTables, collapse = ", ")))
+ }
+
resultsDataModel <- CohortGenerator::readCsv(
file = system.file("databaseMetaDataRdms.csv", package = "Strategus"),
warnOnCaseMismatch = FALSE
@@ -47,6 +60,11 @@ createDatabaseMetaData <- function(executionSettings, keyringName = NULL) {
cdm_database_schema = executionSettings$cdmDatabaseSchema
)
+ # Verify that the cdmSource table has information
+ if (nrow(cdmSource) == 0) {
+ stop("FATAL ERROR: The CDM_SOURCE table in your OMOP CDM is empty. Please populate this table with information about your CDM and organization. For more information, please see: https://ohdsi.github.io/CommonDataModel/cdm53.html#CDM_SOURCE")
+ }
+
# Restrict the cdmSource columns to those that are
# expected in the resultsDataModel
cdmSource <- cdmSource[, which(names(cdmSource) %in% SqlRender::snakeCaseToCamelCase(resultsDataModel$columnName))]
@@ -67,6 +85,12 @@ createDatabaseMetaData <- function(executionSettings, keyringName = NULL) {
cdm_database_schema = executionSettings$cdmDatabaseSchema
)
+ # Verify that the vocabulary_version is found
+ if (nrow(vocabVersion) == 0) {
+ stop("FATAL ERROR: The VOCABULARY table in your OMOP CDM is missing the version. Please verify that your process for loading the vocabulary included an entry in the vocabulary table with vocabulary_id == 'None'")
+ }
+
+
sql <- "SELECT MAX(observation_period_end_date) as max_obs_period_end_date
FROM @cdm_database_schema.observation_period;"
observationPeriodMax <- renderTranslateQuerySql(
@@ -76,6 +100,11 @@ createDatabaseMetaData <- function(executionSettings, keyringName = NULL) {
cdm_database_schema = executionSettings$cdmDatabaseSchema
)
+ # Verify that the MAX(observation_period_end_date) is a valid date
+ if (is.na(observationPeriodMax$maxObsPeriodEndDate)) {
+ stop("FATAL ERROR: The OBSERVATION_PERIOD table in your OMOP CDM lacks a maximum observation_period_end_date. This may be a result of an error in the ETL as each person in the OMOP CDM must have an observation period with a valid start and end date.")
+ }
+
databaseId <- digest::digest2int(paste(cdmSource$cdmSourceName, cdmSource$cdmReleaseDate))
database <- cdmSource %>%
mutate(
diff --git a/tests/testSqlite.R b/tests/testSqlite.R
new file mode 100644
index 00000000..e12a1413
--- /dev/null
+++ b/tests/testSqlite.R
@@ -0,0 +1,3 @@
+library(testthat)
+options(dbms = "sqlite")
+test_check("Strategus")
diff --git a/tests/testthat.R b/tests/testthat.R
deleted file mode 100644
index 22d8a6bd..00000000
--- a/tests/testthat.R
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file is part of the standard setup for testthat.
-# It is recommended that you do not modify it.
-#
-# Where should you do additional test configuration?
-# Learn more about the roles of various files in:
-# * https://r-pkgs.org/tests.html
-# * https://testthat.r-lib.org/reference/test_package.html#special-files
-
-library(testthat)
-library(Strategus)
-library(dplyr)
-
-test_check("Strategus")
diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R
new file mode 100644
index 00000000..5a5ec613
--- /dev/null
+++ b/tests/testthat/setup.R
@@ -0,0 +1,101 @@
+library(testthat)
+library(Strategus)
+library(Eunomia)
+library(dplyr)
+
+dbms <- getOption("dbms", default = "sqlite")
+message("************* Testing on ", dbms, " *************")
+
+if (dir.exists(Sys.getenv("DATABASECONNECTOR_JAR_FOLDER"))) {
+ jdbcDriverFolder <- Sys.getenv("DATABASECONNECTOR_JAR_FOLDER")
+} else {
+ jdbcDriverFolder <- "~/.jdbcDrivers"
+ dir.create(jdbcDriverFolder, showWarnings = FALSE)
+ DatabaseConnector::downloadJdbcDrivers("postgresql", pathToDriver = jdbcDriverFolder)
+
+ if (!dbms %in% c("postgresql", "sqlite")) {
+ DatabaseConnector::downloadJdbcDrivers(dbms, pathToDriver = jdbcDriverFolder)
+ }
+
+ withr::defer(
+ {
+ unlink(jdbcDriverFolder, recursive = TRUE, force = TRUE)
+ },
+ testthat::teardown_env()
+ )
+}
+
+tempDir <- tempfile()
+tempDir <- gsub("\\\\", "/", tempDir) # Correct windows path
+renvCachePath <- file.path(tempDir, "renv")
+withr::defer(
+ {
+ unlink(tempDir, recursive = TRUE, force = TRUE)
+ unlink(renvCachePath, recursive = TRUE, force = TRUE)
+ },
+ testthat::teardown_env()
+)
+
+if (dbms == "sqlite") {
+ eunomiaDbFile <- file.path(tempDir, "data", "testEunomia.sqlite")
+ if (!dir.exists(file.path(tempDir, "data"))) {
+ dir.create(file.path(tempDir, "data"), recursive = T, showWarnings = F)
+ }
+ connectionDetails <- Eunomia::getEunomiaConnectionDetails(
+ databaseFile = eunomiaDbFile
+ )
+ withr::defer(
+ {
+ unlink(eunomiaDbFile, recursive = TRUE, force = TRUE)
+ },
+ testthat::teardown_env()
+ )
+ cdmDatabaseSchema <- "main"
+ workDatabaseSchema <- "main"
+ vocabularyDatabaseSchema <- workDatabaseSchema
+ cohortTable <- "cohort"
+ tempEmulationSchema <- NULL
+} else {
+ if (dbms == "postgresql") {
+ dbUser <- Sys.getenv("CDM5_POSTGRESQL_USER")
+ dbPassword <- Sys.getenv("CDM5_POSTGRESQL_PASSWORD")
+ dbServer <- Sys.getenv("CDM5_POSTGRESQL_SERVER")
+ cdmDatabaseSchema <- Sys.getenv("CDM5_POSTGRESQL_CDM_SCHEMA")
+ vocabularyDatabaseSchema <- Sys.getenv("CDM5_POSTGRESQL_CDM_SCHEMA")
+ tempEmulationSchema <- NULL
+ workDatabaseSchema <- Sys.getenv("CDM5_POSTGRESQL_OHDSI_SCHEMA")
+ } else if (dbms == "oracle") {
+ dbUser <- Sys.getenv("CDM5_ORACLE_USER")
+ dbPassword <- Sys.getenv("CDM5_ORACLE_PASSWORD")
+ dbServer <- Sys.getenv("CDM5_ORACLE_SERVER")
+ cdmDatabaseSchema <- Sys.getenv("CDM5_ORACLE_CDM_SCHEMA")
+ vocabularyDatabaseSchema <- Sys.getenv("CDM5_ORACLE_CDM_SCHEMA")
+ tempEmulationSchema <- Sys.getenv("CDM5_ORACLE_OHDSI_SCHEMA")
+ workDatabaseSchema <- Sys.getenv("CDM5_ORACLE_OHDSI_SCHEMA")
+ options(sqlRenderTempEmulationSchema = tempEmulationSchema)
+ } else if (dbms == "redshift") {
+ dbUser <- Sys.getenv("CDM5_REDSHIFT_USER")
+ dbPassword <- Sys.getenv("CDM5_REDSHIFT_PASSWORD")
+ dbServer <- Sys.getenv("CDM5_REDSHIFT_SERVER")
+ cdmDatabaseSchema <- Sys.getenv("CDM5_REDSHIFT_CDM_SCHEMA")
+ vocabularyDatabaseSchema <- Sys.getenv("CDM5_REDSHIFT_CDM_SCHEMA")
+ tempEmulationSchema <- NULL
+ workDatabaseSchema <- Sys.getenv("CDM5_REDSHIFT_OHDSI_SCHEMA")
+ } else if (dbms == "sql server") {
+ dbUser <- Sys.getenv("CDM5_SQL_SERVER_USER")
+ dbPassword <- Sys.getenv("CDM5_SQL_SERVER_PASSWORD")
+ dbServer <- Sys.getenv("CDM5_SQL_SERVER_SERVER")
+ cdmDatabaseSchema <- Sys.getenv("CDM5_SQL_SERVER_CDM_SCHEMA")
+ vocabularyDatabaseSchema <- Sys.getenv("CDM5_SQL_SERVER_CDM_SCHEMA")
+ tempEmulationSchema <- NULL
+ workDatabaseSchema <- Sys.getenv("CDM5_SQL_SERVER_OHDSI_SCHEMA")
+ }
+
+ connectionDetails <- DatabaseConnector::createConnectionDetails(
+ dbms = dbms,
+ user = dbUser,
+ password = URLdecode(dbPassword),
+ server = dbServer,
+ pathToDriver = jdbcDriverFolder
+ )
+}
diff --git a/tests/testthat/test-DatabaseMetaData.R b/tests/testthat/test-DatabaseMetaData.R
new file mode 100644
index 00000000..16cb8b31
--- /dev/null
+++ b/tests/testthat/test-DatabaseMetaData.R
@@ -0,0 +1,81 @@
+test_that("Test DatabaseMetaData error conditions", {
+ skip_if_not(dbms == "sqlite")
+ connection <- DatabaseConnector::connect(connectionDetails)
+ # Rename all required tables
+ requiredTables <- c("cdm_source", "vocabulary", "observation_period")
+ renameTableSql <- "ALTER TABLE @table RENAME TO @backup_table;"
+ for (i in 1:length(requiredTables)) {
+ DatabaseConnector::renderTranslateExecuteSql(connection = connection,
+ sql = renameTableSql,
+ progressBar = FALSE,
+ reportOverallTime = FALSE,
+ table = requiredTables[i],
+ backup_table = paste0(requiredTables[i], "_bak"))
+ }
+
+ on.exit(expr = {
+ # Put the DB back the way we found it
+ restoreTableSql<- "DROP TABLE IF EXISTS @cdm_table; ALTER TABLE @backup_table RENAME TO @cdm_table;"
+ for (i in 1:length(requiredTables)) {
+ DatabaseConnector::renderTranslateExecuteSql(connection = connection,
+ sql = restoreTableSql,
+ progressBar = FALSE,
+ reportOverallTime = FALSE,
+ backup_table = paste0(requiredTables[i], "_bak"),
+ cdm_table = requiredTables[i])
+ }
+ })
+
+ # Confirm an error is thrown when 1 or more of these tables are missing
+ Strategus::storeConnectionDetails(
+ connectionDetails = connectionDetails,
+ connectionDetailsReference = dbms
+ )
+ executionSettings <- Strategus::createCdmExecutionSettings(
+ connectionDetailsReference = dbms,
+ workDatabaseSchema = workDatabaseSchema,
+ cdmDatabaseSchema = cdmDatabaseSchema,
+ cohortTableNames = CohortGenerator::getCohortTableNames(),
+ workFolder = file.path(tempDir, "EunomiaTestStudy/work_folder"),
+ resultsFolder = file.path(tempDir, "EunomiaTestStudy/results_folder"),
+ minCellCount = 5
+ )
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ regexp = "FATAL ERROR: Your OMOP CDM is missing the following required tables: cdm_source, vocabulary, observation_period")
+
+ # Create required tables with no information verify this throws an error
+ emptyTableSql = "SELECT * INTO @cdm_table FROM @table WHERE 1 = 0;"
+ for (i in 1:length(requiredTables)) {
+ DatabaseConnector::renderTranslateExecuteSql(connection = connection,
+ sql = emptyTableSql,
+ progressBar = FALSE,
+ reportOverallTime = FALSE,
+ table = paste0(requiredTables[i], "_bak"),
+ cdm_table = requiredTables[i])
+ }
+
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ regexp = "FATAL ERROR: The CDM_SOURCE table in your OMOP CDM is empty.")
+
+ # Populate the CDM_SOURCE table so we can check that the vocabulary check works
+ restoreTableSql = "DROP TABLE IF EXISTS @cdm_table; SELECT * INTO @cdm_table FROM @backup_table;"
+ DatabaseConnector::renderTranslateExecuteSql(connection = connection,
+ sql = restoreTableSql,
+ progressBar = FALSE,
+ reportOverallTime = FALSE,
+ cdm_table = "cdm_source",
+ backup_table = "cdm_source_bak")
+
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ regexp = "FATAL ERROR: The VOCABULARY table in your OMOP CDM is missing the version")
+
+ # Populate the VOCABULARY table so we can check that the observation_period check works
+ DatabaseConnector::renderTranslateExecuteSql(connection = connection,
+ sql = restoreTableSql,
+ progressBar = FALSE,
+ reportOverallTime = FALSE,
+ cdm_table = "vocabulary",
+ backup_table = "vocabulary_bak")
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ regexp = "FATAL ERROR: The OBSERVATION_PERIOD table in your OMOP CDM lacks a maximum observation_period_end_date")
+})
diff --git a/tests/testthat/test-Strategus.R b/tests/testthat/test-Strategus.R
index 71d893b2..74922c6a 100644
--- a/tests/testthat/test-Strategus.R
+++ b/tests/testthat/test-Strategus.R
@@ -1,8 +1,7 @@
test_that("Run Eunomia study", {
- tempDir <- tempfile()
- tempDir <- gsub("\\\\", "/", tempDir) # Correct windows path
- renv:::renv_scope_envvars(RENV_PATHS_CACHE = tempDir)
- moduleFolder <- file.path(tempDir, "/strategus/modules")
+ moduleFolder <- file.path(tempDir, "strategus/modules")
+ # NOTE: Need to set this in each test otherwise it goes out of scope
+ renv:::renv_scope_envvars(RENV_PATHS_CACHE = renvCachePath)
# Create a keyring called "strategus" that is password protected
allKeyrings <- keyring::keyring_list()
if (!"strategus" %in% allKeyrings$keyring) {
@@ -11,13 +10,11 @@ test_that("Run Eunomia study", {
# Lock the keyring
keyring::keyring_lock(keyring = "strategus")
- connectionDetails <- Eunomia::getEunomiaConnectionDetails()
-
# Using a named and secured keyring
Sys.setenv("STRATEGUS_KEYRING_PASSWORD" = "foobar")
Strategus::storeConnectionDetails(
connectionDetails = connectionDetails,
- connectionDetailsReference = "eunomia",
+ connectionDetailsReference = dbms,
keyringName = "strategus"
)
@@ -33,9 +30,9 @@ test_that("Run Eunomia study", {
analysisSpecifications$moduleSpecifications <- analysisSpecifications$moduleSpecifications[-c(2:length(analysisSpecifications$moduleSpecifications))]
executionSettings <- createCdmExecutionSettings(
- connectionDetailsReference = "eunomia",
- workDatabaseSchema = "main",
- cdmDatabaseSchema = "main",
+ connectionDetailsReference = dbms,
+ workDatabaseSchema = workDatabaseSchema,
+ cdmDatabaseSchema = cdmDatabaseSchema,
cohortTableNames = CohortGenerator::getCohortTableNames(),
workFolder = file.path(tempDir, "EunomiaTestStudy/work_folder"),
resultsFolder = file.path(tempDir, "EunomiaTestStudy/results_folder"),
@@ -54,6 +51,7 @@ test_that("Run Eunomia study", {
fileName = file.path(tempDir, "EunomiaTestStudy/eunomiaExecutionSettings.json")
)
+ # debugonce(Strategus::execute)
Strategus::execute(
analysisSpecifications = analysisSpecifications,
executionSettings = executionSettings,
@@ -62,4 +60,7 @@ test_that("Run Eunomia study", {
)
expect_true(file.exists(file.path(tempDir, "EunomiaTestStudy/results_folder/CohortGeneratorModule_1/done")))
+
+ unlink(moduleFolder, recursive = TRUE, force = TRUE)
+ unlink(file.path(tempDir, "EunomiaTestStudy"), recursive = TRUE, force = TRUE)
})
From bbe745b7574fc00215e0d50d32f3a182397aa572 Mon Sep 17 00:00:00 2001
From: Anthony Sena
Date: Fri, 24 Mar 2023 16:19:07 -0400
Subject: [PATCH 3/5] Add DB tests - fixes #53
---
tests/testOracle.R | 3 +++
tests/testPostgres.R | 3 +++
tests/testRedshift.R | 3 +++
tests/testSqlServer.R | 3 +++
4 files changed, 12 insertions(+)
create mode 100644 tests/testOracle.R
create mode 100644 tests/testPostgres.R
create mode 100644 tests/testRedshift.R
create mode 100644 tests/testSqlServer.R
diff --git a/tests/testOracle.R b/tests/testOracle.R
new file mode 100644
index 00000000..ed092bec
--- /dev/null
+++ b/tests/testOracle.R
@@ -0,0 +1,3 @@
+library(testthat)
+options(dbms = "oracle")
+test_check("Strategus")
diff --git a/tests/testPostgres.R b/tests/testPostgres.R
new file mode 100644
index 00000000..783539d9
--- /dev/null
+++ b/tests/testPostgres.R
@@ -0,0 +1,3 @@
+library(testthat)
+options(dbms = "postgresql")
+test_check("Strategus")
diff --git a/tests/testRedshift.R b/tests/testRedshift.R
new file mode 100644
index 00000000..d3866880
--- /dev/null
+++ b/tests/testRedshift.R
@@ -0,0 +1,3 @@
+library(testthat)
+options(dbms = "redshift")
+test_check("Strategus")
diff --git a/tests/testSqlServer.R b/tests/testSqlServer.R
new file mode 100644
index 00000000..88270aac
--- /dev/null
+++ b/tests/testSqlServer.R
@@ -0,0 +1,3 @@
+library(testthat)
+options(dbms = "sql server")
+test_check("Strategus")
From 31343cf3ed7803966b54ad1073cbc8ac4e209601 Mon Sep 17 00:00:00 2001
From: Anthony Sena
Date: Fri, 24 Mar 2023 16:20:26 -0400
Subject: [PATCH 4/5] Remove uniqueness check for module table prefix - fixes
#55
---
DESCRIPTION | 11 +-
R/ModuleInstantiation.R | 23 -----
extras/PackageMaintenance.R | 107 +++++++++-----------
inst/csv/modules.csv | 18 ++--
tests/testOracle.R | 6 +-
tests/testSqlServer.R | 1 +
tests/testthat/setup.R | 55 +++++++++-
tests/testthat/test-DatabaseMetaData.R | 29 ++++--
tests/testthat/test-Settings.R | 8 +-
tests/testthat/test-Strategus.R | 40 ++++----
vignettes/CreatingAnalysisSpecification.Rmd | 21 +++-
11 files changed, 186 insertions(+), 133 deletions(-)
diff --git a/DESCRIPTION b/DESCRIPTION
index e39c225f..bbbbc451 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -32,11 +32,12 @@ Imports:
tibble,
SqlRender (>= 1.11.0)
Suggests:
- testthat (>= 3.0.0),
- fs,
- knitr,
- rmarkdown,
- Eunomia
+ testthat (>= 3.0.0),
+ fs,
+ knitr,
+ rmarkdown,
+ Eunomia,
+ withr
Remotes:
ohdsi/CohortGenerator,
ohdsi/Eunomia
diff --git a/R/ModuleInstantiation.R b/R/ModuleInstantiation.R
index 81291792..bab8a742 100644
--- a/R/ModuleInstantiation.R
+++ b/R/ModuleInstantiation.R
@@ -73,29 +73,6 @@ ensureAllModulesInstantiated <- function(analysisSpecifications) {
stop(message)
}
- # Check for colliding result table prefixes
- moduleTablePrefixes <- getModuleTablePrefixes(moduleList = modules)
- if (nrow(moduleTablePrefixes) != length(unique(moduleTablePrefixes$tablePrefix))) {
- moduleTablePrefixesInConflict <- moduleTablePrefixes %>%
- group_by(.data$tablePrefix) %>%
- summarise(totalCount = n()) %>%
- filter(.data$totalCount > 1)
-
- message <- paste(
- c(
- "Detected colliding result table prefixes:",
- sprintf(
- "- Module '%s' (v'%s') table prefix: '%s'",
- moduleTablePrefixesInConflict$moduleName,
- moduleTablePrefixesInConflict$moduleVersion,
- moduleTablePrefixesInConflict$tablePrefix
- )
- ),
- collapse = "\n"
- )
- stop(message)
- }
-
return(modules)
}
diff --git a/extras/PackageMaintenance.R b/extras/PackageMaintenance.R
index e6a6d555..a4968c9a 100644
--- a/extras/PackageMaintenance.R
+++ b/extras/PackageMaintenance.R
@@ -29,6 +29,56 @@ OhdsiRTools::updateCopyrightYearFolder()
OhdsiRTools::findNonAsciiStringsInFolder()
devtools::spell_check()
+# Update the module version information based on updates found on GitHub
+library(dplyr)
+updateModuleVersionInfo <- function() {
+ modules <- CohortGenerator::readCsv(file = "inst/csv/modules.csv")
+ modules <- modules %>%
+ select(-c("mainPackage", "mainPackageTag"))
+ # Get latest module versions ---------------------------------------------------
+ getLatestModuleVersion <- function(remoteRepo, remoteUsername, module) {
+ urlTemplate <- "https://api.%s/repos/%s/%s/releases/latest"
+ release <- jsonlite::fromJSON(sprintf(urlTemplate, remoteRepo, remoteUsername, module))
+ return(release$tag_name)
+ }
+ versions <- tibble::tibble(
+ module = modules$module,
+ moduleVersion = mapply(getLatestModuleVersion, modules$remoteRepo, modules$remoteUsername, modules$module),
+ mainPackage = "",
+ mainPackageTag = ""
+ )
+ # Get referenced main package tag ----------------------------------------------
+ for (i in 1:nrow(modules)) {
+ module <- versions$module[i]
+ if (module == "CohortIncidenceModule") {
+ urlTemplate <- "https://raw.githubusercontent.com/OHDSI/%s/master/renv.lock"
+ } else {
+ urlTemplate <- "https://raw.githubusercontent.com/OHDSI/%s/main/renv.lock"
+ }
+ lock <- jsonlite::fromJSON(sprintf(urlTemplate, module))
+ mainPackage <- gsub("Module", "", module)
+ versions$mainPackage[i] <- mainPackage
+ for (j in seq_along(lock$Packages)) {
+ if (lock$Packages[[j]]$Package == mainPackage) {
+ if (is.null(lock$Packages[[j]]$RemoteRef) || tolower(lock$Packages[[j]]$RemoteRef) == "head") {
+ versions$mainPackageTag[i] <- paste0("v", lock$Packages[[j]]$Version)
+ } else {
+ versions$mainPackageTag[i] <- lock$Packages[[j]]$RemoteRef
+ }
+ break
+ }
+ }
+ }
+ moduleList <- versions %>%
+ dplyr::inner_join(modules, by = c('module' = 'module')) %>%
+ dplyr::mutate(version = moduleVersion) %>%
+ dplyr::select(c(names(modules), "mainPackage", "mainPackageTag"))
+
+ CohortGenerator::writeCsv(x = moduleList,
+ file = "inst/csv/modules.csv")
+}
+updateModuleVersionInfo()
+
# Create manual and vignettes:
unlink("extras/Strategus.pdf")
shell("R CMD Rd2pdf ./ --output=extras/Strategus.pdf")
@@ -65,62 +115,5 @@ pkgdown::build_site()
OhdsiRTools::fixHadesLogo()
-# Maintain a list of supported modules ------------
-
-# CDM Modules
-moduleList <- data.frame(module = "CharacterizationModule",
- version = "v0.2.3",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm")
-moduleList <- rbind(moduleList,
- data.frame(module = "CohortDiagnosticsModule",
- version = "v0.0.7",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm"))
-moduleList <- rbind(moduleList,
- data.frame(module = "CohortGeneratorModule",
- version = "v0.1.0",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm"))
-moduleList <- rbind(moduleList,
- data.frame(module = "CohortIncidenceModule",
- version = "v0.0.6",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm"))
-
-moduleList <- rbind(moduleList,
- data.frame(module = "CohortMethodModule",
- version = "v0.1.0",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm"))
-moduleList <- rbind(moduleList,
- data.frame(module = "PatientLevelPredictionModule",
- version = "v0.0.7",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm"))
-moduleList <- rbind(moduleList,
- data.frame(module = "SelfControlledCaseSeriesModule",
- version = "v0.1.2",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "cdm"))
-
-# Results Modules
-moduleList <- rbind(moduleList,
- data.frame(module = "EvidenceSynthesisModule",
- version = "v0.1.3",
- remoteRepo = "github.com",
- remoteUsername = "OHDSI",
- moduleType = "results"))
-
-CohortGenerator::writeCsv(x = moduleList,
- file = "inst/csv/modules.csv")
-
diff --git a/inst/csv/modules.csv b/inst/csv/modules.csv
index b9fdcd5e..1be0d345 100644
--- a/inst/csv/modules.csv
+++ b/inst/csv/modules.csv
@@ -1,9 +1,9 @@
-module,version,remote_repo,remote_username,module_type
-CharacterizationModule,v0.2.3,github.com,OHDSI,cdm
-CohortDiagnosticsModule,v0.0.7,github.com,OHDSI,cdm
-CohortGeneratorModule,v0.1.0,github.com,OHDSI,cdm
-CohortIncidenceModule,v0.0.6,github.com,OHDSI,cdm
-CohortMethodModule,v0.1.0,github.com,OHDSI,cdm
-PatientLevelPredictionModule,v0.0.7,github.com,OHDSI,cdm
-SelfControlledCaseSeriesModule,v0.1.2,github.com,OHDSI,cdm
-EvidenceSynthesisModule,v0.1.3,github.com,OHDSI,results
+module,version,remote_repo,remote_username,module_type,main_package,main_package_tag
+CharacterizationModule,v0.3.0,github.com,OHDSI,cdm,Characterization,v0.1.1
+CohortDiagnosticsModule,v0.0.7,github.com,OHDSI,cdm,CohortDiagnostics,v3.1.2
+CohortGeneratorModule,v0.1.0,github.com,OHDSI,cdm,CohortGenerator,v0.8.0
+CohortIncidenceModule,v0.0.6,github.com,OHDSI,cdm,CohortIncidence,v3.1.2
+CohortMethodModule,v0.1.0,github.com,OHDSI,cdm,CohortMethod,74f017107e0cc1b740a2badc82879ab6ad291b23
+PatientLevelPredictionModule,v0.0.8,github.com,OHDSI,cdm,PatientLevelPrediction,v6.3.1
+SelfControlledCaseSeriesModule,v0.1.3,github.com,OHDSI,cdm,SelfControlledCaseSeries,15918616814b88137f82bf2aa9986e1dcdf39e74
+EvidenceSynthesisModule,v0.1.3,github.com,OHDSI,results,EvidenceSynthesis,v0.4.0
diff --git a/tests/testOracle.R b/tests/testOracle.R
index ed092bec..9f68e3bb 100644
--- a/tests/testOracle.R
+++ b/tests/testOracle.R
@@ -1,3 +1,3 @@
-library(testthat)
-options(dbms = "oracle")
-test_check("Strategus")
+# library(testthat)
+# options(dbms = "oracle")
+# test_check("Strategus")
diff --git a/tests/testSqlServer.R b/tests/testSqlServer.R
index 88270aac..5b6d3a97 100644
--- a/tests/testSqlServer.R
+++ b/tests/testSqlServer.R
@@ -1,3 +1,4 @@
library(testthat)
options(dbms = "sql server")
test_check("Strategus")
+
diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R
index 5a5ec613..de542e36 100644
--- a/tests/testthat/setup.R
+++ b/tests/testthat/setup.R
@@ -25,13 +25,18 @@ if (dir.exists(Sys.getenv("DATABASECONNECTOR_JAR_FOLDER"))) {
)
}
-tempDir <- tempfile()
+# Create a unique ID for the table identifiers
+tableSuffix <- paste0(substr(.Platform$OS.type, 1, 3), format(Sys.time(), "%y%m%d%H%M%S"), sample(1:100, 1))
+tableSuffix <- abs(digest::digest2int(tableSuffix))
+
+tempDir <- tempfile() # "C:/temp"
tempDir <- gsub("\\\\", "/", tempDir) # Correct windows path
-renvCachePath <- file.path(tempDir, "renv")
+renvCachePath <- file.path(tempDir, "strategus/renv")
+moduleFolder <- file.path(tempDir, "strategus/modules")
+Sys.setenv("INSTANTIATED_MODULES_FOLDER" = moduleFolder)
withr::defer(
{
- unlink(tempDir, recursive = TRUE, force = TRUE)
- unlink(renvCachePath, recursive = TRUE, force = TRUE)
+ unlink(c(tempDir,renvCachePath, moduleFolder), recursive = TRUE, force = TRUE)
},
testthat::teardown_env()
)
@@ -60,6 +65,7 @@ if (dbms == "sqlite") {
dbUser <- Sys.getenv("CDM5_POSTGRESQL_USER")
dbPassword <- Sys.getenv("CDM5_POSTGRESQL_PASSWORD")
dbServer <- Sys.getenv("CDM5_POSTGRESQL_SERVER")
+ dbPort <- 5432
cdmDatabaseSchema <- Sys.getenv("CDM5_POSTGRESQL_CDM_SCHEMA")
vocabularyDatabaseSchema <- Sys.getenv("CDM5_POSTGRESQL_CDM_SCHEMA")
tempEmulationSchema <- NULL
@@ -68,6 +74,7 @@ if (dbms == "sqlite") {
dbUser <- Sys.getenv("CDM5_ORACLE_USER")
dbPassword <- Sys.getenv("CDM5_ORACLE_PASSWORD")
dbServer <- Sys.getenv("CDM5_ORACLE_SERVER")
+ dbPort <- 1521
cdmDatabaseSchema <- Sys.getenv("CDM5_ORACLE_CDM_SCHEMA")
vocabularyDatabaseSchema <- Sys.getenv("CDM5_ORACLE_CDM_SCHEMA")
tempEmulationSchema <- Sys.getenv("CDM5_ORACLE_OHDSI_SCHEMA")
@@ -77,6 +84,7 @@ if (dbms == "sqlite") {
dbUser <- Sys.getenv("CDM5_REDSHIFT_USER")
dbPassword <- Sys.getenv("CDM5_REDSHIFT_PASSWORD")
dbServer <- Sys.getenv("CDM5_REDSHIFT_SERVER")
+ dbPort <- 5439
cdmDatabaseSchema <- Sys.getenv("CDM5_REDSHIFT_CDM_SCHEMA")
vocabularyDatabaseSchema <- Sys.getenv("CDM5_REDSHIFT_CDM_SCHEMA")
tempEmulationSchema <- NULL
@@ -85,6 +93,7 @@ if (dbms == "sqlite") {
dbUser <- Sys.getenv("CDM5_SQL_SERVER_USER")
dbPassword <- Sys.getenv("CDM5_SQL_SERVER_PASSWORD")
dbServer <- Sys.getenv("CDM5_SQL_SERVER_SERVER")
+ dbPort <- 1433
cdmDatabaseSchema <- Sys.getenv("CDM5_SQL_SERVER_CDM_SCHEMA")
vocabularyDatabaseSchema <- Sys.getenv("CDM5_SQL_SERVER_CDM_SCHEMA")
tempEmulationSchema <- NULL
@@ -96,6 +105,44 @@ if (dbms == "sqlite") {
user = dbUser,
password = URLdecode(dbPassword),
server = dbServer,
+ port = dbPort,
pathToDriver = jdbcDriverFolder
)
}
+
+
+# Keyring helpers --------------
+# Set the keyring name & password for testing
+keyringName <- "strategus"
+keyringPassword <- "ohdsi"
+
+deleteKeyringForUnitTest <- function(selectedKeyring = keyringName, selectedKeyringPassword = keyringPassword) {
+ # Create a keyring called "strategus" that is password protected
+ allKeyrings <- keyring::keyring_list()
+ if (selectedKeyring %in% allKeyrings$keyring) {
+ if (keyring::keyring_is_locked(keyring = selectedKeyring)) {
+ keyring::keyring_unlock(keyring = selectedKeyring, password = selectedKeyringPassword)
+ }
+ # Delete all keys from the keyring so we can delete it
+ keys <- keyring::key_list(keyring = selectedKeyring)
+ if (nrow(keys) > 0) {
+ for (i in 1:nrow(keys)) {
+ keyring::key_delete(keys$service[1], keyring = selectedKeyring)
+ }
+ }
+ keyring::keyring_delete(keyring = selectedKeyring)
+ }
+}
+
+createKeyringForUnitTest <- function(selectedKeyring = keyringName, selectedKeyringPassword = keyringPassword) {
+ # Delete any existing keyrings
+ deleteKeyringForUnitTest(selectedKeyring = selectedKeyring)
+ # Create & Lock the keyring
+ keyring::keyring_create(keyring = selectedKeyring, password = selectedKeyringPassword)
+ keyring::keyring_lock(keyring = selectedKeyring)
+}
+
+skip_if_not_secret_service <- function() {
+ if (keyring::default_backend()$name != "secret service") skip("Not secret service")
+ invisible(TRUE)
+}
diff --git a/tests/testthat/test-DatabaseMetaData.R b/tests/testthat/test-DatabaseMetaData.R
index 16cb8b31..8b5f30f2 100644
--- a/tests/testthat/test-DatabaseMetaData.R
+++ b/tests/testthat/test-DatabaseMetaData.R
@@ -1,6 +1,9 @@
test_that("Test DatabaseMetaData error conditions", {
skip_if_not(dbms == "sqlite")
- connection <- DatabaseConnector::connect(connectionDetails)
+ skip_if_not_secret_service()
+ # Run this test in isolation as it will make changes to the CDM schema.
+ eunomiaConnectionDetails <- Eunomia::getEunomiaConnectionDetails()
+ connection <- DatabaseConnector::connect(eunomiaConnectionDetails)
# Rename all required tables
requiredTables <- c("cdm_source", "vocabulary", "observation_period")
renameTableSql <- "ALTER TABLE @table RENAME TO @backup_table;"
@@ -24,12 +27,20 @@ test_that("Test DatabaseMetaData error conditions", {
backup_table = paste0(requiredTables[i], "_bak"),
cdm_table = requiredTables[i])
}
+ DatabaseConnector::disconnect(connection)
+ unlink(eunomiaConnectionDetails$server, recursive = TRUE, force = TRUE)
})
+ # Setup keyring for the test
+ Sys.setenv("STRATEGUS_KEYRING_PASSWORD" = keyringPassword)
+ createKeyringForUnitTest(selectedKeyring = keyringName, selectedKeyringPassword = keyringPassword)
+ on.exit(deleteKeyringForUnitTest())
+
# Confirm an error is thrown when 1 or more of these tables are missing
Strategus::storeConnectionDetails(
- connectionDetails = connectionDetails,
- connectionDetailsReference = dbms
+ connectionDetails = eunomiaConnectionDetails,
+ connectionDetailsReference = dbms,
+ keyringName = keyringName
)
executionSettings <- Strategus::createCdmExecutionSettings(
connectionDetailsReference = dbms,
@@ -40,7 +51,8 @@ test_that("Test DatabaseMetaData error conditions", {
resultsFolder = file.path(tempDir, "EunomiaTestStudy/results_folder"),
minCellCount = 5
)
- expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings,
+ keyringName = keyringName),
regexp = "FATAL ERROR: Your OMOP CDM is missing the following required tables: cdm_source, vocabulary, observation_period")
# Create required tables with no information verify this throws an error
@@ -54,7 +66,8 @@ test_that("Test DatabaseMetaData error conditions", {
cdm_table = requiredTables[i])
}
- expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings,
+ keyringName = keyringName),
regexp = "FATAL ERROR: The CDM_SOURCE table in your OMOP CDM is empty.")
# Populate the CDM_SOURCE table so we can check that the vocabulary check works
@@ -66,7 +79,8 @@ test_that("Test DatabaseMetaData error conditions", {
cdm_table = "cdm_source",
backup_table = "cdm_source_bak")
- expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings,
+ keyringName = keyringName),
regexp = "FATAL ERROR: The VOCABULARY table in your OMOP CDM is missing the version")
# Populate the VOCABULARY table so we can check that the observation_period check works
@@ -76,6 +90,7 @@ test_that("Test DatabaseMetaData error conditions", {
reportOverallTime = FALSE,
cdm_table = "vocabulary",
backup_table = "vocabulary_bak")
- expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings),
+ expect_error(Strategus:::createDatabaseMetaData(executionSettings = executionSettings,
+ keyringName = keyringName),
regexp = "FATAL ERROR: The OBSERVATION_PERIOD table in your OMOP CDM lacks a maximum observation_period_end_date")
})
diff --git a/tests/testthat/test-Settings.R b/tests/testthat/test-Settings.R
index 8e06e44d..acc20e4c 100644
--- a/tests/testthat/test-Settings.R
+++ b/tests/testthat/test-Settings.R
@@ -42,15 +42,15 @@ test_that("Get module list", {
test_that("Verify that unlocking keyring without password fails", {
allKeyrings <- keyring::keyring_list()
- if (!"strategus" %in% allKeyrings$keyring) {
- keyring::keyring_create(keyring = "strategus", password = "foobar")
+ if (!keyringName %in% allKeyrings$keyring) {
+ keyring::keyring_create(keyring = keyringName, password = keyringPassword)
}
# Lock the keyring
- keyring::keyring_lock(keyring = "strategus")
+ keyring::keyring_lock(keyring = keyringName)
# Remove STRATEGUS_KEYRING_PASSWORD in case it is already set
Sys.unsetenv("STRATEGUS_KEYRING_PASSWORD")
# Try to unlock and expect error
- expect_error(unlockKeyring(keyring = "strategus"))
+ expect_error(unlockKeyring(keyring = keyringName))
})
diff --git a/tests/testthat/test-Strategus.R b/tests/testthat/test-Strategus.R
index 74922c6a..ccc5b31e 100644
--- a/tests/testthat/test-Strategus.R
+++ b/tests/testthat/test-Strategus.R
@@ -1,39 +1,47 @@
test_that("Run Eunomia study", {
- moduleFolder <- file.path(tempDir, "strategus/modules")
# NOTE: Need to set this in each test otherwise it goes out of scope
renv:::renv_scope_envvars(RENV_PATHS_CACHE = renvCachePath)
- # Create a keyring called "strategus" that is password protected
- allKeyrings <- keyring::keyring_list()
- if (!"strategus" %in% allKeyrings$keyring) {
- keyring::keyring_create(keyring = "strategus", password = "foobar")
- }
- # Lock the keyring
- keyring::keyring_lock(keyring = "strategus")
+
+ # Setup keyring for the test
+ Sys.setenv("STRATEGUS_KEYRING_PASSWORD" = keyringPassword)
+ createKeyringForUnitTest(selectedKeyring = keyringName, selectedKeyringPassword = keyringPassword)
+ on.exit(deleteKeyringForUnitTest())
# Using a named and secured keyring
- Sys.setenv("STRATEGUS_KEYRING_PASSWORD" = "foobar")
Strategus::storeConnectionDetails(
connectionDetails = connectionDetails,
connectionDetailsReference = dbms,
- keyringName = "strategus"
+ keyringName = keyringName
)
- Sys.setenv("INSTANTIATED_MODULES_FOLDER" = moduleFolder)
analysisSpecifications <- ParallelLogger::loadSettingsFromJson(
fileName = system.file("testdata/analysisSpecification.json",
package = "Strategus"
)
)
+ # Create a unique set of cohort tables for this test run and
+ # ensure they are removed when complete
+ cohortTableNames <- CohortGenerator::getCohortTableNames(cohortTable = paste0("s", tableSuffix))
+ withr::defer(
+ {
+ CohortGenerator::dropCohortStatsTables(connectionDetails = connectionDetails,
+ cohortDatabaseSchema = workDatabaseSchema,
+ cohortTableNames = cohortTableNames,
+ dropCohortTable = TRUE)
+ unlink(file.path(tempDir, "EunomiaTestStudy"), recursive = TRUE, force = TRUE)
+ },
+ testthat::teardown_env()
+ )
+
# Use this line to limit to only running the CohortGeneratorModule
# for testing purposes.
analysisSpecifications$moduleSpecifications <- analysisSpecifications$moduleSpecifications[-c(2:length(analysisSpecifications$moduleSpecifications))]
-
executionSettings <- createCdmExecutionSettings(
connectionDetailsReference = dbms,
workDatabaseSchema = workDatabaseSchema,
cdmDatabaseSchema = cdmDatabaseSchema,
- cohortTableNames = CohortGenerator::getCohortTableNames(),
+ cohortTableNames = cohortTableNames,
workFolder = file.path(tempDir, "EunomiaTestStudy/work_folder"),
resultsFolder = file.path(tempDir, "EunomiaTestStudy/results_folder"),
minCellCount = 5
@@ -51,16 +59,12 @@ test_that("Run Eunomia study", {
fileName = file.path(tempDir, "EunomiaTestStudy/eunomiaExecutionSettings.json")
)
- # debugonce(Strategus::execute)
Strategus::execute(
analysisSpecifications = analysisSpecifications,
executionSettings = executionSettings,
executionScriptFolder = file.path(tempDir, "EunomiaTestStudy/script_folder"),
- keyringName = "strategus"
+ keyringName = keyringName
)
expect_true(file.exists(file.path(tempDir, "EunomiaTestStudy/results_folder/CohortGeneratorModule_1/done")))
-
- unlink(moduleFolder, recursive = TRUE, force = TRUE)
- unlink(file.path(tempDir, "EunomiaTestStudy"), recursive = TRUE, force = TRUE)
})
diff --git a/vignettes/CreatingAnalysisSpecification.Rmd b/vignettes/CreatingAnalysisSpecification.Rmd
index 025abbf7..4855d433 100644
--- a/vignettes/CreatingAnalysisSpecification.Rmd
+++ b/vignettes/CreatingAnalysisSpecification.Rmd
@@ -36,6 +36,21 @@ options(width = 200)
This walk through will show how to use `Strategus` to define an analysis specification on an example study using cohorts from the example problem _What is the risk of gastrointestinal (GI) bleed in new users of celecoxib compared to new users of diclofenac?_ as described in the [Book Of OHDSI Chapter 12 on Population Level Estimation]( https://ohdsi.github.io/TheBookOfOhdsi/PopulationLevelEstimation.html#PopulationLevelEstimation)
+## Setting up your R environment
+
+To start, we must install the [HADES](https://ohdsi.github.io/Hades/) libraries in order to create the settings for the analysis specification. The following script will install the necessary HADES packages for this vignette.
+
+```{r, eval=FALSE}
+# Install correct versions of HADES packages
+remotes::install_github("ohdsi/CohortGenerator", ref = "v0.8.0")
+remotes::install_github("ohdsi/CohortDiagnostics", ref = "v3.1.2")
+remotes::install_github("ohdsi/Characterization", ref = "v0.1.1")
+remotes::install_github("ohdsi/CohortIncidence", ref = "v3.1.2")
+remotes::install_github("ohdsi/CohortMethod", ref = "74f017107e0cc1b740a2badc82879ab6ad291b23")
+remotes::install_github("ohdsi/SelfControlledCaseSeries", ref = "15918616814b88137f82bf2aa9986e1dcdf39e74")
+remotes::install_github("ohdsi/PatientLevelPrediction", ref = "v6.3.1")
+```
+
## Cohorts for the study
To start, we'll need to define cohorts and negative control outcomes to use in our example analysis specification. We've included the cohorts and negative control outcomes in the `Strategus` package for this example and the code below will load them for use when assembling the analysis specification.
@@ -169,7 +184,7 @@ The following code creates the `characterizationModuleSpecifications` to perform
```{r eval=FALSE}
-source("https://raw.githubusercontent.com/OHDSI/CharacterizationModule/v0.2.3/SettingsFunctions.R")
+source("https://raw.githubusercontent.com/OHDSI/CharacterizationModule/v0.3.0/SettingsFunctions.R")
characterizationModuleSpecifications <- createCharacterizationModuleSpecifications(
targetIds = c(1, 2),
outcomeIds = 3,
@@ -295,7 +310,7 @@ The following code creates the `cohortMethodModuleSpecifications` to perform a c
```{r eval=FALSE}
library(SelfControlledCaseSeries)
-source(url = "https://raw.githubusercontent.com/OHDSI/SelfControlledCaseSeriesModule/v0.1.2/SettingsFunctions.R")
+source(url = "https://raw.githubusercontent.com/OHDSI/SelfControlledCaseSeriesModule/v0.1.3/SettingsFunctions.R")
# Exposures-outcomes -----------------------------------------------------------
negativeControlOutcomeIds <- ncoCohortSet$cohortId
@@ -406,7 +421,7 @@ sccsModuleSpecifications <- creatSelfControlledCaseSeriesModuleSpecifications(
The following code creates the `plpModuleSpecifications` to perform a self-controlled case series analysis for this study.
```{r eval=FALSE}
-source("https://raw.githubusercontent.com/OHDSI/PatientLevelPredictionModule/v0.0.7/SettingsFunctions.R")
+source("https://raw.githubusercontent.com/OHDSI/PatientLevelPredictionModule/v0.0.8/SettingsFunctions.R")
makeModelDesignSettings <- function(targetId, outcomeId, popSettings, covarSettings) {
invisible(PatientLevelPrediction::createModelDesign(
From b528b7a4fded9a80b3be5986024390ab24f2415c Mon Sep 17 00:00:00 2001
From: Anthony Sena
Date: Wed, 29 Mar 2023 14:22:09 -0400
Subject: [PATCH 5/5] V0.0.4 release prep (#58)
* Update NEWS and code formatting
* Regenerate vignettes
* Regenerate pkgdown site
---
DESCRIPTION | 2 +-
NEWS.md | 7 ++
R/DatabaseMetaData.R | 6 +-
docs/404.html | 2 +-
.../CreatingAnalysisSpecification.html | 78 +++++++-----
docs/articles/CreatingModules.html | 4 +-
docs/articles/ExecuteStrategus.html | 4 +-
docs/articles/IntroductionToStrategus.html | 4 +-
docs/articles/index.html | 2 +-
docs/authors.html | 2 +-
docs/index.html | 2 +-
docs/news/index.html | 8 +-
docs/pkgdown.yml | 2 +-
docs/pull_request_template.html | 2 +-
docs/reference/Strategus-package.html | 2 +-
docs/reference/addModuleSpecifications.html | 2 +-
docs/reference/addSharedResources.html | 2 +-
.../reference/createCdmExecutionSettings.html | 2 +-
.../createEmptyAnalysisSpecificiations.html | 2 +-
.../createResultsExecutionSettings.html | 2 +-
.../ensureAllModulesInstantiated.html | 2 +-
docs/reference/execute.html | 2 +-
docs/reference/getModuleList.html | 2 +-
docs/reference/index.html | 2 +-
docs/reference/retrieveConnectionDetails.html | 2 +-
docs/reference/storeConnectionDetails.html | 2 +-
docs/reference/unlockKeyring.html | 2 +-
extras/Strategus.pdf | Bin 122966 -> 122940 bytes
inst/doc/CreatingAnalysisSpecification.pdf | Bin 247760 -> 250161 bytes
inst/doc/CreatingModules.pdf | Bin 197578 -> 197690 bytes
inst/doc/ExecuteStrategus.pdf | Bin 200216 -> 200333 bytes
inst/doc/IntroductionToStrategus.pdf | Bin 171108 -> 171218 bytes
tests/testSqlServer.R | 1 -
tests/testthat/setup.R | 2 +-
tests/testthat/test-DatabaseMetaData.R | 116 +++++++++++-------
tests/testthat/test-Strategus.R | 10 +-
36 files changed, 169 insertions(+), 111 deletions(-)
diff --git a/DESCRIPTION b/DESCRIPTION
index bbbbc451..db5f55dc 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -2,7 +2,7 @@ Package: Strategus
Type: Package
Title: Coordinating and Executing Analytics Using HADES Modules
Version: 0.0.4
-Date: 2023-03-10
+Date: 2023-03-29
Authors@R: c(
person("Martijn", "Schuemie", email = "schuemie@ohdsi.org", role = c("aut", "cre")),
person("Anthony", "Sena", email = "sena@ohdsi.org", role = c("aut")),
diff --git a/NEWS.md b/NEWS.md
index d82d98c1..ba8022d9 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,10 @@
+Strategus 0.0.4
+===============
+
+- Add DB Platform Tests (#53)
+- Add error handling for missing/empty tables (#54)
+- Remove uniqueness check for module table prefix (#55)
+
Strategus 0.0.3
===============
diff --git a/R/DatabaseMetaData.R b/R/DatabaseMetaData.R
index 84696562..bcf20a9e 100644
--- a/R/DatabaseMetaData.R
+++ b/R/DatabaseMetaData.R
@@ -38,8 +38,10 @@ createDatabaseMetaData <- function(executionSettings, keyringName = NULL) {
# querying. Then we can stop the processing and provide an informative
# message to the user.
requiredTables <- c("cdm_source", "vocabulary", "observation_period")
- cdmTableList <- DatabaseConnector::getTableNames(connection = connection,
- databaseSchema = executionSettings$cdmDatabaseSchema)
+ cdmTableList <- DatabaseConnector::getTableNames(
+ connection = connection,
+ databaseSchema = executionSettings$cdmDatabaseSchema
+ )
if (!length(cdmTableList[which(x = cdmTableList %in% requiredTables)]) == length(requiredTables)) {
diff --git a/docs/404.html b/docs/404.html
index 5a68b690..cb099a47 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -32,7 +32,7 @@
Strategus
- 0.0.3
+ 0.0.4
diff --git a/docs/articles/CreatingAnalysisSpecification.html b/docs/articles/CreatingAnalysisSpecification.html
index 738a3104..c84b30a0 100644
--- a/docs/articles/CreatingAnalysisSpecification.html
+++ b/docs/articles/CreatingAnalysisSpecification.html
@@ -33,7 +33,7 @@
Strategus
- 0.0.3
+ 0.0.4
@@ -94,7 +94,7 @@ Creating Analysis Specification
Anthony G.
Sena
- 2023-03-10
+ 2023-03-29
Source: vignettes/CreatingAnalysisSpecification.Rmd
CreatingAnalysisSpecification.Rmd
@@ -113,6 +113,22 @@
+
Setting up your R environment
+
+
To start, we must install the HADES libraries in order to
+create the settings for the analysis specification. The following script
+will install the necessary HADES packages for this vignette.
+
+# Install correct versions of HADES packages
+remotes::install_github("ohdsi/CohortGenerator", ref = "v0.8.0")
+remotes::install_github("ohdsi/CohortDiagnostics", ref = "v3.1.2")
+remotes::install_github("ohdsi/Characterization", ref = "v0.1.1")
+remotes::install_github("ohdsi/CohortIncidence", ref = "v3.1.2")
+remotes::install_github("ohdsi/CohortMethod", ref = "74f017107e0cc1b740a2badc82879ab6ad291b23")
+remotes::install_github("ohdsi/SelfControlledCaseSeries", ref = "15918616814b88137f82bf2aa9986e1dcdf39e74")
+remotes::install_github("ohdsi/PatientLevelPrediction", ref = "v6.3.1")
+
+
diff --git a/docs/reference/retrieveConnectionDetails.html b/docs/reference/retrieveConnectionDetails.html
index 757b5cf2..99ff3b1c 100644
--- a/docs/reference/retrieveConnectionDetails.html
+++ b/docs/reference/retrieveConnectionDetails.html
@@ -17,7 +17,7 @@
Strategus
- 0.0.3
+ 0.0.4
diff --git a/docs/reference/storeConnectionDetails.html b/docs/reference/storeConnectionDetails.html
index 4eec3449..d807845e 100644
--- a/docs/reference/storeConnectionDetails.html
+++ b/docs/reference/storeConnectionDetails.html
@@ -17,7 +17,7 @@
Strategus
- 0.0.3
+ 0.0.4
diff --git a/docs/reference/unlockKeyring.html b/docs/reference/unlockKeyring.html
index 65de48ed..f34a58d3 100644
--- a/docs/reference/unlockKeyring.html
+++ b/docs/reference/unlockKeyring.html
@@ -19,7 +19,7 @@
Strategus
- 0.0.3
+ 0.0.4
diff --git a/extras/Strategus.pdf b/extras/Strategus.pdf
index 1cb14f2707502bb878eb8667e5ba82c3e2bd033a..ce2f60dc302ba394f1357c6e8ac5762afd485b79 100644
GIT binary patch
delta 7099
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
auyDT>;j32657NZD^N7|oH@A0j(EJ~$C<@2`
diff --git a/inst/doc/CreatingAnalysisSpecification.pdf b/inst/doc/CreatingAnalysisSpecification.pdf
index 9c950358129e2131b38fa0adfea191fb88b6f9af..c8130e598ae86b621e21e0fd85135343b40c5c60 100644
GIT binary patch
delta 145076
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%0ASMwl}8h4j80p
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