Tutorial
Make Your Dashboard Extension Enterprise-Ready
It’s easy to create a great Tableau dashboard extension with the Dashboard Extensions API. And it’s just as easy to make adjustments, so your extension is professional-looking and safe for enterprise use.
At this point, you have built a functioning dashboard extension using HTML, JavaScript, and CSS. Now you want to ensure your extension follows Tableau’s design and security guidelines, so you can increase usability and user adoption.
This tutorial teaches you how to make your dashboard extension enterprise-ready and publish it for anyone to use, illustrated by a sample Field Notes dashboard extension. First, we’ll make sure the extension follows Tableau’s style guidelines and then we’ll make sure it’s enterprise-ready before deploying to production.
If your current extension meets your basic user requirements or is still under development, follow this tutorial to help make it a secure and standardized user interface (UI) that businesses will want to use.
Our sample extension uses Bootstrap 3 as a starting point for styling some of the controls, but you are free to create your own style sheets from scratch. This sample also uses jQuery to allow JavaScript to create and access HTML elements. You are free to choose any JavaScript libraries.
Field Notes Dashboard Extension
Our sample extension’s name is Field Notes. It’s written in TypeScript, built using webpack, and published by a fictional company named Demo Inc. We demonstrate the extension in a sample Tableau report using Microsoft’s AdventureWorks 2016 database as a data source.
The image below shows the main screen of our example Field Notes extension. It enables a user to save notes about different fields in the current dashboard’s data sources. The user can select a field from the dropdown list, type notes, and press the Add Note button to save their note to the dashboard settings. The user can then delete notes by pressing the Delete button.
This UI looks simple and clean.
Main UI Design Guidelines
Your user interface should look professional and resemble interfaces found in Tableau. It should follow Tableau’s guidelines across various UI design aspects, including window size, control placement, control borders, control sizes, colors, and margins.
Branding
Introduce your company brand to the user by placing a logo in the area above (and maybe below) the main content of your dashboard extension UI. The logo image should not distract from the extension’s functionality and should have proper margins and placement.
The recommended logo height is 26px or 40px if it is used in a configuration dialog larger than 375px in height. Left justify your logo, and set its top and bottom margins to 12px and its left and right margins to 18px. If your logo is taller than 40px, display it in the footer with these margin sizes. Otherwise, place it at the top. Be sure to reuse your logo in pop-up or configuration dialog windows for consistency.
Drop-Down Lists
Style your dropdown lists like those in Tableau. Notice that the field list in the above screenshot mirrors this style. Try to avoid the “boxiness” of default HTML styles. To do so, apply the following CSS style to HTML select tags and use the form-control class from Bootstrap 3.
select { border-top: none !important; border-left: none !important; border-right: none !important; box-shadow: unset !important; padding-bottom: 0px !important; }
The HTML code for our dropdown list is:
<select class="form-control" id="fieldList"> </select>
Make an Icon
You can display your icon in the About Extension dialog. First, design an icon using your favorite image editor, like Paint.NET. Then use an online tool to convert your image file to a Base64 string. Finally, copy this string to the value of the <icon> tag in your .trex file.
The About Extension dialog box should now display your icon. Here is a sample dialog box:
While it’s great to include consistent branding across your extension, be careful not to overdo it.
Use a Configuration Menu
Add a configuration menu UI to your extension so users can configure your extension, make changes to visibility settings, or set feature-specific options. For more information on designing a dialog window for dashboard extensions, see GitHub.
To create a configuration UI, first add the following context-menu tags to your .trex file:
<context-menu> <configure-context-menu-item /> </context-menu>
Then create an HTML file as your configuration menu dialog window. Here is a sample dialog window:
This window’s size follows Tableau’s guidance on dialog widths at 375px wide, and the logo at the top of the dialog window has the proper margins.
Notice that the buttons are properly spaced according to Tableau’s guidance. Be sure to set the corner radius on your buttons to 1px and ensure their colors correctly match their purpose, such as green for “go.”
Launching a Dialog
Enable your configuration dialog to launch when the user clicks on the More Options menu and selects Configure. To do this, add this code to your initialize method in your main UI’s JavaScript file:
public async initialize() { await tableau.extensions.initializeAsync({ 'configure': this.configure }) ...
The corresponding configures function code is:
public async configure(): Promise<object> { const popupUrl = `${window.location.origin}/${Id.parentDirectory} /${Id.fieldNotesDialog}`; var inputPayload = '' try { //display popup dialog await tableau.extensions.ui .displayDialogAsync(popupUrl, inputPayload, { width: 329, height: 200 }); } catch (e) { if (e instanceof Error) { console.log('Custom error', e.name, e.message); } } return }
The above code calls the displayDialogAsync method from the Tableau extension API using the configuration HTML file’s URL. You can pass a value to the dialog as the inputPayload, but this example passes a blank string.
You must pass a dialogOptions object containing width and height values. The height and width values in the example above result in the desired size of the dialog window. There is about a 45px difference between the specified width and the actual width of the configuration window.
When you set up the configuration correctly, the user can click the More Options arrow, then Configure, to open your configuration menu.
Use an Activity Spinner
Like all good UIs, yours should use an activity spinner to tell the user that the extension is fetching data. Tableau provides a set of spinners in the Activity Indicators section for you to download and use in your extension.
This example activity spinner indicates table data is still loading:
Besides indicating some activity, you can also let the user know when there is no data. That might look like this:
You can control the display of spinners and any alternate messages by adding and removing CSS classes from the target HTML element(s). The code below has some helper methods that control visibility modes for our field notes table:
private async displayFieldNotesLoading() {
$(Id.loadingTable)
.removeClass(Css.Hidden)
.addClass(Css.Show);
$(Id.fieldNotesTable)
.removeClass(Css.Show)
.addClass(Css.Hidden);
$(Id.noFieldNotesTable)
.removeClass(Css.Show)
.addClass(Css.Hidden);
}
private async displayNoFieldNotes() {
$(Id.loadingTable)
.removeClass(Css.Show)
.addClass(Css.Hidden);
$(Id.fieldNotesTable)
.removeClass(Css.Show)
.addClass(Css.Hidden);
$(Id.noFieldNotesTable)
.removeClass(Css.Hidden)
.addClass(Css.Show);
}
private async displayFieldNotes() {
$(Id.loadingTable)
.removeClass(Css.Show)
.addClass(Css.Hidden);
$(Id.fieldNotesTable)
.removeClass(Css.Hidden)
.addClass(Css.Show);
$(Id.noFieldNotesTable)
.removeClass(Css.Show)
.addClass(Css.Hidden);
}
Various steps in the data loading or refreshing process call these methods. For example, a function called tableauSettingsChanged (below) runs in response to the saved settings event. This method reloads the field notes table and swaps the display modes.
Be sure to build in activity spinners for any input/output (IO) or asynchronous operations that might take more than an instant to complete. Our field notes example uses an artificial delay of a few seconds to demonstrate the spinner.
private async tableauSettingsChanged(event: SettingsChangedEvent) { this.displayFieldNotesLoading(); var settings = event.newSettings; var json = settings[SettingsFactory.fieldNotesSettingsKey]; if (json) { this.populateNotesTableFromString(json); } else { this.displayNoFieldNotes(); } }
Make Your Extension Secure
Your extension should not only look professional but should be secure against cyberattacks. Secure your extension against vulnerabilities by following these three guidelines:
- Mitigate the risk of cross-site scripting attacks (XSS)
- Specify the correct access permissions in your .trex file
- Host your extension on a web server that uses HTTPS (required)
Let’s look at these first two in more detail. The section on packaging your extension, later in this tutorial, covers this third guideline.
Cross-Site Scripting Attacks
In XSS attacks, a nefarious user types JavaScript, or sometimes carefully crafted HTML, into input text fields on a web page. These find their way into databases or persisted storage and later run in other users’ web browsers. While Tableau (especially the desktop version) does not quite look like your standard web server and browser setup, you should still secure your dashboard extension since it does run JavaScript.
Consider this scenario: User A enters an attack script into a dashboard extension’s UI text field. The extension saves this data into the Tableau settings object. User A then saves the report.
When user B opens the same report and dashboard, their dashboard extension reads the data from the settings object and tries to render it inside an HTML tag. Instead, the attack script runs, causing a cybersecurity breach.
Luckily, you can take steps to prevent this kind of attack.
Step 1: Add a Sanitizer Library
Install a sanitizer library, such as sanitize-html, into your project by running the following command:
npm install sanitize-html
Step 2: Apply Sanitizer to User Input Fields
Tableau dashboard extensions allow users to enter plain text into fields, and this text usually ends up inside Tableau report settings. To avoid letting in unwanted code, sanitize your user input data with code, as shown below. Note the call to the sanitizeHtml method.
private async addNote() { var fieldName = <string>$(Id.fieldList).val(); var fieldNote = <string>$(Id.fieldNote).val(); var currentDate = new Date().toLocaleString(); //prevent xss attacks fieldName = sanitizeHtml(fieldName); fieldNote = sanitizeHtml(fieldNote);
Step 3: Apply Sanitizer to Settings Data
Make sure you sanitize data that comes from Tableau settings as another layer of prevention against XSS attacks. The following is a code sample to sanitize Tableau extension setting field data. This also blocks old attack scripts that were saved to settings before any input field protection.
private addNoteToTableUI(noteSetting: IFieldNoteSetting, showDate: boolean, table: HTMLTableElement) { const rowCount = table.rows.length; const newRow = table.insertRow(rowCount); let cell = 0; //field name const fieldNameCell = newRow.insertCell(cell++); fieldNameCell.innerHTML = sanitizeHtml(noteSetting.FieldName); //field notes const notesCell = newRow.insertCell(cell++); notesCell.innerHTML = sanitizeHtml(noteSetting.FieldNote); //date (if provided) if (showDate) { const dateCell = newRow.insertCell(cell++); dateCell.innerHTML = sanitizeHtml(noteSetting.Date); }
Note that while HTML browsers typically protect the innerHTML property against attacks found on bolded lines above, calling sanitizeHtml is still good practice.
Step 4: Avoid JavaScript Inside Script Tags
You might be tempted to add some JavaScript code directly to your HTML file. However, you should move it out of script tags and into local script files.
For example, convert this:
<script> alert("this could be an attack")</script>
To this:
<script src="./myJavaScript.js"></script>
This measure makes it easier to adopt a content security policy (CSP), limiting script execution to those originating only from specific, trusted sources.
Step 5: Avoid Referencing External JavaScript and CSS Libraries
You want to mitigate the risk of running third-party code that could be nefariously updated in the future. Therefore, your project should use local copies of third-party scripts and CSS, except for large, well-known libraries, like jQuery and Bootstrap, which you can find on a content delivery network (CDN). To store local copies, either bundle your npm packages with your JavaScript code or copy the libraries to your hosted location and reference them using a script tag with a relative local path.
Step 6: Audit Your Libraries
Run npm audit to find vulnerabilities in your extension’s libraries and recommend updates. Executing the tool may look like this:
npm audit
=== npm audit security report ===
# Run npm install --save-dev concurrently@6.0.0 to resolve 1 vulnerability
SEMVER WARNING: Recommended action is a potentially breaking change
Low Prototype Pollution
Package yargs-parser
Dependency of concurrently [dev]
Path concurrently > yargs > yargs-parser
More info https://npmjs.com/advisories/1500
You can see the tool recommends running npm install --save-dev concurrently@6.0.0.
Tableau Access Permissions
Only request full access permissions to the Tableau workbook data if your code requires it. For example, this code reads the underlying tables from the dashboard’s data sources and displays them to the UI:
//print the underlying tables for the sheets in the dashboard dashboard.worksheets.forEach(async ws => { //get tables var logicalTableArray = await Promise.resolve(ws.getUnderlyingTablesAsync()); logicalTableArray.forEach(async ldt => { console.debug(`ldt_id=${ldt.id}`) console.debug(`ldt_caption=${ldt.caption}`) var dt = await Promise.resolve(ws.getUnderlyingTableDataAsync(ldt.id)); this.addToList(dt); }); })
The code above requires the permissions flag in the .trex file set to full data. Remove this setting if it is not required:
<permissions> <permission>full data</permission> </permissions>
You can find the complete list of methods requiring this permission on GitHub.
Share Your Extension
There are several different ways to share an extension with other people. Either way, make sure the runtime code (HTML, JavaScript, CSS, images, and more) is accessible on the web.
The easiest method is to share a Tableau Workbook file (.twb) that already contains the extension. This works, but you may want to allow users to place your extension in their dashboards.
To do this, you can share the .trex file itself with users by either publishing it to a well-known location or sending it directly, for example by email.
Let’s deploy the code to a free HTTPS web host.
When you are satisfied with your extension, you must deploy your extension runtime code to a web server that uses the HTTPS protocol. GitHub Pages is free and easy to use. GitHub Pages’ static web server serves any web-related files it finds in a specially-named repository in the master branch of your GitHub account, something like your-account-name.github.io.
Deploy Website
First, create a GitHub account, then create a GitHub Pages account.
Next, update your .trex file to contain the new website location with the HTTPS address. Alternatively, you can maintain a separate .trex file for production so you can do additional development and testing with a locally hosted dashboard extension.
For example, keep one file with your localhost address. Notice how this URL is only HTTP.
<source-location> <url>http://localhost:8080/DemoInc/FieldNotes.html</url> </source-location>
Maintain a second .trex file that contains the HTTPS hosted location for production:
<source-location> <url>https://your-name.github.io/DemoInc/FieldNotes.html</url> </source-location>
The GitHub Pages hosting service does have one limitation. Your site must contain only client-side files (for example, HTML, JavaScript, CSS, and images). If instead you need a full-stack web application server, or your extension must communicate with its hosting web server through web API requests, choose an appropriate hosting service that supports your extension’s server platform. For example, if you created a Node.js web application, you may want to check out Heroku. Or, if you created an ASP.NET application, check out ASP.NET Hosting
Tableau Server and Online
You can deliver your extension to Tableau Server and Tableau Cloud users. However, these solutions enforce a series of rules on dashboard extensions. In addition to the requirements described above, your extension may need your company’s site or system administrator to grant additional permissions.
Share Your Extension
To distribute your newly hosted dashboard extension to more users, request Tableau to publish your .trex file in Community Portal or the Tableau Exchange if your extension qualifies. If you run into any problems during that process, following the suggestions in this tutorial should help.
To request adding your extension to the Community Portal, submit a pull request to the official extensions API repository. This requires you to add an entry for your extension in the community_extensions.json file found inside the repository. You may provide your extension’s source code, .trex file, or both.
If you want your extension to have even greater visibility, submit a request to the Tableau Exchange. This requires you to follow a few more steps and standards. First, ensure your extension adheres to the controls and UI style guidelines. Second, submit your request using the form. Find additional submission instructions on GitHub.
Next Steps
In this tutorial, you learned how to easily upgrade your prototype dashboard extension and make it enterprise-ready. This includes several steps to mitigate cybersecurity risks like cross-site scripting attacks, as well as improving your UI to match the standards of Tableau’s UI, creating a configuration dialog, and branding your extension.
These improvements help create a great user experience and help your extension qualify for publication on Tableau’s official dashboard extension portals, giving your masterpiece even more attention.
Now you can give your new Tableau extension that final polish before sharing it with the world, get started making your next great extension, or check out the secure extensions others have created on the Tableau website.
Last updated: September 13, 2021