Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finally implement row review #146

Open
wants to merge 87 commits into
base: dev
Choose a base branch
from

Conversation

jthompson-arcus
Copy link
Collaborator

Closes #99

@LDSamson
Copy link
Collaborator

@jthompson-arcus Maybe good to discuss the conceptual UI implementation together.

@jthompson-arcus
Copy link
Collaborator Author

@jthompson-arcus Maybe good to discuss the conceptual UI implementation together.

I am happy for all input on the UI implementation. There are several ways it could go and no clear best option.

@jthompson-arcus jthompson-arcus changed the base branch from dev to jt-99-review_by_row-devex December 2, 2024 19:11
@jthompson-arcus jthompson-arcus changed the base branch from jt-99-review_by_row-devex to dev December 2, 2024 19:12
Comment on lines 200 to 206
observe({
reload_data(reload_data() + 1)
session$userData$update_checkboxes[[form]] <- NULL
session$userData$review_records[[form]] <- data.frame(id = integer(), reviewed = character())
}) |>
bindEvent(r$subject_id, r$review_data,
ignoreInit = TRUE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit duplicated with the observeEvent in mod_review_forms and mod_common_forms. Placing a cat_dev in these three events, and updating subject_id fires multiple updates:
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, each form is it's own module. I would expect it to fire for every module whenever the subject is changed or the review data is updated. I think this can be moved outside of those modules though and be contained in the review forms module.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further inspection, I don't think we really want to change any of this observer. There doesn't appear to be any advantage to further abstracting this logic. It's important to ensure the datatable's server-side object is getting reloaded whenever the subject is changed of the review data is updated.

observeEvent(input$table_review_selection, {
# Update review values for session's user data
session$userData$update_checkboxes[[form]] <- NULL
session$userData$review_records[[form]] <-
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, review_records are the records as per selection status in the checkboxes?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object r$review_data was originally designed to pass through review status of each item. I am still struggling to see why we need so many review objects and can't simplify this. Can't o_reviewed be part of the review_data object instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In essence it is still being used in that way because that's where the field reviewed is coming from. The new field o_reviewed is necessary for the datatable object. Each datatable is pivoted, so the datatable itself would contain multiple records, this needs to be consolidated in some way for a row level review to even be possible.

Comment on lines 208 to 211
}) |>
bindEvent(r$subject_id, r$review_data,
ignoreInit = TRUE)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering: do you have a specific reason why you sometimes use observe/bindevent and sometimes observeEvent?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer using bindEvent() when there are multiple triggers. In my opinion it is more readable.

Comment on lines 239 to 255
dplyr::rows_upsert(
session$userData$review_records[[form]],
input[[review_selection]][c("id", "reviewed")],
by = "id"
) |>
dplyr::filter(!is.na(reviewed)) |>
# Ensure that only the current subject is being reviewed
dplyr::semi_join(
subset(r$review_data, subject_id == r$subject_id & item_group == form),
by = "id"
) |>
dplyr::select(-dplyr::starts_with("SAE"))
# Only update records where the review status is being changed
dplyr::anti_join(
subset(r$review_data, subject_id == r$subject_id & item_group == form),
by = c("id", "reviewed")
) |>
dplyr::arrange(id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the upsert/filter/semi_join and anti_join are a bit hard to follow for me. Is the semi_join really needed for example? Can't you just add subject_id and form to the filter directly?

Also, I think this part could be a candidate to extract in a helper function since it is duplicated in mod_review_forms and is more or less 'business logic'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will take a look.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot add subject_id and item_group to the filter directly because input[[review_selection]] does not contain those values. It only contains the id's. This was intentional redundancy. Even though session$userData$review_records[[form]] gets reset whenever r$subject_id is changed, this ensures that no unintended records are being assessed. The overhead is very little because this is only triggered when a checkbox in a datatable is clicked.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#' Update Review Records
#'
#' Updates the review records data frame when a datatable checkbox is clicked.
#'
#' @param review_records The review records data frame to update.
#' @param review_selection The review selection data frame input from the
#' datatable.
#' @param active_data The active review data frame.
#'
#' @return A data frame containing the updated records data.
#'
#' @details Three main steps are performed: UPSERT, SUBSET, and ANTI-JOIN The
#' UPSERT takes the review selection data frame and upserts it into the review
#' records data frame. (An upsert will insert a record if the unique
#' identifier is not yet present and update a record based on the unique
#' identifier if it already exists.) The SUBSET step removes an empty reviews
#' (partially review rows) and any records not part of the active review (as a
#' precautionary measure). The ANTI-JOIN step removes any records that match
#' the active review (records that will not be changing review status based on
#' user inputs).
#'
#' @noRd
update_review_records <- function(review_records, review_selection, active_data) {
if (is.null(review_records))
review_records <- data.frame(id = integer(), reviewed = character())
review_records |>
dplyr::rows_upsert(
review_selection,
by = "id"
) |>
# Remove empty reviews and inactive data IDs
subset(!is.na(reviewed) | !id %in% active_data$id) |>
# Only update records where the review status is being changed
dplyr::anti_join(
active_data,
by = c("id", "reviewed")
) |>
dplyr::arrange(id)
}

Copy link
Collaborator

@LDSamson LDSamson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a view comments. I will have to stop now, and and probably can only continue in the new year. In general, I like the way it is implemented, but I am just wondering if we can make it a bit simpler and easier to follow. It is a bit hard to follow what happens when exactly, in what order.

Have a nice Christmas and new year!

@jthompson-arcus
Copy link
Collaborator Author

With all the requests for helper functions and such, I decided it was best to move all this logic to it's own module. Now all changes can be made in one location and the differences between the forms are essentially eliminated. The module itself still needs to be documented.

The PR grows and grows...

@jthompson-arcus jthompson-arcus marked this pull request as ready for review December 31, 2024 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants