Skip to content

Commit 1672327

Browse files
authored
Merge pull request #171 from saipavan10-git/improve_logging_in_deletes
Improve logging in project deletes
2 parents 16a8757 + 2d201f5 commit 1672327

File tree

6 files changed

+206
-82
lines changed

6 files changed

+206
-82
lines changed

.github/workflows/run-tests.yaml

+36-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,45 @@ jobs:
1515

1616
env:
1717
CI: "TRUE"
18+
R_LIBS_USER: /github/home/R/x86_64-pc-linux-gnu-library/4.4
19+
R_LIB_FOR_PAK: /usr/local/lib/R/site-library
1820

1921
steps:
2022
- uses: actions/checkout@v2
2123

22-
- name: Check
24+
# Create directories for R libraries if not already present
25+
- name: Create R Library Paths
26+
run: |
27+
mkdir -p /github/home/R/x86_64-pc-linux-gnu-library/4.4
28+
mkdir -p renv/library
29+
30+
# Restore cache for R dependencies
31+
- name: Restore R Dependencies Cache
32+
uses: actions/cache@v4
33+
with:
34+
path: |
35+
/github/home/R/x86_64-pc-linux-gnu-library/4.4
36+
renv/library
37+
key: ${{ runner.os }}-r-libs-${{ hashFiles('DESCRIPTION') }}
38+
restore-keys: |
39+
${{ runner.os }}-r-libs-
40+
41+
# Install R dependencies
42+
- name: Install R Dependencies
43+
uses: r-lib/actions/setup-r-dependencies@v2
44+
with:
45+
cache: false
46+
47+
# Run tests
48+
- name: Run Tests
2349
run: devtools::test(stop_on_failure = TRUE)
2450
shell: Rscript {0}
51+
52+
# Save R dependencies to cache
53+
- name: Save R Dependencies Cache
54+
uses: actions/cache@v4
55+
with:
56+
path: |
57+
/github/home/R/x86_64-pc-linux-gnu-library/4.4
58+
renv/library
59+
key: ${{ runner.os }}-r-libs-${{ hashFiles('DESCRIPTION') }}

DESCRIPTION

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ Imports:
6161
vctrs,
6262
jsonlite,
6363
openxlsx,
64-
quarto
64+
quarto,
65+
getip
6566
Suggests:
6667
RSQLite,
6768
digest,

Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ RUN R -e "install.packages(c( \
2828
'writexl', \
2929
'openxlsx', \
3030
'kableExtra' \
31+
'getip' \
3132
))"
3233

3334
RUN R -e "devtools::install_github('allanvc/mRpostman')"

R/delete_project.R

+71-33
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@
1919
#' @examples
2020
#' \dontrun{
2121
#' conn <- DBI::dbConnect(...)
22-
#' delete_project(c(1,2,3), conn)
22+
#' delete_project(c(1, 2, 3), conn)
2323
#' }
2424
#' @export
2525

