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

shiny app / widget to explore the history of a file #101

Open
andersone1 opened this issue Jun 13, 2024 · 8 comments
Open

shiny app / widget to explore the history of a file #101

andersone1 opened this issue Jun 13, 2024 · 8 comments

Comments

@andersone1
Copy link
Collaborator

No description provided.

@michaelmcd18
Copy link
Collaborator

#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
#    https://shiny.posit.co/
#

library(shiny)
files <- here::here("script/data-assembly/da-study-0025.Rmd")
fillog <- review::svnLog(files)
diff1 <- review::diffPreviousRevisions(files, .previous_revision = 3)
# Define UI for application that draws a histogram
ui <- fluidPage(

  fluidRow(
    column(width = 4, dataTableOutput("filelog")),
    column(width = 8, uiOutput("filediff")))
)

# Define server logic required to draw a histogram
server <- function(input, output) {

  output$filelog <- renderDataTable({
    fillog
  })
  
  output$filediff <- renderUI({
    HTML(as.character(diff1))
  })
  
}

# Run the application 
shinyApp(ui = ui, server = server)

@andersone1
Copy link
Collaborator Author

andersone1 commented Jul 25, 2024

fileDiffApp <- function(.file) {
  
  if (!fs::file_exists(.file)) {
    stop(.file, " not found")
  }
  
  cur_diffobj_context <- getOption("diffobj.context")
  on.exit(options("diffobj.context" = cur_diffobj_context))
  options("diffobj.context" = -1L)
  
  fileLog <-
    review::svnLog(.file) %>%
    dplyr::mutate(
      today = Sys.Date(),
      days_ago = paste0(floor(difftime(today, datetime, units = "days")), " day(s) ago"),
      msg = glue::glue("'{msg}'")
    ) %>%
    dplyr::select(days_ago, rev, author, msg, days_ago) %>%
    tidyr::unite(col = "combined", tidyselect::everything(), sep = " | ") %>%
    dplyr::pull(combined)
  
  # UI ----------------------------------------------------------------------
  ui <- shiny::fluidPage(
    fluidRow(
      shiny::wellPanel(
        fluidRow(
          shiny::column(
            width = 6,
            tags$h4(.file)
          ),
          shiny::column(
            width = 6,
            shiny::checkboxGroupInput(
              "diffOpts",
              label = "Diff Options",
              choices = c("Side by Side", "Ignore White Space"),
              selected = "Side by Side", 
              inline = TRUE
            )
          )
        )
      )
    ),
    shiny::fluidRow(
      shiny::column(
        width = 6,
        shiny::selectInput(
          "revisionA",
          "Revision Left",
          choices = fileLog,
          selected = dplyr::first(fileLog),
          width = "100%"
        )
      ),
      shiny::column(
        width = 6,
        shiny::selectInput(
          "revisionB",
          "Revision Right",
          choices = fileLog,
          selected = dplyr::nth(fileLog, 2),
          width = "100%"
        )
      )
    ),
    shiny::fluidRow(
      shiny::column(
        width = 12,
        shiny::uiOutput("diff")
      )
    )
  )
  
  
  # Server ------------------------------------------------------------------
  server <- function(input, output) {
    output$diff <- shiny::renderUI({
      shiny::req(input$revisionA)
      shiny::req(input$revisionB)
      
      diffObj <- review::diffPreviousRevisions(
        .file = .file,
        .previous_revision = strsplit(input$revisionA, " | ", fixed = TRUE)[[1]][2],
        .current_revision = strsplit(input$revisionB, " | ", fixed = TRUE)[[1]][2],
        .side_by_side = "Side by Side" %in% input$diffOpts,
        .ignore_white_space = "Ignore White Space" %in% input$diffOpts
      )
      if (is.null(diffObj)) {
        HTML("No diffs found")
      } else {
        shiny::HTML(as.character(diffObj))
      }
    })
  }
  
  
  # Run ---------------------------------------------------------------------
  shiny::shinyApp(ui = ui, server = server)
}

@michaelmcd18
Copy link
Collaborator

We should consider adding this function - efficiently returns all commits [author, files modified, date, revision, etc] in a dataframe

