├── screenshots
├── strava-oauth.png
├── strava-settings.png
├── tableau-publish.png
├── tableau-wdc-popup.png
└── tableau-wdc-authorize.png
├── schemas
├── strava
│ ├── gear.json
│ ├── activityStreams.json
│ ├── athlete.json
│ └── activities.json
└── tableau
│ ├── gear.json
│ ├── athlete.json
│ ├── activityStreams.json
│ └── activities.json
├── index.html
├── README.md
├── lib
├── es6-promise.min.js
├── popper.min.js
├── bootstrap.min.js
└── jquery-3.4.1.min.js
└── wdc.js
/screenshots/strava-oauth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takashibinns/tableau-wdc-strava/HEAD/screenshots/strava-oauth.png
--------------------------------------------------------------------------------
/screenshots/strava-settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takashibinns/tableau-wdc-strava/HEAD/screenshots/strava-settings.png
--------------------------------------------------------------------------------
/screenshots/tableau-publish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takashibinns/tableau-wdc-strava/HEAD/screenshots/tableau-publish.png
--------------------------------------------------------------------------------
/screenshots/tableau-wdc-popup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takashibinns/tableau-wdc-strava/HEAD/screenshots/tableau-wdc-popup.png
--------------------------------------------------------------------------------
/screenshots/tableau-wdc-authorize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takashibinns/tableau-wdc-strava/HEAD/screenshots/tableau-wdc-authorize.png
--------------------------------------------------------------------------------
/schemas/strava/gear.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "gear",
3 | "baseUrl": "https://www.strava.com/api/v3/athlete",
4 | "responseArrays":["bikes", "shoes"],
5 | "responseFields": [
6 | {
7 | "dataType": "int",
8 | "path":"id"
9 | },
10 | {
11 | "dataType": "bool",
12 | "path":"primary"
13 | },
14 | {
15 | "dataType": "string",
16 | "path":"name"
17 | },
18 | {
19 | "dataType": "float",
20 | "path":"distance"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/schemas/strava/activityStreams.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "activityStreams",
3 | "baseUrl": "https://www.strava.com/api/v3/activities/",
4 | "responseFields": [
5 | {
6 | "dataType": "string",
7 | "path":"type"
8 | },
9 | {
10 | "dataType": "array",
11 | "path":"data"
12 | },
13 | {
14 | "dataType": "string",
15 | "path":"series_type"
16 | },
17 | {
18 | "dataType": "int",
19 | "path":"original_size"
20 | },
21 | {
22 | "dataType": "string",
23 | "path":"resolution"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/schemas/tableau/gear.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "gear",
3 | "alias": "My Gear",
4 | "columns": [
5 | {
6 | "alias": "GearId",
7 | "dataType": "string",
8 | "id":"gear_id"
9 | },
10 | {
11 | "alias": "IsPrimary",
12 | "dataType": "bool",
13 | "description":"Is this your default gear selection?",
14 | "id":"gear_primary"
15 | },
16 | {
17 | "alias": "Gear Name",
18 | "dataType": "string",
19 | "id":"gear_name"
20 | },
21 | {
22 | "alias": "Total Distance",
23 | "dataType": "float",
24 | "description":"Total distance covered using this gear (meters)",
25 | "id":"gear_distance"
26 | },
27 | {
28 | "alias": "Gear Type",
29 | "dataType": "string",
30 | "description": "Distinguish between shoes and bikes",
31 | "id":"gear_type"
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/schemas/strava/athlete.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "athlete",
3 | "baseUrl": "https://www.strava.com/api/v3/athlete",
4 | "responseFields": [
5 | {
6 | "dataType": "int",
7 | "path":"id"
8 | },
9 | {
10 | "dataType": "string",
11 | "path":"username"
12 | },
13 | {
14 | "dataType": "string",
15 | "path":"firstname"
16 | },
17 | {
18 | "dataType": "string",
19 | "path":"lastname"
20 | },
21 | {
22 | "dataType": "string",
23 | "path":"city"
24 | },
25 | {
26 | "dataType": "string",
27 | "path":"state"
28 | },
29 | {
30 | "dataType": "string",
31 | "path":"country"
32 | },
33 | {
34 | "dataType": "string",
35 | "path":"sex"
36 | },
37 | {
38 | "dataType": "bool",
39 | "path":"premium"
40 | },
41 | {
42 | "dataType": "datetime",
43 | "path":"created_at"
44 | },
45 | {
46 | "dataType": "datetime",
47 | "path":"updated_at"
48 | },
49 | {
50 | "dataType": "int",
51 | "path":"badge_type_id"
52 | },
53 | {
54 | "dataType": "string",
55 | "path":"profile_medium"
56 | },
57 | {
58 | "dataType": "string",
59 | "path":"profile"
60 | },
61 | {
62 | "dataType": "bool",
63 | "path":"summit"
64 | },
65 | {
66 | "dataType": "int",
67 | "path":"follower_count"
68 | },
69 | {
70 | "dataType": "int",
71 | "path":"friend_count"
72 | },
73 | {
74 | "dataType": "float",
75 | "path":"weight"
76 | }
77 | ]
78 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Strava Web Data Connector
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |

22 |
23 |
24 |
Strava: WDC
25 |
26 |
27 |
28 |
Use the instructions here for help getting started.
29 |
30 |
31 |
32 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/schemas/tableau/athlete.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "athlete",
3 | "alias": "My Profile",
4 | "columns": [
5 | {
6 | "alias": "AthleteId",
7 | "dataType": "int",
8 | "columnRole":"dimension",
9 | "columnType":"discrete",
10 | "id":"athlete_id"
11 | },
12 | {
13 | "alias": "Username",
14 | "dataType": "string",
15 | "id":"athlete_username"
16 | },
17 | {
18 | "alias": "First Name",
19 | "dataType": "string",
20 | "id":"athlete_firstname"
21 | },
22 | {
23 | "alias": "Last Name",
24 | "dataType": "string",
25 | "id":"athlete_lastname"
26 | },
27 | {
28 | "alias": "Home City",
29 | "dataType": "string",
30 | "id":"athlete_city"
31 | },
32 | {
33 | "alias": "Home State",
34 | "dataType": "string",
35 | "id":"athlete_state"
36 | },
37 | {
38 | "alias": "Home Country",
39 | "dataType": "string",
40 | "id":"athlete_county"
41 | },
42 | {
43 | "alias": "Sex",
44 | "dataType": "string",
45 | "id":"athlete_sex"
46 | },
47 | {
48 | "alias": "Weight",
49 | "dataType": "float",
50 | "description":"The athelete's weight in kg",
51 | "id":"athlete_weight"
52 | },
53 | {
54 | "alias": "IsPremium",
55 | "dataType": "bool",
56 | "description": "Are you a premium strava member?",
57 | "id":"athlete_premium"
58 | },
59 | {
60 | "alias": "Profile",
61 | "dataType": "string",
62 | "description":"URL to the athelete's profile picture",
63 | "id":"athlete_profile"
64 | },
65 | {
66 | "alias": "IsSummit",
67 | "dataType": "bool",
68 | "description": "Do you a have a strava summit subscription?",
69 | "id":"athlete_summit"
70 | },
71 | {
72 | "alias": "Follower Count",
73 | "dataType": "int",
74 | "id":"athlete_follower_count"
75 | },
76 | {
77 | "alias": "Friend Count",
78 | "dataType": "int",
79 | "id":"athlete_friend_count"
80 | }
81 | ]
82 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tableau Web Data Connector for Strava
2 |
3 | ## WDC Usage
4 |
5 | ### Step 1: Strava.com
6 | Before you can use Strava as a data source, you need to enable a few settings through Strava's website. Login to strava with your web browser and navigate to your [settings page](https://www.strava.com/settings/api). Use the left navigation to find your **API Settings**. From here, you can get your Client ID and Secret. You'll need both these values for the WDC.
7 |
8 | 
9 |
10 | At the bottom of this page, you should see some OAuth settings. Enter _takashibinns.github.io_ as the callback domain and click the **Update** button.
11 |
12 | 
13 |
14 | ### Step 2: Tableau Desktop
15 | From Tableau Desktop, select Web Data Connector as your data source. Copy and paste the following link, into the popup window:
16 |
17 | [https://takashibinns.github.io/tableau-wdc-strava/index.html](https://takashibinns.github.io/tableau-wdc-strava/index.html)
18 |
19 | You should see a prompt for your Strava _Client ID_ and _Client Secret_. Enter the values, and click **Authorize Tableau**.
20 |
21 | 
22 |
23 | Now, you will have to enter your Strave email and password. After logging in, you will get prompted to authorize Tableau to pull data from your account. Click the **Authorize** button and you should be all set.
24 |
25 | 
26 |
27 | ## Tables
28 |
29 | - **My Activities**: The list of the user's activities. This contains aggregated data (avg, max, etc) for each activity.
30 | - **My Activity Streams**: This table contains detail-level stats for each activity. The longer your activity lasts, the more data will appear here. This table requires a separate API call for each activity, so if you have >500 activities in Strava you will likely hit your API limit.
31 | - **My Profile**: this is the logged in user's athelete information, use the AthleteId to join to Activities or Activity Streams
32 | - **My Gear**: The list of the user's bikes and shoes, use the GearId to join this table to Activities or Activity Streams
33 |
34 |
35 |
36 | ## Usage with Tableau Server
37 | To enable on Tableau Server, publish your data source to your Tableau Server as "Strava WDC". When you publish, make sure the Authentication is set to Embedded Password.
38 |
39 | 
40 |
41 | Before you can schedule the extrac refreshes, run the following TSM commands on the Tableau Server:
42 | ```
43 | tsm data-access web-data-connectors add --name 'Strava WDC' --url 'https://takashibinns.github.io:443/tableau-wdc-strava/index.html' --secondary 'https://www.strava.com/(.*),https://takashibinns.github.io/tableau-wdc-strava/(.*)'
44 | tsm pending-changes apply
45 | ```
46 |
47 | This will require a server restart, but once complete you should be able to schedule your refreshes.
48 |
49 | ### Background
50 | This step is needed, because the WDC links out to other 3rd party sites. The WDC webpage is hosted on github (the --url parameter) and this page makes references to other files/web pages (the --secondary parameter). This TSM command tells Tableau server that all these places are OK to be trusted.
51 |
52 | Strava allows API access through the use of refresh and access tokens. The access token is needed for every API call, as a security check. However, these access tokens are only valid for 6 hours. Once they expire, you are required to request a new access token by providing your refresh token. When you publish your Tableau Data Source, the refresh token, access token, and expiration time are embedded in the data source. This way Tableau Server can evaluate the access token when it goes to execute a refresh. If the access token is expired, it will use the access token to request a new one before asking for any data.
53 |
54 | When you publish the WDC to Tableau Server, you are embedding the client id, client secret, & refresh token so that Tableau Server can make all the API calls it needs without prompting you for permission.
55 |
56 | ### Strava Notes
57 | Strava enforces rate limiting on their APIs. According to their website, they limit the number of API calls to 600 requests every 15 minutes, and 30,000 per day. If you notice your WDC is getting stuck without error, it's likely because you've hit your API limit. This is more likely to happen with the Activity Stream table, as we have to query for each activity and then run a query for each activity to get it's data stream. This means if you have 100 activities, we make the initial API call for the list of activities and then 100 more for the stream data of each activity.
58 |
--------------------------------------------------------------------------------
/schemas/strava/activities.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "activities",
3 | "baseUrl": "https://www.strava.com/api/v3/athlete/activities",
4 | "responseFields": [
5 | {
6 | "dataType": "int",
7 | "path":"athlete.id"
8 | },
9 | {
10 | "dataType": "int",
11 | "path":"id"
12 | },
13 | {
14 | "dataType": "string",
15 | "path":"name"
16 | },
17 | {
18 | "dataType": "float",
19 | "path":"distance"
20 | },
21 | {
22 | "dataType": "int",
23 | "path":"moving_time"
24 | },
25 | {
26 | "dataType": "int",
27 | "path":"elapsed_time"
28 | },
29 | {
30 | "dataType": "float",
31 | "path":"total_elevation_gain"
32 | },
33 | {
34 | "dataType": "string",
35 | "path":"type"
36 | },
37 | {
38 | "dataType": "string",
39 | "path":"external_id"
40 | },
41 | {
42 | "dataType": "datetime",
43 | "path":"start_date"
44 | },
45 | {
46 | "dataType": "datetime",
47 | "path":"start_date_local"
48 | },
49 | {
50 | "dataType": "string",
51 | "path":"timezone"
52 | },
53 | {
54 | "dataType": "int",
55 | "path":"utc_offset"
56 | },
57 | {
58 | "dataType": "float",
59 | "path":"start_latlng.0"
60 | },
61 | {
62 | "dataType": "float",
63 | "path":"start_latlng.1"
64 | },
65 | {
66 | "dataType": "float",
67 | "path":"end_latlng.0"
68 | },
69 | {
70 | "dataType": "float",
71 | "path":"end_latlng.1"
72 | },
73 | {
74 | "dataType": "string",
75 | "path":"location_city"
76 | },
77 | {
78 | "dataType": "string",
79 | "path":"location_state"
80 | },
81 | {
82 | "dataType": "string",
83 | "path":"location_country"
84 | },
85 | {
86 | "dataType": "int",
87 | "path":"achievement_count"
88 | },
89 | {
90 | "dataType": "int",
91 | "path":"kudos_count"
92 | },
93 | {
94 | "dataType": "int",
95 | "path":"comment_count"
96 | },
97 | {
98 | "dataType": "int",
99 | "path":"athlete_count"
100 | },
101 | {
102 | "dataType": "int",
103 | "path":"photo_count"
104 | },
105 | {
106 | "dataType": "string",
107 | "path":"map.id"
108 | },
109 | {
110 | "dataType": "string",
111 | "path":"map.summary_polyline"
112 | },
113 | {
114 | "dataType": "bool",
115 | "path":"trainer"
116 | },
117 | {
118 | "dataType": "bool",
119 | "path":"commute"
120 | },
121 | {
122 | "dataType": "bool",
123 | "path":"manual"
124 | },
125 | {
126 | "dataType": "bool",
127 | "path":"private"
128 | },
129 | {
130 | "dataType": "bool",
131 | "path":"flagged"
132 | },
133 | {
134 | "dataType": "string",
135 | "path":"gear_id"
136 | },
137 | {
138 | "dataType": "bool",
139 | "path":"from_accepted_tag"
140 | },
141 | {
142 | "dataType": "float",
143 | "path":"average_speed"
144 | },
145 | {
146 | "dataType": "float",
147 | "path":"max_speed"
148 | },
149 | {
150 | "dataType": "float",
151 | "path":"average_cadence"
152 | },
153 | {
154 | "dataType": "float",
155 | "path":"average_watts"
156 | },
157 | {
158 | "dataType": "float",
159 | "path":"weighted_average_watts"
160 | },
161 | {
162 | "dataType": "float",
163 | "path":"kilojoules"
164 | },
165 | {
166 | "dataType": "float",
167 | "path":"device_watts"
168 | },
169 | {
170 | "dataType": "bool",
171 | "path":"has_heartrate"
172 | },
173 | {
174 | "dataType": "float",
175 | "path":"average_heartrate"
176 | },
177 | {
178 | "dataType": "float",
179 | "path":"max_heartrate"
180 | },
181 | {
182 | "dataType": "float",
183 | "path":"max_watts"
184 | },
185 | {
186 | "id": "PrCount",
187 | "dataType": "int",
188 | "path":"pr_count"
189 | },
190 | {
191 | "dataType": "int",
192 | "path":"total_photo_count"
193 | },
194 | {
195 | "dataType": "bool",
196 | "path":"has_kudos"
197 | },
198 | {
199 | "dataType": "float",
200 | "path":"suffer_score"
201 | }
202 | ]
203 | }
--------------------------------------------------------------------------------
/lib/es6-promise.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @overview es6-promise - a tiny implementation of Promises/A+.
3 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
4 | * @license Licensed under MIT license
5 | * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
6 | * @version 3.2.1
7 | */
8 |
9 | (function(){"use strict";function t(t){return"function"==typeof t||"object"==typeof t&&null!==t}function e(t){return"function"==typeof t}function n(t){G=t}function r(t){Q=t}function o(){return function(){process.nextTick(a)}}function i(){return function(){B(a)}}function s(){var t=0,e=new X(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){t.port2.postMessage(0)}}function c(){return function(){setTimeout(a,1)}}function a(){for(var t=0;J>t;t+=2){var e=tt[t],n=tt[t+1];e(n),tt[t]=void 0,tt[t+1]=void 0}J=0}function f(){try{var t=require,e=t("vertx");return B=e.runOnLoop||e.runOnContext,i()}catch(n){return c()}}function l(t,e){var n=this,r=new this.constructor(p);void 0===r[rt]&&k(r);var o=n._state;if(o){var i=arguments[o-1];Q(function(){x(o,r,i,n._result)})}else E(n,r,t,e);return r}function h(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(p);return g(n,t),n}function p(){}function _(){return new TypeError("You cannot resolve a promise with itself")}function d(){return new TypeError("A promises callback cannot return that same promise.")}function v(t){try{return t.then}catch(e){return ut.error=e,ut}}function y(t,e,n,r){try{t.call(e,n,r)}catch(o){return o}}function m(t,e,n){Q(function(t){var r=!1,o=y(n,e,function(n){r||(r=!0,e!==n?g(t,n):S(t,n))},function(e){r||(r=!0,j(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&o&&(r=!0,j(t,o))},t)}function b(t,e){e._state===it?S(t,e._result):e._state===st?j(t,e._result):E(e,void 0,function(e){g(t,e)},function(e){j(t,e)})}function w(t,n,r){n.constructor===t.constructor&&r===et&&constructor.resolve===nt?b(t,n):r===ut?j(t,ut.error):void 0===r?S(t,n):e(r)?m(t,n,r):S(t,n)}function g(e,n){e===n?j(e,_()):t(n)?w(e,n,v(n)):S(e,n)}function A(t){t._onerror&&t._onerror(t._result),T(t)}function S(t,e){t._state===ot&&(t._result=e,t._state=it,0!==t._subscribers.length&&Q(T,t))}function j(t,e){t._state===ot&&(t._state=st,t._result=e,Q(A,t))}function E(t,e,n,r){var o=t._subscribers,i=o.length;t._onerror=null,o[i]=e,o[i+it]=n,o[i+st]=r,0===i&&t._state&&Q(T,t)}function T(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,o,i=t._result,s=0;si;i++)e.resolve(t[i]).then(n,r)}:function(t,e){e(new TypeError("You must pass an array to race."))})}function F(t){var e=this,n=new e(p);return j(n,t),n}function D(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function K(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function L(t){this[rt]=O(),this._result=this._state=void 0,this._subscribers=[],p!==t&&("function"!=typeof t&&D(),this instanceof L?C(this,t):K())}function N(t,e){this._instanceConstructor=t,this.promise=new t(p),this.promise[rt]||k(this.promise),Array.isArray(e)?(this._input=e,this.length=e.length,this._remaining=e.length,this._result=new Array(this.length),0===this.length?S(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&S(this.promise,this._result))):j(this.promise,U())}function U(){return new Error("Array Methods must be provided an Array")}function W(){var t;if("undefined"!=typeof global)t=global;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=pt)}var z;z=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var B,G,H,I=z,J=0,Q=function(t,e){tt[J]=t,tt[J+1]=e,J+=2,2===J&&(G?G(a):H())},R="undefined"!=typeof window?window:void 0,V=R||{},X=V.MutationObserver||V.WebKitMutationObserver,Z="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),$="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,tt=new Array(1e3);H=Z?o():X?s():$?u():void 0===R&&"function"==typeof require?f():c();var et=l,nt=h,rt=Math.random().toString(36).substring(16),ot=void 0,it=1,st=2,ut=new M,ct=new M,at=0,ft=Y,lt=q,ht=F,pt=L;L.all=ft,L.race=lt,L.resolve=nt,L.reject=ht,L._setScheduler=n,L._setAsap=r,L._asap=Q,L.prototype={constructor:L,then:et,"catch":function(t){return this.then(null,t)}};var _t=N;N.prototype._enumerate=function(){for(var t=this.length,e=this._input,n=0;this._state===ot&&t>n;n++)this._eachEntry(e[n],n)},N.prototype._eachEntry=function(t,e){var n=this._instanceConstructor,r=n.resolve;if(r===nt){var o=v(t);if(o===et&&t._state!==ot)this._settledAt(t._state,e,t._result);else if("function"!=typeof o)this._remaining--,this._result[e]=t;else if(n===pt){var i=new n(p);w(i,t,o),this._willSettleAt(i,e)}else this._willSettleAt(new n(function(e){e(t)}),e)}else this._willSettleAt(r(t),e)},N.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===ot&&(this._remaining--,t===st?j(r,n):this._result[e]=n),0===this._remaining&&S(r,this._result)},N.prototype._willSettleAt=function(t,e){var n=this;E(t,void 0,function(t){n._settledAt(it,e,t)},function(t){n._settledAt(st,e,t)})};var dt=W,vt={Promise:pt,polyfill:dt};"function"==typeof define&&define.amd?define(function(){return vt}):"undefined"!=typeof module&&module.exports?module.exports=vt:"undefined"!=typeof this&&(this.ES6Promise=vt),dt()}).call(this);
--------------------------------------------------------------------------------
/schemas/tableau/activityStreams.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "activityStreams",
3 | "alias": "My Activity Streams",
4 | "columns": [
5 | {
6 | "alias": "AthleteId",
7 | "dataType": "int",
8 | "columnRole":"dimension",
9 | "columnType":"discrete",
10 | "description":"Join to AtheleteId in My Profile",
11 | "id":"activity_athlete_id"
12 | },
13 | {
14 | "alias": "ActivityId",
15 | "dataType": "int",
16 | "columnRole":"dimension",
17 | "columnType":"discrete",
18 | "description":"Unique identifier for this activity",
19 | "id":"activity_id"
20 | },
21 | {
22 | "alias": "Activity Name",
23 | "dataType": "string",
24 | "id":"activity_name"
25 | },
26 | {
27 | "alias": "GearId",
28 | "dataType": "string",
29 | "columnType":"discrete",
30 | "description":"ID of the gear used during this activity, join to GearId in My Gear",
31 | "id":"activity_gear_id"
32 | },
33 | {
34 | "alias": "Activity Total Elapsed Time",
35 | "dataType": "int",
36 | "columnRole":"measure",
37 | "description":"This activity's total time (end time minus start time), in seconds",
38 | "id":"activity_elapsed_time"
39 | },
40 | {
41 | "alias": "Activity Total Moving Time",
42 | "dataType": "int",
43 | "columnRole":"measure",
44 | "description":"This activity's time spent moving, in seconds",
45 | "id":"activity_moving_time"
46 | },
47 | {
48 | "alias": "Activity IsPrivate",
49 | "dataType": "bool",
50 | "description":"Was this activity marked as private?",
51 | "id":"activity_private"
52 | },
53 | {
54 | "alias": "Activity IsCommute",
55 | "dataType": "bool",
56 | "description":"Is this activity marked as a commute?",
57 | "id":"activity_commute"
58 | },
59 | {
60 | "alias": "Activity Type",
61 | "dataType": "string",
62 | "description":"This activity's type (Walk, Run, Ride, etc)",
63 | "id":"activity_type"
64 | },
65 | {
66 | "alias": "Activity IsTrainer",
67 | "dataType": "bool",
68 | "description":"Was this activity recorded on a training machine?",
69 | "id":"activity_trainer"
70 | },
71 | {
72 | "alias": "Activity Start DateTime",
73 | "dataType": "datetime",
74 | "description":"This activity's start date and time (UTC)",
75 | "id":"activity_start_date"
76 | },
77 | {
78 | "alias": "Activity Start DateTime (local)",
79 | "dataType": "datetime",
80 | "description":"This activity's start data and time, in the local timezone",
81 | "id":"activity_start_date_local"
82 | },
83 | {
84 | "alias": "Activity UTC Offset",
85 | "dataType": "int",
86 | "columnRole":"dimension",
87 | "description":"The number of seconds the local timezone is offset from UTC time (-3600 would be -1 hour from UTC)",
88 | "id":"activity_utc_offset"
89 | },
90 | {
91 | "alias": "Activity Timezone",
92 | "dataType": "string",
93 | "id":"activity_timezone"
94 | },
95 | {
96 | "alias": "Activity PR Count",
97 | "dataType": "int",
98 | "columnRole":"measure",
99 | "description":"number of personal records set during this activity",
100 | "id":"activity_pr_count"
101 | },
102 | {
103 | "alias": "Activity Kudos Count",
104 | "dataType": "int",
105 | "columnRole":"measure",
106 | "id":"activity_kudos_count"
107 | },
108 | {
109 | "alias": "Activity Comment Count",
110 | "dataType": "int",
111 | "columnRole":"measure",
112 | "id":"activity_comment_count"
113 | },
114 | {
115 | "alias": "Activity Achievement Count",
116 | "dataType": "int",
117 | "columnRole":"measure",
118 | "id":"activity_achievement_count"
119 | },
120 | {
121 | "alias": "Stream Latitude",
122 | "dataType": "float",
123 | "columnRole":"dimension",
124 | "columnType":"discrete",
125 | "description":"Latitude of each stream point for the activity",
126 | "id":"activitystream_lat"
127 | },
128 | {
129 | "alias": "Stream Longitude",
130 | "dataType": "float",
131 | "columnRole":"dimension",
132 | "columnType":"discrete",
133 | "description":"Longitude of each stream point for the activity",
134 | "id":"activitystream_lng"
135 | },
136 | {
137 | "alias": "Stream Distance",
138 | "dataType": "float",
139 | "columnRole":"measure",
140 | "description":"Cumulative distance at each stream point for the activity",
141 | "id":"activitystream_distance"
142 | },
143 | {
144 | "alias": "Stream Altitude",
145 | "dataType": "float",
146 | "columnRole":"measure",
147 | "description":"Altitude at each stream point for the activity",
148 | "id":"activitystream_altitude"
149 | },
150 | {
151 | "alias": "Stream Time (sec)",
152 | "dataType": "int",
153 | "columnRole":"measure",
154 | "description":"Cumulative time (seconds) at each stream point for the activity",
155 | "id":"activitystream_time"
156 | },
157 | {
158 | "alias": "Stream Velocity",
159 | "dataType": "float",
160 | "columnRole":"measure",
161 | "description":"The velocity (meters/second) at each stream point for the activity",
162 | "id":"activitystream_velocity_smooth"
163 | },
164 | {
165 | "alias": "Stream Heart Rate",
166 | "dataType": "int",
167 | "columnRole":"measure",
168 | "description":"Heart rate at each stream point for the activity",
169 | "id":"activitystream_heartrate"
170 | },
171 | {
172 | "alias": "Stream Grade (%)",
173 | "dataType": "float",
174 | "columnRole":"measure",
175 | "numberFormat":"percentage",
176 | "description":"The grade (%) at each stream point for the activity",
177 | "id":"activitystream_grade_smooth"
178 | }
179 | ]
180 | }
--------------------------------------------------------------------------------
/schemas/tableau/activities.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "activities",
3 | "alias": "My Activities",
4 | "columns": [
5 | {
6 | "alias": "AthleteId",
7 | "dataType": "int",
8 | "columnRole":"dimension",
9 | "columnType":"discrete",
10 | "description":"Join to AtheleteId in My Profile",
11 | "id":"activities_athlete_id"
12 | },
13 | {
14 | "alias": "ActivityId",
15 | "dataType": "int",
16 | "columnRole":"dimension",
17 | "columnType":"discrete",
18 | "description":"Unique identifier for this activity",
19 | "id":"activities_id"
20 | },
21 | {
22 | "alias": "Activity Name",
23 | "dataType": "string",
24 | "id":"activities_name"
25 | },
26 | {
27 | "alias": "Distance",
28 | "dataType": "float",
29 | "description":"Activity distance in meters",
30 | "id":"activities_distance"
31 | },
32 | {
33 | "alias": "Moving Time",
34 | "dataType": "int",
35 | "description":"This activity's time spent moving, in seconds",
36 | "id":"activities_moving_time"
37 | },
38 | {
39 | "alias": "Elapsed Time",
40 | "dataType": "int",
41 | "description":"This activity's total time (end time minus start time), in seconds",
42 | "id":"activities_elapsed_time"
43 | },
44 | {
45 | "alias": "Total Elevation Gain",
46 | "dataType": "float",
47 | "description":"The total elavation gained, in meters, during this activity",
48 | "id":"activities_total_elevation_gain"
49 | },
50 | {
51 | "alias": "Type",
52 | "dataType": "string",
53 | "description":"This activity's type (Walk, Run, Ride, etc)",
54 | "id":"activities_type"
55 | },
56 | {
57 | "alias": "ExternalId",
58 | "dataType": "int",
59 | "columnRole":"dimension",
60 | "columnType":"discrete",
61 | "description":"The upload device's id",
62 | "id":"activities_external_id"
63 | },
64 | {
65 | "alias": "Start DateTime",
66 | "dataType": "datetime",
67 | "description":"This activity's start date and time (UTC)",
68 | "id":"activities_start_date"
69 | },
70 | {
71 | "alias": "Start DateTime (local)",
72 | "dataType": "datetime",
73 | "description":"This activity's start data and time, in the local timezone",
74 | "id":"activities_start_date_local"
75 | },
76 | {
77 | "alias": "Timezone",
78 | "dataType": "string",
79 | "id":"activities_timezone"
80 | },
81 | {
82 | "alias": "UTC Offset",
83 | "dataType": "int",
84 | "columnRole":"dimension",
85 | "columnType":"discrete",
86 | "description":"The number of seconds the local timezone is offset from UTC time (-3600 would be -1 hour from UTC)",
87 | "id":"activities_utc_offset"
88 | },
89 | {
90 | "alias": "Start Latitude",
91 | "dataType": "float",
92 | "columnRole":"dimension",
93 | "columnType":"discrete",
94 | "id":"activities_start_latlng_0"
95 | },
96 | {
97 | "alias": "Start Longitude",
98 | "dataType": "float",
99 | "columnRole":"dimension",
100 | "columnType":"discrete",
101 | "id":"activities_start_latlng_1"
102 | },
103 | {
104 | "alias": "End Latitude",
105 | "dataType": "float",
106 | "columnRole":"dimension",
107 | "columnType":"discrete",
108 | "id":"activities_end_latlng_0"
109 | },
110 | {
111 | "alias": "End Longitude",
112 | "dataType": "float",
113 | "columnRole":"dimension",
114 | "columnType":"discrete",
115 | "id":"activities_end_latlng_1"
116 | },
117 | {
118 | "alias": "City",
119 | "dataType": "string",
120 | "id":"activities_location_city"
121 | },
122 | {
123 | "alias": "State",
124 | "dataType": "string",
125 | "id":"activities_location_state"
126 | },
127 | {
128 | "alias": "Country",
129 | "dataType": "string",
130 | "id":"activities_location_country"
131 | },
132 | {
133 | "alias": "Achievement Count",
134 | "dataType": "int",
135 | "id":"activities_achievement_count"
136 | },
137 | {
138 | "alias": "Kudos Count",
139 | "dataType": "int",
140 | "id":"activities_kudos_count"
141 | },
142 | {
143 | "alias": "Comment Count",
144 | "dataType": "int",
145 | "id":"activities_comment_count"
146 | },
147 | {
148 | "alias": "Athlete Count",
149 | "dataType": "int",
150 | "description":"The number of athletes who participated in this activity",
151 | "id":"activities_athelete_count"
152 | },
153 | {
154 | "alias": "Photo Count",
155 | "dataType": "int",
156 | "description":"The number of photos uploaded to this activity",
157 | "id":"activities_photo_count"
158 | },
159 | {
160 | "alias": "PR Count",
161 | "dataType": "int",
162 | "description":"number of personal records set during this activity",
163 | "id":"activities_pr_count"
164 | },
165 | {
166 | "alias": "Total Photo Count",
167 | "dataType": "int",
168 | "id":"activities_total_photo_count"
169 | },
170 | {
171 | "alias": "Has Kudos",
172 | "dataType": "bool",
173 | "id":"activities_has_kudos"
174 | },
175 | {
176 | "alias": "MapId",
177 | "dataType": "string",
178 | "description":"Unique identifier for this activity's map",
179 | "id":"activities_map_id"
180 | },
181 | {
182 | "alias": "MapSummaryPolyline",
183 | "dataType": "string",
184 | "description":"This field contains an encoded summary of your activity's route. You can use the Google Encoded Polyline Algorithm to decode this into a series of lat/lng points",
185 | "id":"activities_map_summary_polyline"
186 | },
187 | {
188 | "alias": "Trainer",
189 | "dataType": "bool",
190 | "description":"Was this activity recorded on a training machine?",
191 | "id":"activities_trainer"
192 | },
193 | {
194 | "alias": "Commute",
195 | "dataType": "bool",
196 | "description":"Is this activity marked as a commute?",
197 | "id":"activities_commute"
198 | },
199 | {
200 | "alias": "Manual",
201 | "dataType": "bool",
202 | "description":"Was this activity manually uploaded?",
203 | "id":"activities_manual"
204 | },
205 | {
206 | "alias": "Private",
207 | "dataType": "bool",
208 | "description":"Was this activity marked as private?",
209 | "id":"activities_private"
210 | },
211 | {
212 | "alias": "Flagged",
213 | "dataType": "bool",
214 | "id":"activities_flagged"
215 | },
216 | {
217 | "alias": "GearId",
218 | "dataType": "string",
219 | "description":"ID of the gear used during this activity, join to GearId in My Gear",
220 | "id":"activities_gear_id"
221 | },
222 | {
223 | "alias": "FromAcceptedTag",
224 | "dataType": "bool",
225 | "id":"activities_from_accepted_tag"
226 | },
227 | {
228 | "alias": "Average Speed",
229 | "dataType": "float",
230 | "description":"This activity's average speed, measured in meters/second",
231 | "id":"activities_average_speed"
232 | },
233 | {
234 | "alias": "Max Speed",
235 | "dataType": "float",
236 | "description":"This activity's max speed, measured in meters/second",
237 | "id":"activities_max_speed"
238 | },
239 | {
240 | "alias": "Average Cadence",
241 | "dataType": "float",
242 | "description":"This activity's cadence (bike rides only), measured in rotations per minute",
243 | "id":"activities_average_cadence"
244 | },
245 | {
246 | "alias": "Average Watts",
247 | "dataType": "float",
248 | "description":"This activity's average wattage (bike rides only)",
249 | "id":"activities_average_watts"
250 | },
251 | {
252 | "alias": "Max Watts",
253 | "dataType": "float",
254 | "description":"This activity's maximum wattage (bike rides only)",
255 | "id":"activities_max_watts"
256 | },
257 | {
258 | "alias": "Weighted Average Watts",
259 | "dataType": "float",
260 | "description":"Similar to normalized power (bike rides only)",
261 | "id":"activities_weighted_average_watts"
262 | },
263 | {
264 | "alias": "Kilojoules",
265 | "dataType": "float",
266 | "description":"Total work done during the activity (bike rides only)",
267 | "id":"activities_kilojoules"
268 | },
269 | {
270 | "alias": "DeviceWatts",
271 | "dataType": "bool",
272 | "description":"Are watts measured by a power meter? (bike rides only)",
273 | "id":"activities_device_watts"
274 | },
275 | {
276 | "alias": "Has Heartrate",
277 | "dataType": "bool",
278 | "id":"activities_has_heartrate"
279 | },
280 | {
281 | "alias": "Average Heart Rate",
282 | "dataType": "float",
283 | "description":"The average heart rate during this activity, measured in beats/minute",
284 | "id":"activities_average_heartrate"
285 | },
286 | {
287 | "alias": "Max Heart Rate",
288 | "dataType": "float",
289 | "description":"The maximum heart rate during this activity, measured in beats/minute",
290 | "id":"activities_max_heartrate"
291 | },
292 | {
293 | "alias": "Suffer Score",
294 | "dataType": "float",
295 | "description":"A score based on your heart rate, to determine how hard this activity was (summit only",
296 | "id":"activities_suffer_score"
297 | }
298 | ]
299 | }
--------------------------------------------------------------------------------
/lib/popper.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) Federico Zivolo 2017
3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll)/.test(r+s+p)?e:n(o(e))}function r(e){var o=e&&e.offsetParent,i=o&&o.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(o.nodeName)&&'static'===t(o,'position')?r(o):o:e?e.ownerDocument.documentElement:document.documentElement}function p(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||r(e.firstElementChild)===e)}function s(e){return null===e.parentNode?e:s(e.parentNode)}function d(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,i=o?e:t,n=o?t:e,a=document.createRange();a.setStart(i,0),a.setEnd(n,0);var l=a.commonAncestorContainer;if(e!==l&&t!==l||i.contains(n))return p(l)?l:r(l);var f=s(e);return f.host?d(f.host,t):d(e,s(t).host)}function a(e){var t=1=o.clientWidth&&i>=o.clientHeight}),l=0i[e]&&!t.escapeWithReference&&(n=_(p[o],i[e]-('right'===e?p.width:p.height))),pe({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=se({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=X,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var i;if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var n=o.element;if('string'==typeof n){if(n=e.instance.popper.querySelector(n),!n)return e;}else if(!e.instance.popper.contains(n))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',g=a?'bottom':'right',u=L(n)[l];d[g]-us[g]&&(e.offsets.popper[m]+=d[m]+u-s[g]),e.offsets.popper=c(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=J(_(s[l]-u,v),0),e.arrowElement=n,e.offsets.arrow=(i={},pe(i,m,Math.round(v)),pe(i,h,''),i),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(k(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=y(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=x(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case le.FLIP:p=[i,n];break;case le.CLOCKWISE:p=q(i);break;case le.COUNTERCLOCKWISE:p=q(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=x(i);var a=e.offsets.popper,l=e.offsets.reference,f=X,m='left'===i&&f(a.right)>f(l.left)||'right'===i&&f(a.left)f(l.top)||'bottom'===i&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===i&&h||'right'===i&&c||'top'===i&&g||'bottom'===i&&u,w=-1!==['top','bottom'].indexOf(i),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),y&&(r=K(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=se({},e.offsets.popper,S(e.instance.popper,e.offsets.reference,e.placement)),e=C(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[o]-(s?n[p?'width':'height']:0),e.placement=x(t),e.offsets.popper=c(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=T(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right 1) {
310 |
311 | // There is a query string
312 | var qs = window.location.href.split("?")[1].split("&");
313 | var queryParams = {};
314 | for ( param in qs ){
315 | var equation = qs[param].split("=");
316 | queryParams[equation[0]] = equation[1];
317 | }
318 | return queryParams
319 | } else {
320 | // No query string
321 | return {}
322 | }
323 | }
324 |
325 | // Fetch values from an object, based on a path (for nested properties)
326 | /*
327 | path: the path used to find the nested property you want (ex. 'object.subObject.property' or 'array.0.property')
328 | obj: the object to look inside
329 | */
330 | function resolve(path,obj) {
331 | var i, len;
332 |
333 | for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
334 | if(!obj || typeof obj !== 'object') return null;
335 | obj = obj[path[i]];
336 | }
337 |
338 | if(obj === undefined) return null;
339 | return obj;
340 | }
341 |
342 | // Generate column ids for tableau
343 | function getColumnId(tableName, path){
344 | // Replace all dot notations with underscores, and prepend w/ table name
345 | return tableName + '_' + path.replace(/\./g,"_");
346 | }
347 |
348 | // Logging function
349 | /*
350 | message: message to log
351 | abort: should this event also trigger an error and stop the WDC execution? (true/false)
352 | */
353 | function log(text, abort){
354 | var message = "_Strava WDC_ " + text;
355 | console.log(message);
356 | tableau.log(message);
357 | if (abort){
358 | tableau.abortWithError(message);
359 | }
360 | }
361 |
362 | // Function to fetch a schema
363 | /*
364 | type: strava or tableau
365 | schemaName: the name of the schema file to fetch (no file extension needed)
366 | */
367 | function getSchema(type, schemaName){
368 | return new Promise(function(resolve,reject){
369 | $.ajax({
370 | "url": "schemas/" + type + "/" + schemaName + ".json",
371 | "dataType": "json",
372 | "success": function(data){
373 | resolve(data)
374 | },
375 | "error": function(error){
376 | reject({"error":true,"message":"Error fetching " + type + " Schema for " + schemaName})
377 | }})
378 | })
379 | }
380 |
381 | // Function to make API calls
382 | /*
383 | accessToken: access_token required to authenticate each api call
384 | url: base url of the api call
385 | currentPage: if you don't need to page through results enter 0, otherwise 1
386 | callback: function to execute when the API call(s) are finished
387 | */
388 | function executeApi(accessToken, url, currentPage, tableData, callback){
389 |
390 | // Define the default page size
391 | var pageSize = 100;
392 |
393 | // Define the url
394 | var queryString = currentPage==0 ? "" : "?page=" + currentPage + "&per_page=" + pageSize;
395 | //var queryString = currentPage==0 ? "" : "?page=" + currentPage + "&per_page=" + pageSize + "&after=1571626506";
396 |
397 | // Define the API call options
398 | $.ajax({
399 | "url": url + queryString,
400 | "method": "get",
401 | "headers": {
402 | "Authorization": "Bearer " + accessToken
403 | },
404 | "success": function(data){
405 |
406 | // Append these new records to the original data array
407 | var fullData = tableData.concat(data);
408 |
409 | // Do we need to make more API calls for additional pages of data?
410 | if ((currentPage>0) && (pageSize==data.length)){
411 | // Increment the counter, and make another call
412 | executeApi(accessToken, url, currentPage+1, fullData, callback)
413 | } else {
414 | // All done, notify Tableau
415 | callback(fullData);
416 | }
417 | },"error": function(req,status,error){
418 | var message = "Error during getData phase, using access token:" + auth.accessToken + " - " + error;
419 | log(message,true);
420 | }
421 | })
422 | }
423 |
424 | // Create event listeners for when the user submits the form
425 | $(document).ready(function() {
426 | $("#submit").click(authorize);
427 | });
428 |
429 |
430 | /********************************************************************/
431 | /* Business Logic */
432 | /********************************************************************/
433 |
434 | // Interaction Phase: The user opened this WDC in Desktop, entered their information, and clicked the submit button.
435 | // Now we need to start the OAuth flow.
436 | function authorize(){
437 |
438 | // Get the client id and secret
439 | var clientId = document.getElementById("clientId").value,
440 | clientSecret = document.getElementById("clientSecret").value;
441 |
442 | // Save both these values in the browser's session storage
443 | sessionStorage.clear();
444 | sessionStorage.setItem("clientId", clientId);
445 | sessionStorage.setItem("clientSecret", clientSecret);
446 |
447 | // Generate the redirect url
448 | var redirect_url = "https://www.strava.com/oauth/authorize?client_id=" + clientId
449 | + "&scope=activity:read_all,profile:read_all"
450 | + "&response_type=code"
451 | + "&approval_prompt=auto"
452 | + "&redirect_uri=" + window.location.href;
453 |
454 | // redirect the browser window
455 | window.location.href = redirect_url;
456 | return false;
457 | }
458 |
459 | // Promise to get a new access token (needed to fetch data)
460 | function getAccessToken(auth, grantType, initCallback) {
461 |
462 | // Define the payload to send
463 | var payload = {
464 | "client_id": auth.clientId,
465 | "client_secret": auth.clientSecret,
466 | "grant_type": grantType
467 | }
468 |
469 | // Decide what type of code/token to pass in
470 | if (grantType=="refresh_token"){
471 | payload["refresh_token"] = auth.refreshToken;
472 | } else if (grantType=="authorization_code"){
473 | payload["code"] = auth.code;
474 | }
475 |
476 | // Make the API call
477 | return $.ajax({
478 | "url": "https://www.strava.com/api/v3/oauth/token",
479 | "method": "POST",
480 | "data": payload,
481 | "success": function(data) {
482 |
483 | // Store the client id/secret, access code, & refresh token in the connectionData
484 | tableau.connectionData = JSON.stringify({
485 | "clientId": auth.clientId,
486 | "clientSecret": auth.clientSecret,
487 | "code": auth.code,
488 | "refreshToken": data.refresh_token
489 | })
490 |
491 | // Store the access token & expiry time as the password
492 | tableau.password = JSON.stringify({
493 | "accessToken":data.access_token,
494 | "expiry": data.expires_at
495 | })
496 |
497 | // We've got everything we need, initialization complete
498 | initCallback();
499 | tableau.submit();
500 | },
501 | "error": function(xhr,status,error){
502 | var message = "Error while exchanging a " + grantType + " for an access token - " + error;
503 | log(message,true);
504 | reject(xhr)
505 | }
506 | })
507 | }
508 |
509 | /********************************************************************/
510 | /* Define the Tableau Web Data Connector */
511 | /********************************************************************/
512 |
513 | // Create a new connector object
514 | var myConnector = tableau.makeConnector();
515 |
516 | // Define the initialization Phase
517 | //
518 | // This function gets called under the following scenarios:
519 | // Interactive Phase (Desktop): wait for user to enter details, then trigger tableau.submit
520 | // Auth Phase (Server): we have refresh token saved as embeded credentials, need to make an API call to get an access token
521 | // Gather Data Phase: (Desktop & Server): We have a valid access token, can start quering for data
522 |
523 | myConnector.init = function(initCallback) {
524 |
525 | // Specify a custom authentication type, since we're using oauth
526 | tableau.authType = tableau.authTypeEnum.custom;
527 |
528 | // Check connectionData for a saved client id/secret, access code, and refresh token
529 | var connectionData = tableau.connectionData.length ? JSON.parse(tableau.connectionData) : { "clientId":sessionStorage.getItem("clientId"), "clientSecret": sessionStorage.getItem("clientSecret")};
530 |
531 | // Parse the query string, to look for an access code (from oauth redirect)
532 | var queryString = parseQueryString();
533 |
534 | // Check to see if we've already got a saved access token
535 | var password = tableau.password.length ? JSON.parse(tableau.password) : {};
536 |
537 | // Is the auth code still valid? Make sure we've got values for both the access token and it's expiration
538 | // and check to ensure the expiration datetime isn't in the past. Also, always assume its expired if running on Tableau Server
539 | //var accessTokenIsValid = (tableau.authPurpose==="ephemeral") && password.accessToken && password.expiry && ( parseInt(password.expiry)> (new Date().getTime() / 1000));
540 | var accessTokenIsValid = false;
541 |
542 |
543 | //////////////////////////////
544 | // OAuth Business Logic //
545 | //////////////////////////////
546 |
547 | // Do we have a saved client id?
548 | if (connectionData.clientId){
549 |
550 | // Yes, do we have a valid access token?
551 | if (accessTokenIsValid){
552 |
553 | // Yes, we have everything needed to fetch data
554 | initCallback();
555 | tableau.submit();
556 |
557 | // Log the event
558 | var message = "init phase already had a saved access token, using it to fetch data.";
559 | log(message,false);
560 | } else {
561 |
562 | // No access token, but do we have a refresh token?
563 | if (connectionData.refreshToken) {
564 |
565 | // Yes, so make the API call to request a new access token
566 | getAccessToken(connectionData, "refresh_token", initCallback)
567 | .catch(function(error){
568 |
569 | // Error: the refresh token is no longer valid, try using the access code
570 | getAccessToken(connectionData, "authorization_code", initCallback)
571 | .catch( function(error){
572 | // Neither the access code nor refresh token would give us a valid access token
573 | var message = "Error - Both access code & refresh token were denied - " + error;
574 | log(message,true);
575 | })
576 |
577 | // Log the event
578 | var message = "tried using a refresh token to get an access code but it failed. Trying again with the authorization code instead.";
579 | log(message,false);
580 | });
581 |
582 | // Log the event
583 | var message = "init phase didn't have an access token, so we'll use an refresh token to request one.";
584 | log(message,false);
585 | } else {
586 |
587 | // No refresh token, do we have an access code?
588 | if (queryString.code) {
589 |
590 | // Yes, add the access code to connection data & make the API call to request a new access token
591 | connectionData.code = queryString.code;
592 | getAccessToken(connectionData, "authorization_code", initCallback)
593 | .catch(function(error){
594 | // The access code was not able to request a refresh token
595 | var message = "Error - Could not get a refresh token using the access code - " + error;
596 | log(message,true);
597 | })
598 |
599 | // Log the event
600 | var message = "init phase didn't have an access token, so we'll use an access code to request one.";
601 | log(message,false);
602 | } else {
603 | // No refresh token or access code, invalid client id/secret entered?
604 | var error = "We have a client id but no access code for authentication";
605 | log(error,true);
606 | }
607 | }
608 | }
609 | }
610 |
611 | // Tell Tableau that the connector is initialized
612 | //initCallback();
613 | }
614 |
615 | // Define the schema
616 | myConnector.getSchema = function(schemaCallback) {
617 |
618 | // Init an array to hold table metadata
619 | var promises = [];
620 |
621 | // Loop through each table we may pull data for
622 | for (tablename in tables){
623 | // Get the table metadata
624 | promises.push(getSchema("tableau",tablename))
625 | }
626 |
627 | // Execute promises to fetch all schemas, then pass them to the callback function
628 | Promise.all(promises).then( function(schemas){
629 | schemaCallback(schemas);
630 | })
631 | };
632 |
633 | // Define how we will get the data
634 | myConnector.getData = function(table, doneCallback) {
635 |
636 | // Define an array to hold the resul set data
637 | var metadata = tables[table.tableInfo.id],
638 | schema = table.tableInfo.columns;
639 |
640 | // Get the access token from the password property
641 | var auth = JSON.parse(tableau.password);
642 |
643 | // Let the metadata define how we are executing API calls to fetch data
644 | metadata.getData(table, auth.accessToken, doneCallback);
645 |
646 | /*
647 | // Define looping criteria
648 | var pageSize = metadata.pageSize;
649 |
650 | // Function to make the API call
651 | function makeApiCall(currentPage) {
652 |
653 | // Define the url
654 | var queryString = pageSize==0 ? "" : "?page=" + currentPage + "&per_page=" + pageSize;
655 |
656 | // Define the API call options
657 | $.ajax({
658 | "url": metadata.url + queryString,
659 | "method": metadata.method,
660 | "headers": {
661 | "Authorization": "Bearer " + auth.accessToken
662 | },
663 | "success": function(data){
664 |
665 | var tableData = [];
666 |
667 | // Loop through each result
668 | data.forEach( function(row,index){
669 | // Create an object for this record
670 | var record = {};
671 | // Loop through the metadata columns, and fetch the data values
672 | schema.forEach( function(column){
673 | record[column.id] = resolve(column.description, row)
674 | })
675 | // Save the row
676 | tableData.push(record);
677 | })
678 |
679 | // Save the first chunk of data returned
680 | table.appendRows(tableData);
681 |
682 | // Do we need to make more API calls for additional pages of data?
683 | if ((pageSize>0) && (pageSize==data.length)){
684 | // Increment the counter, and make another call
685 | makeApiCall(currentPage+1)
686 | } else {
687 | // All done, notify Tableau
688 | doneCallback();
689 | }
690 | },"error": function(req,status,error){
691 | var message = "Error during getData phase, using access token:" + auth.accessToken + " - " + error;
692 | log(message,true);
693 | }
694 | })
695 | }
696 |
697 | // Recursively call the API
698 | makeApiCall(1);
699 | */
700 | };
701 |
702 | // Register the connector
703 | tableau.registerConnector(myConnector);
704 | })();
--------------------------------------------------------------------------------
/lib/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.0.0 (https://getbootstrap.com)
3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t li > .active",g='[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',p=".dropdown-toggle",m="> .dropdown-menu .active",v=function(){function n(t){this._element=t}var i=n.prototype;return i.show=function(){var e=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&t(this._element).hasClass(a)||t(this._element).hasClass(l))){var n,i,s=t(this._element).closest(f)[0],o=P.getSelectorFromElement(this._element);if(s){var h="UL"===s.nodeName?_:d;i=(i=t.makeArray(t(s).find(h)))[i.length-1]}var c=t.Event(r.HIDE,{relatedTarget:this._element}),u=t.Event(r.SHOW,{relatedTarget:i});if(i&&t(i).trigger(c),t(this._element).trigger(u),!u.isDefaultPrevented()&&!c.isDefaultPrevented()){o&&(n=t(o)[0]),this._activate(this._element,s);var g=function(){var n=t.Event(r.HIDDEN,{relatedTarget:e._element}),s=t.Event(r.SHOWN,{relatedTarget:i});t(i).trigger(n),t(e._element).trigger(s)};n?this._activate(n,n.parentNode,g):g()}}},i.dispose=function(){t.removeData(this._element,e),this._element=null},i._activate=function(e,n,i){var s=this,r=("UL"===n.nodeName?t(n).find(_):t(n).children(d))[0],o=i&&P.supportsTransitionEnd()&&r&&t(r).hasClass(h),a=function(){return s._transitionComplete(e,r,i)};r&&o?t(r).one(P.TRANSITION_END,a).emulateTransitionEnd(150):a()},i._transitionComplete=function(e,n,i){if(n){t(n).removeClass(c+" "+a);var s=t(n.parentNode).find(m)[0];s&&t(s).removeClass(a),"tab"===n.getAttribute("role")&&n.setAttribute("aria-selected",!1)}if(t(e).addClass(a),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!0),P.reflow(e),t(e).addClass(c),e.parentNode&&t(e.parentNode).hasClass(o)){var r=t(e).closest(u)[0];r&&t(r).find(p).addClass(a),e.setAttribute("aria-expanded",!0)}i&&i()},n._jQueryInterface=function(i){return this.each(function(){var s=t(this),r=s.data(e);if(r||(r=new n(this),s.data(e,r)),"string"==typeof i){if("undefined"==typeof r[i])throw new TypeError('No method named "'+i+'"');r[i]()}})},s(n,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),n}();return t(document).on(r.CLICK_DATA_API,g,function(e){e.preventDefault(),v._jQueryInterface.call(t(this),"show")}),t.fn.tab=v._jQueryInterface,t.fn.tab.Constructor=v,t.fn.tab.noConflict=function(){return t.fn.tab=i,v._jQueryInterface},v}(e);!function(t){if("undefined"==typeof t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})});
7 | //# sourceMappingURL=bootstrap.min.js.map
--------------------------------------------------------------------------------
/lib/jquery-3.4.1.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/