📌 Developers & APIs | Resources
Ciara Brennan (Tableau)
📌 Join the Tableau Developer Program and start learning how you can build on the Tableau Developer Platform to fit your organization's unique needs. You'll get everything you need to keep your skills sharp, including tutorials, webinars, the latest news, and even your own personal development site. You will also get access to thousands of DataDevs members for networking, troubleshooting, Q&As, and expert insights.
Lawrence Kachambwa (Member) asked a question.
Hi Tableau community, I am using the Tableau REST API to export a dashboard as a PDF, but I am encountering issues where some graphs are being split across multiple pages.
Here is the API request I am using
Https:{tableau server URL}/api/3.15/sites/{Site Id}/views/{view_id}/pdf&orientation=Portrait&pagesize=A4
The requirement is the page size is A4, Portrait size and it should be paginated
The dashboard height is 4600 pixels so there are 4 pages. I can't seem to find a parameter that mimics the scaling option "at most 1 page width". Tried playing with viz-width , viz-height and scaling parameters but they have zero effects on graphs
Can someone please help.
Best Answer
HI @Lawrence Kachambwa (Member) ,
It is indeed difficult to get PDF formatting 'right' using the REST API.
What I would suggest is exporting the PNG of the dashboard, and convert that PNG to PDF (e.g. using Python/Pillow )
Kind regards,
Johan de Groot
If this post resolves the question, would you be so kind to "Select as Best"?.
This will help other users find the same answer/resolution and help community keep track of answered questions.
Qasim Ali (Member) asked a question.
I've set up a dummy project with a server using node.js:
const express = require("express");
const jwt = require("jsonwebtoken");
const cors = require("cors");
require("dotenv").config();
const { v4: uuidv4 } = require('uuid');
const app = express();
app.use(cors());
app.use(express.json());
const secretValue = process.env.PRIVATE_KEY;
const clientId = process.env.CLIENT_ID;
const secretId = process.env.ISSUER;
const username = process.env.USERNAME;
const tokenExpiryInMinutes = 1;
const scopes = [
"tableau:views:embed",
"tableau:views:embed_authoring",
"tableau:insights:embed",
];
const kid = secretId;
const iss = clientId;
const sub = username;
const aud = "tableau";
const exp = Math.floor(Date.now() / 1000) + (tokenExpiryInMinutes * 60);
const jti = uuidv4();
const scp = scopes;
app.post("/generate-token", (req, res) => {
const payload = {
iss,
exp,
jti,
aud,
sub,
scp,
};
console.log(payload);
console.log(secretValue);
const token = jwt.sign(payload, secretValue, {
algorithm: "HS256",
header: {
kid,
iss,
},
});
console.log(token);
res.json({ token });
});
app.listen(5000, () => console.log("Server running on port 5000"));
And the frontend using React:
import React, { useEffect, useState } from "react";
function TableauEmbed() {
const [token, setToken] = useState(null);
const fetchToken = async () => {
try {
const response = await fetch("http://localhost:5000/generate-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
cache: "no-store",
});
const data = await response.json();
setToken(data.token);
} catch (err) {
console.error("Error fetching token:", err);
}
};
useEffect(() => {
fetchToken();
}, []);
return (
<div>
<h1>Tableau Embedded Dashboard</h1>
{token ? (
<tableau-viz
id="tableauViz"
width="1000"
height="1000"
hide-tabs={false}
touch-optimize={false}
disable-url-actions={false}
debug={false}
src="LINK_HIDDEN"
device="Desktop"
toolbar="bottom"
token={token}
>
</tableau-viz>
) : (
<p>Loading...</p>
)}
</div>
);
}
export default TableauEmbed;
It runs fine when I start the server, but as soon as I access the link via an incognito browser, I get one of two errors:
The same also occurs when I reload the page on just one instance after a while has passed.
What is the problem here?
Best Answer
Thanks for your inputs @Diego Martinez (Member) and @Deepak Rai (Member). I figured out the issue. I had to bring in the jti and exp variables into the body of the post request, so new values were generated every time a call was made to generate token.
I have a server that, when it received a request from the frontend, it used the hardcoded username and password to generate the authentication token as follow:
const express = require("express");
const cors = require("cors");
const axios = require("axios");
require("dotenv").config();
const app = express();
app.use(cors());
app.use(express.json());
const tableauAuthUrl = "https://SERVER_IP/api/3.2/auth/signin";
const username = process.env.USERNAME;
const password = process.env.PASSWORD;
const site = "";
const axiosInstance = axios.create({
httpsAgent: new (require("https").Agent)({ rejectUnauthorized: false }),
});
app.post("/generate-token", async (req, res) => {
try {
const response = await axiosInstance.post(tableauAuthUrl, {
credentials: {
name: username,
password: password,
site: { contentUrl: site },
},
});
const token = response.data.credentials.token;
console.log(response)
console.log(response.data)
console.log(token);
res.json({ token });
} catch (error) {
console.error("Error fetching token:", error.response ? error.response.data : error.message);
res.status(500).json({ error: "Failed to authenticate with Tableau" });
}
});
app.listen(5000, () => console.log("Server running on port 5000"));
Now at the frontend, I'm using tableau API v3 and the <tableau-viz> html element to fetch the dashboard. When using jwt to authenticate, I just put in the token received with the attribute called token. But that doesnt seem to work with this way of authentication. For reference I've attached the code for jwt authentication below:
import React, { useEffect, useState } from "react";
function TableauEmbed() {
const [token, setToken] = useState(null);
const fetchToken = async () => {
try {
const response = await fetch("http://localhost:5000/generate-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
cache: "no-store",
});
const data = await response.json();
setToken(data.token);
} catch (err) {
console.error("Error fetching token:", err);
}
};
useEffect(() => {
fetchToken();
}, []);
return (
<div>
<h1>Tableau Embedded Dashboard</h1>
{token ? (
<tableau-viz
id="tableauViz"
width="1000"
height="1000"
hide-tabs={false}
touch-optimize={false}
disable-url-actions={false}
debug={false}
src="DASHBOARD_URL"
device="Desktop"
toolbar="bottom"
token={token}
>
</tableau-viz>
) : (
<p>Loading...</p>
)}
</div>
);
}
export default TableauEmbed;
So, my question is how do I use this other token in a similar manner using tableau-viz?
Jayesh Patil (Member) asked a question.
I having written python script to automate that uses "Tableau server client" api that applies values to a filter, convert it to pdf and then download it. This is the automation I am working on.
I have a filter called "Summary" it has values : [Null, alpha, beta, delta] in dropdown options. It can accepts multiple values at once for eg. Summary =[alpha, beta] can be selected all at once.
The default value is [alpha, beta]. I wanna include [Null, alpha, beta, delta] as values.
I cant set value as Null. Is there any way to set value as Null or exclude delta?
Ankit Gada (Member) asked a question.
Tableau Extensions: I want to download entire image (or pdf image version of the entire worksheet) of the worksheet present in the dashboard within the Tableau Extension. Could you please suggest if "getImageAsync" is the correct method to fetch the image from the worksheet in Tableau API Extensions?
Below is the javascript code that I have added for an onclick button event but it is not giving any outcome or saving it on desktop on click of the button:
// Function to generate and download image of the worksheet
function downloadWorksheetImage() {
worksheet.getImageAsync().then(function (image) {
let link = document.createElement("a");
link.href = image;
link.download = worksheet.name + ".png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}).catch(function (error) {
console.error("Error downloading image:", error);
console.error("Error details:", error.name, error.message, error.stack); //Log the error details
});
}
// Function to generate and download PDF of the worksheet
function downloadWorksheetPDF() {
worksheet.getImageAsync().then(function (image) {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF();
pdf.addImage(image, 'PNG', 10, 10, 180, 160);
pdf.save(worksheet.name + ".pdf");
}).catch(function (error) {
console.error("Error downloading PDF:", error);
console.error("Error details:", error.name, error.message, error.stack); // Log the error details
});
}
JAD DRIGGERS (Member) asked a question.
We are currently using Tableau on prem with data hosted in AWS Athena tables.
Problem:
We have approximately 15 clients all using the same dashboard template but of course different data. Each of these clients has there own AWS secrets for the data connection.
If we make a change to the tableau template or add a new metric/feature we have to manually go through each client and update to the new workbook, pasting in the AWS secrets for the data connection. Inevitably this breaks some filters and also resets the color schema. We are looking for a way to automate this. From my research it seems like a python script and using the Tableau API is the best option, We are adding more and more clients so this issue will only compound. Also looking to use this process to deploy new clients with this templated dashboard. I'm also curios if there are 3rd party solutions.
Steps:
Best Answer
Hi @JAD DRIGGERS (Member) ,
Programming a script using the REST API does indeed seems the right way to solve this. Not sure if you did this already, but split the Tableau Datasource and Workbook for better maintenance.
You might also want to have a look at the Content Migration Tool (https://help.tableau.com/current/server/en-us/cmt-intro.htm). Although it is not the prime focus of the tool, if could be a good solution.
Kind regards,
Johan de Groot
If this post resolves the question, would you be so kind to "Select as Best"?.
This will help other users find the same answer/resolution and help community keep track of answered questions.
Amber Crowe (Member) asked a question.
We are using the Embedded API to display Tableau dashboards in a web portal. Our row level security is based on the username mapped to a customer ID in an entitlement table, so the user can only see their personal data.
For On-Demand Access (ODA), it looks like we don't have to provision users on our Tableau Server. Rather, access will be controlled on the web portal side, and the web portal can send user attributes to Tableau via the token.
With ODA are we still able to use our entitlement tables for RLS? Will it be secure? It's critical that our customers can not see other user's data.
Right now, our plan is:
For testing, we have my personal email hardcoded in the token under the "sub" as we needed a provisioned user to test the connection to our Tableau site. Can that be removed once everything is set up?
Best Answer
@Amber Crowe (Member)
Hi, everything seems right except the last step. Why?, according to the docs, the user attribute functions don't work with published data sources:
https://help.tableau.com/current/api/embedding_api/en-us/docs/embedding_api_user_attributes.html
So, I have not tested if this restriction still is active (you may test it). So, I recommend you to make three tests with:
a. Published data source (it may fail) which contains the lower([Customer]) = lower(USERATTRIBUTE("Username")) inside the data source in the data source filters section. (so you publish the datasource with this data source filter) -> It should not work.
b. A workbook connected to a published datasource, that has the relationship to your entitlement table but not the datasource filter published in the datasource. Instead, create the calc at the workbook level (lower([Customer]) = lower(USERATTRIBUTE("Username"))) and add it as a datasource filter from the workbook. -> It may work.
c. Test with an embedded data source in the workbook (so the relationship model belongs to the workbook) and create the user filter calc within the workbook and apply as a datasource filter -> This should work.
For testing, we have my personal email hardcoded in the token under the "sub" as we needed a provisioned user to test the connection to our Tableau site. Can that be removed once everything is set up?
It seems you may be using a Tableau Cloud site that does not have ODA and Usage Based Licensing activated, so I guess you are testing on this site without ODA and that is why you need to use the sub clause with an existing user. When moving to the ODA Tableau Cloud site, I recommend you to use still the sub JWT attribute, but with the username of the not provisioned user. If I remember, you may use USERATTRIBUTE("sub"). If the sub attribute is not accesible with userattribute, you may add another attribute to the JWT with the username.
e.g USERATTRIBUTE("username")
Also, you would need to pass the https://tableau.com/oda attribute in the JWT with the true value. (it can only be used with sites with ODA activated and usage based licensing).
If this post resolves the question, would you be so kind to "Select as Best"?. This will help other users find the same answer/resolution and help community keep track of answered questions. Thank you.
Regards,
Diego Martinez
Tableau Visionary and Forums Ambassador
Jian Du (Member) asked a question.
Dear All,
I am a licensed user on the Tableau Server of my company, and I am the site Admin of one the sites.
I need to pull out the full list of all the schedules registered on the server.
I tried graphQL but failed.
I searched through the following page about graphQL RootTypeQuery, it seems that Schedule is not among the fields available for search. Does this mean there is no way to retrieve the schedule list via graphQL?
https://help.tableau.com/current/api/metadata_api/en-us/reference/query.doc.html
Best Regards,
Jian
Best Answer
@Diego Martinez (Member) Thank you very much, I will check REST API out.
Please check out our post with some tips on asking a question and how to help you get answers more quickly.
We use three kinds of cookies on our websites: required, functional, and advertising. You can choose whether functional and advertising cookies apply. Click on the different cookie categories to find out more about each category and to change the default settings.
Privacy Statement
Required cookies are necessary for basic website functionality. Some examples include: session cookies needed to transmit the website, authentication cookies, and security cookies.
Functional cookies enhance functions, performance, and services on the website. Some examples include: cookies used to analyze site traffic, cookies used for market research, and cookies used to display advertising that is not directed to a particular individual.
Advertising cookies track activity across websites in order to understand a viewer’s interests, and direct them specific marketing. Some examples include: cookies used for remarketing, or interest-based advertising.