Mixing Tableau APIs: interoperability between Official and undocumented APIs

 In FOR BI PROS, LEARN, Tableau

By reading the tableau forums it seems the new, completed Tableau 9.0 REST API has some gaps. In this particular example Takaya wanted to get the repository URL for a view (based on view id, which is returned from rest api). From the replies it seems he gave up and used the “undocumented api“, which is pretty shameful from new shiny API’s perspective.

But here comes the trick. You can login first with REST API, then get the repository URLs with the “undocumented” one (spoiler: this will be the example later in this post). Or you can logon with trusted authentication or RSA encrypted passwords on HTTP layer and use the REST API after then (to avoid clear text passwords on the network) as required in this forum post. You can mix things as you want. It seems, even if the common sense push us to rest api direction – use something for the complete functionality outside the API boundaries is a valid real-life requirement.

To show how this works I would use and extend my clojure api example from tableau developer forum.

As it contains some reusable parts, lets start with them. These functions manage the http calls and the basic XML responses.

(ns starschema.tableau
  (:require [clj-http.client :as client]
            [clojure.zip :refer [xml-zip]]
            [clojure.data.zip.xml :refer [xml-> xml1-> attr ]]
            [clojure.data.xml :as xml])
  )
 
(defn tableau-url-for
  "Constructs server API url. Target can be hostname or session returned from logon-to-server"
  [target api-path]
  (if (= (type target) java.lang.String)
    ; connect to host
    (str target "/api/2.0/" api-path )
    ; connect to already established session (target is a session)
    (str (get target :host) "/api/2.0/sites/" (get target :siteid) "/" api-path)
    )
  )
 
(defn get-zip
  "Get xml-zip from http.client response"
  [http-response]
  (-> (get http-response :body)
      xml/parse-str
      xml-zip
      )
  )
 
(defn http
  "Perform a http call to tableau server. Host can be hostname or session"
  [method host api-path http-params]
  (get-zip
    (
      (resolve (symbol (str "client/" method)))
      (tableau-url-for host api-path)
      (merge http-params {:headers {"X-Tableau-Auth" (get host :token)}})
      )
    )
  )

Lines 1-7: First the namespace definition () with necessary requires to the libraries what we are going to use. clj-http for http invocation, data.xml and their zippers for easy xml parsing.

Lines 8-17: Construct URL to invoke. If we do not have session (passing a single host to the function) just concat host + “/api/2.0” + path, while if we already having a session put /sites/<siteid> into the path.

Lines 19-26: Get XML zipper from http-client response. (Get body, parse as XML string, create a zipper from it). Based on Korny’s excellent blog post.

Lines 28-38: Http client. It takes the http method (get, post, delete, etc.), host, path and http parameters. Based on the method name it will find the matching function by symbol name (=cool).

This is all we need as support for communicating with Tableau Server, not more than 40 lines of code. Without using one single variable.

Lets see, what we need to log on to the system:

(defn logindata
  "Creates XML request for logon call"
  [site name password]
  (xml/emit-str
    (xml/element :tsRequest {}
                 (xml/element :credentials {:name     name
                                            :password password}
                              (xml/element :site {:contentUrl site}))))
  )
 
(defn logon-to-server
  "Logon to tableau server by invoking /auth/signin, returns map with token,
 site id and hostname"
  [host site name password]
  (let [ts-response (http "post" host "/auth/signin" {:multipart [{:name    "title"
                                                                   :content (logindata site name password)}]})
        ]
    {:token  (xml1-> ts-response
                     :credentials (attr :token)
                     )
     :siteid (xml1-> ts-response
                     :credentials
                     :site (attr :id))
     :host host
     }
    )
  )

We have two functions here, the first one create the ts-request XML file – it takes site, name and password and creates the XML file as tableau requires. Please note how easy to build XML files without even invoking one single string function.

Logon posts the request to the “/auth/signin” API path and builds a session map from the response.

That’s it. We are ready to test our thesis and check if we are able to perform actions in tableau server with this session or not.

To test, use the following code which:

  1. Logon with the logon-to-server function
  2. That will build the XML, post the server, parse the response and return with a session map
  3. Get the token from the session and print it. We will use this token as workgroup_session_id cookie value
  4. Invoke workbooks/Finance.xml for getting all information for Finance workbook. Method is get, we set only the workgrou_session_id cookie to that cookie value what got from REST Api logon
  5. Get views->view->repository-url XML path contents and print it

In code:

(let [session (logon-to-server "https://preview-online.tableau.com" "starschemaltddemo" "[email protected]" "easypass")]
  (println "Successfully logged on, token: " (get session :token) )
  (->
    (client/get "https://preview-online.tableau.com/t/starschemaltddemo/workbooks/Finance.xml"
              {:cookies {"workgroup_session_id" {:value (get session :token)}}})
    (get-zip)
    (xml1-> :views :view :repository-url text)
    (println)
    )
  )

And the results:

Test mixed Tableau Server invocation

Calling multiple APIs, constructing and parsing the XMLs, within few lines of code, still, without any variables.

So how it will work from the opposite? Can we use trusted authentication or simple RSA based logon? Yes, both will work, in case you just need to use workgroup-session-id cookie as X-Tableau-Auth http header. As you do not need siteid in “undocumented” API, this is something, what you need lookup immediately by getting and parsing the value of “api/2.0/sites/site-content-url?key=contentUrl “.

It seems we have solution for everything as we can mix different APIs with the same login data.

Was your use case covered? Let me know if you need something and you cannot find it in Tableau Server interfaces.

Contact Us

We're not around right now. But you can send us an email and we'll get back to you, asap.

Not readable? Change text. captcha txt