This vignette will walk you through writing plumbertableau extensions in R. plumbertableau extensions are Plumber APIs with a few additional pieces, and this vignette assumes some familiarity with Plumber (https://www.rplumber.io/). If you’re unfamiliar with Plumber, the Quickstart guide gives a good overview of that package. Throughout this guide, we’ll use the terms “web service” and “API” (application programming interface) interchangeably.
You can install the plumbertableau package from CRAN or GitHub.
# from CRAN
install.packages("plumbertableau")
# from GitHub
remotes::install_github("rstudio/plumbertableau")
Once the package is installed, you can start a new RStudio Project with a template file from the New Project menu inside RStudio:
The package also comes with several other example extensions:
plumber::available_apis(package = "plumbertableau")
#> Available Plumber APIs:
#> * plumbertableau
#> - capitalize
#> - loess
#> - mounts
#> - programmatic
#> - stringutils
These examples can be run and optionally viewed using
plumber::plumb_api()
.
plumbertableau extensions respond to requests from Tableau. In a request, Tableau will provide an endpoint — which roughly corresponds to an R function — and will send along one or more arguments, which will generally consist of data from Tableau.
An extension must return data that’s the same number of observations as the arguments it receives. It can do things like transform text, or use a model to predict Y values from a matrix of X values. A plumbertableau extension can’t, say, receive an integer representing the number of elements to sample, and return a vector of that length.
Let’s start with some plain R code that uses the loess()
function to fit a smooth line across some input data and plot the
result.
Here’s that same model in a finished plumbertableau extension.
library(plumber)
library(plumbertableau)
#* @apiTitle Loess Smoothing
#* @apiDescription Loess smoothing for Tableau
#* Fit a loess curve to the inputs and return the curve values
#* @param alpha Degree of smoothing
#* @tableauArg x:integer X values for fitting
#* @tableauArg y:numeric Y values for fitting
#* @tableauReturn numeric Fitted loess values
#* @post /predict
function(x, y, alpha = 0.75) {
alpha <- as.numeric(alpha)
l_out <- loess(y ~ x, span = alpha)
predict(l_out, data.frame(x, y))
}
#* @plumber
tableau_extension
There are a few parts to our extension which are required to make it work:
#*
are annotations,
which define aspects of the API for Plumber and plumbertableau.tableau_extension
object with the @plumber
annotation. This does the work of modifying the Plumber router to be
Tableau-compatible and is a required component of all plumbertableau
extensions.The @apiTitle
and @apiDescription
annotations are used to describe the extension in documentation which is
generated.
Let’s look again at our un-annotated function definition.
#* Annotate me!
function(x, y, alpha = 0.75) {
alpha <- as.numeric(alpha)
l_out <- loess(y ~ x, span = alpha)
predict(l_out, data.frame(x, y))
}
It accepts three inputs: x
, y
, and
alpha
, fits a loess()
model using this data,
and returns the output of the predict()
function for that
model. We need to annotate the function inputs and output so that
plumbertableau can translate the data correctly for Tableau and generate
proper documentation for the extension.
Data provided by Tableau is described using the
@tableauArg
annotation. These annotations require a name
and a type, and can optionally contain a description:
#* @tableauArg Name:Type Description
. In our example:
Here, x:integer
specifies the x
argument by
name, and tells plumbertableau to expect an integer vector. The string
X values for fitting
will be used to describe this argument
in the generated API documentation. If an argument is optional, this can
be noted using ?
after identifying the data type.
#* @tableauArg y:numeric? Y values for fitting
**Data returned to Tableau** is described with `@tableauReturn`. The syntax is similar to `@tableauArg`, without an argument name: `#* @tableauReturn Type Description`.
plumbertableau will expect numeric
data to be returned,
and will describe it as Fitted loess values
in our
documentation.
URL parameters are arguments described with
Plumber’s @param
annotation. These are useful for providing fixed values that don’t use
data from Tableau objects. In our example the optional
alpha
parameter lets Tableau users control the degree of
smoothing used in the loess
model.
Tableau users can now add ?alpha=0.5
to the end of the
extension URL to control the degree of smoothing.
The path to the endpoint where this function is
accessed is annotated with @post
. Tableau only supports
requests using the POST method, so all our Tableau-accessible endpoints
must be annotated with @post
.
plumbertableau understands the following R and Tableau data types.
R type | Tableau type |
---|---|
character |
string , str |
logical |
boolean , bool |
numeric |
real |
integer |
integer , int |
In addition to these described data types, you can use the type
any
to accept any type. ### Tableau Extension Footer
In order for a plumbertableau extension to properly respond to
Tableau requests, we must put this block at the bottom of our extension.
The @plumber
annotation tells Plumber to use the
tableau_extension
object to modify the router object it
uses to handle requests.
Behind the scenes, this object is a Plumber Router Modifier, and is compatible with programmatic usage in Plumber.
plumbertableau uses Plumber’s ability to generate Swagger documentation for APIs.
Once you’ve written an API, it can be tested locally through the
Swagger UI. To start the API, click the Run API button in RStudio, or
run plumber::plumb("path/to/plumber.R")$run()
in the R
console. Once the API is running, you’ll be presented with an interface
like the following:
Click on the /predict
endpoint to expand it. You can
then click “Try it out” to modify the example request, and “Execute” to
send it.
Requests from Tableau consist of a JSON object with two items1. We can see these in the simple mock request that plumbertableau provides following Tableau’s specification.
script
: A string identifying an endpoint. Behind the
scenes, Tableau sends all extension requests to the
/evaluate
endpoint, and plumbertableau uses this string
value to correctly route the request to a the requested endpoint.data
: An object containing arguments for the function
at the endpoint. plumbertableau parses this data and passes it to the R
code. Tableau does not allow explicit naming of arguments, instead
naming them _arg1
, _arg2
, …
,_argN
. Arguments are passed to R in the order in which
they appear. In this case, _arg1
will be passed a
x
and _arg2
will be passed a y
to
the underlying R function.To generate a more complex example, we can use
mock_tableau_request()
to create a more full-featured
request for use with Swagger. Here, we pass in a data frame with two
columns, and mock_tableau_request()
uses those columns for
_arg
and _arg2
.
mock_tableau_request(script = "/predict",
data = mtcars[,c("hp", "mpg")])
#> {
#> "script": "/predict",
#> "data": {
#> "_arg1": [
#> 110,
#> 110,
#> 93,
#> 110,
#> 175,
#> 105,
#> 245,
#> 62,
#> 95,
#> 123,
#> 123,
#> 180,
#> 180,
#> 180,
#> 205,
#> 215,
#> 230,
#> 66,
#> 52,
#> 65,
#> 97,
#> 150,
#> 150,
#> 245,
#> 175,
#> 66,
#> 91,
#> 113,
#> 264,
#> 175,
#> 335,
#> 109
#> ],
#> "_arg2": [
#> 21,
#> 21,
#> 22.8,
#> 21.4,
#> 18.7,
#> 18.1,
#> 14.3,
#> 24.4,
#> 22.8,
#> 19.2,
#> 17.8,
#> 16.4,
#> 17.3,
#> 15.2,
#> 10.4,
#> 10.4,
#> 14.7,
#> 32.4,
#> 30.4,
#> 33.9,
#> 21.5,
#> 15.5,
#> 15.2,
#> 13.3,
#> 19.2,
#> 27.3,
#> 26,
#> 30.4,
#> 15.8,
#> 19.7,
#> 15,
#> 21.4
#> ]
#> }
#> }
#>
Note that because the running API occupies your R session, you’ll
have to stop running it to use mock_talbeau_request()
. Once
you’ve generated a mock request, you can run the API again and paste the
request into the Swagger documentation to test the API. The data that
would be sent back to Tableau appears under “Response body”.
It’s also worth noting that the curl
command used by
OpenAPI to submit the request doesn’t exactly match the request sent by
Tableau, since the curl
request goes to the actual endpoint
(in this case /predict
) and requests from Tableau will go
to /evaluate
and then be routed to /predict
.
The request response will still be the same.
plumbertableau supports debug logging via the debugme
R
package.
To turn on debug messages in R, set the DEBUGME
environment variable to a value containing plumbertableau
in R before the package is loaded.
Additional messages will be logged to the R console while the extension is running, with information about the contents and processing of each request the extension receives.
Now that we’ve written our extension, we can deploy it to RStudio Connect and use it from Tableau.