@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,162 @@
|
|
|
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 manageProjects = SlateTool.create(spec, {
|
|
8
|
+
name: 'Manage Projects',
|
|
9
|
+
key: 'manage_projects',
|
|
10
|
+
description: `List, create, update, or delete projects. Projects organize workbooks, data sources, and other content in Tableau.`,
|
|
11
|
+
tags: { destructive: true }
|
|
12
|
+
})
|
|
13
|
+
.input(
|
|
14
|
+
z.object({
|
|
15
|
+
action: z.enum(['list', 'create', 'update', 'delete']).describe('Operation to perform'),
|
|
16
|
+
projectId: z.string().optional().describe('Project LUID (required for update, delete)'),
|
|
17
|
+
name: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Project name (required for create, optional for update)'),
|
|
21
|
+
description: z.string().optional().describe('Project description'),
|
|
22
|
+
parentProjectId: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Parent project LUID for nested projects'),
|
|
26
|
+
contentPermissions: z
|
|
27
|
+
.enum(['ManagedByOwner', 'LockedToProject', 'LockedToProjectWithoutNested'])
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Content permission model'),
|
|
30
|
+
pageSize: z.number().optional().describe('Number of items per page'),
|
|
31
|
+
pageNumber: z.number().optional().describe('Page number (1-based)'),
|
|
32
|
+
filter: z.string().optional().describe('Filter expression for list'),
|
|
33
|
+
sort: z.string().optional().describe('Sort expression for list')
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
.output(
|
|
37
|
+
z.object({
|
|
38
|
+
projects: z
|
|
39
|
+
.array(
|
|
40
|
+
z.object({
|
|
41
|
+
projectId: z.string(),
|
|
42
|
+
name: z.string().optional(),
|
|
43
|
+
description: z.string().optional(),
|
|
44
|
+
parentProjectId: z.string().optional(),
|
|
45
|
+
contentPermissions: z.string().optional(),
|
|
46
|
+
createdAt: z.string().optional(),
|
|
47
|
+
updatedAt: z.string().optional()
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
.optional(),
|
|
51
|
+
project: z
|
|
52
|
+
.object({
|
|
53
|
+
projectId: z.string(),
|
|
54
|
+
name: z.string().optional(),
|
|
55
|
+
description: z.string().optional(),
|
|
56
|
+
parentProjectId: z.string().optional(),
|
|
57
|
+
contentPermissions: z.string().optional(),
|
|
58
|
+
createdAt: z.string().optional(),
|
|
59
|
+
updatedAt: z.string().optional()
|
|
60
|
+
})
|
|
61
|
+
.optional(),
|
|
62
|
+
totalCount: z.number().optional(),
|
|
63
|
+
deleted: z.boolean().optional()
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
.handleInvocation(async ctx => {
|
|
67
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
68
|
+
let { action } = ctx.input;
|
|
69
|
+
|
|
70
|
+
if (action === 'list') {
|
|
71
|
+
let result = await client.queryProjects({
|
|
72
|
+
pageSize: ctx.input.pageSize,
|
|
73
|
+
pageNumber: ctx.input.pageNumber,
|
|
74
|
+
filter: ctx.input.filter,
|
|
75
|
+
sort: ctx.input.sort
|
|
76
|
+
});
|
|
77
|
+
let pagination = result.pagination || {};
|
|
78
|
+
let projects = (result.projects?.project || []).map((p: any) => ({
|
|
79
|
+
projectId: p.id,
|
|
80
|
+
name: p.name,
|
|
81
|
+
description: p.description,
|
|
82
|
+
parentProjectId: p.parentProjectId,
|
|
83
|
+
contentPermissions: p.contentPermissions,
|
|
84
|
+
createdAt: p.createdAt,
|
|
85
|
+
updatedAt: p.updatedAt
|
|
86
|
+
}));
|
|
87
|
+
return {
|
|
88
|
+
output: { projects, totalCount: Number(pagination.totalAvailable || 0) },
|
|
89
|
+
message: `Found **${projects.length}** projects (${pagination.totalAvailable || 0} total).`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (action === 'create') {
|
|
94
|
+
if (!ctx.input.name) throw tableauServiceError('name is required for create action.');
|
|
95
|
+
|
|
96
|
+
let p = await client.createProject(ctx.input.name, {
|
|
97
|
+
description: ctx.input.description,
|
|
98
|
+
parentProjectId: ctx.input.parentProjectId,
|
|
99
|
+
contentPermissions: ctx.input.contentPermissions
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
output: {
|
|
103
|
+
project: {
|
|
104
|
+
projectId: p.id,
|
|
105
|
+
name: p.name,
|
|
106
|
+
description: p.description,
|
|
107
|
+
parentProjectId: p.parentProjectId,
|
|
108
|
+
contentPermissions: p.contentPermissions
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
message: `Created project **${p.name}**.`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (action === 'update') {
|
|
116
|
+
if (!ctx.input.projectId) {
|
|
117
|
+
throw tableauServiceError('projectId is required for update action.');
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
ctx.input.name === undefined &&
|
|
121
|
+
ctx.input.description === undefined &&
|
|
122
|
+
ctx.input.parentProjectId === undefined &&
|
|
123
|
+
ctx.input.contentPermissions === undefined
|
|
124
|
+
) {
|
|
125
|
+
throw tableauServiceError('Provide at least one field to update a project.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let p = await client.updateProject(ctx.input.projectId, {
|
|
129
|
+
name: ctx.input.name,
|
|
130
|
+
description: ctx.input.description,
|
|
131
|
+
parentProjectId: ctx.input.parentProjectId,
|
|
132
|
+
contentPermissions: ctx.input.contentPermissions
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
output: {
|
|
136
|
+
project: {
|
|
137
|
+
projectId: p.id,
|
|
138
|
+
name: p.name,
|
|
139
|
+
description: p.description,
|
|
140
|
+
parentProjectId: p.parentProjectId,
|
|
141
|
+
contentPermissions: p.contentPermissions
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
message: `Updated project **${p.name}**.`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (action === 'delete') {
|
|
149
|
+
if (!ctx.input.projectId) {
|
|
150
|
+
throw tableauServiceError('projectId is required for delete action.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await client.deleteProject(ctx.input.projectId);
|
|
154
|
+
return {
|
|
155
|
+
output: { deleted: true },
|
|
156
|
+
message: `Deleted project \`${ctx.input.projectId}\`.`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
161
|
+
})
|
|
162
|
+
.build();
|
|
@@ -0,0 +1,184 @@
|
|
|
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 manageUsers = SlateTool.create(spec, {
|
|
8
|
+
name: 'Manage Users',
|
|
9
|
+
key: 'manage_users',
|
|
10
|
+
description: `List, get, add, update, or remove users on the Tableau site. Use the **action** field to select the operation.`,
|
|
11
|
+
tags: { destructive: true }
|
|
12
|
+
})
|
|
13
|
+
.input(
|
|
14
|
+
z.object({
|
|
15
|
+
action: z
|
|
16
|
+
.enum(['list', 'get', 'add', 'update', 'remove'])
|
|
17
|
+
.describe('Operation to perform'),
|
|
18
|
+
userId: z.string().optional().describe('User LUID (required for get, update, remove)'),
|
|
19
|
+
username: z.string().optional().describe('Username (required for add)'),
|
|
20
|
+
siteRole: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(
|
|
24
|
+
'Site role, e.g. "Viewer", "Explorer", "Creator", "SiteAdministratorExplorer", "SiteAdministratorCreator" (required for add)'
|
|
25
|
+
),
|
|
26
|
+
fullName: z.string().optional().describe('Full display name (for update)'),
|
|
27
|
+
email: z.string().optional().describe('Email address (for update)'),
|
|
28
|
+
authSetting: z.string().optional().describe('Auth setting (for add/update)'),
|
|
29
|
+
pageSize: z.number().optional().describe('Number of items per page'),
|
|
30
|
+
pageNumber: z.number().optional().describe('Page number (1-based)'),
|
|
31
|
+
filter: z.string().optional().describe('Filter expression for list'),
|
|
32
|
+
sort: z.string().optional().describe('Sort expression for list')
|
|
33
|
+
})
|
|
34
|
+
)
|
|
35
|
+
.output(
|
|
36
|
+
z.object({
|
|
37
|
+
users: z
|
|
38
|
+
.array(
|
|
39
|
+
z.object({
|
|
40
|
+
userId: z.string(),
|
|
41
|
+
name: z.string().optional(),
|
|
42
|
+
fullName: z.string().optional(),
|
|
43
|
+
email: z.string().optional(),
|
|
44
|
+
siteRole: z.string().optional(),
|
|
45
|
+
authSetting: z.string().optional(),
|
|
46
|
+
lastLogin: z.string().optional()
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
.optional(),
|
|
50
|
+
user: z
|
|
51
|
+
.object({
|
|
52
|
+
userId: z.string(),
|
|
53
|
+
name: z.string().optional(),
|
|
54
|
+
fullName: z.string().optional(),
|
|
55
|
+
email: z.string().optional(),
|
|
56
|
+
siteRole: z.string().optional(),
|
|
57
|
+
authSetting: z.string().optional(),
|
|
58
|
+
lastLogin: z.string().optional()
|
|
59
|
+
})
|
|
60
|
+
.optional(),
|
|
61
|
+
totalCount: z.number().optional(),
|
|
62
|
+
removed: z.boolean().optional()
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
.handleInvocation(async ctx => {
|
|
66
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
67
|
+
let { action } = ctx.input;
|
|
68
|
+
|
|
69
|
+
if (action === 'list') {
|
|
70
|
+
let result = await client.queryUsers({
|
|
71
|
+
pageSize: ctx.input.pageSize,
|
|
72
|
+
pageNumber: ctx.input.pageNumber,
|
|
73
|
+
filter: ctx.input.filter,
|
|
74
|
+
sort: ctx.input.sort
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
let pagination = result.pagination || {};
|
|
78
|
+
let users = (result.users?.user || []).map((u: any) => ({
|
|
79
|
+
userId: u.id,
|
|
80
|
+
name: u.name,
|
|
81
|
+
fullName: u.fullName,
|
|
82
|
+
email: u.email,
|
|
83
|
+
siteRole: u.siteRole,
|
|
84
|
+
authSetting: u.authSetting,
|
|
85
|
+
lastLogin: u.lastLogin
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
output: { users, totalCount: Number(pagination.totalAvailable || 0) },
|
|
90
|
+
message: `Found **${users.length}** users (${pagination.totalAvailable || 0} total).`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (action === 'get') {
|
|
95
|
+
if (!ctx.input.userId) throw tableauServiceError('userId is required for get action.');
|
|
96
|
+
|
|
97
|
+
let u = await client.getUser(ctx.input.userId);
|
|
98
|
+
return {
|
|
99
|
+
output: {
|
|
100
|
+
user: {
|
|
101
|
+
userId: u.id,
|
|
102
|
+
name: u.name,
|
|
103
|
+
fullName: u.fullName,
|
|
104
|
+
email: u.email,
|
|
105
|
+
siteRole: u.siteRole,
|
|
106
|
+
authSetting: u.authSetting,
|
|
107
|
+
lastLogin: u.lastLogin
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
message: `Retrieved user **${u.name}** (${u.siteRole}).`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (action === 'add') {
|
|
115
|
+
if (!ctx.input.username)
|
|
116
|
+
throw tableauServiceError('username is required for add action.');
|
|
117
|
+
if (!ctx.input.siteRole)
|
|
118
|
+
throw tableauServiceError('siteRole is required for add action.');
|
|
119
|
+
|
|
120
|
+
let u = await client.addUser(
|
|
121
|
+
ctx.input.username,
|
|
122
|
+
ctx.input.siteRole,
|
|
123
|
+
ctx.input.authSetting
|
|
124
|
+
);
|
|
125
|
+
return {
|
|
126
|
+
output: {
|
|
127
|
+
user: {
|
|
128
|
+
userId: u.id,
|
|
129
|
+
name: u.name,
|
|
130
|
+
siteRole: u.siteRole,
|
|
131
|
+
authSetting: u.authSetting
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
message: `Added user **${u.name}** as ${u.siteRole}.`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (action === 'update') {
|
|
139
|
+
if (!ctx.input.userId)
|
|
140
|
+
throw tableauServiceError('userId is required for update action.');
|
|
141
|
+
if (
|
|
142
|
+
ctx.input.fullName === undefined &&
|
|
143
|
+
ctx.input.email === undefined &&
|
|
144
|
+
ctx.input.siteRole === undefined &&
|
|
145
|
+
ctx.input.authSetting === undefined
|
|
146
|
+
) {
|
|
147
|
+
throw tableauServiceError('Provide at least one field to update a user.');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let u = await client.updateUser(ctx.input.userId, {
|
|
151
|
+
fullName: ctx.input.fullName,
|
|
152
|
+
email: ctx.input.email,
|
|
153
|
+
siteRole: ctx.input.siteRole,
|
|
154
|
+
authSetting: ctx.input.authSetting
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
output: {
|
|
158
|
+
user: {
|
|
159
|
+
userId: u.id,
|
|
160
|
+
name: u.name,
|
|
161
|
+
fullName: u.fullName,
|
|
162
|
+
email: u.email,
|
|
163
|
+
siteRole: u.siteRole,
|
|
164
|
+
authSetting: u.authSetting
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
message: `Updated user **${u.name}**.`
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (action === 'remove') {
|
|
172
|
+
if (!ctx.input.userId)
|
|
173
|
+
throw tableauServiceError('userId is required for remove action.');
|
|
174
|
+
|
|
175
|
+
await client.removeUser(ctx.input.userId);
|
|
176
|
+
return {
|
|
177
|
+
output: { removed: true },
|
|
178
|
+
message: `Removed user \`${ctx.input.userId}\` from the site.`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
183
|
+
})
|
|
184
|
+
.build();
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
import { normalizeBoolean } from '../lib/normalizers';
|
|
7
|
+
|
|
8
|
+
export let manageWorkbook = SlateTool.create(spec, {
|
|
9
|
+
name: 'Manage Workbook',
|
|
10
|
+
key: 'manage_workbook',
|
|
11
|
+
description: `Get details, update properties, delete, refresh extracts, or manage tags for a workbook. Use the **action** field to select the operation.`,
|
|
12
|
+
tags: { destructive: true }
|
|
13
|
+
})
|
|
14
|
+
.input(
|
|
15
|
+
z.object({
|
|
16
|
+
action: z
|
|
17
|
+
.enum(['get', 'update', 'delete', 'refresh', 'addTags', 'removeTags'])
|
|
18
|
+
.describe('Operation to perform'),
|
|
19
|
+
workbookId: z.string().describe('LUID of the workbook'),
|
|
20
|
+
name: z.string().optional().describe('New name (for update)'),
|
|
21
|
+
description: z.string().optional().describe('New description (for update)'),
|
|
22
|
+
projectId: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('New project LUID to move workbook to (for update)'),
|
|
26
|
+
ownerUserId: z.string().optional().describe('New owner user LUID (for update)'),
|
|
27
|
+
showTabs: z.boolean().optional().describe('Whether to show tabs (for update)'),
|
|
28
|
+
tags: z.array(z.string()).optional().describe('Tags to add or remove')
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
.output(
|
|
32
|
+
z.object({
|
|
33
|
+
workbookId: z.string().optional(),
|
|
34
|
+
name: z.string().optional(),
|
|
35
|
+
description: z.string().optional(),
|
|
36
|
+
contentUrl: z.string().optional(),
|
|
37
|
+
showTabs: z.boolean().optional(),
|
|
38
|
+
projectId: z.string().optional(),
|
|
39
|
+
projectName: z.string().optional(),
|
|
40
|
+
ownerId: z.string().optional(),
|
|
41
|
+
createdAt: z.string().optional(),
|
|
42
|
+
updatedAt: z.string().optional(),
|
|
43
|
+
jobId: z.string().optional(),
|
|
44
|
+
deleted: z.boolean().optional(),
|
|
45
|
+
connections: z.array(z.any()).optional(),
|
|
46
|
+
views: z.array(z.any()).optional()
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
.handleInvocation(async ctx => {
|
|
50
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
51
|
+
let { action, workbookId } = ctx.input;
|
|
52
|
+
|
|
53
|
+
if (action === 'get') {
|
|
54
|
+
let wb = await client.getWorkbook(workbookId);
|
|
55
|
+
let conns = await client.getWorkbookConnections(workbookId);
|
|
56
|
+
let views = await client.queryViewsForWorkbook(workbookId);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
output: {
|
|
60
|
+
workbookId: wb.id,
|
|
61
|
+
name: wb.name,
|
|
62
|
+
description: wb.description,
|
|
63
|
+
contentUrl: wb.contentUrl,
|
|
64
|
+
showTabs: normalizeBoolean(wb.showTabs),
|
|
65
|
+
projectId: wb.project?.id,
|
|
66
|
+
projectName: wb.project?.name,
|
|
67
|
+
ownerId: wb.owner?.id,
|
|
68
|
+
createdAt: wb.createdAt,
|
|
69
|
+
updatedAt: wb.updatedAt,
|
|
70
|
+
connections: conns.connections?.connection || [],
|
|
71
|
+
views: (views.views?.view || []).map((v: any) => ({
|
|
72
|
+
viewId: v.id,
|
|
73
|
+
name: v.name,
|
|
74
|
+
contentUrl: v.contentUrl
|
|
75
|
+
}))
|
|
76
|
+
},
|
|
77
|
+
message: `Retrieved workbook **${wb.name}** with ${(conns.connections?.connection || []).length} connections and ${(views.views?.view || []).length} views.`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (action === 'update') {
|
|
82
|
+
if (
|
|
83
|
+
ctx.input.name === undefined &&
|
|
84
|
+
ctx.input.description === undefined &&
|
|
85
|
+
ctx.input.projectId === undefined &&
|
|
86
|
+
ctx.input.ownerUserId === undefined &&
|
|
87
|
+
ctx.input.showTabs === undefined
|
|
88
|
+
) {
|
|
89
|
+
throw tableauServiceError('Provide at least one field to update a workbook.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let wb = await client.updateWorkbook(workbookId, {
|
|
93
|
+
name: ctx.input.name,
|
|
94
|
+
description: ctx.input.description,
|
|
95
|
+
projectId: ctx.input.projectId,
|
|
96
|
+
ownerUserId: ctx.input.ownerUserId,
|
|
97
|
+
showTabs: ctx.input.showTabs
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
output: {
|
|
102
|
+
workbookId: wb.id,
|
|
103
|
+
name: wb.name,
|
|
104
|
+
description: wb.description,
|
|
105
|
+
contentUrl: wb.contentUrl,
|
|
106
|
+
showTabs: normalizeBoolean(wb.showTabs),
|
|
107
|
+
projectId: wb.project?.id,
|
|
108
|
+
projectName: wb.project?.name,
|
|
109
|
+
ownerId: wb.owner?.id,
|
|
110
|
+
updatedAt: wb.updatedAt
|
|
111
|
+
},
|
|
112
|
+
message: `Updated workbook **${wb.name}**.`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (action === 'delete') {
|
|
117
|
+
await client.deleteWorkbook(workbookId);
|
|
118
|
+
return {
|
|
119
|
+
output: { workbookId, deleted: true },
|
|
120
|
+
message: `Deleted workbook \`${workbookId}\`.`
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (action === 'refresh') {
|
|
125
|
+
let job = await client.refreshWorkbook(workbookId);
|
|
126
|
+
return {
|
|
127
|
+
output: { workbookId, jobId: job?.id },
|
|
128
|
+
message: `Triggered extract refresh for workbook \`${workbookId}\`. Job ID: \`${job?.id}\`.`
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (action === 'addTags') {
|
|
133
|
+
if (!ctx.input.tags?.length) {
|
|
134
|
+
throw tableauServiceError('tags is required for addTags action.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await client.addTagsToWorkbook(workbookId, ctx.input.tags);
|
|
138
|
+
return {
|
|
139
|
+
output: { workbookId },
|
|
140
|
+
message: `Added tags [${ctx.input.tags.join(', ')}] to workbook \`${workbookId}\`.`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (action === 'removeTags') {
|
|
145
|
+
if (!ctx.input.tags?.length) {
|
|
146
|
+
throw tableauServiceError('tags is required for removeTags action.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (let tag of ctx.input.tags) {
|
|
150
|
+
await client.deleteTagFromWorkbook(workbookId, tag);
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
output: { workbookId },
|
|
154
|
+
message: `Removed tags [${ctx.input.tags.join(', ')}] from workbook \`${workbookId}\`.`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw tableauServiceError(`Unknown action: ${action}`);
|
|
159
|
+
})
|
|
160
|
+
.build();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { SlateTrigger } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { createClient } from '../lib/helpers';
|
|
5
|
+
|
|
6
|
+
let eventNameMap: Record<string, string> = {
|
|
7
|
+
'webhook-source-event-datasourcecreated': 'datasource.created',
|
|
8
|
+
'webhook-source-event-datasourceupdated': 'datasource.updated',
|
|
9
|
+
'webhook-source-event-datasourcedeleted': 'datasource.deleted',
|
|
10
|
+
'webhook-source-event-datasourcerefreshstarted': 'datasource.refresh_started',
|
|
11
|
+
'webhook-source-event-datasourcerefreshsucceeded': 'datasource.refresh_succeeded',
|
|
12
|
+
'webhook-source-event-datasourcerefreshfailed': 'datasource.refresh_failed',
|
|
13
|
+
DatasourceCreated: 'datasource.created',
|
|
14
|
+
DatasourceUpdated: 'datasource.updated',
|
|
15
|
+
DatasourceDeleted: 'datasource.deleted',
|
|
16
|
+
DatasourceRefreshStarted: 'datasource.refresh_started',
|
|
17
|
+
DatasourceRefreshSucceeded: 'datasource.refresh_succeeded',
|
|
18
|
+
DatasourceRefreshFailed: 'datasource.refresh_failed'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let webhookEventNames = [
|
|
22
|
+
'DatasourceCreated',
|
|
23
|
+
'DatasourceUpdated',
|
|
24
|
+
'DatasourceDeleted',
|
|
25
|
+
'DatasourceRefreshStarted',
|
|
26
|
+
'DatasourceRefreshSucceeded',
|
|
27
|
+
'DatasourceRefreshFailed'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export let datasourceEvents = SlateTrigger.create(spec, {
|
|
31
|
+
name: 'Data Source Events',
|
|
32
|
+
key: 'datasource_events',
|
|
33
|
+
description:
|
|
34
|
+
'Triggers when a data source is created, updated, deleted, or when an extract refresh starts, succeeds, or fails.'
|
|
35
|
+
})
|
|
36
|
+
.input(
|
|
37
|
+
z.object({
|
|
38
|
+
eventType: z.string().describe('Tableau webhook event type'),
|
|
39
|
+
resourceId: z.string().describe('LUID of the affected data source'),
|
|
40
|
+
resourceName: z.string().describe('Name of the affected data source'),
|
|
41
|
+
siteId: z.string().describe('LUID of the site'),
|
|
42
|
+
timestamp: z.string().describe('Event timestamp')
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
.output(
|
|
46
|
+
z.object({
|
|
47
|
+
datasourceId: z.string().describe('LUID of the affected data source'),
|
|
48
|
+
datasourceName: z.string().describe('Name of the data source'),
|
|
49
|
+
siteId: z.string().describe('LUID of the site'),
|
|
50
|
+
timestamp: z.string().describe('When the event occurred')
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
.webhook({
|
|
54
|
+
autoRegisterWebhook: async ctx => {
|
|
55
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
56
|
+
let webhookIds: Record<string, string> = {};
|
|
57
|
+
|
|
58
|
+
for (let eventName of webhookEventNames) {
|
|
59
|
+
let webhook = await client.createWebhook(
|
|
60
|
+
`slates-datasource-${eventName}`,
|
|
61
|
+
eventName,
|
|
62
|
+
ctx.input.webhookBaseUrl
|
|
63
|
+
);
|
|
64
|
+
webhookIds[eventName] = webhook.id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { registrationDetails: { webhookIds } };
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
autoUnregisterWebhook: async ctx => {
|
|
71
|
+
let client = createClient(ctx.config, ctx.auth);
|
|
72
|
+
let webhookIds = ctx.input.registrationDetails?.webhookIds || {};
|
|
73
|
+
|
|
74
|
+
for (let webhookId of Object.values(webhookIds) as string[]) {
|
|
75
|
+
try {
|
|
76
|
+
await client.deleteWebhook(webhookId);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// Webhook may already be deleted
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
handleRequest: async ctx => {
|
|
84
|
+
let body = (await ctx.request.json()) as any;
|
|
85
|
+
|
|
86
|
+
let eventType = body.resource_type
|
|
87
|
+
? `${body.resource_type}${body.event_type ? '_' + body.event_type : ''}`
|
|
88
|
+
: body.eventType || body['webhook-source-event-name'] || 'unknown';
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
inputs: [
|
|
92
|
+
{
|
|
93
|
+
eventType: eventType,
|
|
94
|
+
resourceId: body.resource_luid || body.resourceId || '',
|
|
95
|
+
resourceName: body.resource_name || body.resourceName || '',
|
|
96
|
+
siteId: body.site_luid || body.siteId || '',
|
|
97
|
+
timestamp: body.created_at || body.timestamp || new Date().toISOString()
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
handleEvent: async ctx => {
|
|
104
|
+
let mappedType =
|
|
105
|
+
eventNameMap[ctx.input.eventType] || `datasource.${ctx.input.eventType}`;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
type: mappedType,
|
|
109
|
+
id: `${ctx.input.resourceId}-${ctx.input.timestamp}`,
|
|
110
|
+
output: {
|
|
111
|
+
datasourceId: ctx.input.resourceId,
|
|
112
|
+
datasourceName: ctx.input.resourceName,
|
|
113
|
+
siteId: ctx.input.siteId,
|
|
114
|
+
timestamp: ctx.input.timestamp
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.build();
|