tidyverse / googlesheets4

Google Spreadsheets R API (reboot of the googlesheets package)
https://googlesheets4.tidyverse.org
Other
357 stars 51 forks source link

Add example apps similar to old googlesheets #158

Open bwganblack opened 4 years ago

bwganblack commented 4 years ago

Would it be possible please to update the example shiny apps for googlesheets for use with googlesheets4? The ones found here would be very useful:

https://github.com/jennybc/googlesheets/tree/master/inst/shiny-examples

In particular, I'm trying to replicate the functionality of the My Google Explorer app when deployed to shinyapps.io. My app works ok locally but gs4_find and gs4_create trigger a disconnect from the server on shinyapp.io rather than opening the Tidyverse consent screen. I get the same disconnect when using my own OAuth client.

bwganblack commented 4 years ago

If I may expand on my previous comment with a reprex?

library(shiny)
library(googlesheets4)
options(gargle_quiet = FALSE)

ui <- fluidPage(
  titlePanel("Googlesheets4 Authentication"),
  sidebarLayout(
    sidebarPanel(
      actionButton("login", "Login")
    ),
    mainPanel(
      with(tags, dl(dt("E-mail", dd(textOutput("g_email")))))
    )
  )
)

server <- function(input, output, session) {
  dat = eventReactive(input$login,{
    if(!gs4_has_token()){
      gs4_auth(email = FALSE, cache = ".secrets")
    }
    dat = gs4_user()
    return (dat)
  })
  output$g_email = renderText({dat()})
}

shinyApp(ui = ui, server = server)

This app starts with an empty .secrets folder in the app directory. When run locally the function gs4_auth(email = FALSE, cache = ".secrets") successfully skips the console interaction regarding pre-authorised accounts and goes directly to the browser dance. It then stores a token in .secrets as indicated by the console messages:

Listening on http://127.0.0.1:4372 trying token_fetch() trying credentials_service_account() Error: Argument 'txt' must be a JSON string, URL or file. trying credentials_app_default() trying credentials_gce() trying credentials_byo_oauth() Error: inherits(token, "Token2.0") is not TRUE trying credentials_user_oauth2() Gargle2.0 initialize attempt from: googlesheets4 adding 'userinfo.email' scope loading token from the cache no matching token in the cache initiating new token Waiting for authentication in browser... Press Esc/Ctrl + C to abort Authentication complete. putting token into the cache: .secrets

However, when deployed to shinyapps.io, clicking the button causes it to crashes with an error before opening the browser (my actual app disconnects but the log messages are the same). I put a blank csv file in .secrets because otherwise RStudio wouldn't bundle it in the upload.

trying token_fetch() trying credentials_service_account() Error: Argument 'txt' must be a JSON string, URL or file. trying credentials_app_default() trying credentials_gce() Error: inherits(token, "Token2.0") is not TRUE trying credentials_user_oauth2() adding 'userinfo.email' scope loading token from the cache no matching token in the cache initiating new token trying credentials_byo_oauth() gs4_deauth() to prevent the attempt to get credentials. Call gs4_auth() directly with all necessary specifics. Error: OAuth2 flow requires an interactive session Warning: Error in : Can't get Google credentials. Are you running googlesheets4 in a non-interactive session? Consider: Gargle2.0 initialize See gargle's "Non-interactive auth" vignette for more details: attempt from: googlesheets4 https://gargle.r-lib.org/articles/non-interactive-auth.html

Is this expected behaviour? More likely I am missing something obvious, but I could find very little in the way of explicit examples for multi-user deployed apps. Are they actually possible? Many thanks.

jennybc commented 4 years ago

We could adapt the strategies used in googlesheets again, to make this work.

But what is really needed is to add a new web application credential function to gargle, which would make all gargle-using packages much easier to use with Shiny. This is an acknowledged "to do" and is being tracked here:

https://github.com/r-lib/gargle/issues/14

bwganblack commented 4 years ago

I managed to get authorization to any account to work on an app deployed on shinyapps.io by adapting the old googlesheets code as you suggested Jenny. This required setting up my own Google web client (web application type, Google Drive API and Google Sheets API enabled). Doesn't quite solve my problem as there now seems to be a issue when using the method with shinyMobile apps, but that is another issue.

As you say it would be ideal if gargle was updated so that we could use googlesheets4 for this. In the meantime, I thought perhaps someone might find the code useful as a workaround. Thanks.

library(shiny)
library(DT)
library(googlesheets)

options("googlesheets.webapp.client_id" = "your web client id")
options("googlesheets.webapp.client_secret" = "your web client secret")
options("googlesheets.webapp.redirect_uri" = "url for your app on shinyapps.io")

gs_webapp_auth_url_edit = function (client_id = getOption("googlesheets.webapp.client_id"), 
          redirect_uri = getOption("googlesheets.webapp.redirect_uri"), 
          access_type = "online", approval_prompt = "auto") 
  {
    scope_list <- paste("https://www.googleapis.com/auth/drive", 
                        "https://www.googleapis.com/auth/spreadsheets")
    url <- httr::modify_url(httr::oauth_endpoints("google")$authorize, 
                            query = list(response_type = "code", client_id = client_id, 
                                         redirect_uri = redirect_uri, scope = scope_list, 
                                         state = "securitytoken", access_type = access_type, 
                                         approval_prompt = approval_prompt))
    url
  }

ui = fluidPage(

   titlePanel("My Google Sheets Explorer"),
    sidebarLayout(
    sidebarPanel(
      uiOutput("loginButton"),
    ),
    mainPanel(
      DT::dataTableOutput("listSheets")
    )
  )
)

server = function(input, output, session) {

  output$loginButton <- renderUI({
    if (is.null(isolate(access_token()))) {
      tags$a("Authorize App",
             href = gs_webapp_auth_url_edit(),
             class = "btn btn-default")
    } else {
      return()
    }
  })

  access_token  <- reactive({
    pars <- parseQueryString(session$clientData$url_search)
    if (length(pars$code) > 0) {
      gs_webapp_get_token(auth_code = pars$code)
    } else {
      NULL
    }
  })

  gsLs <- reactive({
    gs_ls()
  })

  output$listSheets <- DT::renderDataTable({
    validate(
      need(!is.null(access_token()),
           message =
             paste("Click 'Authorize App' to redirect to a Google page where",
                   "you will authenticate yourself and authorize",
                   "this app to access your Google Sheets and Google Drive."))
    )
    dat <- gsLs()
    DT::datatable(dat)
  })
}

shinyApp(ui = ui, server = server)
MateusMaiaDS commented 4 years ago

Anyone could use the googlesheets4 and the a deplyoed shiny? Here its crash anytime that I tried to modify the sheet with the error

Listening on http://127.0.0.1:3935
Warning: Error in : Client error: (403) PERMISSION_DENIED
  * Client does not have sufficient permission. This can happen because the OAuth token does not have the right scopes, the client doesn't have permission, or the API has not been enabled for the client project.
  * Request had insufficient authentication scopes.
  105: <Anonymous>