@slates-integrations/tableau 0.2.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +85 -0
  2. package/docs/SPEC.md +61 -0
  3. package/logo.png +0 -0
  4. package/package.json +19 -0
  5. package/slate.json +18 -0
  6. package/src/auth.ts +237 -0
  7. package/src/config.ts +17 -0
  8. package/src/index.ts +55 -0
  9. package/src/lib/client.ts +959 -0
  10. package/src/lib/errors.ts +94 -0
  11. package/src/lib/helpers.ts +15 -0
  12. package/src/lib/normalizers.ts +9 -0
  13. package/src/spec.ts +12 -0
  14. package/src/tools/export-view.ts +112 -0
  15. package/src/tools/get-site-info.ts +47 -0
  16. package/src/tools/get-view-data.ts +31 -0
  17. package/src/tools/index.ts +18 -0
  18. package/src/tools/list-datasources.ts +78 -0
  19. package/src/tools/list-views.ts +70 -0
  20. package/src/tools/list-workbooks.ts +88 -0
  21. package/src/tools/manage-alerts.ts +139 -0
  22. package/src/tools/manage-collections.ts +254 -0
  23. package/src/tools/manage-custom-views.ts +159 -0
  24. package/src/tools/manage-datasource.ts +129 -0
  25. package/src/tools/manage-favorites.ts +80 -0
  26. package/src/tools/manage-flows.ts +170 -0
  27. package/src/tools/manage-groups.ts +178 -0
  28. package/src/tools/manage-jobs.ts +120 -0
  29. package/src/tools/manage-permissions.ts +118 -0
  30. package/src/tools/manage-projects.ts +162 -0
  31. package/src/tools/manage-users.ts +184 -0
  32. package/src/tools/manage-workbook.ts +160 -0
  33. package/src/triggers/datasource-events.ts +119 -0
  34. package/src/triggers/index.ts +6 -0
  35. package/src/triggers/label-events.ts +98 -0
  36. package/src/triggers/site-events.ts +97 -0
  37. package/src/triggers/user-events.ts +98 -0
  38. package/src/triggers/view-events.ts +83 -0
  39. package/src/triggers/workbook-events.ts +108 -0
  40. package/tsconfig.json +23 -0
