From a8a9d9b2f12ce83c1e02c6620c19e300b461c169 Mon Sep 17 00:00:00 2001
From: jshoughtaling
Date: Wed, 18 Oct 2023 16:26:57 -0400
Subject: [PATCH 01/11] Create function for exporting dqdashboard_results table
to json file
---
NAMESPACE | 3 +-
R/writeDBResultsTo.R | 104 +++++++++++++++++++++++++
man/writeDBResultsToJson.Rd | 34 ++++++++
tests/testthat/test-writeDBResultsTo.R | 42 ++++++++++
4 files changed, 182 insertions(+), 1 deletion(-)
create mode 100644 R/writeDBResultsTo.R
create mode 100644 man/writeDBResultsToJson.Rd
create mode 100644 tests/testthat/test-writeDBResultsTo.R
diff --git a/NAMESPACE b/NAMESPACE
index 690105a7..26cb2a43 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -5,6 +5,7 @@ export(executeDqChecks)
export(listDqChecks)
export(reEvaluateThresholds)
export(viewDqDashboard)
+export(writeDBResultsToJson)
export(writeJsonResultsToCsv)
export(writeJsonResultsToTable)
import(DatabaseConnector)
@@ -30,4 +31,4 @@ importFrom(tools,file_path_sans_ext)
importFrom(utils,install.packages)
importFrom(utils,menu)
importFrom(utils,packageVersion)
-importFrom(utils,write.table)
+importFrom(utils,write.table)
\ No newline at end of file
diff --git a/R/writeDBResultsTo.R b/R/writeDBResultsTo.R
new file mode 100644
index 00000000..9f475543
--- /dev/null
+++ b/R/writeDBResultsTo.R
@@ -0,0 +1,104 @@
+# Copyright 2023 Observational Health Data Sciences and Informatics
+#
+# This file is part of DataQualityDashboard
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#' Write DQD results database table to json
+#'
+#' @param connection A connection object
+#' @param connectionDetails A connectionDetails object for connecting to the CDM database
+#' @param resultsDatabaseSchema The fully qualified database name of the results schema
+#' @param cdmDatabaseSchema The fully qualified database name of the CDM schema
+#' @param writeTableName Name of table in the database to write results to
+#' @param outputFolder The output folder
+#' @param outputFile The output filename
+#'
+#' @export
+#'
+
+writeDBResultsToJson <- function(connection,
+ connectionDetails,
+ resultsDatabaseSchema,
+ cdmDatabaseSchema,
+ writeTableName,
+ outputFolder,
+ outputFile) {
+ startTime <- Sys.time()
+
+ sql <- SqlRender::render(
+ sql = "select * from @cdmDatabaseSchema.cdm_source;",
+ cdmDatabaseSchema = cdmDatabaseSchema
+ )
+
+ sql <- SqlRender::translate(
+ sql = sql,
+ targetDialect = connectionDetails$dbms
+ )
+
+ metadata <- DatabaseConnector::querySql(
+ connection = connection,
+ sql = sql,
+ snakeCaseToCamelCase = TRUE
+ )
+
+ sql <- SqlRender::render(
+ sql = "select * from @resultsDatabaseSchema.@writeTableName;",
+ resultsDatabaseSchema = resultsDatabaseSchema,
+ writeTableName = writeTableName
+ )
+
+ sql <- SqlRender::translate(
+ sql = sql,
+ targetDialect = connectionDetails$dbms
+ )
+
+ checkResults <- DatabaseConnector::querySql(
+ connection,
+ sql,
+ snakeCaseToCamelCase = TRUE
+ )
+
+ # Quick patch for missing value issues related to SQL Only Implementation
+ checkResults["error"][checkResults["error"] == ''] <- NA
+ checkResults["warning"][checkResults["warning"] == ''] <- NA
+ checkResults["executionTime"][checkResults["executionTime"] == ''] <- '0.1 secs'
+ checkResults["queryText"][checkResults["queryText"] == ''] <- '[Generated via SQL Only]'
+
+ overview <- .summarizeResults(
+ checkResults = checkResults
+ )
+
+ endTime <- Sys.time()
+
+ delta <- startTime - endTime
+
+ # Quick patch for non-camel-case column name
+ names(checkResults)[names(checkResults) == "checkid"] <- "checkId"
+
+ allResults <- list(
+ startTimestamp = Sys.time(),
+ endTimestamp = Sys.time(),
+ executionTime = sprintf("%.0f %s", delta, attr(delta, "units")),
+ CheckResults = checkResults,
+ Metadata = metadata,
+ Overview = overview
+ )
+
+ .writeResultsToJson(
+ allResults,
+ outputFolder,
+ outputFile
+ )
+
+}
\ No newline at end of file
diff --git a/man/writeDBResultsToJson.Rd b/man/writeDBResultsToJson.Rd
new file mode 100644
index 00000000..67f23ac4
--- /dev/null
+++ b/man/writeDBResultsToJson.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/writeDBResultsTo.R
+\name{writeDBResultsToJson}
+\alias{writeDBResultsToJson}
+\title{Write DQD results database table to json}
+\usage{
+writeDBResultsToJson(
+ connection,
+ connectionDetails,
+ resultsDatabaseSchema,
+ cdmDatabaseSchema,
+ writeTableName,
+ outputFolder,
+ outputFile
+)
+}
+\arguments{
+\item{connection}{A connection object}
+
+\item{connectionDetails}{A connectionDetails object for connecting to the CDM database}
+
+\item{resultsDatabaseSchema}{The fully qualified database name of the results schema}
+
+\item{cdmDatabaseSchema}{The fully qualified database name of the CDM schema}
+
+\item{writeTableName}{Name of table in the database to write results to}
+
+\item{outputFolder}{The output folder}
+
+\item{outputFile}{The output filename}
+}
+\description{
+Write DQD results database table to json
+}
\ No newline at end of file
diff --git a/tests/testthat/test-writeDBResultsTo.R b/tests/testthat/test-writeDBResultsTo.R
new file mode 100644
index 00000000..4579d9a7
--- /dev/null
+++ b/tests/testthat/test-writeDBResultsTo.R
@@ -0,0 +1,42 @@
+library(testthat)
+
+test_that("Write DB results to json", {
+ outputFolder <- tempfile("dqd_")
+ on.exit(unlink(outputFolder, recursive = TRUE))
+ connectionDetailsEunomia <- Eunomia::getEunomiaConnectionDetails()
+ cdmDatabaseSchemaEunomia <- "main"
+ resultsDatabaseSchemaEunomia <- "main"
+
+ results <- DataQualityDashboard::executeDqChecks(
+ connectionDetails = connectionDetailsEunomia,
+ cdmDatabaseSchema = cdmDatabaseSchemaEunomia,
+ resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
+ cdmSourceName = "Eunomia",
+ checkNames = "measurePersonCompleteness",
+ outputFolder = outputFolder,
+ writeToTable = TRUE,
+ writeTableName = "dqdashboard_results"
+ )
+
+
+ connection <- DatabaseConnector::connect(connectionDetailsEunomia)
+ tableNames <- DatabaseConnector::getTableNames(connection = connection, databaseSchema = resultsDatabaseSchemaEunomia)
+ expect_true("dqdashboard_results" %in% tolower(tableNames))
+
+ testExportFile <- "dq-result-test.json"
+
+ DataQualityDashboard::writeDBResultsToJson(
+ connection,
+ connectionDetailsEunomia,
+ resultsDatabaseSchemaEunomia,
+ cdmDatabaseSchemaEunomia,
+ "dqdashboard_results",
+ outputFolder,
+ testExportFile
+ )
+
+ on.exit(DatabaseConnector::disconnect(connection), add = TRUE)
+ expect_true(file.exists(file.path(outputFolder,testExportFile)))
+
+
+})
\ No newline at end of file
From b3b65dc65f8765a06bf62a47feb82479efef3247 Mon Sep 17 00:00:00 2001
From: jshoughtaling
Date: Sat, 21 Oct 2023 12:37:18 -0400
Subject: [PATCH 02/11] Update function, tests, and documentation in response
to PR Rev
---
R/writeDBResultsTo.R | 46 ++++++++--------------
tests/testthat/test-writeDBResultsTo.R | 36 +++++++++++------
vignettes/SqlOnly.rmd | 53 ++++++--------------------
3 files changed, 50 insertions(+), 85 deletions(-)
diff --git a/R/writeDBResultsTo.R b/R/writeDBResultsTo.R
index 9f475543..a0617c82 100644
--- a/R/writeDBResultsTo.R
+++ b/R/writeDBResultsTo.R
@@ -20,9 +20,9 @@
#' @param connectionDetails A connectionDetails object for connecting to the CDM database
#' @param resultsDatabaseSchema The fully qualified database name of the results schema
#' @param cdmDatabaseSchema The fully qualified database name of the CDM schema
-#' @param writeTableName Name of table in the database to write results to
-#' @param outputFolder The output folder
-#' @param outputFile The output filename
+#' @param writeTableName Name of DQD results table in the database to read from
+#' @param outputFolder The folder to output the json results file to
+#' @param outputFile The output filename of the json results file
#'
#' @export
#'
@@ -36,43 +36,27 @@ writeDBResultsToJson <- function(connection,
outputFile) {
startTime <- Sys.time()
- sql <- SqlRender::render(
+ metadata <- DatabaseConnector::renderTranslateQuerySql(
+ connection,
sql = "select * from @cdmDatabaseSchema.cdm_source;",
- cdmDatabaseSchema = cdmDatabaseSchema
+ cdmDatabaseSchema = cdmDatabaseSchema,
+ targetDialect = connectionDetails$dbms,
+ snakeCaseToCamelCase = TRUE
)
- sql <- SqlRender::translate(
- sql = sql,
- targetDialect = connectionDetails$dbms
- )
-
- metadata <- DatabaseConnector::querySql(
- connection = connection,
- sql = sql,
- snakeCaseToCamelCase = TRUE
- )
-
- sql <- SqlRender::render(
+ checkResults <- DatabaseConnector::renderTranslateQuerySql(
+ connection,
sql = "select * from @resultsDatabaseSchema.@writeTableName;",
resultsDatabaseSchema = resultsDatabaseSchema,
- writeTableName = writeTableName
- )
-
- sql <- SqlRender::translate(
- sql = sql,
- targetDialect = connectionDetails$dbms
- )
-
- checkResults <- DatabaseConnector::querySql(
- connection,
- sql,
- snakeCaseToCamelCase = TRUE
+ writeTableName = writeTableName,
+ targetDialect = connectionDetails$dbms,
+ snakeCaseToCamelCase = TRUE
)
# Quick patch for missing value issues related to SQL Only Implementation
checkResults["error"][checkResults["error"] == ''] <- NA
checkResults["warning"][checkResults["warning"] == ''] <- NA
- checkResults["executionTime"][checkResults["executionTime"] == ''] <- '0.1 secs'
+ checkResults["executionTime"][checkResults["executionTime"] == ''] <- '0 secs'
checkResults["queryText"][checkResults["queryText"] == ''] <- '[Generated via SQL Only]'
overview <- .summarizeResults(
@@ -89,7 +73,7 @@ writeDBResultsToJson <- function(connection,
allResults <- list(
startTimestamp = Sys.time(),
endTimestamp = Sys.time(),
- executionTime = sprintf("%.0f %s", delta, attr(delta, "units")),
+ executionTime = '0 secs',
CheckResults = checkResults,
Metadata = metadata,
Overview = overview
diff --git a/tests/testthat/test-writeDBResultsTo.R b/tests/testthat/test-writeDBResultsTo.R
index 4579d9a7..66159efc 100644
--- a/tests/testthat/test-writeDBResultsTo.R
+++ b/tests/testthat/test-writeDBResultsTo.R
@@ -6,22 +6,21 @@ test_that("Write DB results to json", {
connectionDetailsEunomia <- Eunomia::getEunomiaConnectionDetails()
cdmDatabaseSchemaEunomia <- "main"
resultsDatabaseSchemaEunomia <- "main"
+ writeTableName <- "dqdashboard_results"
results <- DataQualityDashboard::executeDqChecks(
- connectionDetails = connectionDetailsEunomia,
- cdmDatabaseSchema = cdmDatabaseSchemaEunomia,
- resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
- cdmSourceName = "Eunomia",
- checkNames = "measurePersonCompleteness",
- outputFolder = outputFolder,
- writeToTable = TRUE,
- writeTableName = "dqdashboard_results"
+ connectionDetails = connectionDetailsEunomia,
+ cdmDatabaseSchema = cdmDatabaseSchemaEunomia,
+ resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
+ cdmSourceName = "Eunomia",
+ checkNames = "measurePersonCompleteness",
+ outputFolder = outputFolder,
+ writeToTable = TRUE,
+ writeTableName = writeTableName
)
connection <- DatabaseConnector::connect(connectionDetailsEunomia)
- tableNames <- DatabaseConnector::getTableNames(connection = connection, databaseSchema = resultsDatabaseSchemaEunomia)
- expect_true("dqdashboard_results" %in% tolower(tableNames))
testExportFile <- "dq-result-test.json"
@@ -30,13 +29,26 @@ test_that("Write DB results to json", {
connectionDetailsEunomia,
resultsDatabaseSchemaEunomia,
cdmDatabaseSchemaEunomia,
- "dqdashboard_results",
+ writeTableName,
outputFolder,
testExportFile
- )
+ )
on.exit(DatabaseConnector::disconnect(connection), add = TRUE)
+
+ # Check that file was exported properly
expect_true(file.exists(file.path(outputFolder,testExportFile)))
+ # Check that export length matches length of db table
+ results <- jsonlite::fromJSON(file.path(outputFolder,testExportFile))
+ table_rows <- DatabaseConnector::renderTranslateQuerySql(
+ connection,
+ sql = "select count(*) from @resultsDatabaseSchema.@writeTableName;",
+ resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
+ writeTableName = writeTableName,
+ targetDialect = connectionDetailsEunomia$dbms,
+ snakeCaseToCamelCase = TRUE
+ )
+ expect_true(length(results$CheckResults) == table_rows)
})
\ No newline at end of file
diff --git a/vignettes/SqlOnly.rmd b/vignettes/SqlOnly.rmd
index 9fabcce0..dcaad7f1 100644
--- a/vignettes/SqlOnly.rmd
+++ b/vignettes/SqlOnly.rmd
@@ -171,49 +171,18 @@ for (dqdSqlFile in dqdSqlFiles) {
)
}
-# Get results
-checkResults <- DatabaseConnector::querySql(
- c,
- SqlRender::render(
- "SELECT * FROM @resultsDatabaseSchema.@writeTableName",
- resultsDatabaseSchema = resultsDatabaseSchema,
- writeTableName = writeTableName
- ),
- snakeCaseToCamelCase = TRUE
-)
-DatabaseConnector::disconnect(c)
-
-# convert check ID column name to correct format
-colnames(checkResults)[colnames(checkResults) == "checkid"] ="checkId"
-
-# Get overview of DQD results
-library(DataQualityDashboard)
-overview <- DataQualityDashboard:::.summarizeResults(checkResults = checkResults)
-
-# Create results object, adding fake metadata
-result <- list(
- startTimestamp = Sys.time(),
- endTimestamp = Sys.time(),
- executionTime = "",
- Metadata = data.frame(
- cdmSourceName = cdmSourceName,
- cdmSourceAbbreviation = cdmSourceName,
- cdmHolder = "",
- sourceDescription = "",
- sourceDocumentationReference = "",
- cdmEtlReference = "",
- sourceReleaseDate = "",
- cdmReleaseDate = "",
- cdmVersion = cdmVersion,
- cdmVersionConceptId = 0,
- vocabularyVersion = "",
- dqdVersion = as.character(packageVersion("DataQualityDashboard"))
- ),
- Overview = overview,
- CheckResults = checkResults
-)
+# Extract results table to JSON file for viewing or secondary use
+
+DataQualityDashboard::writeDBResultsToJson(
+ c,
+ connectionDetails,
+ resultsDatabaseSchema,
+ cdmDatabaseSchema,
+ writeTableName,
+ jsonOutputFolder,
+ jsonOutputFile
+ )
-DataQualityDashboard:::.writeResultsToJson(result, jsonOutputFolder, jsonOutputFile)
jsonFilePath <- R.utils::getAbsolutePath(file.path(jsonOutputFolder, jsonOutputFile))
DataQualityDashboard::viewDqDashboard(jsonFilePath)
From 4aa1ddefe192644cd360a17714ca02d9fde770ce Mon Sep 17 00:00:00 2001
From: jshoughtaling
Date: Sat, 21 Oct 2023 13:58:52 -0400
Subject: [PATCH 03/11] Remove unused start and end times
---
R/writeDBResultsTo.R | 6 ------
1 file changed, 6 deletions(-)
diff --git a/R/writeDBResultsTo.R b/R/writeDBResultsTo.R
index a0617c82..66134ae5 100644
--- a/R/writeDBResultsTo.R
+++ b/R/writeDBResultsTo.R
@@ -34,8 +34,6 @@ writeDBResultsToJson <- function(connection,
writeTableName,
outputFolder,
outputFile) {
- startTime <- Sys.time()
-
metadata <- DatabaseConnector::renderTranslateQuerySql(
connection,
sql = "select * from @cdmDatabaseSchema.cdm_source;",
@@ -63,10 +61,6 @@ writeDBResultsToJson <- function(connection,
checkResults = checkResults
)
- endTime <- Sys.time()
-
- delta <- startTime - endTime
-
# Quick patch for non-camel-case column name
names(checkResults)[names(checkResults) == "checkid"] <- "checkId"
From ad114994526da68839f0aa0ab54d6caa7366e3f6 Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Tue, 31 Oct 2023 11:51:27 -0400
Subject: [PATCH 04/11] warn wrong sqlonly param combo
---
R/executeDqChecks.R | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/R/executeDqChecks.R b/R/executeDqChecks.R
index 5e9d1070..d7d5342b 100644
--- a/R/executeDqChecks.R
+++ b/R/executeDqChecks.R
@@ -93,6 +93,10 @@ executeDqChecks <- function(connectionDetails,
if (!str_detect(cdmVersion, regex(acceptedCdmRegex))) {
stop("cdmVersion must contain a version of the form '5.X' where X is an integer between 2 and 4 inclusive.")
}
+
+ if (sqlOnlyIncrementalInsert == TRUE && sqlOnly == FALSE) {
+ stop("Set `sqlOnly` to TRUE in order to use `sqlOnlyIncrementalInsert` mode.")
+ }
stopifnot(is.character(cdmDatabaseSchema), is.character(resultsDatabaseSchema), is.numeric(numThreads))
stopifnot(is.character(cdmSourceName), is.logical(sqlOnly), is.character(outputFolder), is.logical(verboseMode))
From 87fc78a885b551ed8bd7dc7ee16f35e14eb6c123 Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Tue, 31 Oct 2023 11:56:53 -0400
Subject: [PATCH 05/11] clean sqlonly write condition
---
NAMESPACE | 2 +-
R/runCheck.R | 2 +-
man/writeDBResultsToJson.Rd | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/NAMESPACE b/NAMESPACE
index 26cb2a43..8420f28a 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -31,4 +31,4 @@ importFrom(tools,file_path_sans_ext)
importFrom(utils,install.packages)
importFrom(utils,menu)
importFrom(utils,packageVersion)
-importFrom(utils,write.table)
\ No newline at end of file
+importFrom(utils,write.table)
diff --git a/R/runCheck.R b/R/runCheck.R
index 85c31945..e4e2bd78 100644
--- a/R/runCheck.R
+++ b/R/runCheck.R
@@ -124,7 +124,7 @@
dfs <- do.call(rbind, dfs)
- if (sqlOnlyIncrementalInsert) {
+ if (sqlOnly && sqlOnlyIncrementalInsert) {
sqlToUnion <- dfs$query
if (length(sqlToUnion) > 0) {
.writeSqlOnlyQueries(sqlToUnion, sqlOnlyUnionCount, resultsDatabaseSchema, writeTableName, connectionDetails$dbms, outputFolder, checkDescription)
diff --git a/man/writeDBResultsToJson.Rd b/man/writeDBResultsToJson.Rd
index 67f23ac4..94b00b82 100644
--- a/man/writeDBResultsToJson.Rd
+++ b/man/writeDBResultsToJson.Rd
@@ -23,12 +23,12 @@ writeDBResultsToJson(
\item{cdmDatabaseSchema}{The fully qualified database name of the CDM schema}
-\item{writeTableName}{Name of table in the database to write results to}
+\item{writeTableName}{Name of DQD results table in the database to read from}
-\item{outputFolder}{The output folder}
+\item{outputFolder}{The folder to output the json results file to}
-\item{outputFile}{The output filename}
+\item{outputFile}{The output filename of the json results file}
}
\description{
Write DQD results database table to json
-}
\ No newline at end of file
+}
From 426b342030d23d38f297e575485df5b23f0b2ab9 Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Thu, 2 Nov 2023 21:03:31 -0400
Subject: [PATCH 06/11] allow multiple cdm_source rows
---
R/executeDqChecks.R | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/R/executeDqChecks.R b/R/executeDqChecks.R
index d7d5342b..a9e6b55a 100644
--- a/R/executeDqChecks.R
+++ b/R/executeDqChecks.R
@@ -138,6 +138,10 @@ executeDqChecks <- function(connectionDetails,
if (nrow(metadata) < 1) {
stop("Please populate the cdm_source table before executing data quality checks.")
}
+ if (nrow(metadata) > 1) {
+ metadata <- metadata[1]
+ warning("The cdm_source table has more than 1 row. A single row from this table has been selected to populate DQD metadata.")
+ }
metadata$dqdVersion <- as.character(packageVersion("DataQualityDashboard"))
DatabaseConnector::disconnect(connection)
} else {
From c3d375a7a507bc012ceb02c956df4ba31a62bd06 Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Fri, 3 Nov 2023 10:48:46 -0400
Subject: [PATCH 07/11] forgot comma
---
R/executeDqChecks.R | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/R/executeDqChecks.R b/R/executeDqChecks.R
index a9e6b55a..29082f33 100644
--- a/R/executeDqChecks.R
+++ b/R/executeDqChecks.R
@@ -139,7 +139,7 @@ executeDqChecks <- function(connectionDetails,
stop("Please populate the cdm_source table before executing data quality checks.")
}
if (nrow(metadata) > 1) {
- metadata <- metadata[1]
+ metadata <- metadata[1,]
warning("The cdm_source table has more than 1 row. A single row from this table has been selected to populate DQD metadata.")
}
metadata$dqdVersion <- as.character(packageVersion("DataQualityDashboard"))
From 460ffb139aaad087206b0eb1921eaba2e86e6cff Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Fri, 3 Nov 2023 12:07:18 -0400
Subject: [PATCH 08/11] removed unneeded param writeDBResults
---
R/writeDBResultsTo.R | 12 ++++--------
man/writeDBResultsToJson.Rd | 3 ---
tests/testthat/test-writeDBResultsTo.R | 24 ++++++++++++------------
3 files changed, 16 insertions(+), 23 deletions(-)
diff --git a/R/writeDBResultsTo.R b/R/writeDBResultsTo.R
index 66134ae5..39ef902f 100644
--- a/R/writeDBResultsTo.R
+++ b/R/writeDBResultsTo.R
@@ -17,7 +17,6 @@
#' Write DQD results database table to json
#'
#' @param connection A connection object
-#' @param connectionDetails A connectionDetails object for connecting to the CDM database
#' @param resultsDatabaseSchema The fully qualified database name of the results schema
#' @param cdmDatabaseSchema The fully qualified database name of the CDM schema
#' @param writeTableName Name of DQD results table in the database to read from
@@ -28,7 +27,6 @@
#'
writeDBResultsToJson <- function(connection,
- connectionDetails,
resultsDatabaseSchema,
cdmDatabaseSchema,
writeTableName,
@@ -37,18 +35,16 @@ writeDBResultsToJson <- function(connection,
metadata <- DatabaseConnector::renderTranslateQuerySql(
connection,
sql = "select * from @cdmDatabaseSchema.cdm_source;",
- cdmDatabaseSchema = cdmDatabaseSchema,
- targetDialect = connectionDetails$dbms,
- snakeCaseToCamelCase = TRUE
+ snakeCaseToCamelCase = TRUE,
+ cdmDatabaseSchema = cdmDatabaseSchema
)
checkResults <- DatabaseConnector::renderTranslateQuerySql(
connection,
sql = "select * from @resultsDatabaseSchema.@writeTableName;",
+ snakeCaseToCamelCase = TRUE,
resultsDatabaseSchema = resultsDatabaseSchema,
- writeTableName = writeTableName,
- targetDialect = connectionDetails$dbms,
- snakeCaseToCamelCase = TRUE
+ writeTableName = writeTableName
)
# Quick patch for missing value issues related to SQL Only Implementation
diff --git a/man/writeDBResultsToJson.Rd b/man/writeDBResultsToJson.Rd
index 94b00b82..3efc6b03 100644
--- a/man/writeDBResultsToJson.Rd
+++ b/man/writeDBResultsToJson.Rd
@@ -6,7 +6,6 @@
\usage{
writeDBResultsToJson(
connection,
- connectionDetails,
resultsDatabaseSchema,
cdmDatabaseSchema,
writeTableName,
@@ -17,8 +16,6 @@ writeDBResultsToJson(
\arguments{
\item{connection}{A connection object}
-\item{connectionDetails}{A connectionDetails object for connecting to the CDM database}
-
\item{resultsDatabaseSchema}{The fully qualified database name of the results schema}
\item{cdmDatabaseSchema}{The fully qualified database name of the CDM schema}
diff --git a/tests/testthat/test-writeDBResultsTo.R b/tests/testthat/test-writeDBResultsTo.R
index 66159efc..312f4607 100644
--- a/tests/testthat/test-writeDBResultsTo.R
+++ b/tests/testthat/test-writeDBResultsTo.R
@@ -8,25 +8,26 @@ test_that("Write DB results to json", {
resultsDatabaseSchemaEunomia <- "main"
writeTableName <- "dqdashboard_results"
- results <- DataQualityDashboard::executeDqChecks(
- connectionDetails = connectionDetailsEunomia,
- cdmDatabaseSchema = cdmDatabaseSchemaEunomia,
- resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
- cdmSourceName = "Eunomia",
- checkNames = "measurePersonCompleteness",
- outputFolder = outputFolder,
- writeToTable = TRUE,
- writeTableName = writeTableName
+ expect_warning(
+ results <- DataQualityDashboard::executeDqChecks(
+ connectionDetails = connectionDetailsEunomia,
+ cdmDatabaseSchema = cdmDatabaseSchemaEunomia,
+ resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
+ cdmSourceName = "Eunomia",
+ checkNames = "measurePersonCompleteness",
+ outputFolder = outputFolder,
+ writeToTable = TRUE,
+ writeTableName = writeTableName
+ ),
+ regexp = "^Missing check names.*"
)
-
connection <- DatabaseConnector::connect(connectionDetailsEunomia)
testExportFile <- "dq-result-test.json"
DataQualityDashboard::writeDBResultsToJson(
connection,
- connectionDetailsEunomia,
resultsDatabaseSchemaEunomia,
cdmDatabaseSchemaEunomia,
writeTableName,
@@ -46,7 +47,6 @@ test_that("Write DB results to json", {
sql = "select count(*) from @resultsDatabaseSchema.@writeTableName;",
resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
writeTableName = writeTableName,
- targetDialect = connectionDetailsEunomia$dbms,
snakeCaseToCamelCase = TRUE
)
expect_true(length(results$CheckResults) == table_rows)
From 5684275c03036ed5d10b8391dcef736db48f080a Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Sat, 4 Nov 2023 16:35:31 -0400
Subject: [PATCH 09/11] fix bugs
---
R/writeJsonResultsTo.R | 2 +-
inst/sql/sql_server/field_fk_class.sql | 6 +++---
inst/sql/sql_server/field_fk_domain.sql | 6 +++---
inst/sql/sql_server/field_plausible_temporal_after.sql | 10 +++++-----
tests/testthat/test-writeDBResultsTo.R | 2 +-
tests/testthat/test-writeJsonResultsTo.R | 6 +++---
6 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/R/writeJsonResultsTo.R b/R/writeJsonResultsTo.R
index a5a70b20..869539b6 100644
--- a/R/writeJsonResultsTo.R
+++ b/R/writeJsonResultsTo.R
@@ -47,7 +47,7 @@ writeJsonResultsToTable <- function(connectionDetails,
ParallelLogger::logInfo(sprintf("Writing results to table %s", tableName))
- if ("unitConceptId" %in% colnames(df)) {
+ if ("conceptId" %in% colnames(df)) {
ddl <- SqlRender::loadRenderTranslateSql(
sqlFilename = "result_table_ddl_concept.sql",
packageName = "DataQualityDashboard",
diff --git a/inst/sql/sql_server/field_fk_class.sql b/inst/sql/sql_server/field_fk_class.sql
index e9d6871e..2505fefc 100755
--- a/inst/sql/sql_server/field_fk_class.sql
+++ b/inst/sql/sql_server/field_fk_class.sql
@@ -4,7 +4,7 @@ FK_CLASS
Drug era standard concepts, ingredients only
Parameters used in this template:
-cdmDatabaseSchema = @cdmDatabaseSchema
+schema = @schema
vocabDatabaseSchema = @vocabDatabaseSchema
cdmTableName = @cdmTableName
cdmFieldName = @cdmFieldName
@@ -32,7 +32,7 @@ FROM (
SELECT
'@cdmTableName.@cdmFieldName' AS violating_field,
cdmTable.*
- FROM @cdmDatabaseSchema.@cdmTableName cdmTable
+ FROM @schema.@cdmTableName cdmTable
LEFT JOIN @vocabDatabaseSchema.concept co
ON cdmTable.@cdmFieldName = co.concept_id
{@cohort & '@runForCohort' == 'Yes'}?{
@@ -48,7 +48,7 @@ FROM (
(
SELECT
COUNT_BIG(*) AS num_rows
- FROM @cdmDatabaseSchema.@cdmTableName cdmTable
+ FROM @schema.@cdmTableName cdmTable
{@cohort & '@runForCohort' == 'Yes'}?{
JOIN @cohortDatabaseSchema.@cohortTableName c
ON cdmTable.person_id = c.subject_id
diff --git a/inst/sql/sql_server/field_fk_domain.sql b/inst/sql/sql_server/field_fk_domain.sql
index ce596c47..a3e188f8 100755
--- a/inst/sql/sql_server/field_fk_domain.sql
+++ b/inst/sql/sql_server/field_fk_domain.sql
@@ -5,7 +5,7 @@ FIELD_FK_DOMAIN
all standard concept ids are part of specified domain
Parameters used in this template:
-cdmDatabaseSchema = @cdmDatabaseSchema
+schema = @schema
vocabDatabaseSchema = @vocabDatabaseSchema
cdmTableName = @cdmTableName
cdmFieldName = @cdmFieldName
@@ -32,7 +32,7 @@ FROM (
SELECT
'@cdmTableName.@cdmFieldName' AS violating_field,
cdmTable.*
- FROM @cdmDatabaseSchema.@cdmTableName cdmTable
+ FROM @schema.@cdmTableName cdmTable
LEFT JOIN @vocabDatabaseSchema.concept co
ON cdmTable.@cdmFieldName = co.concept_id
{@cohort & '@runForCohort' == 'Yes'}?{
@@ -48,7 +48,7 @@ FROM (
(
SELECT
COUNT_BIG(*) AS num_rows
- FROM @cdmDatabaseSchema.@cdmTableName cdmTable
+ FROM @schema.@cdmTableName cdmTable
{@cohort & '@runForCohort' == 'Yes'}?{
JOIN @cohortDatabaseSchema.@cohortTableName c
ON cdmTable.PERSON_ID = c.SUBJECT_ID
diff --git a/inst/sql/sql_server/field_plausible_temporal_after.sql b/inst/sql/sql_server/field_plausible_temporal_after.sql
index 8dd28eab..034ce0b8 100755
--- a/inst/sql/sql_server/field_plausible_temporal_after.sql
+++ b/inst/sql/sql_server/field_plausible_temporal_after.sql
@@ -4,7 +4,7 @@ PLAUSIBLE_TEMPORAL_AFTER
get number of records and the proportion to total number of eligible records with datetimes that do not occur on or after their corresponding datetimes
Parameters used in this template:
-cdmDatabaseSchema = @cdmDatabaseSchema
+schema = @schema
cdmTableName = @cdmTableName
cdmFieldName = @cdmFieldName
plausibleTemporalAfterTableName = @plausibleTemporalAfterTableName
@@ -33,9 +33,9 @@ FROM
SELECT
'@cdmTableName.@cdmFieldName' AS violating_field,
cdmTable.*
- FROM @cdmDatabaseSchema.@cdmTableName cdmTable
- {@cdmDatabaseSchema.@cdmTableName != @cdmDatabaseSchema.@plausibleTemporalAfterTableName}?{
- JOIN @cdmDatabaseSchema.@plausibleTemporalAfterTableName plausibleTable ON cdmTable.person_id = plausibleTable.person_id}
+ FROM @schema.@cdmTableName cdmTable
+ {@schema.@cdmTableName != @schema.@plausibleTemporalAfterTableName}?{
+ JOIN @schema.@plausibleTemporalAfterTableName plausibleTable ON cdmTable.person_id = plausibleTable.person_id}
{@cohort & '@runForCohort' == 'Yes'}?{
JOIN @cohortDatabaseSchema.@cohortTableName c ON cdmTable.person_id = c.subject_id
AND c.cohort_definition_id = @cohortDefinitionId
@@ -55,7 +55,7 @@ FROM
(
SELECT
COUNT_BIG(*) AS num_rows
- FROM @cdmDatabaseSchema.@cdmTableName cdmTable
+ FROM @schema.@cdmTableName cdmTable
{@cohort & '@runForCohort' == 'Yes'}?{
JOIN @cohortDatabaseSchema.@cohortTableName c ON cdmTable.person_id = c.subject_id
AND c.cohort_definition_id = @cohortDefinitionId
diff --git a/tests/testthat/test-writeDBResultsTo.R b/tests/testthat/test-writeDBResultsTo.R
index 312f4607..9cc11808 100644
--- a/tests/testthat/test-writeDBResultsTo.R
+++ b/tests/testthat/test-writeDBResultsTo.R
@@ -6,7 +6,7 @@ test_that("Write DB results to json", {
connectionDetailsEunomia <- Eunomia::getEunomiaConnectionDetails()
cdmDatabaseSchemaEunomia <- "main"
resultsDatabaseSchemaEunomia <- "main"
- writeTableName <- "dqdashboard_results"
+ writeTableName <- "dqd_db_results"
expect_warning(
results <- DataQualityDashboard::executeDqChecks(
diff --git a/tests/testthat/test-writeJsonResultsTo.R b/tests/testthat/test-writeJsonResultsTo.R
index 7aff258f..c9df7f0d 100644
--- a/tests/testthat/test-writeJsonResultsTo.R
+++ b/tests/testthat/test-writeJsonResultsTo.R
@@ -29,11 +29,11 @@ test_that("Write JSON results", {
connectionDetails = connectionDetailsEunomia,
resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
jsonFilePath = jsonPath,
- writeTableName = "dqd_results"
+ writeTableName = "dqd_json_results"
)
connection <- DatabaseConnector::connect(connectionDetailsEunomia)
on.exit(DatabaseConnector::disconnect(connection), add = TRUE)
tableNames <- DatabaseConnector::getTableNames(connection = connection, databaseSchema = resultsDatabaseSchemaEunomia)
- expect_true("dqd_results" %in% tolower(tableNames))
- DatabaseConnector::renderTranslateExecuteSql(connection, "DROP TABLE @database_schema.dqd_results;", database_schema = resultsDatabaseSchemaEunomia)
+ expect_true("dqd_json_results" %in% tolower(tableNames))
+ DatabaseConnector::renderTranslateExecuteSql(connection, "DROP TABLE @database_schema.dqd_json_results;", database_schema = resultsDatabaseSchemaEunomia)
})
From 16990b4731c334c3978d1c105f703c93a6053ca0 Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Sat, 4 Nov 2023 17:26:48 -0400
Subject: [PATCH 10/11] test for cdm_source warning
---
tests/testthat/test-executeDqChecks.R | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/tests/testthat/test-executeDqChecks.R b/tests/testthat/test-executeDqChecks.R
index 01cb11b3..35fbff93 100644
--- a/tests/testthat/test-executeDqChecks.R
+++ b/tests/testthat/test-executeDqChecks.R
@@ -328,3 +328,30 @@ test_that("Incremental insert SQL is valid.", {
DatabaseConnector::renderTranslateExecuteSql(connection, "DROP TABLE @database_schema.dqd_results;", database_schema = resultsDatabaseSchemaEunomia)
})
+
+test_that("Multiple cdm_source rows triggers warning.", {
+ outputFolder <- tempfile("dqd_")
+ on.exit(unlink(outputFolder, recursive = TRUE))
+
+ connectionDetailsEunomiaCS <- Eunomia::getEunomiaConnectionDetails()
+ connection <- DatabaseConnector::connect(connectionDetailsEunomiaCS)
+ on.exit(DatabaseConnector::disconnect(connection), add = TRUE)
+ DatabaseConnector::renderTranslateExecuteSql(connection, "INSERT INTO @database_schema.cdm_source VALUES ('foo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);", database_schema = cdmDatabaseSchemaEunomia)
+
+ w <- capture_warnings(
+ results <- executeDqChecks(
+ connectionDetails = connectionDetailsEunomiaCS,
+ cdmDatabaseSchema = cdmDatabaseSchemaEunomia,
+ resultsDatabaseSchema = resultsDatabaseSchemaEunomia,
+ cdmSourceName = "Eunomia",
+ checkNames = "measurePersonCompleteness",
+ outputFolder = outputFolder,
+ writeToTable = F
+ ))
+
+ expect_match(w, "Missing check names", all = FALSE)
+ expect_match(w, "The cdm_source table has", all = FALSE)
+
+ expect_true(nrow(results$CheckResults) > 1)
+})
+
From ef4f61f177d5d56ef4a5b4fe2f5368419804b462 Mon Sep 17 00:00:00 2001
From: Katy Sadowski
Date: Sat, 4 Nov 2023 18:22:00 -0400
Subject: [PATCH 11/11] release prep
---
DESCRIPTION | 4 +-
R/executeDqChecks.R | 4 +-
R/writeDBResultsTo.R | 85 +++++-----
_pkgdown.yml | 4 +
docs/404.html | 2 +-
docs/LICENSE-text.html | 2 +-
docs/articles/AddNewCheck.html | 4 +-
docs/articles/CheckStatusDefinitions.html | 4 +-
docs/articles/CheckTypeDescriptions.html | 4 +-
docs/articles/DataQualityDashboard.html | 4 +-
docs/articles/DqdForCohorts.html | 4 +-
docs/articles/SqlOnly.html | 55 ++-----
docs/articles/Thresholds.html | 4 +-
docs/articles/index.html | 2 +-
docs/authors.html | 2 +-
docs/index.html | 2 +-
docs/news/index.html | 2 +-
docs/pkgdown.yml | 2 +-
docs/pull_request_template.html | 2 +-
.../reference/convertJsonResultsFileCase.html | 2 +-
docs/reference/dot-evaluateThresholds.html | 2 +-
docs/reference/dot-getCheckId.html | 2 +-
docs/reference/dot-processCheck.html | 2 +-
docs/reference/dot-recordResult.html | 2 +-
docs/reference/dot-runCheck.html | 2 +-
docs/reference/dot-summarizeResults.html | 2 +-
docs/reference/dot-writeResultsToCsv.html | 2 +-
docs/reference/dot-writeResultsToJson.html | 2 +-
docs/reference/dot-writeResultsToTable.html | 2 +-
docs/reference/executeDqChecks.html | 2 +-
docs/reference/index.html | 10 +-
docs/reference/listDqChecks.html | 2 +-
docs/reference/reEvaluateThresholds.html | 2 +-
docs/reference/viewDqDashboard.html | 2 +-
docs/reference/writeDBResultsToJson.html | 153 ++++++++++++++++++
docs/reference/writeJsonResultsToCsv.html | 2 +-
docs/reference/writeJsonResultsToTable.html | 2 +-
docs/sitemap.xml | 3 +
extras/DataQualityDashboard.pdf | Bin 92126 -> 93600 bytes
inst/doc/AddNewCheck.pdf | Bin 233581 -> 232789 bytes
inst/doc/CheckStatusDefinitions.pdf | Bin 160838 -> 160745 bytes
inst/doc/CheckTypeDescriptions.pdf | Bin 206994 -> 206895 bytes
inst/doc/DataQualityDashboard.pdf | Bin 243315 -> 243747 bytes
inst/doc/DqdForCohorts.pdf | Bin 195492 -> 195380 bytes
inst/doc/SqlOnly.pdf | Bin 230207 -> 228245 bytes
inst/doc/Thresholds.pdf | Bin 275404 -> 275585 bytes
tests/testthat/test-executeDqChecks.R | 12 +-
tests/testthat/test-writeDBResultsTo.R | 7 +-
48 files changed, 272 insertions(+), 137 deletions(-)
create mode 100644 docs/reference/writeDBResultsToJson.html
diff --git a/DESCRIPTION b/DESCRIPTION
index 910a09e7..0f0e746c 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,8 +1,8 @@
Package: DataQualityDashboard
Type: Package
Title: Execute and View Data Quality Checks on OMOP CDM Database
-Version: 2.4.1
-Date: 2023-10-18
+Version: 2.5.0
+Date: 2023-11-04
Authors@R: c(
person("Katy", "Sadowski", email = "sadowski@ohdsi.org", role = c("aut", "cre")),
person("Clair", "Blacketer", role = c("aut")),
diff --git a/R/executeDqChecks.R b/R/executeDqChecks.R
index 29082f33..178e0ab4 100644
--- a/R/executeDqChecks.R
+++ b/R/executeDqChecks.R
@@ -93,7 +93,7 @@ executeDqChecks <- function(connectionDetails,
if (!str_detect(cdmVersion, regex(acceptedCdmRegex))) {
stop("cdmVersion must contain a version of the form '5.X' where X is an integer between 2 and 4 inclusive.")
}
-
+
if (sqlOnlyIncrementalInsert == TRUE && sqlOnly == FALSE) {
stop("Set `sqlOnly` to TRUE in order to use `sqlOnlyIncrementalInsert` mode.")
}
@@ -139,7 +139,7 @@ executeDqChecks <- function(connectionDetails,
stop("Please populate the cdm_source table before executing data quality checks.")
}
if (nrow(metadata) > 1) {
- metadata <- metadata[1,]
+ metadata <- metadata[1, ]
warning("The cdm_source table has more than 1 row. A single row from this table has been selected to populate DQD metadata.")
}
metadata$dqdVersion <- as.character(packageVersion("DataQualityDashboard"))
diff --git a/R/writeDBResultsTo.R b/R/writeDBResultsTo.R
index 39ef902f..2b4e446a 100644
--- a/R/writeDBResultsTo.R
+++ b/R/writeDBResultsTo.R
@@ -27,52 +27,51 @@
#'
writeDBResultsToJson <- function(connection,
- resultsDatabaseSchema,
- cdmDatabaseSchema,
- writeTableName,
- outputFolder,
- outputFile) {
- metadata <- DatabaseConnector::renderTranslateQuerySql(
- connection,
- sql = "select * from @cdmDatabaseSchema.cdm_source;",
- snakeCaseToCamelCase = TRUE,
- cdmDatabaseSchema = cdmDatabaseSchema
- )
+ resultsDatabaseSchema,
+ cdmDatabaseSchema,
+ writeTableName,
+ outputFolder,
+ outputFile) {
+ metadata <- DatabaseConnector::renderTranslateQuerySql(
+ connection,
+ sql = "select * from @cdmDatabaseSchema.cdm_source;",
+ snakeCaseToCamelCase = TRUE,
+ cdmDatabaseSchema = cdmDatabaseSchema
+ )
- checkResults <- DatabaseConnector::renderTranslateQuerySql(
- connection,
- sql = "select * from @resultsDatabaseSchema.@writeTableName;",
- snakeCaseToCamelCase = TRUE,
- resultsDatabaseSchema = resultsDatabaseSchema,
- writeTableName = writeTableName
- )
+ checkResults <- DatabaseConnector::renderTranslateQuerySql(
+ connection,
+ sql = "select * from @resultsDatabaseSchema.@writeTableName;",
+ snakeCaseToCamelCase = TRUE,
+ resultsDatabaseSchema = resultsDatabaseSchema,
+ writeTableName = writeTableName
+ )
- # Quick patch for missing value issues related to SQL Only Implementation
- checkResults["error"][checkResults["error"] == ''] <- NA
- checkResults["warning"][checkResults["warning"] == ''] <- NA
- checkResults["executionTime"][checkResults["executionTime"] == ''] <- '0 secs'
- checkResults["queryText"][checkResults["queryText"] == ''] <- '[Generated via SQL Only]'
+ # Quick patch for missing value issues related to SQL Only Implementation
+ checkResults["error"][checkResults["error"] == ""] <- NA
+ checkResults["warning"][checkResults["warning"] == ""] <- NA
+ checkResults["executionTime"][checkResults["executionTime"] == ""] <- "0 secs"
+ checkResults["queryText"][checkResults["queryText"] == ""] <- "[Generated via SQL Only]"
- overview <- .summarizeResults(
- checkResults = checkResults
- )
+ overview <- .summarizeResults(
+ checkResults = checkResults
+ )
- # Quick patch for non-camel-case column name
- names(checkResults)[names(checkResults) == "checkid"] <- "checkId"
+ # Quick patch for non-camel-case column name
+ names(checkResults)[names(checkResults) == "checkid"] <- "checkId"
- allResults <- list(
- startTimestamp = Sys.time(),
- endTimestamp = Sys.time(),
- executionTime = '0 secs',
- CheckResults = checkResults,
- Metadata = metadata,
- Overview = overview
- )
+ allResults <- list(
+ startTimestamp = Sys.time(),
+ endTimestamp = Sys.time(),
+ executionTime = "0 secs",
+ CheckResults = checkResults,
+ Metadata = metadata,
+ Overview = overview
+ )
- .writeResultsToJson(
- allResults,
- outputFolder,
- outputFile
- )
-
-}
\ No newline at end of file
+ .writeResultsToJson(
+ allResults,
+ outputFolder,
+ outputFile
+ )
+}
diff --git a/_pkgdown.yml b/_pkgdown.yml
index 57941eb8..63139cb3 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -63,3 +63,7 @@ reference:
desc: >
Function to convert the case of a results JSON file between snakecase and camelcase
contents: convertJsonResultsFileCase
+ - title: "Write database results to a JSON file"
+ desc: >
+ Function to write DQD results from a database table into a JSON file
+ contents: writeDBResultsToJson
diff --git a/docs/404.html b/docs/404.html
index ca0796ad..727f16f7 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -32,7 +32,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html
index 31e872f3..10a745e8 100644
--- a/docs/LICENSE-text.html
+++ b/docs/LICENSE-text.html
@@ -17,7 +17,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
diff --git a/docs/articles/AddNewCheck.html b/docs/articles/AddNewCheck.html
index 22f4f9a4..345db18c 100644
--- a/docs/articles/AddNewCheck.html
+++ b/docs/articles/AddNewCheck.html
@@ -33,7 +33,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
@@ -108,7 +108,7 @@
Add a New Data Quality Check
Don Torok
- 2023-10-18
+ 2023-11-04
Source: vignettes/AddNewCheck.rmd
AddNewCheck.rmd
diff --git a/docs/articles/CheckStatusDefinitions.html b/docs/articles/CheckStatusDefinitions.html
index f75e735e..ac597143 100644
--- a/docs/articles/CheckStatusDefinitions.html
+++ b/docs/articles/CheckStatusDefinitions.html
@@ -33,7 +33,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
@@ -109,7 +109,7 @@ Check Status Descriptions
Dmitry
Ilyn
- 2023-10-18
+ 2023-11-04
Source: vignettes/CheckStatusDefinitions.rmd
CheckStatusDefinitions.rmd
diff --git a/docs/articles/CheckTypeDescriptions.html b/docs/articles/CheckTypeDescriptions.html
index 3e1ec5c5..ac9d2909 100644
--- a/docs/articles/CheckTypeDescriptions.html
+++ b/docs/articles/CheckTypeDescriptions.html
@@ -33,7 +33,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
@@ -109,7 +109,7 @@ Data Quality Check Type Definitions
Clair
Blacketer
- 2023-10-18
+ 2023-11-04
Source: vignettes/CheckTypeDescriptions.rmd
CheckTypeDescriptions.rmd
diff --git a/docs/articles/DataQualityDashboard.html b/docs/articles/DataQualityDashboard.html
index 023b49f0..fd3146d7 100644
--- a/docs/articles/DataQualityDashboard.html
+++ b/docs/articles/DataQualityDashboard.html
@@ -33,7 +33,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
@@ -109,7 +109,7 @@ Getting Started
Clair
Blacketer
- 2023-10-18
+ 2023-11-04
Source: vignettes/DataQualityDashboard.rmd
DataQualityDashboard.rmd
diff --git a/docs/articles/DqdForCohorts.html b/docs/articles/DqdForCohorts.html
index b0aa7263..4b7fae0e 100644
--- a/docs/articles/DqdForCohorts.html
+++ b/docs/articles/DqdForCohorts.html
@@ -33,7 +33,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
@@ -109,7 +109,7 @@ Running the DQD on a Cohort
Clair
Blacketer
- 2023-10-18
+ 2023-11-04
Source: vignettes/DqdForCohorts.rmd
DqdForCohorts.rmd
diff --git a/docs/articles/SqlOnly.html b/docs/articles/SqlOnly.html
index 3ad568b6..47dec90d 100644
--- a/docs/articles/SqlOnly.html
+++ b/docs/articles/SqlOnly.html
@@ -33,7 +33,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
@@ -109,7 +109,7 @@ SqlOnly
Maxim
Moinat
- 2023-10-18
+ 2023-11-04
Source: vignettes/SqlOnly.rmd
SqlOnly.rmd
@@ -324,49 +324,18 @@
Convert JSON results file case |
+
+ Write database results to a JSON file
+ Function to write DQD results from a database table into a JSON file
+ |
+
---|
+ writeDBResultsToJson()
+ |
+ Write DQD results database table to json |
diff --git a/docs/reference/reEvaluateThresholds.html b/docs/reference/reEvaluateThresholds.html
index 0a7df4a7..04a13f52 100644
--- a/docs/reference/reEvaluateThresholds.html
+++ b/docs/reference/reEvaluateThresholds.html
@@ -17,7 +17,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
diff --git a/docs/reference/viewDqDashboard.html b/docs/reference/viewDqDashboard.html
index 41557122..9ee5dba0 100644
--- a/docs/reference/viewDqDashboard.html
+++ b/docs/reference/viewDqDashboard.html
@@ -17,7 +17,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
diff --git a/docs/reference/writeDBResultsToJson.html b/docs/reference/writeDBResultsToJson.html
new file mode 100644
index 00000000..a3423b33
--- /dev/null
+++ b/docs/reference/writeDBResultsToJson.html
@@ -0,0 +1,153 @@
+
+Write DQD results database table to json — writeDBResultsToJson • DataQualityDashboard
+
+
+
+
+
+
+
+
+
Write DQD results database table to json
+
+
+
+
writeDBResultsToJson(
+ connection,
+ resultsDatabaseSchema,
+ cdmDatabaseSchema,
+ writeTableName,
+ outputFolder,
+ outputFile
+)
+
+
+
+
Arguments
+
- connection
+A connection object
+
+
+- resultsDatabaseSchema
+The fully qualified database name of the results schema
+
+
+- cdmDatabaseSchema
+The fully qualified database name of the CDM schema
+
+
+- writeTableName
+Name of DQD results table in the database to read from
+
+
+- outputFolder
+The folder to output the json results file to
+
+
+- outputFile
+The output filename of the json results file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/reference/writeJsonResultsToCsv.html b/docs/reference/writeJsonResultsToCsv.html
index 3083ad75..3bbd7d1f 100644
--- a/docs/reference/writeJsonResultsToCsv.html
+++ b/docs/reference/writeJsonResultsToCsv.html
@@ -17,7 +17,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
diff --git a/docs/reference/writeJsonResultsToTable.html b/docs/reference/writeJsonResultsToTable.html
index 43b428f9..d23c3f39 100644
--- a/docs/reference/writeJsonResultsToTable.html
+++ b/docs/reference/writeJsonResultsToTable.html
@@ -17,7 +17,7 @@
DataQualityDashboard
- 2.4.1
+ 2.5.0
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 99f8a396..5ae0b4bc 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -117,6 +117,9 @@
/reference/viewDqDashboard.html
+
+ /reference/writeDBResultsToJson.html
+
/reference/writeJsonResultsToCsv.html
diff --git a/extras/DataQualityDashboard.pdf b/extras/DataQualityDashboard.pdf
index 24a693fad383d3486c1315f39993e2dacff52759..eb9dfa03e38cf32ca65f9e3295624a70b6c7ed3c 100644
GIT binary patch
delta 32236
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
C)Hx;q
delta 30819
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