getCommitHistory <- function() {
  
  svn_all <- tryCatch(
    review:::svnCommand("log", .flags = "-v"),
    error = identity
  )
  
  if (inherits(svn_all, "error")) {
    stop("svn log failed")
  }
  
  dplyr::bind_rows(svn_all) %>% 
    tidyr::unnest(paths) %>% 
    tidyr::unnest(paths) %>% 
    dplyr::filter(grepl("/", paths, fixed = TRUE)) %>% 
    dplyr::rename(
      rev = .attrs,
      file = paths
    ) %>% 
    dplyr::mutate(
      date = as.Date(substr(date, 1, 10)),
      file = sub('.', '', file))
  
}

@michaelmcd18
Copy link
Collaborator

Iterating on the shiny app code above, we could have a layout as shown below. This would allow the user to view the svnLog, QC history and diffs all in one place:
Screenshot 2024-07-30 at 12 20 33 PM

@michaelmcd18
Copy link
Collaborator

Individual file table of commit and QC history

dfH <- review::repoHistory()
dfH$edit <- "Modification"
dfH$rev <- as.numeric(dfH$rev)

dfQC <- review:::logRead()
dfQC <- dfQC[dfQC$revf != 0,]
dfQC$edit <- "QC"
dfQC$date <- as.Date(dfQC$time)
dfQC$rev <- dfQC$revf
dfQC$author <- dfQC$reviewer
dfQC$msg <- ""

qc_files <- unique(dfQC$file)

for (file.i in qc_files) {
  
  history.i <- dfH[dfH$file == file.i,]
  qc.i <- dfQC[dfQC$file == file.i,]
  qc.i <- qc.i[names(qc.i) %in% names(history.i)]
  
  all.i <- rbind(history.i, qc.i)
  all.i <- all.i[order(all.i$date, decreasing = TRUE),]
}

all.i %>% 
  dplyr::select(rev, edit, author, date, msg) %>% 
  dplyr::mutate(pan = if_else(edit == "QC",
                              paste0("Revision ", rev, " QCed by ", author),
                              NA_character_)) %>% 
  dplyr::group_by(rev) %>% 
  tidyr::fill(pan, .direction = "downup") %>% 
  dplyr::ungroup() %>% 
  tidyr::fill(pan, .direction = "down") %>% 
  dplyr::mutate(
    pan = tidyr::replace_na(pan, "Edits since last QC")
  ) %>% 
  dplyr::filter(edit != "QC") %>% 
  dplyr::select(-edit) %>% 
  pmtables::st_new() %>% 
  pmtables::st_rename("Revision" = "rev") %>% 
  pmtables::st_rename("Author" = "author") %>% 
  pmtables::st_rename("Date" = "date") %>% 
  pmtables::st_rename("Commit message" = "msg") %>% 
  pmtables::st_panel("pan") %>% 
  pmtables::st_center(msg = pmtables::col_ragged(6)) %>% 
  pmtables::stable() %>% 
  pmtables::st2report()

@andersone1
Copy link
Collaborator Author

library(shiny)
library(DT)
library(bslib)


review_dashboard <- function(){
  
  ui <- page_sidebar(
    theme = bs_theme(version = 5),
    title = "Log Summary App",
    sidebar = sidebar(
      navset_pill(
        nav_panel(
          title = "Log Pending",
          icon = icon("clock")
        )
      )
    ),
    layout_column_wrap(
      width = 1,
      card(
        card_header("Log Pending Data"),
        DTOutput("logPendingTable")
      )
    ),
    layout_column_wrap(
      width = 1,
      card(
        card_header("Selected Row"),
        uiOutput("filediff")
      )
    )
  )
  
  server <- function(input, output, session) {
    # Generate log pending data
    logPendingData <- reactive({
      review::logPending()
    })
    
    # Render the DT table
    output$logPendingTable <- renderDT({
      datatable(logPendingData(), 
                selection = 'single', 
                options = list(pageLength = 5),
                style = "auto",
                class = "display compact cell-border")
    })
    
    # Observe event for row click
    observeEvent(input$logPendingTable_rows_selected, {
      row_number <- input$logPendingTable_rows_selected
      row.i <- logPendingData()[row_number,]
      message(row.i)
      diff.i <- 
        if(row.i$revf != 0){
          review::diffQced(row.i$file)
        } else {
          "No previous QC"
        }
      
      output$filediff <- renderUI({
        HTML(as.character(diff.i))
      })
    })
  }
  
  shinyApp(ui, server)
  
}