@@ -0,0 +1,959 @@
1
+ import { createAxios } from 'slates';
2
+ import type { AxiosInstance } from 'axios';
3
+ import { tableauApiError, tableauServiceError } from './errors';
4
+
5
+ export interface TableauClientConfig {
6
+ serverUrl: string;
7
+ apiVersion: string;
8
+ siteId: string;
9
+ token: string;
10
+ userId?: string;
11
+ expiresAt?: string;
12
+ }
13
+
14
+ export type ViewExportFormat = 'csv' | 'image' | 'pdf';
15
+
16
+ export interface ViewExportOptions {
17
+ maxAgeMinutes?: number;
18
+ filters?: Record<string, string | number | boolean>;
19
+ imageResolution?: 'high';
20
+ vizHeight?: number;
21
+ vizWidth?: number;
22
+ pdfPageType?: string;
23
+ pdfOrientation?: 'Portrait' | 'Landscape';
24
+ }
25
+
26
+ export interface CollectionItemInput {
27
+ contentLuid: string;
28
+ contentType: string;
29
+ contentName?: string;
30
+ }
31
+
32
+ let applyTableauErrorInterceptor = (http: AxiosInstance) => {
33
+ http.interceptors.response.use(
34
+ response => response,
35
+ error => Promise.reject(tableauApiError(error))
36
+ );
37
+ };
38
+
39
+ let buildViewExportParams = (options?: ViewExportOptions) => {
40
+ let params: Record<string, string> = {};
41
+
42
+ if (options?.maxAgeMinutes !== undefined) {
43
+ params.maxAge = String(options.maxAgeMinutes);
44
+ }
45
+ if (options?.imageResolution !== undefined) {
46
+ params.resolution = options.imageResolution;
47
+ }
48
+ if (options?.vizHeight !== undefined) {
49
+ params.vizHeight = String(options.vizHeight);
50
+ }
51
+ if (options?.vizWidth !== undefined) {
52
+ params.vizWidth = String(options.vizWidth);
53
+ }
54
+ if (options?.pdfPageType !== undefined) {
55
+ params.type = options.pdfPageType;
56
+ }
57
+ if (options?.pdfOrientation !== undefined) {
58
+ params.orientation = options.pdfOrientation;
59
+ }
60
+
61
+ for (let [field, value] of Object.entries(options?.filters || {})) {
62
+ params[`vf_${field}`] = String(value);
63
+ }
64
+
65
+ return params;
66
+ };
67
+
68
+ let encodeResponseData = (data: unknown) => Buffer.from(data as any).toString('base64');
69
+
70
+ let unwrapCollection = (result: any) => result?.collection ?? result;
71
+
72
+ let assertCollectionBatchSucceeded = (result: any, action: string) => {
73
+ let errors = Array.isArray(result?.errors) ? result.errors : [];
74
+ if (errors.length === 0) return;
75
+
76
+ let details = errors
77
+ .map((error: any) => error?.errorMessage || error?.message || error?.errorCode)
78
+ .filter(Boolean)
79
+ .join('; ');
80
+ throw tableauServiceError(
81
+ `Tableau collection ${action} failed${details ? `: ${details}` : '.'}`
82
+ );
83
+ };
84
+
85
+ export class TableauClient {
86
+ private http: AxiosInstance;
87
+ private resourceHttp: AxiosInstance;
88
+ private siteId: string;
89
+ private userId?: string;
90
+
91
+ constructor(config: TableauClientConfig) {
92
+ let baseUrl = config.serverUrl.replace(/\/+$/, '');
93
+ this.siteId = config.siteId;
94
+ this.userId = config.userId;
95
+
96
+ this.http = createAxios({
97
+ baseURL: `${baseUrl}/api/${config.apiVersion}`,
98
+ headers: {
99
+ 'X-Tableau-Auth': config.token,
100
+ 'Content-Type': 'application/json',
101
+ Accept: 'application/json'
102
+ }
103
+ });
104
+
105
+ this.resourceHttp = createAxios({
106
+ baseURL: `${baseUrl}/api/-`,
107
+ headers: {
108
+ 'X-Tableau-Auth': config.token,
109
+ 'Content-Type': 'application/json',
110
+ Accept: 'application/json'
111
+ }
112
+ });
113
+
114
+ applyTableauErrorInterceptor(this.http);
115
+ applyTableauErrorInterceptor(this.resourceHttp);
116
+
117
+ if (config.expiresAt && Date.parse(config.expiresAt) <= Date.now()) {
118
+ throw tableauServiceError(
119
+ 'Tableau credentials token is expired. Reconnect the Tableau authentication profile to get a fresh token.'
120
+ );
121
+ }
122
+ }
123
+
124
+ // --- Workbooks ---
125
+
126
+ async queryWorkbooks(params?: {
127
+ pageSize?: number;
128
+ pageNumber?: number;
129
+ filter?: string;
130
+ sort?: string;
131
+ }): Promise<any> {
132
+ let queryParams: Record<string, string> = {};
133
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
134
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
135
+ if (params?.filter) queryParams['filter'] = params.filter;
136
+ if (params?.sort) queryParams['sort'] = params.sort;
137
+
138
+ let response = await this.http.get(`/sites/${this.siteId}/workbooks`, {
139
+ params: queryParams
140
+ });
141
+ return response.data;
142
+ }
143
+
144
+ async getWorkbook(workbookId: string): Promise<any> {
145
+ let response = await this.http.get(`/sites/${this.siteId}/workbooks/${workbookId}`);
146
+ return response.data.workbook;
147
+ }
148
+
149
+ async updateWorkbook(
150
+ workbookId: string,
151
+ updates: {
152
+ name?: string;
153
+ description?: string;
154
+ projectId?: string;
155
+ ownerUserId?: string;
156
+ showTabs?: boolean;
157
+ }
158
+ ): Promise<any> {
159
+ let workbook: Record<string, any> = {};
160
+ if (updates.name !== undefined) workbook.name = updates.name;
161
+ if (updates.description !== undefined) workbook.description = updates.description;
162
+ if (updates.showTabs !== undefined) workbook.showTabs = updates.showTabs;
163
+ if (updates.projectId) workbook.project = { id: updates.projectId };
164
+ if (updates.ownerUserId) workbook.owner = { id: updates.ownerUserId };
165
+
166
+ let response = await this.http.put(`/sites/${this.siteId}/workbooks/${workbookId}`, {
167
+ workbook
168
+ });
169
+ return response.data.workbook;
170
+ }
171
+
172
+ async deleteWorkbook(workbookId: string): Promise<void> {
173
+ await this.http.delete(`/sites/${this.siteId}/workbooks/${workbookId}`);
174
+ }
175
+
176
+ async getWorkbookConnections(workbookId: string): Promise<any> {
177
+ let response = await this.http.get(
178
+ `/sites/${this.siteId}/workbooks/${workbookId}/connections`
179
+ );
180
+ return response.data;
181
+ }
182
+
183
+ async queryViewsForWorkbook(workbookId: string): Promise<any> {
184
+ let response = await this.http.get(`/sites/${this.siteId}/workbooks/${workbookId}/views`);
185
+ return response.data;
186
+ }
187
+
188
+ async addTagsToWorkbook(workbookId: string, tags: string[]): Promise<any> {
189
+ let response = await this.http.put(`/sites/${this.siteId}/workbooks/${workbookId}/tags`, {
190
+ tags: { tag: tags.map(t => ({ label: t })) }
191
+ });
192
+ return response.data;
193
+ }
194
+
195
+ async deleteTagFromWorkbook(workbookId: string, tagName: string): Promise<void> {
196
+ await this.http.delete(`/sites/${this.siteId}/workbooks/${workbookId}/tags/${tagName}`);
197
+ }
198
+
199
+ // --- Data Sources ---
200
+
201
+ async queryDatasources(params?: {
202
+ pageSize?: number;
203
+ pageNumber?: number;
204
+ filter?: string;
205
+ sort?: string;
206
+ }): Promise<any> {
207
+ let queryParams: Record<string, string> = {};
208
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
209
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
210
+ if (params?.filter) queryParams['filter'] = params.filter;
211
+ if (params?.sort) queryParams['sort'] = params.sort;
212
+
213
+ let response = await this.http.get(`/sites/${this.siteId}/datasources`, {
214
+ params: queryParams
215
+ });
216
+ return response.data;
217
+ }
218
+
219
+ async getDatasource(datasourceId: string): Promise<any> {
220
+ let response = await this.http.get(`/sites/${this.siteId}/datasources/${datasourceId}`);
221
+ return response.data.datasource;
222
+ }
223
+
224
+ async updateDatasource(
225
+ datasourceId: string,
226
+ updates: {
227
+ name?: string;
228
+ description?: string;
229
+ projectId?: string;
230
+ ownerUserId?: string;
231
+ isCertified?: boolean;
232
+ certificationNote?: string;
233
+ }
234
+ ): Promise<any> {
235
+ let datasource: Record<string, any> = {};
236
+ if (updates.name !== undefined) datasource.name = updates.name;
237
+ if (updates.description !== undefined) datasource.description = updates.description;
238
+ if (updates.isCertified !== undefined) datasource.isCertified = updates.isCertified;
239
+ if (updates.certificationNote !== undefined)
240
+ datasource.certificationNote = updates.certificationNote;
241
+ if (updates.projectId) datasource.project = { id: updates.projectId };
242
+ if (updates.ownerUserId) datasource.owner = { id: updates.ownerUserId };
243
+
244
+ let response = await this.http.put(`/sites/${this.siteId}/datasources/${datasourceId}`, {
245
+ datasource
246
+ });
247
+ return response.data.datasource;
248
+ }
249
+
250
+ async deleteDatasource(datasourceId: string): Promise<void> {
251
+ await this.http.delete(`/sites/${this.siteId}/datasources/${datasourceId}`);
252
+ }
253
+
254
+ async getDatasourceConnections(datasourceId: string): Promise<any> {
255
+ let response = await this.http.get(
256
+ `/sites/${this.siteId}/datasources/${datasourceId}/connections`
257
+ );
258
+ return response.data;
259
+ }
260
+
261
+ async refreshDatasource(datasourceId: string): Promise<any> {
262
+ let response = await this.http.post(
263
+ `/sites/${this.siteId}/datasources/${datasourceId}/refresh`,
264
+ {}
265
+ );
266
+ return response.data.job;
267
+ }
268
+
269
+ // --- Views ---
270
+
271
+ async queryViews(params?: {
272
+ pageSize?: number;
273
+ pageNumber?: number;
274
+ filter?: string;
275
+ sort?: string;
276
+ }): Promise<any> {
277
+ let queryParams: Record<string, string> = {};
278
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
279
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
280
+ if (params?.filter) queryParams['filter'] = params.filter;
281
+ if (params?.sort) queryParams['sort'] = params.sort;
282
+
283
+ let response = await this.http.get(`/sites/${this.siteId}/views`, { params: queryParams });
284
+ return response.data;
285
+ }
286
+
287
+ async getViewData(viewId: string, options?: ViewExportOptions): Promise<string> {
288
+ let response = await this.http.get(`/sites/${this.siteId}/views/${viewId}/data`, {
289
+ params: buildViewExportParams(options),
290
+ responseType: 'text',
291
+ headers: { Accept: '*/*' }
292
+ });
293
+ return response.data;
294
+ }
295
+
296
+ async exportView(
297
+ viewId: string,
298
+ format: ViewExportFormat,
299
+ options?: ViewExportOptions
300
+ ): Promise<{
301
+ data: string;
302
+ contentType: string;
303
+ encoding: 'text' | 'base64';
304
+ }> {
305
+ if (format === 'csv') {
306
+ let data = await this.getViewData(viewId, options);
307
+ return {
308
+ data,
309
+ contentType: 'text/csv',
310
+ encoding: 'text'
311
+ };
312
+ }
313
+
314
+ let endpoint = format === 'image' ? 'image' : 'pdf';
315
+ let contentType = format === 'image' ? 'image/png' : 'application/pdf';
316
+ let response = await this.http.get(`/sites/${this.siteId}/views/${viewId}/${endpoint}`, {
317
+ params: buildViewExportParams(options),
318
+ responseType: 'arraybuffer',
319
+ headers: { Accept: '*/*' }
320
+ });
321
+
322
+ return {
323
+ data: encodeResponseData(response.data),
324
+ contentType,
325
+ encoding: 'base64'
326
+ };
327
+ }
328
+
329
+ // --- Custom Views ---
330
+
331
+ async queryCustomViews(params?: { pageSize?: number; pageNumber?: number }): Promise<any> {
332
+ let queryParams: Record<string, string> = {};
333
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
334
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
335
+
336
+ let response = await this.http.get(`/sites/${this.siteId}/customviews`, {
337
+ params: queryParams
338
+ });
339
+ return response.data;
340
+ }
341
+
342
+ async getCustomView(customViewId: string): Promise<any> {
343
+ let response = await this.http.get(`/sites/${this.siteId}/customviews/${customViewId}`);
344
+ return response.data.customView;
345
+ }
346
+
347
+ async updateCustomView(
348
+ customViewId: string,
349
+ updates: {
350
+ name?: string;
351
+ ownerUserId?: string;
352
+ }
353
+ ): Promise<any> {
354
+ let customView: Record<string, any> = {};
355
+ if (updates.name !== undefined) customView.name = updates.name;
356
+ if (updates.ownerUserId) customView.owner = { id: updates.ownerUserId };
357
+
358
+ let response = await this.http.put(`/sites/${this.siteId}/customviews/${customViewId}`, {
359
+ customView
360
+ });
361
+ return response.data.customView;
362
+ }
363
+
364
+ async deleteCustomView(customViewId: string): Promise<void> {
365
+ await this.http.delete(`/sites/${this.siteId}/customviews/${customViewId}`);
366
+ }
367
+
368
+ async exportCustomView(
369
+ customViewId: string,
370
+ format: ViewExportFormat,
371
+ options?: ViewExportOptions
372
+ ): Promise<{
373
+ data: string;
374
+ contentType: string;
375
+ encoding: 'text' | 'base64';
376
+ }> {
377
+ let endpoint = format === 'csv' ? 'data' : format;
378
+ let contentType =
379
+ format === 'csv' ? 'text/csv' : format === 'image' ? 'image/png' : 'application/pdf';
380
+ let response = await this.http.get(
381
+ `/sites/${this.siteId}/customviews/${customViewId}/${endpoint}`,
382
+ {
383
+ params: buildViewExportParams(options),
384
+ responseType: format === 'csv' ? 'text' : 'arraybuffer',
385
+ headers: { Accept: '*/*' }
386
+ }
387
+ );
388
+
389
+ return {
390
+ data: format === 'csv' ? response.data : encodeResponseData(response.data),
391
+ contentType,
392
+ encoding: format === 'csv' ? 'text' : 'base64'
393
+ };
394
+ }
395
+
396
+ // --- Users ---
397
+
398
+ async queryUsers(params?: {
399
+ pageSize?: number;
400
+ pageNumber?: number;
401
+ filter?: string;
402
+ sort?: string;
403
+ }): Promise<any> {
404
+ let queryParams: Record<string, string> = {};
405
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
406
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
407
+ if (params?.filter) queryParams['filter'] = params.filter;
408
+ if (params?.sort) queryParams['sort'] = params.sort;
409
+
410
+ let response = await this.http.get(`/sites/${this.siteId}/users`, { params: queryParams });
411
+ return response.data;
412
+ }
413
+
414
+ async getUser(userId: string): Promise<any> {
415
+ let response = await this.http.get(`/sites/${this.siteId}/users/${userId}`);
416
+ return response.data.user;
417
+ }
418
+
419
+ async addUser(name: string, siteRole: string, authSetting?: string): Promise<any> {
420
+ let user: Record<string, any> = { name, siteRole };
421
+ if (authSetting) user.authSetting = authSetting;
422
+
423
+ let response = await this.http.post(`/sites/${this.siteId}/users`, { user });
424
+ return response.data.user;
425
+ }
426
+
427
+ async updateUser(
428
+ userId: string,
429
+ updates: {
430
+ fullName?: string;
431
+ email?: string;
432
+ siteRole?: string;
433
+ authSetting?: string;
434
+ }
435
+ ): Promise<any> {
436
+ let user: Record<string, any> = {};
437
+ if (updates.fullName !== undefined) user.fullName = updates.fullName;
438
+ if (updates.email !== undefined) user.email = updates.email;
439
+ if (updates.siteRole !== undefined) user.siteRole = updates.siteRole;
440
+ if (updates.authSetting !== undefined) user.authSetting = updates.authSetting;
441
+
442
+ let response = await this.http.put(`/sites/${this.siteId}/users/${userId}`, { user });
443
+ return response.data.user;
444
+ }
445
+
446
+ async removeUser(userId: string): Promise<void> {
447
+ await this.http.delete(`/sites/${this.siteId}/users/${userId}`);
448
+ }
449
+
450
+ // --- Groups ---
451
+
452
+ async queryGroups(params?: {
453
+ pageSize?: number;
454
+ pageNumber?: number;
455
+ filter?: string;
456
+ sort?: string;
457
+ }): Promise<any> {
458
+ let queryParams: Record<string, string> = {};
459
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
460
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
461
+ if (params?.filter) queryParams['filter'] = params.filter;
462
+ if (params?.sort) queryParams['sort'] = params.sort;
463
+
464
+ let response = await this.http.get(`/sites/${this.siteId}/groups`, {
465
+ params: queryParams
466
+ });
467
+ return response.data;
468
+ }
469
+
470
+ async createGroup(name: string, minimumSiteRole?: string): Promise<any> {
471
+ let group: Record<string, any> = { name };
472
+ if (minimumSiteRole) group.minimumSiteRole = minimumSiteRole;
473
+
474
+ let response = await this.http.post(`/sites/${this.siteId}/groups`, { group });
475
+ return response.data.group;
476
+ }
477
+
478
+ async updateGroup(
479
+ groupId: string,
480
+ updates: {
481
+ name?: string;
482
+ minimumSiteRole?: string;
483
+ }
484
+ ): Promise<any> {
485
+ let group: Record<string, any> = {};
486
+ if (updates.name !== undefined) group.name = updates.name;
487
+ if (updates.minimumSiteRole !== undefined) group.minimumSiteRole = updates.minimumSiteRole;
488
+
489
+ let response = await this.http.put(`/sites/${this.siteId}/groups/${groupId}`, { group });
490
+ return response.data.group;
491
+ }
492
+
493
+ async deleteGroup(groupId: string): Promise<void> {
494
+ await this.http.delete(`/sites/${this.siteId}/groups/${groupId}`);
495
+ }
496
+
497
+ async addUserToGroup(groupId: string, userId: string): Promise<any> {
498
+ let response = await this.http.post(`/sites/${this.siteId}/groups/${groupId}/users`, {
499
+ user: { id: userId }
500
+ });
501
+ return response.data.user;
502
+ }
503
+
504
+ async removeUserFromGroup(groupId: string, userId: string): Promise<void> {
505
+ await this.http.delete(`/sites/${this.siteId}/groups/${groupId}/users/${userId}`);
506
+ }
507
+
508
+ async getUsersInGroup(
509
+ groupId: string,
510
+ params?: {
511
+ pageSize?: number;
512
+ pageNumber?: number;
513
+ }
514
+ ): Promise<any> {
515
+ let queryParams: Record<string, string> = {};
516
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
517
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
518
+
519
+ let response = await this.http.get(`/sites/${this.siteId}/groups/${groupId}/users`, {
520
+ params: queryParams
521
+ });
522
+ return response.data;
523
+ }
524
+
525
+ // --- Projects ---
526
+
527
+ async queryProjects(params?: {
528
+ pageSize?: number;
529
+ pageNumber?: number;
530
+ filter?: string;
531
+ sort?: string;
532
+ }): Promise<any> {
533
+ let queryParams: Record<string, string> = {};
534
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
535
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
536
+ if (params?.filter) queryParams['filter'] = params.filter;
537
+ if (params?.sort) queryParams['sort'] = params.sort;
538
+
539
+ let response = await this.http.get(`/sites/${this.siteId}/projects`, {
540
+ params: queryParams
541
+ });
542
+ return response.data;
543
+ }
544
+
545
+ async createProject(
546
+ name: string,
547
+ opts?: {
548
+ description?: string;
549
+ parentProjectId?: string;
550
+ contentPermissions?: string;
551
+ }
552
+ ): Promise<any> {
553
+ let project: Record<string, any> = { name };
554
+ if (opts?.description) project.description = opts.description;
555
+ if (opts?.parentProjectId) project.parentProjectId = opts.parentProjectId;
556
+ if (opts?.contentPermissions) project.contentPermissions = opts.contentPermissions;
557
+
558
+ let response = await this.http.post(`/sites/${this.siteId}/projects`, { project });
559
+ return response.data.project;
560
+ }
561
+
562
+ async updateProject(
563
+ projectId: string,
564
+ updates: {
565
+ name?: string;
566
+ description?: string;
567
+ parentProjectId?: string;
568
+ contentPermissions?: string;
569
+ }
570
+ ): Promise<any> {
571
+ let project: Record<string, any> = {};
572
+ if (updates.name !== undefined) project.name = updates.name;
573
+ if (updates.description !== undefined) project.description = updates.description;
574
+ if (updates.parentProjectId !== undefined)
575
+ project.parentProjectId = updates.parentProjectId;
576
+ if (updates.contentPermissions !== undefined)
577
+ project.contentPermissions = updates.contentPermissions;
578
+
579
+ let response = await this.http.put(`/sites/${this.siteId}/projects/${projectId}`, {
580
+ project
581
+ });
582
+ return response.data.project;
583
+ }
584
+
585
+ async deleteProject(projectId: string): Promise<void> {
586
+ await this.http.delete(`/sites/${this.siteId}/projects/${projectId}`);
587
+ }
588
+
589
+ // --- Permissions ---
590
+
591
+ async queryPermissions(resourceType: string, resourceId: string): Promise<any> {
592
+ let response = await this.http.get(
593
+ `/sites/${this.siteId}/${resourceType}/${resourceId}/permissions`
594
+ );
595
+ return response.data.permissions;
596
+ }
597
+
598
+ async addPermissions(
599
+ resourceType: string,
600
+ resourceId: string,
601
+ permissions: {
602
+ granteeType: 'user' | 'group';
603
+ granteeId: string;
604
+ capabilities: { name: string; mode: 'Allow' | 'Deny' }[];
605
+ }[]
606
+ ): Promise<any> {
607
+ let granteeCapabilities = permissions.map(p => {
608
+ let grantee =
609
+ p.granteeType === 'user'
610
+ ? { user: { id: p.granteeId } }
611
+ : { group: { id: p.granteeId } };
612
+
613
+ return {
614
+ ...grantee,
615
+ capabilities: {
616
+ capability: p.capabilities
617
+ }
618
+ };
619
+ });
620
+
621
+ let response = await this.http.put(
622
+ `/sites/${this.siteId}/${resourceType}/${resourceId}/permissions`,
623
+ {
624
+ permissions: { granteeCapabilities }
625
+ }
626
+ );
627
+ return response.data.permissions;
628
+ }
629
+
630
+ async deletePermission(
631
+ resourceType: string,
632
+ resourceId: string,
633
+ granteeType: 'users' | 'groups',
634
+ granteeId: string,
635
+ capabilityName: string,
636
+ capabilityMode: 'Allow' | 'Deny'
637
+ ): Promise<void> {
638
+ await this.http.delete(
639
+ `/sites/${this.siteId}/${resourceType}/${resourceId}/permissions/${granteeType}/${granteeId}/${capabilityName}/${capabilityMode}`
640
+ );
641
+ }
642
+
643
+ // --- Jobs ---
644
+
645
+ async queryJobs(params?: {
646
+ pageSize?: number;
647
+ pageNumber?: number;
648
+ filter?: string;
649
+ }): Promise<any> {
650
+ let queryParams: Record<string, string> = {};
651
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
652
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
653
+ if (params?.filter) queryParams['filter'] = params.filter;
654
+
655
+ let response = await this.http.get(`/sites/${this.siteId}/jobs`, { params: queryParams });
656
+ return response.data;
657
+ }
658
+
659
+ async getJob(jobId: string): Promise<any> {
660
+ let response = await this.http.get(`/sites/${this.siteId}/jobs/${jobId}`);
661
+ return response.data.job;
662
+ }
663
+
664
+ async cancelJob(jobId: string): Promise<void> {
665
+ await this.http.put(`/sites/${this.siteId}/jobs/${jobId}`, {});
666
+ }
667
+
668
+ // --- Schedules ---
669
+
670
+ async querySchedules(): Promise<any> {
671
+ let response = await this.http.get(`/schedules`);
672
+ return response.data;
673
+ }
674
+
675
+ // --- Favorites ---
676
+
677
+ async addFavorite(
678
+ userId: string,
679
+ favoriteType: string,
680
+ resourceId: string,
681
+ label: string
682
+ ): Promise<any> {
683
+ let favorite: Record<string, any> = { label };
684
+ favorite[favoriteType] = { id: resourceId };
685
+
686
+ let response = await this.http.put(`/sites/${this.siteId}/favorites/${userId}`, {
687
+ favorite
688
+ });
689
+ return response.data;
690
+ }
691
+
692
+ async deleteFavorite(
693
+ userId: string,
694
+ favoriteType: string,
695
+ resourceId: string
696
+ ): Promise<void> {
697
+ await this.http.delete(
698
+ `/sites/${this.siteId}/favorites/${userId}/${favoriteType}/${resourceId}`
699
+ );
700
+ }
701
+
702
+ async getFavorites(userId: string): Promise<any> {
703
+ let response = await this.http.get(`/sites/${this.siteId}/favorites/${userId}`);
704
+ return response.data;
705
+ }
706
+
707
+ // --- Flows ---
708
+
709
+ async queryFlows(params?: {
710
+ pageSize?: number;
711
+ pageNumber?: number;
712
+ filter?: string;
713
+ sort?: string;
714
+ }): Promise<any> {
715
+ let queryParams: Record<string, string> = {};
716
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
717
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
718
+ if (params?.filter) queryParams['filter'] = params.filter;
719
+ if (params?.sort) queryParams['sort'] = params.sort;
720
+
721
+ let response = await this.http.get(`/sites/${this.siteId}/flows`, { params: queryParams });
722
+ return response.data;
723
+ }
724
+
725
+ async getFlow(flowId: string): Promise<any> {
726
+ let response = await this.http.get(`/sites/${this.siteId}/flows/${flowId}`);
727
+ return response.data.flow;
728
+ }
729
+
730
+ async updateFlow(
731
+ flowId: string,
732
+ updates: {
733
+ name?: string;
734
+ description?: string;
735
+ projectId?: string;
736
+ ownerUserId?: string;
737
+ }
738
+ ): Promise<any> {
739
+ let flow: Record<string, any> = {};
740
+ if (updates.name !== undefined) flow.name = updates.name;
741
+ if (updates.description !== undefined) flow.description = updates.description;
742
+ if (updates.projectId) flow.project = { id: updates.projectId };
743
+ if (updates.ownerUserId) flow.owner = { id: updates.ownerUserId };
744
+
745
+ let response = await this.http.put(`/sites/${this.siteId}/flows/${flowId}`, { flow });
746
+ return response.data.flow;
747
+ }
748
+
749
+ async deleteFlow(flowId: string): Promise<void> {
750
+ await this.http.delete(`/sites/${this.siteId}/flows/${flowId}`);
751
+ }
752
+
753
+ async runFlow(flowId: string): Promise<any> {
754
+ let response = await this.http.post(`/sites/${this.siteId}/flows/${flowId}/run`, {});
755
+ return response.data.job;
756
+ }
757
+
758
+ // --- Extract Refresh ---
759
+
760
+ async refreshWorkbook(workbookId: string): Promise<any> {
761
+ let response = await this.http.post(
762
+ `/sites/${this.siteId}/workbooks/${workbookId}/refresh`,
763
+ {}
764
+ );
765
+ return response.data.job;
766
+ }
767
+
768
+ // --- Webhooks ---
769
+
770
+ async listWebhooks(): Promise<any> {
771
+ let response = await this.http.get(`/sites/${this.siteId}/webhooks`);
772
+ return response.data;
773
+ }
774
+
775
+ async createWebhook(name: string, eventName: string, destinationUrl: string): Promise<any> {
776
+ let response = await this.http.post(`/sites/${this.siteId}/webhooks`, {
777
+ webhook: {
778
+ 'webhook-destination-http': {
779
+ method: 'POST',
780
+ url: destinationUrl
781
+ },
782
+ 'webhook-source': {
783
+ 'webhook-source-event-name': eventName
784
+ },
785
+ name
786
+ }
787
+ });
788
+ return response.data.webhook;
789
+ }
790
+
791
+ async deleteWebhook(webhookId: string): Promise<void> {
792
+ await this.http.delete(`/sites/${this.siteId}/webhooks/${webhookId}`);
793
+ }
794
+
795
+ async getWebhook(webhookId: string): Promise<any> {
796
+ let response = await this.http.get(`/sites/${this.siteId}/webhooks/${webhookId}`);
797
+ return response.data.webhook;
798
+ }
799
+
800
+ // --- Data-Driven Alerts ---
801
+
802
+ async queryAlerts(params?: { pageSize?: number; pageNumber?: number }): Promise<any> {
803
+ let queryParams: Record<string, string> = {};
804
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
805
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
806
+
807
+ let response = await this.http.get(`/sites/${this.siteId}/dataAlerts`, {
808
+ params: queryParams
809
+ });
810
+ return response.data;
811
+ }
812
+
813
+ async getAlert(alertId: string): Promise<any> {
814
+ let response = await this.http.get(`/sites/${this.siteId}/dataAlerts/${alertId}`);
815
+ return response.data.dataAlert;
816
+ }
817
+
818
+ async deleteAlert(alertId: string): Promise<void> {
819
+ await this.http.delete(`/sites/${this.siteId}/dataAlerts/${alertId}`);
820
+ }
821
+
822
+ async addUserToAlert(alertId: string, userId: string): Promise<void> {
823
+ await this.http.post(`/sites/${this.siteId}/dataAlerts/${alertId}/users`, {
824
+ user: { id: userId }
825
+ });
826
+ }
827
+
828
+ async removeUserFromAlert(alertId: string, userId: string): Promise<void> {
829
+ await this.http.delete(`/sites/${this.siteId}/dataAlerts/${alertId}/users/${userId}`);
830
+ }
831
+
832
+ // --- Collections ---
833
+
834
+ async queryCollections(params?: {
835
+ pageSize?: number;
836
+ pageNumber?: number;
837
+ filter?: string;
838
+ sort?: string;
839
+ }): Promise<any> {
840
+ let queryParams: Record<string, string> = {};
841
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
842
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
843
+ if (params?.filter) queryParams['filter'] = params.filter;
844
+ if (params?.sort) queryParams['sort'] = params.sort;
845
+
846
+ let response = await this.resourceHttp.get(`/collections`, {
847
+ params: queryParams
848
+ });
849
+ return response.data;
850
+ }
851
+
852
+ async getCollection(collectionId: string): Promise<any> {
853
+ let response = await this.resourceHttp.get(`/collections/${collectionId}`);
854
+ return unwrapCollection(response.data);
855
+ }
856
+
857
+ async createCollection(name: string, description?: string): Promise<any> {
858
+ let collection: Record<string, any> = { name };
859
+ if (description) collection.description = description;
860
+
861
+ let response = await this.resourceHttp.post(`/collections`, collection);
862
+ return response.data;
863
+ }
864
+
865
+ async updateCollection(
866
+ collectionId: string,
867
+ updates: {
868
+ name?: string;
869
+ description?: string;
870
+ ownerId?: string;
871
+ }
872
+ ): Promise<any> {
873
+ let current = await this.getCollection(collectionId);
874
+ let ownerLuid = updates.ownerId ?? this.userId;
875
+
876
+ if (!ownerLuid) {
877
+ throw tableauServiceError(
878
+ 'ownerId is required to update a collection when the authenticated Tableau user ID is unavailable.'
879
+ );
880
+ }
881
+
882
+ let collection: Record<string, any> = { luid: collectionId };
883
+ if (updates.name !== undefined) {
884
+ collection.name = updates.name;
885
+ } else if (typeof current.name === 'string') {
886
+ collection.name = current.name;
887
+ }
888
+ if (updates.description !== undefined) {
889
+ collection.description = updates.description;
890
+ } else if (typeof current.description === 'string') {
891
+ collection.description = current.description;
892
+ }
893
+ collection.ownerLuid = ownerLuid;
894
+
895
+ let response = await this.resourceHttp.post(`/collections/batchUpdate`, [collection]);
896
+ assertCollectionBatchSucceeded(response.data, 'update');
897
+ return await this.getCollection(collectionId);
898
+ }
899
+
900
+ async deleteCollection(collectionId: string): Promise<void> {
901
+ await this.resourceHttp.delete(`/collections/${collectionId}`);
902
+ }
903
+
904
+ async listCollectionItems(
905
+ collectionId: string,
906
+ params?: {
907
+ pageSize?: number;
908
+ pageNumber?: number;
909
+ filter?: string;
910
+ sort?: string;
911
+ }
912
+ ): Promise<any> {
913
+ let queryParams: Record<string, string> = {};
914
+ if (params?.pageSize) queryParams['pageSize'] = String(params.pageSize);
915
+ if (params?.pageNumber) queryParams['pageNumber'] = String(params.pageNumber);
916
+ if (params?.filter) queryParams['filter'] = params.filter;
917
+ if (params?.sort) queryParams['sort'] = params.sort;
918
+
919
+ let response = await this.resourceHttp.get(`/collections/${collectionId}/items`, {
920
+ params: queryParams
921
+ });
922
+ return response.data;
923
+ }
924
+
925
+ async addItemsToCollection(
926
+ collectionId: string,
927
+ items: CollectionItemInput[]
928
+ ): Promise<any> {
929
+ let response = await this.resourceHttp.post(
930
+ `/collections/${collectionId}/items/batchCreate`,
931
+ {
932
+ items
933
+ }
934
+ );
935
+ assertCollectionBatchSucceeded(response.data, 'add items');
936
+ return response.data;
937
+ }
938
+
939
+ async removeItemsFromCollection(
940
+ collectionId: string,
941
+ items: CollectionItemInput[]
942
+ ): Promise<any> {
943
+ let response = await this.resourceHttp.post(
944
+ `/collections/${collectionId}/items/batchDelete`,
945
+ {
946
+ items
947
+ }
948
+ );
949
+ assertCollectionBatchSucceeded(response.data, 'remove items');
950
+ return response.data;
951
+ }
952
+
953
+ // --- Site ---
954
+
955
+ async getSiteInfo(): Promise<any> {
956
+ let response = await this.http.get(`/sites/${this.siteId}`);
957
+ return response.data.site;
958
+ }
959
+ }