Skip to content

Commit bd5c18c

Browse files
authored
Merge pull request #247 from tableau/dev
Release Extensions 1.3
2 parents 1c10be3 + ab99a0d commit bd5c18c

File tree

120 files changed

+14856
-17330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+14856
-17330
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ node_modules/
66
npm-debug.log
77
package-lock.json
88
Gemfile.lock
9+
yarn.lock
10+
11+
# Ignore webpack generated files
12+
dist/

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ The Extensions API lets you do more without leaving Tableau. Build Tableau exten
1515
1. Copy the `.trex` files of the sample you wish to run to `~\Documents\My Tableau Repository (Beta)\Extensions` so they are available to Tableau.
1616
2. Open a command prompt window to the location where you cloned this repo.
1717
3. Run `npm install`.
18-
4. Run `npm start`.
19-
5. Launch Tableau and use the sample in a dashboard.
18+
4. Run `npm run build`.
19+
5. Run `npm start`.
20+
6. Launch Tableau and use the sample in a dashboard.
2021

21-
>**Note** The web server just serves the extension samples and tutorial, which have URLs similar to the following: `http://localhost:8765/Samples/DataSources/datasources.html`
22+
### Typescript Development
23+
Samples written in Typescript are located in the Samples-Typescript folder. To support local typescript development, `npm run dev` command starts up the http server and actively listens for changes to the .ts files located in the Sample-Typescript folder.
24+
25+
>**Note** The web server just serves the extension samples and tutorial, which have URLs similar to the following: `http://localhost:8765/Samples/DataSources/datasources.html` or `http://localhost:8765/Samples-Typescript/DataSources/datasources.html`
2226
> This local web server is not intended to serve the Extensions API Help pages.
2327
> View the Help on GitHub at [https://tableau.github.io/extensions-api](https://tableau.github.io/extensions-api).
2428
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest manifest-version="0.1" xmlns="http://www.tableau.com/xml/extension_manifest">
3+
<dashboard-extension id="com.tableau.extensions.samples.datasources" extension-version="0.6.0">
4+
<default-locale>en_US</default-locale>
5+
<name resource-id="name"/>
6+
<description>DataSources Sample</description>
7+
<author name="tableau" email="[email protected]" organization="tableau" website="https://www.tableau.com"/>
8+
<min-api-version>0.8</min-api-version>
9+
<source-location>
10+
<url>http://localhost:8765/Samples-Typescript/DataSources/datasources.html</url>
11+
</source-location>
12+
<icon>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAlhJREFUOI2Nkt9vy1EYh5/3bbsvRSySCZbIxI+ZCKsN2TKtSFyIrV2WuRCJuBiJWxfuxCVXbvwFgiEtposgLFJElnbU1SxIZIIRJDKTrdu+53Uhra4mce7Oe57Pcz7JOULFisViwZ+29LAzOSjQYDgz1ZcCvWuXV11MJpN+OS/lm6179teqH0yDqxPTCyKSA8DcDsyOmOprnCaeP7459pdgy969i0LTC3IO/RQMyoHcQN+3cnljW3dNIFC47qDaK3g7BwdTkwBaBELT4ZPOUVWgKl4ZBnjxJPUlMDnTDrp0pmr6RHFeEjjcUUXPDGeSEwDN0Xg8sivxMhJNjGzbHd8PkM3eHRfkrBM5NkcQaY2vUnTlrDIA0NoaX+KLXFFlowr14tvVpqb2MICzmQcKqxvbumv+NAhZGCCIPwEw6QWXKYRL/VUXO0+rAUJiPwAk5MIlgVfwPjjHLCL1APmHN94ZdqeYN+NW/mn6I4BvwQYchcLnwFhJMDiYmlRxAzjpKWZkYkUCcZ2I61wi37tLbYyjiN0fHk5Oz3nGSLSzBbNHCF35R7f6K1/hN9PRhek11FrymfQQQKB4+Gl05P2qNRtmETlXW7e+b2z01dfycGNbfFMAbqNyKp9Jp4rzOT8RYFs0njJkc2iqsCObvTsOsDWWqA5C1uFy+Uz/oXJeKwVT4h0RmPUXhi79vuC0Ku6yOffTK3g9lfxfDQAisY516sg5kfOCiJk7HoLt2cf9b/9LANAc7dznm98PagG1fUOZ9IP5uMB8Q4CPoyNvausapkTt3rNMuvdf3C/o6+czhtdwmwAAAABJRU5ErkJggg==</icon>
13+
<permissions>
14+
<permission>full data</permission>
15+
</permissions>
16+
</dashboard-extension>
17+
<resources>
18+
<resource id="name">
19+
<text locale="en_US">DataSources Sample</text>
20+
</resource>
21+
</resources>
22+
</manifest>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<meta charset="utf-8">
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<title>Datasources Sample</title>
9+
10+
<!-- jQuery -->
11+
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
12+
13+
<!-- Bootstrap -->
14+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
15+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" ></script>
16+
17+
<!-- Extensions Library (this will be hosted on a CDN eventually) -->
18+
<script src="../../lib/tableau.extensions.1.latest.js"></script>
19+
20+
<!-- Our webpack'd extension's code -->
21+
<script src="../../dist/datasources.js"></script>
22+
</head>
23+
<body>
24+
<div class="container">
25+
<!-- DataSources Table -->
26+
<div id="dataSources">
27+
<h4>All DataSources</h4>
28+
<div class="table-responsive">
29+
<table id="loading" class="table">
30+
<tbody><tr><td>Loading...</td></tr></tbody>
31+
</table>
32+
<table id="dataSourcesTable" class="table table-striped hidden">
33+
<thead>
34+
<tr>
35+
<th>DataSource Name</th>
36+
<th>Auto Refresh</th>
37+
<th style="width: 100%">Info</th>
38+
</tr>
39+
</thead>
40+
<tbody>
41+
</tbody>
42+
</table>
43+
</div>
44+
</div>
45+
46+
<!-- More dataSource info modal -->
47+
<div class="modal fade" id="infoModal" role="dialog">
48+
<div class="modal-dialog">
49+
<!-- Modal content-->
50+
<div class="modal-content">
51+
<div class="modal-header">
52+
<button type="button" class="close" data-dismiss="modal">&times;</button>
53+
<h4 class="modal-title">DataSource Details</h4>
54+
</div>
55+
<div id="dataSourceDetails" class="modal-body">
56+
<div class="table-responsive">
57+
<table id="detailsTable" class="table">
58+
<tbody>
59+
<tr>
60+
<td>DataSource Name</td>
61+
<td id="nameDetail"></td>
62+
</tr>
63+
<tr>
64+
<td>DataSource Id</td>
65+
<td id="idDetail"></td>
66+
</tr>
67+
<tr>
68+
<td>Type</td>
69+
<td id="typeDetail"></td>
70+
</tr>
71+
<tr>
72+
<td>Fields</td>
73+
<td id="fieldsDetail"></td>
74+
</tr>
75+
<tr>
76+
<td>Connections</td>
77+
<td id="connectionsDetail"></td>
78+
</tr>
79+
<tr>
80+
<td>Active Tables</td>
81+
<td id="activeTablesDetail"></td>
82+
</tr>
83+
</tbody>
84+
</table>
85+
</div>
86+
<div class="modal-footer">
87+
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
88+
</div>
89+
</div>
90+
</div>
91+
</div>
92+
93+
</div>
94+
</div>
95+
</div>
96+
</body>
97+
</html>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { DataSource } from '@tableau/extensions-api-types';
2+
3+
// Wrap everything in an anonymous function to avoid polluting the global namespace
4+
(async () => {
5+
class DataSources {
6+
// Avoid globals.
7+
constructor(private _$: JQueryStatic) { }
8+
9+
/**
10+
* Refreshes the given dataSource
11+
* @param dataSource
12+
*/
13+
private static async refreshDataSource(dataSource: DataSource) {
14+
await dataSource.refreshAsync();
15+
console.log(dataSource.name + ': Refreshed Successfully');
16+
}
17+
18+
/**
19+
* Initializes the extension
20+
*/
21+
public async initialize() {
22+
console.log('Waiting for DOM ready');
23+
await this._$.ready;
24+
console.log('Initializing extension API');
25+
await tableau.extensions.initializeAsync();
26+
27+
// Since dataSource info is attached to the worksheet, we will perform
28+
// one async call per worksheet to get every dataSource used in this
29+
// dashboard. This demonstrates the use of Promise.all to combine
30+
// promises together and wait for each of them to resolve.
31+
const dataSourceFetchPromises: Array<Promise<DataSource[]>> = [];
32+
33+
// To get dataSource info, first get the dashboard.
34+
const dashboard = tableau.extensions.dashboardContent.dashboard;
35+
// Then loop through each worksheet and get its dataSources, save promise for later.
36+
dashboard.worksheets.forEach(worksheet => dataSourceFetchPromises.push(worksheet.getDataSourcesAsync()));
37+
const fetchResults = await Promise.all(dataSourceFetchPromises);
38+
39+
// Maps dataSource id to dataSource so we can keep track of unique dataSources.
40+
const dataSourcesCheck = {};
41+
const dashboardDataSources: DataSource[] = [];
42+
43+
fetchResults.forEach(dss => {
44+
dss.forEach(ds => {
45+
if (!dataSourcesCheck[ds.id]) {
46+
// We've already seen it, skip it.
47+
dataSourcesCheck[ds.id] = true;
48+
dashboardDataSources.push(ds);
49+
}
50+
});
51+
});
52+
53+
this.buildDataSourcesTable(dashboardDataSources);
54+
55+
// This just modifies the UI by removing the loading banner and showing the dataSources table.
56+
this._$('#loading').addClass('hidden');
57+
this._$('#dataSourcesTable')
58+
.removeClass('hidden')
59+
.addClass('show');
60+
}
61+
62+
/**
63+
* Displays a modal dialog with more details about the given dataSource.
64+
* @param dataSource
65+
*/
66+
private async showModal(dataSource: DataSource) {
67+
const modal = this._$('#infoModal');
68+
69+
this._$('#nameDetail').text(dataSource.name);
70+
this._$('#idDetail').text(dataSource.id);
71+
this._$('#typeDetail').text((dataSource.isExtract) ? 'Extract' : 'Live');
72+
73+
// Loop through every field in the dataSource and concat it to a string.
74+
let fieldNamesStr = '';
75+
dataSource.fields.forEach(function(field) {
76+
fieldNamesStr += field.name + ', ';
77+
});
78+
// Slice off the last ", " for formatting.
79+
this._$('#fieldsDetail').text(fieldNamesStr.slice(0, -2));
80+
81+
// Loop through each connection summary and list the connection's
82+
// name and type in the info field
83+
const connectionSummaries = await dataSource.getConnectionSummariesAsync();
84+
let connectionsStr = '';
85+
connectionSummaries.forEach(function(summary) {
86+
connectionsStr += summary.name + ': ' + summary.type + ', ';
87+
});
88+
// Slice of the last ", " for formatting.
89+
this._$('#connectionsDetail').text(connectionsStr.slice(0, -2));
90+
91+
// Loop through each table that was used in creating this datasource
92+
const activeTables = await dataSource.getActiveTablesAsync();
93+
let tableStr = '';
94+
activeTables.forEach(function(table) {
95+
tableStr += table.name + ', ';
96+
});
97+
// Slice of the last ", " for formatting.
98+
this._$('#activeTablesDetail').text(tableStr.slice(0, -2));
99+
100+
// @ts-ignore
101+
modal.modal('show');
102+
}
103+
104+
/**
105+
* Constructs UI that displays all the dataSources in this dashboard
106+
* given a mapping from dataSourceId to dataSource objects.
107+
* @param dataSources
108+
*/
109+
private buildDataSourcesTable(dataSources: DataSource[]) {
110+
// Clear the table first.
111+
this._$('#dataSourcesTable > tbody tr').remove();
112+
const dataSourcesTable = this._$('#dataSourcesTable > tbody')[0];
113+
114+
// Add an entry to the dataSources table for each dataSource.
115+
for (const dataSource of dataSources) {
116+
// @ts-ignore
117+
const newRow = dataSourcesTable.insertRow(dataSourcesTable.rows.length);
118+
const nameCell = newRow.insertCell(0);
119+
const refreshCell = newRow.insertCell(1);
120+
const infoCell = newRow.insertCell(2);
121+
122+
const refreshButton = document.createElement('button');
123+
refreshButton.innerHTML = 'Refresh Now';
124+
refreshButton.type = 'button';
125+
refreshButton.className = 'btn btn-primary';
126+
refreshButton.addEventListener('click', () => DataSources.refreshDataSource(dataSource));
127+
128+
const infoSpan = document.createElement('span');
129+
infoSpan.className = 'glyphicon glyphicon-info-sign';
130+
infoSpan.addEventListener('click', () => this.showModal(dataSource));
131+
132+
nameCell.innerHTML = dataSource.name;
133+
refreshCell.appendChild(refreshButton);
134+
infoCell.appendChild(infoSpan);
135+
}
136+
}
137+
}
138+
139+
console.log('Initializing DataSources extension.');
140+
await new DataSources($).initialize();
141+
})();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest manifest-version="0.1" xmlns="http://www.tableau.com/xml/extension_manifest">
3+
<dashboard-extension id="com.tableau.extensions.samples.filtering" extension-version="0.6.0">
4+
<default-locale>en_US</default-locale>
5+
<name resource-id="name"/>
6+
<description>Filtering Sample</description>
7+
<author name="tableau" email="[email protected]" organization="tableau" website="https://www.tableau.com"/>
8+
<min-api-version>0.8</min-api-version>
9+
<source-location>
10+
<url>http://localhost:8765/Samples-Typescript/Filtering/filtering.html</url>
11+
</source-location>
12+
<icon>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAlhJREFUOI2Nkt9vy1EYh5/3bbsvRSySCZbIxI+ZCKsN2TKtSFyIrV2WuRCJuBiJWxfuxCVXbvwFgiEtposgLFJElnbU1SxIZIIRJDKTrdu+53Uhra4mce7Oe57Pcz7JOULFisViwZ+29LAzOSjQYDgz1ZcCvWuXV11MJpN+OS/lm6179teqH0yDqxPTCyKSA8DcDsyOmOprnCaeP7459pdgy969i0LTC3IO/RQMyoHcQN+3cnljW3dNIFC47qDaK3g7BwdTkwBaBELT4ZPOUVWgKl4ZBnjxJPUlMDnTDrp0pmr6RHFeEjjcUUXPDGeSEwDN0Xg8sivxMhJNjGzbHd8PkM3eHRfkrBM5NkcQaY2vUnTlrDIA0NoaX+KLXFFlowr14tvVpqb2MICzmQcKqxvbumv+NAhZGCCIPwEw6QWXKYRL/VUXO0+rAUJiPwAk5MIlgVfwPjjHLCL1APmHN94ZdqeYN+NW/mn6I4BvwQYchcLnwFhJMDiYmlRxAzjpKWZkYkUCcZ2I61wi37tLbYyjiN0fHk5Oz3nGSLSzBbNHCF35R7f6K1/hN9PRhek11FrymfQQQKB4+Gl05P2qNRtmETlXW7e+b2z01dfycGNbfFMAbqNyKp9Jp4rzOT8RYFs0njJkc2iqsCObvTsOsDWWqA5C1uFy+Uz/oXJeKwVT4h0RmPUXhi79vuC0Ku6yOffTK3g9lfxfDQAisY516sg5kfOCiJk7HoLt2cf9b/9LANAc7dznm98PagG1fUOZ9IP5uMB8Q4CPoyNvausapkTt3rNMuvdf3C/o6+czhtdwmwAAAABJRU5ErkJggg==</icon>
13+
</dashboard-extension>
14+
<resources>
15+
<resource id="name">
16+
<text locale="en_US">Filtering Sample</text>
17+
</resource>
18+
</resources>
19+
</manifest>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<meta charset="utf-8">
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<title>Filtering Sample</title>
9+
10+
<!-- jQuery -->
11+
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
12+
13+
<!-- Bootstrap -->
14+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
15+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" ></script>
16+
17+
<!-- Extensions Library (this will be hosted on a CDN eventually) -->
18+
<script src="../../lib/tableau.extensions.1.latest.js"></script>
19+
20+
<!-- Our webpack'd extension's code -->
21+
<script src="../../dist/filtering.js"></script>
22+
</head>
23+
<body>
24+
<div class="container">
25+
<!-- Filters Table -->
26+
<div id="filters">
27+
<h4>Current Filters</h4>
28+
<div class="table-responsive">
29+
<table id="loading" class="table">
30+
<tbody><tr><td>Loading...</td></tr></tbody>
31+
</table>
32+
<table id="filtersTable" class="table table-striped hidden">
33+
<thead>
34+
<tr>
35+
<th>Filtered Field</th>
36+
<th>Filtered Worksheet</th>
37+
<th>Filter Type</th>
38+
<th style="width: 100%">Current Values</th>
39+
</tr>
40+
</thead>
41+
<tbody>
42+
</tbody>
43+
</table>
44+
<table id="noFiltersWarning" class="table bg-danger hidden">
45+
<tbody><tr><td>There are no filters currently active in this dashboard.</td></tr></tbody>
46+
</table>
47+
</div>
48+
</div>
49+
50+
<!-- New Settings Submission Form -->
51+
<div id="filterActions">
52+
<button id="clear" type="button" class="btn btn-primary">Clear All</button>
53+
</div>
54+
</div>
55+
</body>
56+
</html>

0 commit comments

Comments
 (0)