@andersone1
Copy link
Collaborator Author

library(shiny)
library(DT)
library(bslib)
library(tidyverse)

review_dashboard <- function() {
  
  ui <- page_sidebar(
    theme = bs_theme(version = 5),
    title = "Log Summary App",
    sidebar = sidebar(
      navset_pill(
        nav_panel(
          title = "Log Pending",
          icon = icon("clock")
        ),
        nav_panel(
          title = "File Drill Down",
          icon = icon("folder-open"),
          p("Placeholder content for File Drill Down")
        )
      )
    ),
    layout_column_wrap(
      width = NULL, 
      gap = "20px",  # Add spacing between the columns
      card(
        width = 4, # DT table takes 4 columns
        card_header("Log Pending Data"),
        DTOutput("logPendingTable")
      ),
      card(
        width = 8, # File diff takes 8 columns
        card_header("Selected Row"),
        uiOutput("filediff")
      )
    )
  )
  
  server <- function(input, output, session) {
    # Generate log pending data
    logPendingData <- reactive({
      review::logPending() %>% 
        transmute(
          File = file,
          `QCed Revision` = revf,
          `Current Revision` = headf,
          Reviewer = reviewer,
          Datetime = lubridate::ymd_hms(time)
        )
    })
    
    # Render the DT table
    output$logPendingTable <- renderDT({
      datatable(logPendingData(), 
                selection = 'single', 
                options = list(pageLength = 5),
                style = "auto",
                rownames = FALSE,
                class = "display compact cell-border")
    })
    
    # Observe event for row click
    observeEvent(input$logPendingTable_rows_selected, {
      row_number <- input$logPendingTable_rows_selected
      row.i <- logPendingData()[row_number, ]
      message(row.i)
      diff.i <- 
        if(row.i$`QCed Revision` != 0) {
          review::diffQced(row.i$File)
        } else {
          "No previous QC"
        }
      
      output$filediff <- renderUI({
        HTML(as.character(diff.i))
      })
    })
  }
  
  shinyApp(ui, server)
  
}

review_dashboard()

@andersone1
Copy link
Collaborator Author

# Log Pending UI
#'
#' Creates the user interface for the Log Pending module.
#'
#' @param id A namespace identifier for the module.
#'
#' @return A UI layout for the Log Pending module.
log_pending_ui <- function(id) {
  ns <- shiny::NS(id)
  
  bslib::layout_column_wrap(
    width = 12,
    gap = "20px",
    bslib::card(
      width = 3,
      bslib::card_header("Log Pending"),
      shiny::div(style = "min-height: 800px;",
          DT::DTOutput(ns("logPendingTable"))
      )
    ),
    bslib::card(
      width = 9,
      bslib::card_header("Diff Since QC"),
      shiny::div(style = "min-height: 800px;",
          shiny::uiOutput(ns("filediff"))
      )
    )
  )
}

# Log Pending Server Logic
#'
#' Handles the server-side logic for the Log Pending module.
#'
#' @param id A namespace identifier for the module.
#'
#' @return The server logic that manages the Log Pending data table and displays file differences.
log_pending_server <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    logPendingData <- shiny::reactive({
      review::logPending() %>% 
        dplyr::transmute(
          File = file,
          `QCed Revision` = revf,
          `Current Revision` = headf,
          Reviewer = reviewer,
          Datetime = lubridate::ymd_hms(time)
        )
    })
    
    output$logPendingTable <- DT::renderDT({
      DT::datatable(logPendingData(), 
                    selection = 'single', 
                    options = list(pageLength = 10),
                    style = "auto",
                    rownames = FALSE,
                    class = "display compact cell-border")
    })
    
    shiny::observeEvent(input$logPendingTable_rows_selected, {
      row_number <- input$logPendingTable_rows_selected
      row.i <- logPendingData()[row_number, ]
      diff.i <- 
        if(row.i$`QCed Revision` != 0) {
          review::diffQced(row.i$File)
        } else {
          "No previous QC"
        }
      
      output$filediff <- shiny::renderUI({
        shiny::HTML(as.character(diff.i))
      })
    })
  })
}

