Skip to content

Commit

Permalink
Initial version of Insight RStudio add-in (#27)
Browse files Browse the repository at this point in the history
* Address compatibility issues with testthat 2.0.0; fix minor DESCRIPTION issue w dup keys

* Initial rough-in of insight addin

* Added copyright notice

* Added copyright notice

* Finish initial version of insight add-in

* Qualify renderImage() with package

* Apply data.world css

* Add basic test coverage for insight addin

* Remove covr exclusion for addin; factor out addin project filtering

* Implement designer feedback

* Fix issue running addin without dw packages attached; cleaned up .onLoad/.onAttach functions

* Suppress warning message on package load if no config file

* Adjust font and image sizes per designer feedback

* Further designer tweaks to proportions, centering, etc.

* Tweak text, dependencies and coverage

* Address lintr warning

* Clear CMD CHECK issues, improve test coverage

* Prepare for CRAN release
  • Loading branch information
scottcame authored and rflprr committed Jan 12, 2018
1 parent 2134122 commit ec66e74
Show file tree
Hide file tree
Showing 37 changed files with 8,573 additions and 139 deletions.
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
^revdep$
^.circleci$
^ci-build$
^cobertura.xml$
27 changes: 15 additions & 12 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
Package: data.world
Title: Main Package for Working with 'data.world' Data Sets
Version: 1.1.1
Title: Functions and Add-Ins for Working with 'data.world' Data Sets and Projects
Version: 1.2.0
Authors@R: c(
person("Rafael", "Pereira", email = "rafael.pereira@data.world", role = c("aut", "cre")),
person("Triet", "Le", email = "triet.le@data.world", role = c("aut")),
person("Bryon", "Jacob", email = "bryon.jacob@data.world", role = c("aut")))
Description: High-level tools for working with data.world data sets. data.world is a community
person("Bryon", "Jacob", email = "bryon.jacob@data.world", role = c("aut")),
person("Scott", "Came", email = "scott@cascadia-analytics.com", role = c("aut")))
Description: High-level tools for working with 'data.world' data sets. 'data.world' is a platform
where you can find interesting data, store and showcase your own data and data projects,
and find and collaborate with other members. In addition to exploring, querying and
charting data on the data.world site, you can access data via 'API' endpoints and
integrations. Use this package to access, query and explore data sets, and to
integrate data into R projects. Visit <https://data.world>, for additional information.
Depends: R (>= 3.3.0)
License: Apache License 2.0
LazyData: TRUE
publish your insights. Visit <https://data.world>, for additional information.
Depends: R (>= 3.3.0), dwapi
Imports:
dwapi,
httr,
ini
ini,
miniUI,
shiny,
stringi
Suggests:
testthat,
covr,
knitr,
lintr,
readr,
rmarkdown,
readr
testthat (>= 2.0.0)
License: Apache License 2.0
Encoding: UTF-8
LazyData: true
Expand Down
12 changes: 12 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ export(qry_sql)
export(query)
export(save_config)
export(set_config)
importFrom(dwapi,create_insight)
importFrom(dwapi,get_projects_user_contributing)
importFrom(dwapi,get_projects_user_own)
importFrom(dwapi,insight_create_request)
importFrom(dwapi,upload_file)
importFrom(grDevices,dev.copy)
importFrom(grDevices,dev.list)
importFrom(grDevices,dev.off)
importFrom(grDevices,png)
importFrom(graphics,mtext)
importFrom(graphics,plot.new)
importFrom(utils,URLencode)
12 changes: 9 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# 1.1.0
# 1.2.0

* Delete all defunct and deprecated functions
* First attempted CRAN release
* Introduce "Add Insight" add-in
* Improve reloading of saved API tokens
* Address compatibility issues with `testthat` 2.0

# 1.1.1

* Address requests from CRAN reviewers related to 1.1.0

# 1.1.0

