").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
5 |
--------------------------------------------------------------------------------
/js/lunr.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.7.2
3 | * Copyright (C) 2016 Oliver Nightingale
4 | * @license MIT
5 | */
6 | !function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.7.2",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.utils.asString=function(t){return void 0===t||null===t?"":t.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(e){if(!arguments.length||null==e||void 0==e)return[];if(Array.isArray(e))return e.map(function(e){return t.utils.asString(e).toLowerCase()});var n=t.tokenizer.seperator||t.tokenizer.separator;return e.toString().trim().toLowerCase().split(n)},t.tokenizer.seperator=!1,t.tokenizer.separator=/[\s\-]+/,t.tokenizer.load=function(t){var e=this.registeredFunctions[t];if(!e)throw new Error("Cannot load un-registered function: "+t);return e},t.tokenizer.label="default",t.tokenizer.registeredFunctions={"default":t.tokenizer},t.tokenizer.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing tokenizer: "+n),e.label=n,this.registeredFunctions[n]=e},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,r=0;n>r;r++){for(var o=t[r],s=0;i>s&&(o=this._stack[s](o,r,t),void 0!==o&&""!==o);s++);void 0!==o&&""!==o&&e.push(o)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(e
n.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(o===t)return r;t>o&&(e=r),o>t&&(n=r),i=n-e,r=e+Math.floor(i/2),o=this.elements[r]}return o===t?r:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,r=e+Math.floor(i/2),o=this.elements[r];i>1;)t>o&&(e=r),o>t&&(n=r),i=n-e,r=e+Math.floor(i/2),o=this.elements[r];return o>t?r:t>o?r+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,r=0,o=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>o-1||r>s-1)break;a[i]!==h[r]?a[i]h[r]&&r++:(n.add(a[i]),i++,r++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone();for(var r=0,o=n.toArray();rp;p++)c[p]===a&&d++;h+=d/f*l.boost}}this.tokenStore.add(a,{ref:o,tf:h})}n&&this.eventEmitter.emit("add",e,this)},t.Index.prototype.remove=function(t,e){var n=t[this._ref],e=void 0===e?!0:e;if(this.documentStore.has(n)){var i=this.documentStore.get(n);this.documentStore.remove(n),i.forEach(function(t){this.tokenStore.remove(t,n)},this),e&&this.eventEmitter.emit("remove",t,this)}},t.Index.prototype.update=function(t,e){var e=void 0===e?!0:e;this.remove(t,!1),this.add(t,!1),e&&this.eventEmitter.emit("update",t,this)},t.Index.prototype.idf=function(t){var e="@"+t;if(Object.prototype.hasOwnProperty.call(this._idfCache,e))return this._idfCache[e];var n=this.tokenStore.count(t),i=1;return n>0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(this.tokenizerFn(e)),i=new t.Vector,r=[],o=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*o,h=this,u=this.tokenStore.expand(e).reduce(function(n,r){var o=h.corpusTokens.indexOf(r),s=h.idf(r),u=1,l=new t.SortedSet;if(r!==e){var c=Math.max(3,r.length-e.length);u=1/Math.log(c)}o>-1&&i.insert(o,a*s*u);for(var f=h.tokenStore.get(r),d=Object.keys(f),p=d.length,v=0;p>v;v++)l.add(f[d[v]].ref);return n.union(l)},new t.SortedSet);r.push(u)},this);var a=r.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,r=new t.Vector,o=0;i>o;o++){var s=n.elements[o],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);r.insert(this.corpusTokens.indexOf(s),a*h)}return r},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,tokenizer:this.tokenizerFn.label,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",r=n+"[^aeiouy]*",o=i+"[aeiou]*",s="^("+r+")?"+o+r,a="^("+r+")?"+o+r+"("+o+")?$",h="^("+r+")?"+o+r+o+r,u="^("+r+")?"+i,l=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(u),p=/^(.+?)(ss|i)es$/,v=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,y=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),k=new RegExp("^"+r+i+"[^aeiouwxy]$"),x=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,F=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,_=/^(.+?)(s|t)(ion)$/,z=/^(.+?)e$/,O=/ll$/,P=new RegExp("^"+r+i+"[^aeiouwxy]$"),T=function(n){var i,r,o,s,a,h,u;if(n.length<3)return n;if(o=n.substr(0,1),"y"==o&&(n=o.toUpperCase()+n.substr(1)),s=p,a=v,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=g,a=m,s.test(n)){var T=s.exec(n);s=l,s.test(T[1])&&(s=y,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,u=k,a.test(n)?n+="e":h.test(n)?(s=y,n=n.replace(s,"")):u.test(n)&&(n+="e"))}if(s=x,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],r=T[2],s=l,s.test(i)&&(n=i+t[r])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],r=T[2],s=l,s.test(i)&&(n=i+e[r])}if(s=F,a=_,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=z,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=P,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=O,a=c,s.test(n)&&a.test(n)&&(s=y,n=n.replace(s,"")),"y"==o&&(n=o.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.generateStopWordFilter=function(t){var e=t.reduce(function(t,e){return t[e]=e,t},{});return function(t){return t&&e[t]!==t?t:void 0}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){return t.replace(/^\W+/,"").replace(/\W+$/,"")},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t.charAt(0),r=t.slice(1);return i in n||(n[i]={docs:{}}),0===r.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(r,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n" + title + "";
57 | container.innerHTML += "" + getResultBlurb(search_blob[ref].content) + "...
";
58 | }
59 | }
60 | } else {
61 | container.innerHTML += "No results found.
";
62 | }
63 | }
64 |
65 | addContentToIndex();
66 | window.addEventListener("load", function () {
67 | displaySearchHeading(searchQuery);
68 | displaySearchResults(getRawSearchResults(searchQuery));
69 | });
70 |
71 | })();
72 |
--------------------------------------------------------------------------------
/pages/01_embedding_and_jsapi.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Embedding Tableau Views
3 | ---
4 |
5 | The easiest method for embedding a Tableau view (dashboard or visualization) is with the copy-paste embed code. Navigate to a view on Tableau Server and copy the Embed Code from the Share toolbar option.
6 |
7 | 
8 |
9 | Once acquired, this code can be pasted into HTML.
10 | This method is useful for simple embedding, such as embedding into blogs or internal knowledge bases, but **only very simple embedding scenarios should use the embed code. Most deployments should instead use the Embedding API v3.** It is not significantly more work to embed with the Embedding API v3, and doing so will gain you flexibility and power in your embedded deployment.
11 |
12 | ## Using the Embedding API v3
13 |
14 | The [Tableau Embedding API v3](https://help.tableau.com/current/api/embedding_api/en-us/index.html) provides the latest iteration of embedding capabilities for the front-end of your application. It features a modern approach to initializing embedded content using [web components](https://www.webcomponents.org/introduction). The Embedding API v3 provides improved security and integration capabilities through Connected Apps or with an External Authorization Server (EAS), both based on the JWT standard. If you are starting a new project, we recommend using this API.
15 |
16 | The basic embed code using the Embedding API v3 and the `` web component looks something like this:
17 |
18 |
19 | ```html
20 |
25 |
26 |
27 |
28 |
32 |
33 |
36 |
37 |
38 | ```
39 |
40 | Here's what it looks like when you use the Embedding API v3 code to embed the viz on a web page:
41 |
42 |
43 |
44 |
45 |
46 | ---
47 |
48 | ### Join the Tableau Developer Program
49 |
50 | To keep up to date with future releases, join the [Tableau Developer Program](https://www.tableau.com/developer) to gain access to developer previews and demos, as well as your own Tableau Cloud Developer site.
51 |
52 | ---
53 |
54 | ## Use cases for the Embedding API v3
55 |
56 | The Embedding API v3 provide access to a large set of capabilities that allow for granular control of interactivity with embedded views, as well as the customization needed to create a seamless analytics experience within custom apps. Common use cases include:
57 |
58 | **Filtering and setting parameters on load** -- The options object gives you a clean interface for filtering the visualization as it loads. This is useful for loading the viz with the correct context given where the user is in your application or choices they have made.
59 | Note: Filtering with the Embedding API v3 is **not** a security mechanism. If you wish to have tamper-proof filters applied, you should use [user filters or database security]({{ site.baseurl }}/pages/04_multitenancy_and_rls).
60 |
61 | **Custom interfaces for interacting with the view** -- Dashboards often have elements for filtering, changing parameters, and switching tabs, amongst other things. When integrating with another application, you may want to match the look and feel of dashboard interfaces with the look and feel of the embedding application. For example, you can create a drop-down with HTML/CSS/JS that matches the style of your application and have that drop-down make JavaScript calls to filter the viz or change a parameter.
62 |
63 | **Custom interactions** -- Because the Embedding API gives you programmatic control over interaction, you can combine calls to create interactions that would not be possible otherwise. For example, you could create a button that switches to a specific visualization, changes a parameter, and then selects a set of marks all with a single click by the user. Or, when a user clicks a mark on a viz, you could have that drive a parameter change on another sheet. The combination of API calls is limited only by your imagination.
64 |
65 | **Integration with other systems** -- If a user finds an insight from a visualization, it is likely the user will want to take action on that insight elsewhere in the embedding application. With the Embedding API v3, you can query data from the visualization, listen to user events, and execute code to perform an appropriate action. This allows Tableau visual analytics to integrate within your application's workflows.
66 |
67 | In the image below, an HR-focused dashboard allows the user to select employees for an audit. When the user is ready, he or she can click "Run Audit" to generate a non-Tableau audit based on the selections made inside the Tableau viz.
68 |
69 | 
70 |
71 | ## Embedding API Resources
72 |
73 | Link | Description
74 | ---- | -----------
75 | [Embedding API v3 Documentation](https://help.tableau.com/current/api/embedding_api/en-us/index.html) | Official documentation for the Embedding API v3, including overview, key concepts, and API reference
76 | [Tableau Developer Program](https://www.tableau.com/developer) | Join the Tableau Developer Program and keep up to date with the latest news and previews of developer tools and APIs
77 | [Embedding API Tutorial](https://help.tableau.com/current/api/embedding_api/en-us/tutorial/tutorial.htm) | An interactive tutorial that will walk you through the key concepts of the Embedding API
78 | [Embedding API Samples](https://github.com/tableau/embedding-api-v3-samples#embedding-api-v3-samples) | Official samples, created and maintained by Tableau
79 | [Embedding Playground](https://developer.salesforce.com/tableau/embedding-playground/overview) | Provides developers an interactive learning environment for rapidly developing embedded analytics solutions
80 |
81 |
82 |
83 | *Next section: [Authentication and Single Sign-On]({{ site.baseurl }}/pages/02_auth_and_sso)*
84 |
--------------------------------------------------------------------------------
/pages/02_auth_and_sso.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Authentication and Single Sign-On (SSO)
3 | ---
4 |
5 | In most embedding scenarios, you will want to enable single sign-on so that the users that are signed in to your application do not have to also sign into Tableau Server or Tableau Cloud. There are various options to enable single sign-on (SSO) to Tableau.
6 |
7 | **Note:** This page discusses users logging into Tableau Server and Tableau Cloud. Related, but separate, is the issue of [user management]({{ site.baseurl }}/pages/03_server_management_and_restapi) in which you ensure all relevant users are registered and provisioned with Tableau.
8 |
9 | The guidance for which single sign-on option to use is:
10 |
11 | * **Connected Apps:** Use Connected Apps if you want to facilitate an explicit trust relationship between Tableau Cloud or Tableau Server and external applications where Tableau content is embedded. The trust relationship is established and verified through an authentication token in the [JSON Web Token (JWT) standard](https://datatracker.ietf.org/doc/html/rfc7519).
12 |
13 | * **External Authorization Servers (EAS):** Use EAS if you prefer to establish a trust relationship between Tableau Server and an identity provider you've already configured for Tableau Server. A standard OAuth flow is used to provide your users a single sign-on experience to Tableau content embedded in your external applications.
14 |
15 | * **Trusted Authentication:** Use Trusted Authentication if you wish to establish trust between Tableau Server and one or more web servers using an IP allowlist. Until the release of Connected Apps and EAS, Trusted Authentication was the most commonly implemented single sign-on solution. If advanced capabilities are required, Trusted Authentication will still be the best fit.
16 |
17 | * **Active Directory + Kerberos:** If all of your users are registered in your Active Directory instance and you already use Kerberos for authentication for other applications, use Active Directory + Kerberos.
18 |
19 | * **Active Directory + 'Enable automatic logon':** If all of your users are registered in your Active Directory instance, but you do not use Kerberos, use Active Directory with the 'Enable automatic logon' option (which uses Microsoft SSPI).
20 |
21 | * **SAML or OpenID:** If you have already use SAML or OpenID in your systems, configure Tableau Server to use your existing SAML or OpenID deployment.
22 |
23 | ## Connected Apps and External Authorization Servers (EAS)
24 |
25 | With Connected Apps (CA) and External Authorization Server (EAS), you have two modern options to implement seamless SSO authentication for embedded Tableau views. You can either setup a trust relationship between Tableau Server, or Tableau Cloud, and your external application (CA) using an authentication token in the JWT standard. Or you can establish a trust relationship between Tableau Server and an identity provider (EAS) to implement a standard OAuth flow. Both options provide additional security and control scopes over Trusted Authentication. To leverage either of these methods, you must use Tableau 2021.4 (or later) and the Embedding API v3 to embed your views.
26 |
27 | ### Connected Apps
28 |
29 | For information about using connected apps for embedding views from Tableau Cloud, see [Configure Tableau Connected Apps to Enable SSO for Embedded Content](https://help.tableau.com/current/online/en-us/connected_apps.htm). For information about setting up a connected app on Tableau Server or Tableau Cloud using the Tableau REST API, see the [Connected App Methods](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_connected_app.htm).
30 |
31 | Here is a short summary of the steps you need to take. There are four parts to enabling your embedded view as a connected app.
32 |
33 | 1. As a Tableau site administrator, login in to Tableau Cloud and create a new connected app. Or for Tableau Server or Tableau Cloud, use the REST API connected apps methods to create a new connected app). Make note of the client ID, as you will need this to create the JWT.
34 |
35 | 1. Generate the secret(s) for the connected app. Make note of this secret ID and secret value as you will need these when you create the JWT.
36 |
37 | 1. Configure the web server that hosts your embedded application to generate the (JWT). The JWT is generated dynamically for each user. There are JWT libraries and packages in various languages that you can use to build the JWT.
38 |
39 | 1. After you have the JWT, you need to pass this value to the Tableau viz web component ``. Once configured, users can securely view embedded content in your application without going through login screens.
40 |
41 | ### External Authorization Servers
42 |
43 | If you are using an IdP on Tableau Server to authenticate users, you can use an external authorization server (EAS). The EAS must be set up to provide a JSON web token (JWT) for each user. You use the JWT when you embed the Tableau view as a web component in your application. When the embedded content is loaded, the standard OAuth flow is used. After users sign in to the IdP, they are automatically signed in to Tableau Server. For information, see [Register EAS to Enable SSO for Embedded Content (Linux)](https://help.tableau.com/current/server-linux/en-us/connected_apps_eas.htm) or [Register EAS to Enable SSO for Embedded Content (Windows)](https://help.tableau.com/current/server/en-us/connected_apps_eas.htm).
44 |
45 | ### Add the JWT to the Tableau viz component
46 |
47 | Whether you are configuring your embedded web application to use EAS for Tableau Server, or as a connected app on Tableau Cloud or Tableau Server, you need to explicitly pass the JWT that is generated by the EAS or by your web server to the `` web component. You do this using the `token` attribute.
48 |
49 | For example, if you programmatically build the JWT for each user and assign it to a variable `JWT`, you might use a template literal to reference the JWT on your HTML page.
50 |
51 | ```html
52 |
53 |
56 |
57 |
58 |
59 | ```
60 |
61 | ## Trusted Authentication
62 |
63 | Trusted authentication is a piece of functionality specific to Tableau Server. It allows you to trust specific machines to authenticate users on their behalf. Because the authentication happens with simple HTTP requests, it is a versatile single sign-on option and can be used to integrate with, essentially, all other authentication systems or web auth flows.
64 |
65 | [The Trusted Authentication documentation](https://help.tableau.com/current/server/en-us/trusted_auth.htm) is a good resource for getting up and running, but below is a summary of the three steps in the trusted authentication workflow:
66 |
67 | 1. Configuration: This is a one-time step where you configure Tableau Server to 'trust' specific IP addresses, which will then be allowed to authenticate users. The machines to trust are usually the machines running your web application. [[Details]](https://help.tableau.com/current/server/en-us/trusted_auth_trustIP.htm)
68 | 1. POST Request: When the user navigates to a page in your web application that contains Tableau content, the web application will make a server-side POST request to Tableau Server passing in the users's Tableau Server username, the site the content exists on, and, optionally, the client's IP address in the form data. If the IP address making the request is trusted, and the user exists in Tableau Server, Tableau Server will return a ticket. [[Details]](https://help.tableau.com/current/server/en-us/trusted_auth_webrequ.htm)
69 | 1. Client loads the view with the ticket: Your web application now instructs the client to load the url of the desired resource, with the ticket inserted. If the ticket is valid, Tableau Server will start a session for the user and the user will see the visualization. Of course, the user does not see the HTTP requests going on behind the scenes, but simply loads a page in your application and sees embedded Tableau content without having to sign in. [[Details]](https://help.tableau.com/current/server/en-us/trusted_auth_webURL.htm)
70 |
71 | Additional considerations:
72 |
73 | * A common desire is to use a single 'service' account to authenticate the users. This is not a recommended approach, because it does not allow you to apply [data security]({{ site.baseurl }}/pages/04_multitenancy_and_rls) or to track usage on a per-user basis.
74 | * The trusted ticket is redeemable only once within three minutes of being issued and establishes a Tableau Server session for the user. The session allows the user to access any of the views that they have access to, as determined by the user and content permissions on the server. For more information, see [Trusted Authentication](https://help.tableau.com/current/server/en-us/trusted_auth.htm).
75 | * By default, tickets can be redeemed only for embedded visualizations, and not for other content pages in Tableau Server. To enable the user to see those, you must configure [unrestricted tickets](https://kb.tableau.com/articles/issue/login-prompt-when-embedding-server). See also: the [embedding non-view content]({{ site.baseurl }}/pages/06_embedding_non_view_content) page in this playbook.
76 | * If your web application has dynamic IP addresses, such that it is not feasible to trust a specific set of static IP addresses, you have a couple of options. You could create a small 'ticket requester' application that only allows requests from your web application. The 'ticket requester' requests tickets from the server, and then returns them to your web application. You can then deploy this 'ticket requester' application to a static IP address. Or you could consider leveraging one of the other authentication mechanisms listed above that do not depend on an IP allowlist.
77 |
78 | ## Kerberos, Active Directory, SAML, and OpenID
79 |
80 | * To use Kerberos for SSO, you must first [configure Tableau Server to Use Active Directory](https://help.tableau.com/current/server/en-us/config_general.htm#UserAuth) and then [configure Tableau Server to use Kerberos](https://help.tableau.com/current/server/en-us/config_kerberos.htm)
81 |
82 | * To use SSPI for single sign-on, check the 'Enable automatic logon' option when [configuring Tableau Server to Use Active Directory](https://help.tableau.com/current/server/en-us/config_general.htm#UserAuth)
83 |
84 | * [Configuring Tableau Server for Server-wide SAML](https://help.tableau.com/current/server/en-us/config_saml.htm)
85 | Alternatively, if each of your clients will have their own SAML iDP, you will need to [configure Tableau Server for site-specific SAML](https://help.tableau.com/current/server/en-us/saml_site_specific.htm)
86 |
87 | * [Configure Tableau Server for OpenID](https://help.tableau.com/current/server/en-us/openid_auth_server_config.htm)
88 |
89 |
90 | *Next section: [User Management, Content Management & Display with the REST API]({{ site.baseurl }}/pages/03_server_management_and_restapi)*
91 |
--------------------------------------------------------------------------------
/pages/03_server_management_and_restapi.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: User Management, Content Management, and Display with the REST API
3 | ---
4 |
5 | Embedding a single visualization with the [Embedding API v3]({{ site.baseurl }}/pages/01_embedding_and_jsapi) and enabling [single sign-on]({{ site.baseurl }}/pages/02_auth_and_sso) is a crucial first step, but is only part of the story when building scaled solutions that use Tableau content as components. You often will have to use the REST API to integrate the users and content between your system and Tableau Server.
6 |
7 | The REST API allows you to query and manage sites, users, groups, workbooks, data sources, projects, and subscriptions/schedules. The automation and integration use cases are effectively infinite, but the most common workflows it is used for in embedded analytics are:
8 |
9 | * User creation to sync with the users in the embedding application
10 | * Dynamic display of workbooks and data sources that a user has access to
11 | * Publishing workbooks as the last step in a content-integration workflow
12 |
13 | Because the REST API is called via HTTP, you can use whichever programming language is most appropriate. However, there is also the [Server Client Library](https://github.com/tableau/server-client-python) which simplifies the code required. The Server Client Library is currently only available in Python.
14 |
15 | Most commonly, you will make REST API calls from your web application's server-side code. In some cases, you will want to make those calls client-side (for example, if you are building web pages with no control over the Server-side logic). For those scenarios, [Tableau Server enables CORS support](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_fundamentals.htm#Enabling).
16 |
17 | Below are high-level descriptions of the flows required to enable the most common REST API use cases, but you should [read the documentation](https://help.tableau.com/current/api/rest_api/en-us/help.htm) to learn how to make the individual calls. [The concepts](https://help.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_concepts.htm%3FTocPath%3DConcepts%7C_____0) section is a good place to start and to learn about subjects such as filtering, sorting, and paginating. Also, be sure to explore [the REST API samples repository](https://github.com/tableau/rest-api-samples).
18 |
19 | ## User Creation
20 |
21 | Often, your application will store users. Except in the case of syncing with Active Directory, you will need to replicate those users into Tableau Server. You will likely have a script that adds all existing users to Server as a set-up step, and some code that executes when a new user is added to your system. To add a single user to Tableau Server:
22 |
23 | 1. [Sign in](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_auth.htm)
24 | 1. [Add user to site](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_users_and_groups.htm#add_user_to_site) (If the user does not exist, they will be added to Tableau Server and to the site you specify)
25 | 1. [Sign out](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_auth.htm)
26 |
27 | That's all there is to adding a new user, but you will likely want to make some other calls to ensure the new user has access to the correct content. See the [Tableau REST API](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api.htm) documentation for more details and examples.
28 |
29 | ## Dynamic Display of Content
30 |
31 | If your users have access to multiple workbooks and data sources, you will likely want to make a Table of Contents page similar to the below.
32 |
33 | 
34 |
35 | And if *different* users have access to *different* sets of content, it will not make sense for the list of available content to be hard-coded. Therefore, you need to use the REST API to query the workbooks available to the logged-in user. To achieve the above, you also need to grab the thumbnails.
36 |
37 | 1. [Sign in](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_auth.htm) (as admin)
38 | 1. [Query workbooks for user](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#query_workbooks_for_user) passing in the username of the user currently logged in to your application
39 | 1. For each workbook returned: [Grab the Preview Image](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#query_view_with_preview)
40 | 1. Populate the Table of Contents with links to pages that embed the workbooks, using the preview images.
41 | 1. When the user clicks one of the links, they are brought to a page in your application that embeds the chosen dashboard. To avoid having to hard-code these embedding pages, you will likely create a dynamic web page that can be passed the workbook name and view name so that you can construct the url for the Embedding API v3 to use. The details of engineering that dynamic webpage depend on your web application and front-end framework.
42 |
43 | ## Publishing Workbooks
44 |
45 | The typical workflow for building and publishing content is to do it manually with Tableau Desktop. You might need to publish with the REST API for a variety of reasons. For example:
46 |
47 | * Using the [Document API]({{ site.baseurl }}/pages/04_multitenancy_and_rls) to generate workbooks from a template and then publishing each of those to Server
48 | * Creating a script that runs on install of Server to publish a set of pre-built dashboards
49 | * Scripting migration from site to site or server to server
50 |
51 | Depending on the size of the workbook or data source, it may be possible to publish with a single call. If the file is large, you will need to send the file in pieces. For more details, see [the documentation on publishing with the REST API](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_publish.htm?TocPath=Concepts%7C_____11).
52 |
53 |
54 | *Next section: [Multi-Tenancy and Row-Level Security]({{ site.baseurl }}/pages/04_multitenancy_and_rls)*
55 |
--------------------------------------------------------------------------------
/pages/04_multitenancy_and_rls.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Multi-tenancy & Row-level Security
3 | ---
4 |
5 | Scaling an embedded analytics deployment of Tableau often means providing content to different groups of users. There is a security implication of making sure the different groups do not see each others' data (and metadata). There is also a deployment and maintenance consideration: Common dashboards should have to be built only once, even if the different groups of users access different data with those common dashboards.
6 |
7 | ## Multi-tenancy, Sites, & Projects
8 |
9 | The two key tools for defining which users can see which workbooks and datasources are **Projects** and **Sites**.
10 |
11 | * [Sites](https://help.tableau.com/current/server/en-us/sites_intro.htm) act as a logical firewall. If you create a site for each client or company that will access your Tableau content, you can be assured that users on one site will not learn about users or access content on another site.
12 |
13 | * [Projects](https://help.tableau.com/current/server/en-us/projects.htm) (combined with Permissions) allow you to define which users inside the site can access which pieces of content. Permissions can be set at a granular level so that you can give different levels of permission to different users or groups.
14 |
15 | If you have a multi-tenant deployment of Tableau Server, you should have one site per tenant. This is the only bullet-proof method of assuring different tenants do not learn about each other or see each others' data. Projects should be used in addition to sites to give access to different groups inside of a company.
16 |
17 | ## Row-level Security
18 |
19 | Row-level Security is both a security and content management concern. The simplest way to make sure users only see their data is to manually build a dashboard for each of them. This is not scalable, so Tableau provides a variety of ways to build a single dashboard that is filtered to the correct data. The correct method to use depends on your data environment.
20 |
21 | Here is some guidance (details for each of the methods are below):
22 |
23 | * If you have a separate database for each tenant, build a template dashboard and use the Document API to clone the dashboard for each tenant.
24 | * If your tenants share a database and there is filtering/entitlement logic to separate who should see which row, use user filters.
25 | * Use Initial SQL + in-database security if you already have in-database security set up *and* plan on using live connections to the database
26 |
27 | ### Template Dashboard + Document API
28 |
29 | [The Document API](https://github.com/tableau/document-api-python) is a Python API for modifying and querying Tableau content files (TWB/TWBX, TDS/TDSX). If your data environment is such that each tenant has their own database (and they are each structurally similar), then you can use the Document API to create a single workbook for each of your tenants. The workflow is:
30 |
31 | First, build a 'template' workbook that points at a single database. Build the necessary visualizations and dashboards.
32 | Then, create a script that:
33 |
34 | 1. For each tenant, uses the Document API to change the database connection information, and save a copy. [Sample Script](https://github.com/tableau/document-api-python/blob/master/samples/replicate-workbook/replicate_workbook.py)
35 | 1. Uses the [REST API (or Server Client Library)]({{ site.baseurl }}/pages/03_server_management_and_restapi) to publish each tenant's workbook to Server.
36 |
37 | The above is all that is necessary if your workbooks have 'embedded datasources' (the datasources haven't been published separately). If you do choose to [publish the datasources to take advantage of Tableau Server's Data Server](https://help.tableau.com/current/pro/desktop/en-us/publish_datasources.htm), the Document API will still work. You will just have to apply the above workflow first to the datasource(s) and then to the workbooks:
38 |
39 | First, build a 'template' datasource, publish it, and build a 'template' workbook that points at the published datasource.
40 | Then, create a script that:
41 |
42 | 1. For each tenant, uses the Document API to change the database connection information in the TDS (datasource file), and save a copy.
43 | 1. Uses the [REST API (or Server Client Library)]({{ site.baseurl }}/pages/03_server_management_and_restapi) to publish each tenant's datasource to Server. The datasource will now have a connection string that you can use in the workbooks...
44 | 1. For each tenant, for each workbook, uses the Document API to change the published datasource connection information, and save a copy.
45 | 1. Uses the [REST API (or Server Client Library)]({{ site.baseurl }}/pages/03_server_management_and_restapi) to publish each all of the workbooks to Server.
46 |
47 | ### User Filters
48 |
49 | If your tenants 'share' a database server, and there is logic in the database that determines which users can see which rows, you can build your datasources and workbooks using 'User Filters' which is a way to filter based on which user is currently logged in to Tableau Server.
50 |
51 | Essentially, when connecting to your data in Tableau Desktop you will JOIN the tables containing the data necessary for analysis to the entitlements table so that each row has a column ([Owner] in the below example) specifying which Tableau Server user it belongs to. Then you can create a calculation `username() = [Owner]` and filter to `True`.
52 |
53 | **See also**
54 |
55 | Link | Description
56 | ---- | -----------
57 | [Setting Up User Filters](https://help.tableau.com/current/pro/desktop/en-us/publish_userfilters_create.htm) | Base help article on setting up user filters using the username() method
58 | [Customize and Control Data Access Using User Attributes](https://help.tableau.com/current/api/embedding_api/en-us/docs/embedding_api_user_attributes.html) | Control and customize the user experience based on users’ contexts
59 |
60 | ### In-database Security and Initial SQL
61 |
62 | If your database has existing security, such that setting the user to run the queries as will return the correct rows for that user, you can leverage that security with Initial SQL. Because this Initial SQL command will only run when queries are sent from Tableau to the database, this option only works for live connections to the database.
63 |
64 | To use Initial SQL: When connecting to your datasource in Tableau Desktop, click "Initial SQL". Type the correct command that will set all following queries to run as the logged-in user. For example, in Microsoft SQL Server, you might run:
65 |
66 | ```
67 | EXECUTE AS USER = [TableauServerUser] WITH NO REVERT;
68 | ```
69 |
70 | And now build your workbook normally. All sessions with the database will begin by running the Initial SQL that you specified.
71 |
72 | **See also**
73 |
74 | Link | Description
75 | ---- | -----------
76 | [Setting Up Initial SQL](https://help.tableau.com/current/pro/desktop/en-us/connect_basic_initialsql.htm) | Base help article on using setting up and using Initial SQL
77 |
78 |
79 |
80 | *Next section: [Embedding in Salesforce]({{ site.baseurl }}/pages/05_embedding_in_other_apps)*
81 |
--------------------------------------------------------------------------------
/pages/05_embedding_in_other_apps.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Embedding in Salesforce
3 | ---
4 |
5 | ## Embedding into Salesforce Lightning
6 |
7 | Much of this playbook focuses on embedding Tableau content in custom-developed web applications, but there is also support for a common embedding scenario with Salesforce.
8 |
9 | Tableau provides a free Salesforce Lightning web component that facilitates embedding scenarios within the Salesforce ecosystem.
10 |
11 | * For information about the Tableau View and Tableau Pulse Lightning web components, see the [Tableau Lightning Web Components](https://help.tableau.com/current/online/en-us/lwc_seamless_auth.htm) documentation.
12 | * Tableau LWC components can also be customized; see the [Tableau Insights Delivered Directly to Salesforce with Dynamic Data Security](https://developer.salesforce.com/blogs/2024/07/tableau-insights-delivered-directly-to-salesforce-with-dynamic-data-security) blog post for details and an example.
13 |
14 |
15 | *Next section: [Embedding Analytics Experiences]({{ site.baseurl }}/pages/06_embedding_non_view_content)*
16 |
--------------------------------------------------------------------------------
/pages/06_embedding_non_view_content.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Embedding Analytics Experiences
3 | ---
4 |
5 | Almost all embedded deployments of Tableau embed pre-built dashboards. But some embed other components of Tableau such as:
6 |
7 | * web authoring experience
8 | * The Tableau Server UI (embedded for integration and single sign-on reasons). (Note that Tableau does not offer support for this deployment).
9 |
10 | The easiest and best way to include web authoring in your embedding scenario is to use the [Tableau Embedding API v3](https://help.tableau.com/current/api/embedding_api/en-us/index.html). The Tableau Embedding API v3 provides a web component, ``, which in just a few lines of HTML code, allows users to author the view in your web application (if permissions allow). You can also use the Embedding API v3 to call JavaScript methods that let you to customize the authoring lifecycle. For example, you can dynamically configure and switch between the authoring or viewing experience.
11 |
12 | ## Embedding Web Authoring
13 |
14 | Web authoring allows someone to edit a visualization just as they would in Tableau Desktop.
15 | Embedding a view with web authoring gives your users the ability to modify and update a view, all while staying in their workflow. Embedded web authoring means that you can create comprehensive embedded applications for your customers that include options for self-servicing.
16 |
17 | When it comes to editing a visualization, it is often acceptable to simply use the default settings and allow the user to click **Edit** on the toolbar of an embedded dashboard. The result will be a new browser tab opened to the web authoring screen for that workbook. However, now you can embed the Tableau authoring experience directly in the application. There's no need to have users jump to another window or tab. You can use the **Edit** button in the toolbar, or create your own edit button that matches the look and feel of your application. To do that:
18 |
19 | * Add a `` element to your HTML code or use the Embedding API v3 JavaScript methods to create a `TableauAuthoringViz` object. Set the `src` attribute or property to the URL of the viz. Then link to, or import, the Embedding API v3 library.
20 |
21 | ```html
22 |
24 |
25 |
26 | ```
27 |
28 | ### Create a custom editing workflow
29 |
30 | To create your own editing workflows, you can hide the default **Edit**, **Close**, and **Edit in Desktop** buttons on the toolbar, by setting attributes on the `` and `` web components, or by setting those properties on the JavaScript objects. And then create your own custom buttons in your application and switch between authoring and viewing.
31 |
32 | You could also change a setting to suppress the default actions that occur when the **Close**, **Edit**, or **Edit in Desktop** buttons in the toolbar are clicked. You can then setup event listeners (for example, `EditButtonClick`, or `PublishedAs`) to handle the button clicks and customize the authoring and publishing flow to suit the needs of your users. For example, you might follow this scenario to switch between viewing and web authoring modes.
33 |
34 | 1. Create a `` component (or `TableauViz` object), set the `suppress-default-edit-behavior` attribute to turn off the default actions that occur when the user clicks the **Edit** button, or closes, or publishes the view.
35 |
36 | 1. In the `` component, set the `src` URL and then add the `onEditButtonClicked` event listener to call your custom handler (`handleEditButtonClicked()`) when the user wants to switch to edit mode.
37 |
38 | ```html
39 |
40 |
46 |
47 |
48 | ```
49 |
50 | 1. Create a `` component or `TableauAuthoringViz` object, but don't specify the `src` URL to the view, so that the view doesn't appear until after the **Edit** button is clicked. You can also set the HTML style properties to hide the component or `` that will contain the authoring view.
51 |
52 | 1. In your custom `handleEditButtonClicked()` method, you should assign the `src` URL to the `TableauAuthoringViz` object so that when it is rendered it shows the view. You should also set the style properties to show the authoring component and to hide the `TableauViz` object.
53 |
54 | 1. In the `
` component, add the `onWorkbookPublishedAs` event attribute or property to be able to get the new URL for the saved workbook. In your custom handler for the published-as event, assign the new URL to the `TableauViz` object.
55 |
56 | 1. In the `` component, add the `onWorkbookReadyToClose` event attribute to know when to switch back to view mode. In your custom event handler for the close event, hide the authoring component from view and show the viewing component.
57 |
58 | For an overview of the embedded web authoring feature, see [How to Enable Self-Service Analytics in Your Application with Embedded Web Authoring](https://www.tableau.com/blog/how-enable-self-service-analytics-your-application-embedded-web-authoring). For a hands-on tutorial, see [Embedded API - Web Authoring Tutorial](https://www.tableau.com/developer/learning/embedding-api-web-authoring-tutorial). And for more information, see [Embedded Web Authoring](https://help.tableau.com/current/api/embedding_api/en-us/docs/embedding_api_web_authoring.html) in the Embedding API v3 Help documentation.
59 |
60 | Additional Considerations:
61 |
62 | * You will likely want to allow your users to save their edits, but not have their personalized versions affect other users. In that case, you should turn off save permissions, but allow save-as permissions. It also makes sense to give each of the users a 'sandbox' project that only they can save to.
63 |
64 | * To give your user access to personalized content that they create, your application will have to be dynamic to show users all content they have access to, not just those you create. See the page on [Using the REST API to display dynamic content]({{ site.baseurl }}/pages/03_server_management_and_restapi).
65 |
66 | * If you are using Connected Apps for embedding views, be sure to set the scope (`scp`) claim in the JSON Web Token (JWT) to include both web authoring (`tableau:views:embed_authoring`) and embedded viewing (`tableau:views:embed`). For information about Connected Apps, see [Authentication and Single Sign-On (SSO)]({{site.baseurl}}/pages/02_auth_and_sso) and [Configure Tableau Connected Apps to Enable SSO for Embedded Content](https://help.tableau.com/current/online/en-us/connected_apps.htm).
67 |
68 | ## Embedding other Tableau Server pages
69 |
70 | You can also use the iframe approach to embed the Tableau Server UI. This is a rarer technique, but is useful if you want to use the out-of-the-box UI, but want to achieve single sign-on from your application.
71 |
72 | To do so, simply embed http://{server}/trusted/{ticket}/t/{site}/ into an iframe. Be sure to enable [unrestricted tickets](https://kb.tableau.com/articles/issue/login-prompt-when-embedding-server).
73 |
74 |
75 | *Next section: [Development and Deployment]({{ site.baseurl }}/pages/07_development_and_deployment)*
76 |
--------------------------------------------------------------------------------
/pages/07_development_and_deployment.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Development and Deployment
3 | ---
4 |
5 | Developing content in Tableau does not require traditional source control or dev-test-prod techniques. However, you may wish to integrate the Tableau content development and deployment into your existing development systems. In this article, we specifically cover dev-test-prod and source control.
6 |
7 | ## Dev-Test-Prod
8 |
9 | If you have a workbook that is in development or test modes and needs to be promoted to the next steps, you may have to do one or both of these things:
10 |
11 | * Point the workbook to a different datasource
12 | * Promote the workbook inside Tableau Server
13 |
14 | To point the workbook to a different datasource, you can use the [Document API](https://github.com/tableau/document-api-python) to modify the connection string of the workbook. If the workbook is already on Server, you will need to:
15 |
16 | 1. Use the [REST API]({{ site.baseurl }}/pages/03_server_management_and_restapi) to download the workbook
17 | 1. Use the Document API to change the connection string
18 | 1. Republish the workbook with the REST API
19 |
20 | The technique for promoting inside of Tableau Server depends on whether you separate your environments with projects, with sites, or with different servers.
21 | To promote from one project to another, simply use the [REST API Update Workbook endpoint](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_workbook) to change the project.
22 |
23 | To promote from one site to another:
24 |
25 | 1. Download with the REST API
26 | 1. (Optional) Use the REST API to delete the workbook from the old environment
27 | 1. Use the Document API to change the connection string (if necessary)
28 | 1. Publish with the REST API to the new site
29 |
30 | To promote from one server to another, use the site-to-site technique, but you will have to sign in to the new server with the REST API to publish.
31 |
32 | Here are some samples:
33 |
34 | * [Migrate projects using the Python Server Client Library](https://github.com/tableau/server-client-python/blob/master/samples/move_workbook_projects.py)
35 | * [Migrate sites using the Python Server Client Library](https://github.com/tableau/server-client-python/blob/master/samples/move_workbook_sites.py)
36 | * [C# tool for migrating with the REST API](https://github.com/tableau/TabMigrate)
37 | * [Modify connection information with the Document API](https://github.com/tableau/document-api-python/blob/master/samples/replicate-workbook/replicate_workbook.py)
38 |
39 | ## Source Control
40 |
41 | Tableau Server has built in [revision history](https://help.tableau.com/current/server/en-us/revision_history_maintain.htm), but you must enable it for a given site. Once enabled, it stores each published version of a workbook or data source, with full access to download or restore back to a given point. Revision history can also be accessed via the Tableau [REST API](https://help.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Get_Workbook_Revisions%3FTocPath%3DAPI%2520Reference%7C_____42), so this functionality can be integrated in with other tools you have.
42 |
43 | The Tableau content files (TDS and TWB) are XML files and thus can be easily stored in any sort of external revision control system. When you create an extract (TDE) or save a packaged workbook (TDSX or TWBX file), the files are binary and thus source control is less useful. Doing diff processes on workbook and data source files is not particularly useful, since there is no real easy way to merge changes with Tableau files, but having a check-in process to manage who is working on a workbook or data source can definitely have advantages.
44 |
--------------------------------------------------------------------------------
/pages/search.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Search
3 | layout: search
4 | ---
5 |
6 |
--------------------------------------------------------------------------------