@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.
- package/README.md +85 -0
- package/docs/SPEC.md +61 -0
- package/logo.png +0 -0
- package/package.json +19 -0
- package/slate.json +18 -0
- package/src/auth.ts +237 -0
- package/src/config.ts +17 -0
- package/src/index.ts +55 -0
- package/src/lib/client.ts +959 -0
- package/src/lib/errors.ts +94 -0
- package/src/lib/helpers.ts +15 -0
- package/src/lib/normalizers.ts +9 -0
- package/src/spec.ts +12 -0
- package/src/tools/export-view.ts +112 -0
- package/src/tools/get-site-info.ts +47 -0
- package/src/tools/get-view-data.ts +31 -0
- package/src/tools/index.ts +18 -0
- package/src/tools/list-datasources.ts +78 -0
- package/src/tools/list-views.ts +70 -0
- package/src/tools/list-workbooks.ts +88 -0
- package/src/tools/manage-alerts.ts +139 -0
- package/src/tools/manage-collections.ts +254 -0
- package/src/tools/manage-custom-views.ts +159 -0
- package/src/tools/manage-datasource.ts +129 -0
- package/src/tools/manage-favorites.ts +80 -0
- package/src/tools/manage-flows.ts +170 -0
- package/src/tools/manage-groups.ts +178 -0
- package/src/tools/manage-jobs.ts +120 -0
- package/src/tools/manage-permissions.ts +118 -0
- package/src/tools/manage-projects.ts +162 -0
- package/src/tools/manage-users.ts +184 -0
- package/src/tools/manage-workbook.ts +160 -0
- package/src/triggers/datasource-events.ts +119 -0
- package/src/triggers/index.ts +6 -0
- package/src/triggers/label-events.ts +98 -0
- package/src/triggers/site-events.ts +97 -0
- package/src/triggers/user-events.ts +98 -0
- package/src/triggers/view-events.ts +83 -0
- package/src/triggers/workbook-events.ts +108 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { createClient } from '../lib/helpers';
|
|
5
|
+
import { tableauServiceError } from '../lib/errors';
|
|
6
|
+
|
|
7
|
+
export let manageAlerts = SlateTool.create(spec, {
|
|
8
|
+
name: 'Manage Data-Driven Alerts',
|
|
9
|
+
key: 'manage_alerts',
|
|
10
|
+
description: `List, get, delete data-driven alerts, and add or remove users from alert recipient lists. Data-driven alerts trigger when data in a view meets specified conditions.`
|
|
11
|
+
})
|
|
12
|
+
.input(
|
|
13
|
+
z.object({
|
|
14
|
+
action: z
|
|
15
|
+
.enum(['list', 'get', 'delete', 'addUser', 'removeUser'])
|
|
16
|
+
.describe('Operation to perform'),
|
|
17
|
+
alertId: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Alert LUID (required for get, delete, addUser, removeUser)'),
|
|
21
|
+
userId: z.string().optional().describe('User LUID (for addUser, removeUser)'),
|
|
22
|
+
pageSize: z.number().optional().describe('Page size for list'),
|
|
23
|
+
pageNumber: z.number().optional().describe('Page number for list')
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
.output(
|
|
27
|
+
z.object({
|
|
28
|
+
alerts: z
|
|
29
|
+
.array(
|
|
30
|
+
z.object({
|
|
31
|
+
alertId: z.string(),
|
|
32
|
+
subject: z.string().optional(),
|
|
33
|
+
creatorId: z.string().optional(),
|
|
34
|
+
createdAt: z.string().optional(),
|
|
35
|
+
updatedAt: z.string().optional()
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
.optional(),
|
|
39
|
+
alert: z
|
|
40
|
+
.object({
|
|
41
|
+
alertId: z.string(),
|
|
42
|
+
subject: z.string().optional(),
|
|
43
|
+
creatorId: z.string().optional(),
|
|
44
|
+
createdAt: z.string().optional(),
|
|
45
|
+
updatedAt: z.string().optional(),
|
|
46
|
+
frequency: z.string().optional(),
|
|
47
|
+
ownerName: z.string().optional()
|
|
48
|
+
})
|
|
49
|
+
.optional(),
|
|
50
|
+
totalCount: z.number().optional(),
|
|
51
|
+
deleted: z.boolean().optional(),
|
|
52
|
+
userAdded: z.boolean().optional(),
|
|
53
|
+
userRemoved: z.boolean().optional()
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
.handleInvocation(async ctx => {
|
|
57
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
58
|
+
let { action } = ctx.input;
|
|
59
|
+
|
|
60
|
+
if (action === 'list') {
|
|
61
|
+
let result = await client.queryAlerts({
|
|
62
|
+
pageSize: ctx.input.pageSize,
|
|
63
|
+
pageNumber: ctx.input.pageNumber
|
|
64
|
+
});
|
|
65
|
+
let alerts = (result.dataAlerts?.dataAlert || []).map((a: any) => ({
|
|
66
|
+
alertId: a.id,
|
|
67
|
+
subject: a.subject,
|
|
68
|
+
creatorId: a.creator?.id,
|
|
69
|
+
createdAt: a.createdAt,
|
|
70
|
+
updatedAt: a.updatedAt
|
|
71
|
+
}));
|
|
72
|
+
return {
|
|
73
|
+
output: { alerts, totalCount: alerts.length },
|
|
74
|
+
message: `Found **${alerts.length}** data-driven alerts.`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (action === 'get') {
|
|
79
|
+
if (!ctx.input.alertId) throw tableauServiceError('alertId is required for get action.');
|
|
80
|
+
|
|
81
|
+
let a = await client.getAlert(ctx.input.alertId);
|
|
82
|
+
return {
|
|
83
|
+
output: {
|
|
84
|
+
alert: {
|
|
85
|
+
alertId: a.id,
|
|
86
|
+
subject: a.subject,
|
|
87
|
+
creatorId: a.creator?.id,
|
|
88
|
+
createdAt: a.createdAt,
|
|
89
|
+
updatedAt: a.updatedAt,
|
|
90
|
+
frequency: a.frequency,
|
|
91
|
+
ownerName: a.owner?.name
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
message: `Retrieved alert **${a.subject}**.`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (action === 'delete') {
|
|
99
|
+
if (!ctx.input.alertId)
|
|
100
|
+
throw tableauServiceError('alertId is required for delete action.');
|
|
101
|
+
|
|
102
|
+
await client.deleteAlert(ctx.input.alertId);
|
|
103
|
+
return {
|
|
104
|
+
output: { deleted: true },
|
|
105
|
+
message: `Deleted alert \`${ctx.input.alertId}\`.`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (action === 'addUser') {
|
|
110
|
+
if (!ctx.input.alertId) {
|
|
111
|
+
throw tableauServiceError('alertId is required for addUser action.');
|
|
112
|
+
}
|
|
113
|
+
if (!ctx.input.userId)
|
|
114
|
+
throw tableauServiceError('userId is required for addUser action.');
|
|
115
|
+
|
|
116
|
+
await client.addUserToAlert(ctx.input.alertId, ctx.input.userId);
|
|
117
|
+
return {
|
|
118
|
+
output: { userAdded: true },
|
|
119
|
+
message: `Added user \`${ctx.input.userId}\` to alert \`${ctx.input.alertId}\`.`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (action === 'removeUser') {
|
|
124
|
+
if (!ctx.input.alertId) {
|
|
125
|
+
throw tableauServiceError('alertId is required for removeUser action.');
|
|
126
|
+
}
|
|
127
|
+
if (!ctx.input.userId)
|
|
128
|
+
throw tableauServiceError('userId is required for removeUser action.');
|
|
129
|
+
|
|
130
|
+
await client.removeUserFromAlert(ctx.input.alertId, ctx.input.userId);
|
|
131
|
+
return {
|
|
132
|
+
output: { userRemoved: true },
|
|
133
|
+
message: `Removed user \`${ctx.input.userId}\` from alert \`${ctx.input.alertId}\`.`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
138
|
+
})
|
|
139
|
+
.build();
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { createClient } from '../lib/helpers';
|
|
5
|
+
import { tableauServiceError } from '../lib/errors';
|
|
6
|
+
|
|
7
|
+
let collectionSchema = z.object({
|
|
8
|
+
collectionId: z.string(),
|
|
9
|
+
name: z.string().optional(),
|
|
10
|
+
description: z.string().optional(),
|
|
11
|
+
ownerName: z.string().optional(),
|
|
12
|
+
ownerId: z.string().optional(),
|
|
13
|
+
visibility: z.string().optional(),
|
|
14
|
+
createdAt: z.string().optional(),
|
|
15
|
+
updatedAt: z.string().optional(),
|
|
16
|
+
totalItemCount: z.number().optional(),
|
|
17
|
+
permissionedItemCount: z.number().optional()
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let collectionItemSchema = z.object({
|
|
21
|
+
contentLuid: z.string().optional(),
|
|
22
|
+
contentType: z.string().optional(),
|
|
23
|
+
contentName: z.string().optional(),
|
|
24
|
+
collectionLuid: z.string().optional(),
|
|
25
|
+
addedAt: z.string().optional()
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let optionalString = (value: unknown) => (typeof value === 'string' ? value : undefined);
|
|
29
|
+
let optionalIdString = (value: unknown) =>
|
|
30
|
+
typeof value === 'string' ? value : typeof value === 'number' ? String(value) : undefined;
|
|
31
|
+
|
|
32
|
+
let collectionItems = (result: any) => {
|
|
33
|
+
if (Array.isArray(result.items)) return result.items;
|
|
34
|
+
if (Array.isArray(result.collections)) return result.collections;
|
|
35
|
+
if (Array.isArray(result.collections?.collection)) return result.collections.collection;
|
|
36
|
+
return [];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let normalizeCollection = (collection: any) => ({
|
|
40
|
+
collectionId:
|
|
41
|
+
optionalString(collection.luid) ||
|
|
42
|
+
optionalString(collection.collectionLuid) ||
|
|
43
|
+
optionalIdString(collection.id) ||
|
|
44
|
+
'',
|
|
45
|
+
name: optionalString(collection.name),
|
|
46
|
+
description: optionalString(collection.description),
|
|
47
|
+
ownerName: optionalString(collection.ownerName),
|
|
48
|
+
ownerId: optionalString(collection.userId) || optionalString(collection.ownerLuid),
|
|
49
|
+
visibility: optionalString(collection.visibility),
|
|
50
|
+
createdAt: optionalString(collection.createdTime) || optionalString(collection.createdAt),
|
|
51
|
+
updatedAt: optionalString(collection.updatedTime) || optionalString(collection.updatedAt),
|
|
52
|
+
totalItemCount:
|
|
53
|
+
collection.totalItemCount != null ? Number(collection.totalItemCount) : undefined,
|
|
54
|
+
permissionedItemCount:
|
|
55
|
+
collection.permissionedItemCount != null
|
|
56
|
+
? Number(collection.permissionedItemCount)
|
|
57
|
+
: undefined
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let normalizeCollectionItem = (item: any) => ({
|
|
61
|
+
contentLuid: optionalString(item.content?.luid) || optionalString(item.contentLuid),
|
|
62
|
+
contentType: optionalString(item.content?.contentType) || optionalString(item.contentType),
|
|
63
|
+
contentName: optionalString(item.content?.name) || optionalString(item.contentName),
|
|
64
|
+
collectionLuid: optionalString(item.collectionLuid),
|
|
65
|
+
addedAt: optionalString(item.addedDate) || optionalString(item.addedAt)
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export let manageCollections = SlateTool.create(spec, {
|
|
69
|
+
name: 'Manage Collections',
|
|
70
|
+
key: 'manage_collections',
|
|
71
|
+
description: `List, get, create, update, or delete collections, and add, remove, or list collection items. Collections are curated groups of Tableau content.`,
|
|
72
|
+
tags: { destructive: true }
|
|
73
|
+
})
|
|
74
|
+
.input(
|
|
75
|
+
z.object({
|
|
76
|
+
action: z
|
|
77
|
+
.enum([
|
|
78
|
+
'list',
|
|
79
|
+
'get',
|
|
80
|
+
'create',
|
|
81
|
+
'update',
|
|
82
|
+
'delete',
|
|
83
|
+
'listItems',
|
|
84
|
+
'addItems',
|
|
85
|
+
'removeItems'
|
|
86
|
+
])
|
|
87
|
+
.describe('Operation to perform'),
|
|
88
|
+
collectionId: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('Collection LUID (required except for list and create)'),
|
|
92
|
+
name: z.string().optional().describe('Collection name (required for create)'),
|
|
93
|
+
description: z.string().optional().describe('Collection description'),
|
|
94
|
+
ownerId: z
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe(
|
|
98
|
+
'Owner user LUID for update actions. Defaults to the authenticated Tableau user.'
|
|
99
|
+
),
|
|
100
|
+
items: z
|
|
101
|
+
.array(
|
|
102
|
+
z.object({
|
|
103
|
+
contentLuid: z.string().describe('LUID of the content item'),
|
|
104
|
+
contentType: z.string().describe('Tableau content type, such as workbook or view'),
|
|
105
|
+
contentName: z.string().optional().describe('Display name of the content item')
|
|
106
|
+
})
|
|
107
|
+
)
|
|
108
|
+
.optional()
|
|
109
|
+
.describe('Items to add or remove from the collection'),
|
|
110
|
+
pageSize: z.number().int().positive().optional().describe('Page size for list actions'),
|
|
111
|
+
pageNumber: z
|
|
112
|
+
.number()
|
|
113
|
+
.int()
|
|
114
|
+
.positive()
|
|
115
|
+
.optional()
|
|
116
|
+
.describe('Page number for list actions'),
|
|
117
|
+
filter: z.string().optional().describe('Filter expression for list actions'),
|
|
118
|
+
sort: z.string().optional().describe('Sort expression for list actions')
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
.output(
|
|
122
|
+
z.object({
|
|
123
|
+
collections: z.array(collectionSchema).optional(),
|
|
124
|
+
collection: collectionSchema.optional(),
|
|
125
|
+
items: z.array(collectionItemSchema).optional(),
|
|
126
|
+
totalCount: z.number().optional(),
|
|
127
|
+
deleted: z.boolean().optional(),
|
|
128
|
+
updatedItems: z.any().optional()
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
.handleInvocation(async ctx => {
|
|
132
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
133
|
+
let { action } = ctx.input;
|
|
134
|
+
|
|
135
|
+
if (action === 'list') {
|
|
136
|
+
let result = await client.queryCollections({
|
|
137
|
+
pageSize: ctx.input.pageSize,
|
|
138
|
+
pageNumber: ctx.input.pageNumber,
|
|
139
|
+
filter: ctx.input.filter,
|
|
140
|
+
sort: ctx.input.sort
|
|
141
|
+
});
|
|
142
|
+
let collections = collectionItems(result).map(normalizeCollection);
|
|
143
|
+
let pagination = result.pagination || {};
|
|
144
|
+
return {
|
|
145
|
+
output: {
|
|
146
|
+
collections,
|
|
147
|
+
totalCount: Number(
|
|
148
|
+
result.totalCount || pagination.totalAvailable || collections.length
|
|
149
|
+
)
|
|
150
|
+
},
|
|
151
|
+
message: `Found **${collections.length}** collections.`
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (action === 'create') {
|
|
156
|
+
if (!ctx.input.name) throw tableauServiceError('name is required for create action.');
|
|
157
|
+
|
|
158
|
+
let collection = await client.createCollection(ctx.input.name, ctx.input.description);
|
|
159
|
+
return {
|
|
160
|
+
output: {
|
|
161
|
+
collection: normalizeCollection(collection)
|
|
162
|
+
},
|
|
163
|
+
message: `Created collection **${collection.name}**.`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!ctx.input.collectionId) {
|
|
168
|
+
throw tableauServiceError(`collectionId is required for ${action} action.`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (action === 'get') {
|
|
172
|
+
let collection = await client.getCollection(ctx.input.collectionId);
|
|
173
|
+
return {
|
|
174
|
+
output: {
|
|
175
|
+
collection: normalizeCollection(collection)
|
|
176
|
+
},
|
|
177
|
+
message: `Retrieved collection **${collection.name}**.`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (action === 'update') {
|
|
182
|
+
if (ctx.input.name === undefined && ctx.input.description === undefined) {
|
|
183
|
+
throw tableauServiceError('Provide name or description to update a collection.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let collection = await client.updateCollection(ctx.input.collectionId, {
|
|
187
|
+
name: ctx.input.name,
|
|
188
|
+
description: ctx.input.description,
|
|
189
|
+
ownerId: ctx.input.ownerId
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
output: {
|
|
193
|
+
collection: normalizeCollection(collection)
|
|
194
|
+
},
|
|
195
|
+
message: `Updated collection **${collection.name}**.`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (action === 'delete') {
|
|
200
|
+
await client.deleteCollection(ctx.input.collectionId);
|
|
201
|
+
return {
|
|
202
|
+
output: { deleted: true },
|
|
203
|
+
message: `Deleted collection \`${ctx.input.collectionId}\`.`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (action === 'listItems') {
|
|
208
|
+
let result = await client.listCollectionItems(ctx.input.collectionId, {
|
|
209
|
+
pageSize: ctx.input.pageSize,
|
|
210
|
+
pageNumber: ctx.input.pageNumber,
|
|
211
|
+
filter: ctx.input.filter,
|
|
212
|
+
sort: ctx.input.sort
|
|
213
|
+
});
|
|
214
|
+
let items = (result.items || []).map(normalizeCollectionItem);
|
|
215
|
+
let pagination = result.pagination || {};
|
|
216
|
+
return {
|
|
217
|
+
output: {
|
|
218
|
+
items,
|
|
219
|
+
totalCount: Number(result.totalCount || pagination.totalAvailable || items.length)
|
|
220
|
+
},
|
|
221
|
+
message: `Found **${items.length}** collection items.`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (action === 'addItems') {
|
|
226
|
+
if (!ctx.input.items?.length) {
|
|
227
|
+
throw tableauServiceError('items is required for addItems action.');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let result = await client.addItemsToCollection(ctx.input.collectionId, ctx.input.items);
|
|
231
|
+
return {
|
|
232
|
+
output: { updatedItems: result },
|
|
233
|
+
message: `Added **${ctx.input.items.length}** item(s) to collection \`${ctx.input.collectionId}\`.`
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (action === 'removeItems') {
|
|
238
|
+
if (!ctx.input.items?.length) {
|
|
239
|
+
throw tableauServiceError('items is required for removeItems action.');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let result = await client.removeItemsFromCollection(
|
|
243
|
+
ctx.input.collectionId,
|
|
244
|
+
ctx.input.items
|
|
245
|
+
);
|
|
246
|
+
return {
|
|
247
|
+
output: { updatedItems: result },
|
|
248
|
+
message: `Removed **${ctx.input.items.length}** item(s) from collection \`${ctx.input.collectionId}\`.`
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
253
|
+
})
|
|
254
|
+
.build();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { createClient } from '../lib/helpers';
|
|
5
|
+
import { tableauServiceError } from '../lib/errors';
|
|
6
|
+
|
|
7
|
+
let filterValueSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
8
|
+
|
|
9
|
+
let customViewOutputSchema = z.object({
|
|
10
|
+
customViewId: z.string(),
|
|
11
|
+
name: z.string().optional(),
|
|
12
|
+
ownerId: z.string().optional(),
|
|
13
|
+
ownerName: z.string().optional(),
|
|
14
|
+
workbookId: z.string().optional(),
|
|
15
|
+
viewId: z.string().optional(),
|
|
16
|
+
createdAt: z.string().optional(),
|
|
17
|
+
updatedAt: z.string().optional()
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let normalizeCustomView = (customView: any) => ({
|
|
21
|
+
customViewId: customView.id,
|
|
22
|
+
name: customView.name,
|
|
23
|
+
ownerId: customView.owner?.id,
|
|
24
|
+
ownerName: customView.owner?.name,
|
|
25
|
+
workbookId: customView.workbook?.id,
|
|
26
|
+
viewId: customView.view?.id,
|
|
27
|
+
createdAt: customView.createdAt,
|
|
28
|
+
updatedAt: customView.updatedAt
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export let manageCustomViews = SlateTool.create(spec, {
|
|
32
|
+
name: 'Manage Custom Views',
|
|
33
|
+
key: 'manage_custom_views',
|
|
34
|
+
description: `List, get, update, delete, or export Tableau custom views. Custom views are saved user-specific configurations of workbook views.`,
|
|
35
|
+
tags: { destructive: true }
|
|
36
|
+
})
|
|
37
|
+
.input(
|
|
38
|
+
z.object({
|
|
39
|
+
action: z
|
|
40
|
+
.enum(['list', 'get', 'update', 'delete', 'export'])
|
|
41
|
+
.describe('Operation to perform'),
|
|
42
|
+
customViewId: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Custom view LUID (required for get, update, delete, export)'),
|
|
46
|
+
name: z.string().optional().describe('New custom view name for update'),
|
|
47
|
+
ownerUserId: z.string().optional().describe('New owner user LUID for update'),
|
|
48
|
+
exportFormat: z
|
|
49
|
+
.enum(['csv', 'image', 'pdf'])
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('Format to export for the export action'),
|
|
52
|
+
pageSize: z.number().int().positive().optional().describe('Page size for list'),
|
|
53
|
+
pageNumber: z.number().int().positive().optional().describe('Page number for list'),
|
|
54
|
+
maxAgeMinutes: z
|
|
55
|
+
.number()
|
|
56
|
+
.int()
|
|
57
|
+
.min(1)
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('Minimum cache age before Tableau refreshes exported content'),
|
|
60
|
+
filters: z
|
|
61
|
+
.record(z.string(), filterValueSchema)
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('View filters keyed by field name; sent as vf_<field>=value')
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
.output(
|
|
67
|
+
z.object({
|
|
68
|
+
customViews: z.array(customViewOutputSchema).optional(),
|
|
69
|
+
customView: customViewOutputSchema.optional(),
|
|
70
|
+
totalCount: z.number().optional(),
|
|
71
|
+
deleted: z.boolean().optional(),
|
|
72
|
+
exportFormat: z.enum(['csv', 'image', 'pdf']).optional(),
|
|
73
|
+
contentType: z.string().optional(),
|
|
74
|
+
csvData: z.string().optional(),
|
|
75
|
+
contentBase64: z.string().optional()
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
.handleInvocation(async ctx => {
|
|
79
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
80
|
+
let { action } = ctx.input;
|
|
81
|
+
|
|
82
|
+
if (action === 'list') {
|
|
83
|
+
let result = await client.queryCustomViews({
|
|
84
|
+
pageSize: ctx.input.pageSize,
|
|
85
|
+
pageNumber: ctx.input.pageNumber
|
|
86
|
+
});
|
|
87
|
+
let pagination = result.pagination || {};
|
|
88
|
+
let customViews = (result.customViews?.customView || []).map(normalizeCustomView);
|
|
89
|
+
return {
|
|
90
|
+
output: {
|
|
91
|
+
customViews,
|
|
92
|
+
totalCount: Number(pagination.totalAvailable || customViews.length)
|
|
93
|
+
},
|
|
94
|
+
message: `Found **${customViews.length}** custom views.`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!ctx.input.customViewId) {
|
|
99
|
+
throw tableauServiceError(`customViewId is required for ${action} action.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (action === 'get') {
|
|
103
|
+
let customView = await client.getCustomView(ctx.input.customViewId);
|
|
104
|
+
return {
|
|
105
|
+
output: { customView: normalizeCustomView(customView) },
|
|
106
|
+
message: `Retrieved custom view **${customView.name}**.`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (action === 'update') {
|
|
111
|
+
if (ctx.input.name === undefined && ctx.input.ownerUserId === undefined) {
|
|
112
|
+
throw tableauServiceError('Provide name or ownerUserId to update a custom view.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let customView = await client.updateCustomView(ctx.input.customViewId, {
|
|
116
|
+
name: ctx.input.name,
|
|
117
|
+
ownerUserId: ctx.input.ownerUserId
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
output: { customView: normalizeCustomView(customView) },
|
|
121
|
+
message: `Updated custom view **${customView.name}**.`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (action === 'delete') {
|
|
126
|
+
await client.deleteCustomView(ctx.input.customViewId);
|
|
127
|
+
return {
|
|
128
|
+
output: { deleted: true },
|
|
129
|
+
message: `Deleted custom view \`${ctx.input.customViewId}\`.`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (action === 'export') {
|
|
134
|
+
if (!ctx.input.exportFormat) {
|
|
135
|
+
throw tableauServiceError('exportFormat is required for export action.');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let result = await client.exportCustomView(
|
|
139
|
+
ctx.input.customViewId,
|
|
140
|
+
ctx.input.exportFormat,
|
|
141
|
+
{
|
|
142
|
+
maxAgeMinutes: ctx.input.maxAgeMinutes,
|
|
143
|
+
filters: ctx.input.filters
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
return {
|
|
147
|
+
output: {
|
|
148
|
+
exportFormat: ctx.input.exportFormat,
|
|
149
|
+
contentType: result.contentType,
|
|
150
|
+
csvData: result.encoding === 'text' ? result.data : undefined,
|
|
151
|
+
contentBase64: result.encoding === 'base64' ? result.data : undefined
|
|
152
|
+
},
|
|
153
|
+
message: `Exported custom view \`${ctx.input.customViewId}\` as ${ctx.input.exportFormat}.`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
158
|
+
})
|
|
159
|
+
.build();
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { createClient } from '../lib/helpers';
|
|
5
|
+
import { tableauServiceError } from '../lib/errors';
|
|
6
|
+
|
|
7
|
+
export let manageDatasource = SlateTool.create(spec, {
|
|
8
|
+
name: 'Manage Data Source',
|
|
9
|
+
key: 'manage_datasource',
|
|
10
|
+
description: `Get details, update, delete, or trigger extract refresh for a data source. Use the **action** field to select the operation.`,
|
|
11
|
+
tags: { destructive: true }
|
|
12
|
+
})
|
|
13
|
+
.input(
|
|
14
|
+
z.object({
|
|
15
|
+
action: z.enum(['get', 'update', 'delete', 'refresh']).describe('Operation to perform'),
|
|
16
|
+
datasourceId: z.string().describe('LUID of the data source'),
|
|
17
|
+
name: z.string().optional().describe('New name (for update)'),
|
|
18
|
+
description: z.string().optional().describe('New description (for update)'),
|
|
19
|
+
projectId: z.string().optional().describe('New project LUID (for update)'),
|
|
20
|
+
ownerUserId: z.string().optional().describe('New owner LUID (for update)'),
|
|
21
|
+
isCertified: z.boolean().optional().describe('Certification status (for update)'),
|
|
22
|
+
certificationNote: z.string().optional().describe('Certification note (for update)')
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
.output(
|
|
26
|
+
z.object({
|
|
27
|
+
datasourceId: z.string().optional(),
|
|
28
|
+
name: z.string().optional(),
|
|
29
|
+
description: z.string().optional(),
|
|
30
|
+
contentUrl: z.string().optional(),
|
|
31
|
+
type: z.string().optional(),
|
|
32
|
+
isCertified: z.boolean().optional(),
|
|
33
|
+
certificationNote: z.string().optional(),
|
|
34
|
+
projectId: z.string().optional(),
|
|
35
|
+
projectName: z.string().optional(),
|
|
36
|
+
ownerId: z.string().optional(),
|
|
37
|
+
createdAt: z.string().optional(),
|
|
38
|
+
updatedAt: z.string().optional(),
|
|
39
|
+
connections: z.array(z.any()).optional(),
|
|
40
|
+
jobId: z.string().optional(),
|
|
41
|
+
deleted: z.boolean().optional()
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
.handleInvocation(async ctx => {
|
|
45
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
46
|
+
let { action, datasourceId } = ctx.input;
|
|
47
|
+
|
|
48
|
+
if (action === 'get') {
|
|
49
|
+
let ds = await client.getDatasource(datasourceId);
|
|
50
|
+
let conns = await client.getDatasourceConnections(datasourceId);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
output: {
|
|
54
|
+
datasourceId: ds.id,
|
|
55
|
+
name: ds.name,
|
|
56
|
+
description: ds.description,
|
|
57
|
+
contentUrl: ds.contentUrl,
|
|
58
|
+
type: ds.type,
|
|
59
|
+
isCertified: ds.isCertified,
|
|
60
|
+
certificationNote: ds.certificationNote,
|
|
61
|
+
projectId: ds.project?.id,
|
|
62
|
+
projectName: ds.project?.name,
|
|
63
|
+
ownerId: ds.owner?.id,
|
|
64
|
+
createdAt: ds.createdAt,
|
|
65
|
+
updatedAt: ds.updatedAt,
|
|
66
|
+
connections: conns.connections?.connection || []
|
|
67
|
+
},
|
|
68
|
+
message: `Retrieved data source **${ds.name}** with ${(conns.connections?.connection || []).length} connections.`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action === 'update') {
|
|
73
|
+
if (
|
|
74
|
+
ctx.input.name === undefined &&
|
|
75
|
+
ctx.input.description === undefined &&
|
|
76
|
+
ctx.input.projectId === undefined &&
|
|
77
|
+
ctx.input.ownerUserId === undefined &&
|
|
78
|
+
ctx.input.isCertified === undefined &&
|
|
79
|
+
ctx.input.certificationNote === undefined
|
|
80
|
+
) {
|
|
81
|
+
throw tableauServiceError('Provide at least one field to update a data source.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let ds = await client.updateDatasource(datasourceId, {
|
|
85
|
+
name: ctx.input.name,
|
|
86
|
+
description: ctx.input.description,
|
|
87
|
+
projectId: ctx.input.projectId,
|
|
88
|
+
ownerUserId: ctx.input.ownerUserId,
|
|
89
|
+
isCertified: ctx.input.isCertified,
|
|
90
|
+
certificationNote: ctx.input.certificationNote
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
output: {
|
|
95
|
+
datasourceId: ds.id,
|
|
96
|
+
name: ds.name,
|
|
97
|
+
description: ds.description,
|
|
98
|
+
contentUrl: ds.contentUrl,
|
|
99
|
+
type: ds.type,
|
|
100
|
+
isCertified: ds.isCertified,
|
|
101
|
+
certificationNote: ds.certificationNote,
|
|
102
|
+
projectId: ds.project?.id,
|
|
103
|
+
projectName: ds.project?.name,
|
|
104
|
+
ownerId: ds.owner?.id,
|
|
105
|
+
updatedAt: ds.updatedAt
|
|
106
|
+
},
|
|
107
|
+
message: `Updated data source **${ds.name}**.`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (action === 'delete') {
|
|
112
|
+
await client.deleteDatasource(datasourceId);
|
|
113
|
+
return {
|
|
114
|
+
output: { datasourceId, deleted: true },
|
|
115
|
+
message: `Deleted data source \`${datasourceId}\`.`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (action === 'refresh') {
|
|
120
|
+
let job = await client.refreshDatasource(datasourceId);
|
|
121
|
+
return {
|
|
122
|
+
output: { datasourceId, jobId: job?.id },
|
|
123
|
+
message: `Triggered extract refresh for data source \`${datasourceId}\`. Job ID: \`${job?.id}\`.`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
128
|
+
})
|
|
129
|
+
.build();
|