# File Drill Down UI
#'
#' Creates the user interface for the File Drill Down module.
#'
#' @param id A namespace identifier for the module.
#'
#' @return A UI layout for the File Drill Down module.
file_drilldown_ui <- function(id) {
  ns <- shiny::NS(id)
  
  bslib::layout_column_wrap(
    width = 12,
    gap = "20px",
    bslib::card(
      width = 5,
      bslib::card_header("File Drill Down"),
      shiny::selectInput(ns("file_selector"), 
                         "Select a file:", 
                         choices = fs::dir_ls(here::here("script")),
                         selected = NULL,
                         selectize = FALSE,
                         size = 33,
                         multiple = FALSE,
                         width = '100%')
    ),
    bslib::card(
      width = 7,
      bslib::card_header("SVN Log"),
      shiny::div(style = "min-height: 400px;", # Adjusted height
                 DT::DTOutput(ns("svnLogTable"))
      )
    ),
    bslib::card(
      width = 7,
      bslib::card_header("Changes"),
      shiny::div(style = "min-height: 400px;", # Adjusted height
                 shiny::uiOutput(ns("changesOutput"))
      )
    )
  )
}

# File Drill Down Server Logic
#'
#' Handles the server-side logic for the File Drill Down module.
#'
#' @param id A namespace identifier for the module.
#'
#' @return The server logic that manages the file selection, SVN log, and displays file changes.
file_drilldown_server <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    svnLogData <- shiny::reactive({
      shiny::req(input$file_selector)
      review::svnLog(input$file_selector)
    })
    
    output$svnLogTable <- DT::renderDT({
      DT::datatable(svnLogData(), 
                    selection = 'single', # Allow row selection
                    options = list(pageLength = 5),
                    style = "auto",
                    rownames = FALSE,
                    class = "compact stripe cell-border")
    })
    
    shiny::observeEvent(input$svnLogTable_rows_selected, {
      row_number <- input$svnLogTable_rows_selected
      selected_row <- svnLogData()[row_number, ]
      rev_number <- selected_row$rev
      
      changes <- review::diffPreviousRevisions(input$file_selector, rev_number)
      
      output$changesOutput <- shiny::renderUI({
        shiny::HTML(as.character(changes))
      })
    })
  })
}

# Main Review Dashboard App
#'
#' Creates and runs the review dashboard Shiny app.
#'
#' @return The full Shiny app with a Log Pending and File Drill Down module.
review_dashboard <- function() {
  
  ui <- bslib::page_sidebar(
    theme = bslib::bs_theme(version = 5),
    title = "review.dashboard",
    sidebar = bslib::sidebar(
      bslib::navset_pill(
        id = "sidebar_tabs",
        bslib::nav_panel(
          title = "Log Pending",
          icon = shiny::icon("clock")
        ),
        bslib::nav_panel(
          title = "File Drill Down",
          icon = shiny::icon("folder-open")
        )
      )
    ),
    bslib::layout_column_wrap(
      width = 12,
      gap = "20px",
      shiny::uiOutput("mainContent")
    )
  )
  
  server <- function(input, output, session) {
    output$mainContent <- shiny::renderUI({
      if (input$sidebar_tabs == "Log Pending") {
        log_pending_ui("log_pending")
      } else if (input$sidebar_tabs == "File Drill Down") {
        file_drilldown_ui("file_drilldown")
      }
    })
    
    log_pending_server("log_pending")
    file_drilldown_server("file_drilldown")
  }
  
  shiny::shinyApp(ui, server, options = list(launch.browser = TRUE))
}

#' Run the Review Dashboard App
#'
#' This function launches the Review Dashboard app.
review_dashboard()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants