Setting Permissions in the Tableau with the REST API

This post in conjunction with the release of tableau_rest_api 1.1 at PyPI and on my GitHub repository. It contains much improved functionality for working with permissions — most of what is explained here is now built into the library.

Update: Please use at least version 1.1.4 where the update_permissions_by_gcap_obj_list method now only removes permissions that are going to be updated, rather than all as occurred in 1.1.3 and before.

Permissions are way easier to visualize and test in Tableau 9.0, as anyone who has seen the new Permissions views in Tableau Server can attest. The basic model has not changed much, but there is no “inherit” concept any more. Permissions are made of ‘capabilities’, and each capability can have one of three ‘modes’ — (1) Allow (2) Deny (3) Unspecified. If a user ever has a Deny set on a capability, either individually or in a Group they belong to, that capability will be denied.

(more…)

Embedding Tableau Server Views in C# Application Part 2: Web Edit

When a Tableau viz is embedded inside an iframe, the Web Edit button generates a pop-up window (or new tab) holding the Edit screen. When the user finishes editing, they should be sent back to the original page. This is fine in a modern web browser, but you don’t want them jumping out into a new Internet Explorer window when they are working within your embedded app.

(more…)

Embedding Tableau Server Views in a C# Application

It is possible, and not even that difficult, to embed Tableau Server views into a C# based Windows application. However, the putting all the pieces together to make it work smoothly is pretty difficult — the documentation on the Internet that describes the HOW is all over the place and unless you really know what Tableau is doing, it is not obvious what pieces of the .Net framework are actually useful. Hopefully this post will put it all together enough for anyone interested to get started.

Preface: I’m not a C# programmer and my entire knowledge of this is centered around the WebBrowser part and how it interacts with Tableau Server

Update 2015-07-02: Adding to this and Part 2 with far more depth

You will need:

  • Tableau Server
  • A separate web server, with some facility for dynamic web pages. Apache + PHP, or IIS + ASP.Net for example.
  • Visual Studio

(more…)

draw_tableau_polygons_on_background_image now shows guide lines

The draw_tableau_polygons_on_background_image.html tool has been updated so show a guide line after you’ve clicked the first point of each polygon, so it now works like the Google map polygon drawing tool. It still only records points when you click, and you should finish the polygon with a right-click rather than drawing the last side.

Download the new code here:

https://github.com/bryantbhowell/tableau-map-pack/blob/master/draw_tableau_polygons_on_background_image.html

Detecting the datasources in a workbook using the Tableau JS API

It’s not immediately apparent when getting started with the Tableau JavaScript API that when you initialize and set a callback function using onFirstInteractive that an object is actually passed to the callback. This object has a getViz() method that let’s you get the Viz object that was just created. Once you have the Viz object, you can use its other methods to get information out.

var options = {
// This function runs after the viz is loaded, so all additional API calls should generate from there
onFirstInteractive : completeLoad,
toolbarPosition : tableau.ToolbarPosition.TOP
};
viz = new tableau.Viz( $("#tableauViz").get(), vizUrl, options);
function completeLoad(e) {
viz = e.getViz();
workbook = viz.getWorkbook();
active_sheet = workbook.getActiveSheet();
}

getActiveSheet() can return either a Dashboard or a Worksheet object. The easiest way to detect is to look for the getWorksheets method, which is only present on a Dashboard object. On a dashboard, you can iterate through the sheets and then use the getDataSourcesAsync() method on each sheet.

Iterating through is only necessary if you are concerned about what is the Primary data source on different sheets. In the same workbook, getDataSourcesAsync() will return all data sources on every sheet, even if there is no data blending occurring. It does not appear to be possible to detect when a secondary data source is actually in use (data blending) vs. just part of the workbook overall.

// Detect if active sheet is a Dashboard
if (active_sheet.getWorksheets){
// Dashboard
published_sheets = active_sheet.getWorksheets();
for (i=0;i<published_sheets.length;i++){
published_sheets[i].getDataSourcesAsync().then(
function (e){
console.log(e);
for(j=0;j<e.length;j++){
ds_name = e[j].getName();
ds_is_primary = e[j].getIsPrimary();
fields = e[j].getFields();
}
}
);
}
}
else{
// Worksheet
active_sheet.getDataSourcesAsync().then(
function (e){
console.log(e);
for(j=0;j<e.length;j++){
ds_name = e[j].getName();
ds_is_primary = e[j].getIsPrimary();
fields = e[j].getFields();
}
}
);
}