2626
delete_project <- function(project_id, conn) {
27-
2827
redcap_projects <- DBI::dbGetQuery(
2928
conn,
3029
sprintf(
@@ -34,47 +33,86 @@ delete_project <- function(project_id, conn) {
3433
log_event_table
3534
from redcap_projects
3635
where project_id in (%s)",
37-
paste0(project_id, collapse = ",")
36+
paste0(project_id, collapse = ",")
3837
)
3938
)
4039

41-
# select projects for deletion
4240
projects_to_delete <- redcap_projects[is.na(redcap_projects$date_deleted), ]
4341
redcap_project_ids <- projects_to_delete$project_id
4442
redcap_log_tables <- projects_to_delete$log_event_table
4543

44+
4645
if (nrow(projects_to_delete) > 0) {
47-
tryCatch({
48-
deleted_projects <- DBI::dbExecute(
49-
conn,
50-
sprintf(
51-
"update redcap_projects set date_deleted = now() where project_id in (%s)",
52-
paste0(redcap_project_ids, collapse = ",")
53-
)
54-
)
55-
}, error = function(error_message) {
56-
print(error_message)
57-
return(FALSE)
58-
})
46+
delete_sql <- sprintf(
47+
"UPDATE redcap_projects SET date_deleted = NOW() WHERE project_id IN (%s)",
48+
paste0(redcap_project_ids, collapse = ",")
49+
)
5950

60-
# log the event
61-
tryCatch({
62-
inserted_rows <- purrr::map2(
63-
redcap_log_tables,
64-
redcap_project_ids,
65-
~ DBI::dbExecute(
66-
conn,
67-
sprintf(
68-
"insert into %s (object_type, event, project_id, description)
69-
values ('redcap_projects', 'MANAGE', %d, 'delete project')",
70-
.x,
71-
.y)
51+
tryCatch(
52+
{
53+
deleted_projects <- DBI::dbExecute(conn, delete_sql)
54+
},
55+
error = function(error_message) {
56+
print(error_message)
57+
return(FALSE)
58+
}
59+
)
60+
61+
# Define logging parameters
62+
ts <- format(Sys.time(), "%Y%m%d%H%M%S") # Time stamp
63+
user <- ifelse(is.null(get_script_name()), "admin", get_script_name())
64+
ip <- getip::getip("local")
65+
page <- "rcc.billing::delete_abandoned_projects"
66+
event <- "MANAGE"
67+
object_type <- "redcap_projects"
68+
description <- "Delete project"
69+
legacy <- 0
70+
change_reason <- NULL
71+
72+
tryCatch(
73+
{
74+
inserted_rows <- purrr::map2(
75+
redcap_log_tables,
76+
redcap_project_ids,
77+
~ {
78+
pk <- .y
79+
data_values <- sprintf("project_id = %d", .y)
80+
81+
DBI::dbExecute(
82+
conn,
83+
sprintf(
84+
"INSERT INTO %s
85+
(log_event_id, project_id, ts, user, ip, page, event,
86+
object_type, sql_log, pk, event_id, data_values,
87+
description, legacy, change_reason)
88+
VALUES
89+
(NULL, %d, '%s', '%s', '%s', '%s', '%s',
90+
'%s', '%s', '%d', NULL, '%s',
91+
'%s', %d, %s)",
92+
.x, # Log table
93+
.y, # Project ID
94+
ts,
95+
user,
96+
ip,
97+
page,
98+
event,
99+
object_type,
100+
delete_sql,
101+
pk,
102+
data_values,
103+
description,
104+
legacy,
105+
ifelse(is.null(change_reason), "NULL", sprintf("'%s'", change_reason))
106+
)
107+
)
108+
}
72109
)
73-
)
74-
}, error = function(error_message) {
75-
print(error_message)
76-
return(FALSE)
77-
})
110+
},
111+
error = function(error_message) {
112+
print(error_message)
113+
return(FALSE)
114+
}
115+
)
78116
} else {
79117
deleted_projects <- NULL
80118
inserted_rows <- NULL

man/delete_project.Rd

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-delete_project.R

+95-46
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,135 @@
1-
# create SQL tables
1+
library(DBI)
2+
library(duckdb)
3+
library(dplyr)
4+
library(lubridate)
5+
library(testthat)
6+
7+
# Create SQL tables
28
redcap_projects <- data.frame(
39
project_id = 1:6,
410
date_deleted = c(rep(NA, 5), format(Sys.time() - 86400, "%Y-%m-%d %H:%M:%S")),
5-
log_event_table = c(rep('redcap_log_event1', 3), rep('redcap_log_event2', 3))
11+
log_event_table = c(rep("redcap_log_event1", 3), rep("redcap_log_event2", 3))
612
)
713

814
redcap_log_event1 <- data.frame(
9-
object_type = NA_character_,
10-
event = NA_character_,
15+
log_event_id = NA_integer_,
1116
project_id = NA_integer_,
12-
description = NA_character_
13-
)
14-
15-
redcap_log_event2 <- data.frame(
16-
object_type = NA_character_,
17+
ts = NA_character_,
18+
user = NA_character_,
19+
ip = NA_character_,
20+
page = NA_character_,
1721
event = NA_character_,
18-
project_id = NA_integer_,
19-
description = NA_character_
22+
object_type = NA_character_,
23+
sql_log = NA_character_,
24+
pk = NA_character_,
25+
event_id = NA_character_,
26+
data_values = NA_character_,
27+
description = NA_character_,
28+
legacy = NA_integer_,
29+
change_reason = NA_character_
2030
)
2131

22-
# write SQL tables
32+
redcap_log_event2 <- redcap_log_event1
33+
34+
# Write SQL tables
2335
conn <- DBI::dbConnect(duckdb::duckdb(), dbname = ":memory:")
2436
DBI::dbWriteTable(conn, "redcap_projects", redcap_projects)
2537
DBI::dbWriteTable(conn, "redcap_log_event1", redcap_log_event1)
2638
DBI::dbWriteTable(conn, "redcap_log_event2", redcap_log_event2)
2739

28-
# create comparison dfs
29-
expected_redcap_projects <- data.frame(
30-
project_id = 1:6,
31-
# convert to UTC to prevent test from failing due to timezone differences
32-
date_deleted = c(rep(as.Date(lubridate::with_tz(Sys.time(), "UTC")), 5), Sys.Date() - 1),
33-
log_event_table = c(rep('redcap_log_event1', 3), rep('redcap_log_event2', 3))
34-
)
40+
current_ts <- format(Sys.time(), "%Y%m%d%H%M%S")
41+
42+
# Expected results
43+
expected_redcap_projects <- redcap_projects |>
44+
mutate(
45+
date_deleted = if_else(is.na(date_deleted), as.character(as.Date(Sys.time())), date_deleted)
46+
)
3547

3648
expected_redcap_log_event1 <- data.frame(
37-
object_type = c(NA, rep("redcap_projects", 3)),
38-
event = c(NA, rep("MANAGE", 3)),
49+
log_event_id = NA_integer_,
3950
project_id = c(NA, 1:3),
40-
description = c(NA, rep("delete project", 3))
51+
ts = c(NA, rep(current_ts, 3)),
52+
user = c(NA, rep("admin", 3)),
53+
ip = c(NA, rep(getip::getip("local"), 3)),
54+
page = c(NA, rep("rcc.billing::delete_abandoned_projects", 3)),
55+
event = c(NA, rep("MANAGE", 3)),
56+
object_type = c(NA, rep("redcap_projects", 3)),
57+
sql_log = c(NA, rep("UPDATE redcap_projects SET date_deleted = NOW() WHERE project_id IN (1,2,3,4,5)", 3)),
58+
pk = c(NA, as.character(1:3)),
59+
event_id = NA_character_,
60+
data_values = c(NA, sprintf("project_id = %d", 1:3)),
61+
description = c(NA, rep("Delete project", 3)),
62+
legacy = c(NA, rep(0, 3)),
63+
change_reason = NA_character_
4164
)
4265

4366
expected_redcap_log_event2 <- data.frame(
44-
object_type = c(NA, rep("redcap_projects", 2)),
67+
log_event_id = NA_integer_,
68+
project_id = c(NA, 4, 5),
69+
ts = c(NA, rep(current_ts, 2)),
70+
user = c(NA, rep("admin", 2)),
71+
ip = c(NA, rep(getip::getip("local"), 2)),
72+
page = c(NA, rep("rcc.billing::delete_abandoned_projects", 2)),
4573
event = c(NA, rep("MANAGE", 2)),
46-
project_id = c(NA, 4:5),
47-
description = c(NA, rep("delete project", 2))
74+
object_type = c(NA, rep("redcap_projects", 2)),
75+
sql_log = c(NA, rep("UPDATE redcap_projects SET date_deleted = NOW() WHERE project_id IN (1,2,3,4,5)", 2)),
76+
pk = c(NA, "4", "5"),
77+
event_id = NA_character_,
78+
data_values = c(NA, sprintf("project_id = %d", 4:5)),
79+
description = c(NA, rep("Delete project", 2)),
80+
legacy = c(NA, rep(0, 2)),
81+
change_reason = NA_character_
4882
)
4983

5084
expected_result <- data.frame(
5185
project_id = 1:8,
5286
status = c(rep("deleted", 5), "previously deleted", rep("does not exist", 2))
5387
)
5488

55-
# test function
89+
# Test function
5690
project_ids <- 1:8
5791
deleted_projects <- delete_project(project_ids, conn)
5892

59-
testthat::test_that("delete_project deletes, updates and returns the correct project IDs", {
60-
expect_equal(
61-
DBI::dbGetQuery(conn, "select * from redcap_projects") |>
62-
# convert date_deleted to yyyy-mm-dd to allow comparison with expected_redcap_projects
63-
dplyr::mutate(date_deleted = as.Date(date_deleted)),
64-
expected_redcap_projects
65-
)
66-
67-
testthat::expect_equal(
68-
DBI::dbGetQuery(conn, "select * from redcap_log_event1"),
69-
expected_redcap_log_event1
70-
)
71-
testthat::expect_equal(
72-
DBI::dbGetQuery(conn, "select * from redcap_log_event2"),
73-
expected_redcap_log_event2
74-
)
93+
testthat::test_that("delete_project deletes, updates, and returns the correct project IDs and logs", {
94+
remove_seconds <- function(ts) {
95+
if (is.na(ts)) {
96+
return(NA)
97+
}
98+
substr(ts, 1, 12)
99+
}
100+
101+
actual_redcap_log_event1 <- DBI::dbGetQuery(conn, "SELECT * FROM redcap_log_event1") |>
102+
select(-log_event_id) |>
103+
mutate(
104+
ts = sapply(ts, remove_seconds)
105+
)
106+
107+
expected_redcap_log_event1 <- expected_redcap_log_event1 |>
108+
select(-log_event_id) |>
109+
mutate(
110+
ts = sapply(ts, remove_seconds)
111+
)
112+
113+
testthat::expect_equal(actual_redcap_log_event1, expected_redcap_log_event1)
114+
115+
actual_redcap_log_event2 <- DBI::dbGetQuery(conn, "SELECT * FROM redcap_log_event2") |>
116+
select(-log_event_id) |>
117+
mutate(
118+
ts = sapply(ts, remove_seconds)
119+
)
120+
121+
expected_redcap_log_event2 <- expected_redcap_log_event2 |>
122+
select(-log_event_id) |>
123+
mutate(
124+
ts = sapply(ts, remove_seconds)
125+
)
126+
127+
testthat::expect_equal(actual_redcap_log_event2, expected_redcap_log_event2)
75128

76129
testthat::expect_equal(deleted_projects$n, 5)
77-
78130
testthat::expect_equal(deleted_projects$number_rows_logged, 5)
79-
80131
testthat::expect_equal(deleted_projects$project_ids_deleted, 1:5)
81-
82132
testthat::expect_equal(deleted_projects$data, expected_result)
83-
84133
})
85134

86135
DBI::dbDisconnect(conn, shutdown = TRUE)

0 commit comments

Comments
 (0)