* Delete all defunct and deprecated functions
* First attempted CRAN release
8 changes: 5 additions & 3 deletions R/config.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"data.world-r
Copyright 2017 data.world, Inc.
Copyright 2018 data.world, Inc.
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
Expand All @@ -20,7 +20,9 @@ https://data.world"
#'
#' @param cfg Configuration object.
#' @examples
#' data.world::set_config(data.world::cfg_saved())
#' data.world::set_config(data.world::cfg_env())
#' data.world::set_config(data.world::cfg("YOUR_TOKEN"))
#' @export
set_config <- function(cfg) {
UseMethod("set_config")
Expand Down Expand Up @@ -67,7 +69,7 @@ set_config.cfg_saved <- function(cfg) {
profile <- config[[cfg$profile]]
if (!is.null(profile)) {
# delegate to default method
data.world::set_config(cfg(auth_token = profile$auth_token))
set_config(cfg(auth_token = profile$auth_token))
} else {
warning(
"Configuration profile \"", cfg$profile,
Expand Down Expand Up @@ -119,7 +121,7 @@ cfg_saved <- function(profile = "DEFAULT") {
return(me)
}

#' Save configuration to file.
#' Save configuration to file in the user's home directory.
#'
#' @param auth_token API authorization token.
#' @param ... Reserved for future configuration parameters.
Expand Down
2 changes: 1 addition & 1 deletion R/data.world-defunct.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"data.world-r
Copyright 2017 data.world, Inc.
Copyright 2018 data.world, Inc.
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
Expand Down
6 changes: 5 additions & 1 deletion R/data.world.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"data.world-r
Copyright 2017 data.world, Inc.
Copyright 2018 data.world, Inc.
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,6 +30,10 @@ https://data.world"
#' Use \code{\link{qry_sql}} and \code{\link{qry_sparql}} to construct query objects and
#' to pass parameters to queries.
#'
#' @section Add-ins:
#'
#' Use the included "New insight" add-in to publish plots as project insights from R Studio.
#'
#' @section REST API:
#'
#' data.world's REST APIs can be accessed via the \code{dwapi} package.
Expand Down
225 changes: 225 additions & 0 deletions R/insight-addin.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
"data.world-r
Copyright 2018 data.world, Inc.
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.
This product includes software developed at data.world, Inc.
https://data.world"

#' Driver function for data.world Insight Add-in
#' @keywords internal
#' @importFrom dwapi get_projects_user_own get_projects_user_contributing
#' @importFrom grDevices dev.copy dev.list dev.off png
#' @importFrom graphics mtext plot.new
# nocov start
add_insight_addin <- function() {

MAX_PROJECT_TITLE_LABEL <- 34

api_token <- getOption("dwapi.auth_token")

if (is.null(api_token)) {
#nolint start
stop(paste0("API authentication must be configured. ",
"To configure, use data.world::set_config(save_config(auth_token = \"YOUR API TOKEN\")) or ",
"dwapi::configure()"))
#nolint end
}

project_list <- insight_project_filter(
c(get_projects_user_own()$records,
get_projects_user_contributing()$records))

project_choice_list <- sapply(
USE.NAMES = FALSE, project_list, function(project) {
paste0(project$owner, "/", project$id)
}
)

names(project_choice_list) <- sapply(
USE.NAMES = FALSE, project_list, function(project) {
ret <- paste0(project$owner, "/", project$title)
if (nchar(ret) > MAX_PROJECT_TITLE_LABEL) {
ret <- paste0(substr(ret, 1, MAX_PROJECT_TITLE_LABEL), "...",
collapse = "")
}
ret
}
)

ui <- miniUI::miniPage(

shiny::includeCSS(system.file("dw-bootstrap.css", package = "data.world")),

shiny::tags$head(
shiny::tags$style(
type = "text/css",
paste(".form-group {font-size: 14px;}",
".form-control {font-size: 14px;}",
"#done {line-height: 1rem;}",
"#cancel {line-height: 1rem;}",
"#title {font-size: 14px;}",
"#description {font-size: 14px; min-height: 110px;}",
"div.gadget-title {background-color: #fff}",
"div.gadget-content {background-color: #fff}",
sep = "\n"
)
),
shiny::tags$link(
rel = "stylesheet", type = "text/css",
href = "http://fonts.googleapis.com/css?family=Lato:300,300i,400,400i,700,700i") # nolint
),

miniUI::gadgetTitleBar("Create a new insight",
right = miniUI::miniTitleBarButton("done",
"Create insight",
primary = TRUE)),
miniUI::miniContentPanel(
shiny::fillRow(
shiny::column(12,
shiny::imageOutput("thumb", height = NULL, width = "90%")),
shiny::column(12,
shiny::selectInput("project", "Project:",
choices = c("", project_choice_list)),
shiny::textInput("title", "Title:"),
shiny::textAreaInput("description", "Description:",
height = "85px")
),
flex = c(5, 3)
)
)
)

server <- function(input, output, session) {

current_plot_exists <- "RStudioGD" %in% names(dev.list())

shiny::observeEvent(input$done, {

if (current_plot_exists) {
save_image_as_insight(input$project, input$title, input$description,
session$userData$f) # nolint
} else {
writeLines("Nothing to do...no current plot")
}
shiny::stopApp()
})

output$thumb <- shiny::renderImage(deleteFile = FALSE, {

tf <- tempfile(fileext = ".png")
session$userData$f <- tf # nolint

if (current_plot_exists) {
dev.copy(png, filename = tf, height = 300, width = 467)
dev.off()
} else {
png(filename = tf, height = 300, width = 467)
plot.new()
mtext("No current plot available")
dev.off()
}

list(src = tf, alt = "Plot thumb")

})

output$logo <- shiny::renderImage(deleteFile = FALSE, {
list(src = system.file("dw-logo@2x.png", package = "data.world"),
alt = "logo")
})

}

viewer <- shiny::dialogViewer("New insight", height = 380, width = 850)
shiny::runGadget(ui, server, viewer = viewer)

}
# nocov end

#' Filter the specified list of projects to those suitable for selection in
#' the add-in
#' @param project_list the list of projects
#' @return the list filtered for those suitable for selection
#' @keywords internal
insight_project_filter <- function(project_list) {

project_list <- lapply(project_list, function(project) {
ret <- NULL
if (project[["accessLevel"]] %in% c("ADMIN", "WRITE")) {
ret <- project
}
ret
}
)

project_list[sapply(project_list, function(item) {
!is.null(item)
})]

}

#' Save an image file as a data.world insight
#' @param project_id the fully-qualified id of the project to house the insight
#' @param title the title of the insight
#' @param description the description of the insight (optional)
#' @param image_file the file path containing the image
#' @return a list containing the values returned by the upload_file and
#' create_insight dwapi functions
#' @importFrom dwapi upload_file create_insight insight_create_request
#' @importFrom utils URLencode
#' @keywords internal
save_image_as_insight <- function(project_id, title, description=NULL,
image_file) {

if (is.null(project_id) || project_id == "") {
stop("project_id cannot be null")
}

if (!grepl(x = project_id, pattern = "(.+)/(.+)")) {
stop("project_id invalid: must be ownerid/projectid")
}

if (is.null(title)) {
stop("title cannot be null")
}

fn <- paste0(title, ".png")

upload_result <- upload_file(project_id, image_file, fn)

unlink(image_file)

project_parts <- unlist(stringi::stri_split(project_id, regex = "/"))

image_url <- paste0("https://data.world/api/",
project_parts[1],
"/", "dataset", "/",
project_parts[2],
"/", "file", "/", "raw", "/",
URLencode(fn))

insight_result <- create_insight(
project_parts[1], project_parts[2],
insight_create_request(title, description, image_url))

ret <- list(upload_result = upload_result, insight_result = insight_result)

writeLines(paste0(
"Successfully created insight ", title, " within project ",
project_id
))

ret

}
Loading

0 comments on commit ec66e74

Please sign in to comment.