Getting Started with the TABLEAU_REST_API library

This is the first post on how to actually use the tableau_rest_api Python library. The library handles most of the difficult things for you, but there are still some concepts that are worth getting a hang of. I highly recommend using an IDE, such as PyCharm, so you can easily see all the parameters of each of the methods.

Initial setup

Once the library is installed, you can import it into your scripts and get going. We’re going to create a TableauRestApi object to handle our requests, and a Logger object that can be shared between any TableauRestApi objects we create. Except for the initial sign-in action, all REST API calls are made when logged in to a particular site, so if you are doing actions across multiple sites, you’ll have to create a new TableauRestApi object for each site. You can log them all in the same file by passing the same Logger object, or pass separate ones to keep your logs separated.

We do a full import of ‘tableau_rest_api.tableau_rest_api’ because there are also some Exception classes we may want to reference when we do exception handling later. urllib2 is imported for its Exceptions as well. There are some actions that shouldn’t be done too quickly, so the standard ‘time’ package is also convenient to have available for its sleep function.

from tableau_rest_api.tableau_rest_api import *
import urllib2
import time
server = 'http://127.0.0.1'
username = 'my_username'
password = 'my_password'
tab_srv = TableauRestApi(server, username, password, site_content_url='default')
logger = Logger('rest_test.log')
tab_srv.enable_logging(logger)
tab_srv.signin()

LUIDs

As mentioned in a previous post on the REST API, all content / objects on the Tableau Server are referenced via local unique IDs (LUIDs), which are all alphanumeric codes that never change for that particular object. The library has many methods built in for getting the specific LUID of an object, and ‘create’ and ‘publish’ methods will return the LUID of the newly created/published content.

For example, let’s say we want to create a new user and then add them to an existing group called “Sandbox Users”.

try:
    group_luid = tab_srv.query_project_luid_by_name('Sandbox Users')
    # add_user actually adds and updates everything about the user
    # add_user(username, fullname, site_role='Unlicensed', password=None, email=None):
    new_user_luid = new_user_luid = tab_srv_3.add_user('newuser1', 'New User 1',
                                                       'Interactor', 'password', 'newuser1@nowhere.com')
    # Alternatively, you could do add_user_by_username(self, username, site_role='Unlicensed')
    # This does not define any of the additional properties
    # The methods that have plurals (like add_users) can take a single string or a list of luids
    tab_srv.add_users_to_group_by_luid(new_user_luid, group_luid)
except NoMatchFoundException as e:
    print e.msg

You’ll notice some of the style of the library methods — they are verbose and specify what type of input they are looking for: ‘_by_luid’ ends almost all of the ‘baseline’ methods that directly do actions, while ‘by_name’ methods do look ups to find a match. ‘by_name’ methods also can throw NoMatchFoundException class Exceptions, and then you can decide what to do from there.

Iterating through lists

The ‘query’ methods that end in a plural, like ‘query_groups()’ and ‘query_projects()’ return iterable lxml objects based on every item that came back. The REST API actually returns paginated results when the lists get large enough, but the library paginates through for you automatically and combines them into a single lxml object. You can then do any of the regular ElementTree or lxml methods to work with these objects, for example .xpath() for very specific querying.

There is also a static method of the TableauRestApi class which takes any of these lxml objects and converts it to a standard Python dict using { name : luid } as a pattern. This gives you an easily iterable dict that you can loop through to do mass changes.

groups = tab_srv.query_groups()
groups_dict = tab_srv.convert_xml_list_to_name_id_dict(groups)
for group in groups_dict:
    print "Group named {} is LUID {}".format(group, groups_dict.get(group))

tableau_rest_api Module for Python v1.0 released!

I’m excited to announce the v1.0 release of the tableau_rest_api module for Python 2.7. This is as far as I know the first publicly available full implementation of the Tableau Server 9.0 REST API. Every method implemented in the API reference docs is implemented, and all except for the AD sync methods are fully tested (this testing is occurring soon). The only requirement outside of the standard Python libraries is lxml, which is not being used to it’s fullest capacities but will be in later releases. At minimum, you get full XPath querying capabilities when working with the larger lists returned by some queries.

Available now on PyPI

https://pypi.python.org/pypi/tableau_rest_api

with source code hosted, as always on my GitHub.

Many more blog posts to come explaining how to use the library, and the design decisions that went into its current form.

I expect many bug fixes and additional features to come shortly as people begin using it.