@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.24af54e
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/dist/cli/index.js +12374 -883
- package/dist/connectors/airtable-oauth.js +257 -46
- package/dist/connectors/airtable.js +294 -51
- package/dist/connectors/amplitude.js +297 -47
- package/dist/connectors/anthropic.js +135 -47
- package/dist/connectors/asana.js +302 -49
- package/dist/connectors/attio.js +277 -49
- package/dist/connectors/aws-billing.js +262 -46
- package/dist/connectors/azure-sql.js +396 -102
- package/dist/connectors/backlog-api-key.js +292 -47
- package/dist/connectors/clickup.js +313 -49
- package/dist/connectors/cosmosdb.js +280 -50
- package/dist/connectors/customerio.js +294 -47
- package/dist/connectors/dbt.js +315 -47
- package/dist/connectors/freshdesk.js +317 -53
- package/dist/connectors/freshsales.js +308 -52
- package/dist/connectors/freshservice.js +336 -53
- package/dist/connectors/gamma.js +302 -52
- package/dist/connectors/gemini.js +134 -47
- package/dist/connectors/github.js +361 -49
- package/dist/connectors/gmail-oauth.js +179 -7
- package/dist/connectors/gmail.js +325 -47
- package/dist/connectors/google-ads.js +263 -46
- package/dist/connectors/google-analytics-oauth.js +285 -46
- package/dist/connectors/google-analytics.js +387 -49
- package/dist/connectors/google-audit-log.js +413 -47
- package/dist/connectors/google-calendar-oauth.js +234 -46
- package/dist/connectors/google-calendar.js +334 -47
- package/dist/connectors/google-docs.js +195 -6
- package/dist/connectors/google-drive.js +237 -5
- package/dist/connectors/google-search-console-oauth.js +231 -46
- package/dist/connectors/google-sheets.js +247 -47
- package/dist/connectors/google-slides.js +180 -6
- package/dist/connectors/grafana.js +307 -49
- package/dist/connectors/hubspot-oauth.js +183 -5
- package/dist/connectors/hubspot.js +281 -49
- package/dist/connectors/influxdb.js +391 -51
- package/dist/connectors/intercom-oauth.js +185 -5
- package/dist/connectors/intercom.js +277 -49
- package/dist/connectors/jdbc.js +737 -110
- package/dist/connectors/jira-api-key.js +301 -47
- package/dist/connectors/kintone-api-token.js +256 -47
- package/dist/connectors/kintone.js +303 -47
- package/dist/connectors/linear.js +305 -49
- package/dist/connectors/linkedin-ads.js +243 -50
- package/dist/connectors/mailchimp-oauth.js +243 -46
- package/dist/connectors/mailchimp.js +295 -49
- package/dist/connectors/meta-ads-oauth.js +248 -48
- package/dist/connectors/meta-ads.js +260 -50
- package/dist/connectors/mixpanel.js +313 -47
- package/dist/connectors/monday.js +335 -49
- package/dist/connectors/mongodb.js +294 -57
- package/dist/connectors/notion-oauth.js +206 -5
- package/dist/connectors/notion.js +298 -51
- package/dist/connectors/openai.js +134 -47
- package/dist/connectors/oracle.js +414 -103
- package/dist/connectors/outlook-oauth.js +179 -5
- package/dist/connectors/powerbi-oauth.js +226 -5
- package/dist/connectors/salesforce.js +359 -49
- package/dist/connectors/semrush.js +289 -49
- package/dist/connectors/sentry.js +264 -50
- package/dist/connectors/shopify-oauth.js +162 -5
- package/dist/connectors/shopify.js +332 -47
- package/dist/connectors/sqlserver.js +390 -102
- package/dist/connectors/stripe-api-key.js +244 -46
- package/dist/connectors/stripe-oauth.js +177 -5
- package/dist/connectors/supabase.js +278 -48
- package/dist/connectors/tableau.js +389 -184
- package/dist/connectors/tiktok-ads.js +254 -48
- package/dist/connectors/wix-store.js +295 -49
- package/dist/connectors/zendesk-oauth.js +214 -5
- package/dist/connectors/zendesk.js +333 -47
- package/dist/index.d.ts +149 -1
- package/dist/index.js +13677 -1969
- package/dist/main.js +13627 -1927
- package/dist/vite-plugin.js +12391 -890
- package/package.json +1 -1
|
@@ -1,48 +1,60 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
1
6
|
// ../connectors/src/parameter-definition.ts
|
|
2
|
-
var ParameterDefinition
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
var ParameterDefinition;
|
|
8
|
+
var init_parameter_definition = __esm({
|
|
9
|
+
"../connectors/src/parameter-definition.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
ParameterDefinition = class {
|
|
12
|
+
slug;
|
|
13
|
+
name;
|
|
14
|
+
description;
|
|
15
|
+
envVarBaseKey;
|
|
16
|
+
type;
|
|
17
|
+
secret;
|
|
18
|
+
required;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.slug = config.slug;
|
|
21
|
+
this.name = config.name;
|
|
22
|
+
this.description = config.description;
|
|
23
|
+
this.envVarBaseKey = config.envVarBaseKey;
|
|
24
|
+
this.type = config.type;
|
|
25
|
+
this.secret = config.secret;
|
|
26
|
+
this.required = config.required;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the parameter value from a ConnectorConnectionObject.
|
|
30
|
+
*/
|
|
31
|
+
getValue(connection2) {
|
|
32
|
+
const param = connection2.parameters.find(
|
|
33
|
+
(p) => p.parameterSlug === this.slug
|
|
34
|
+
);
|
|
35
|
+
if (!param || param.value == null) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return param.value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
44
|
+
*/
|
|
45
|
+
tryGetValue(connection2) {
|
|
46
|
+
const param = connection2.parameters.find(
|
|
47
|
+
(p) => p.parameterSlug === this.slug
|
|
48
|
+
);
|
|
49
|
+
if (!param || param.value == null) return void 0;
|
|
50
|
+
return param.value;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
42
53
|
}
|
|
43
|
-
};
|
|
54
|
+
});
|
|
44
55
|
|
|
45
56
|
// ../connectors/src/connectors/tableau/parameters.ts
|
|
57
|
+
init_parameter_definition();
|
|
46
58
|
var parameters = {
|
|
47
59
|
serverUrl: new ParameterDefinition({
|
|
48
60
|
slug: "server-url",
|
|
@@ -93,64 +105,26 @@ var parameters = {
|
|
|
93
105
|
|
|
94
106
|
// ../connectors/src/connectors/tableau/sdk/index.ts
|
|
95
107
|
var DEFAULT_API_VERSION = "3.28";
|
|
96
|
-
|
|
108
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
109
|
+
function createClient(params, fetchFn = fetch) {
|
|
97
110
|
const serverUrl = params[parameters.serverUrl.slug];
|
|
98
|
-
const siteContentUrl = params[parameters.siteContentUrl.slug];
|
|
99
|
-
const patName = params[parameters.patName.slug];
|
|
100
|
-
const patSecret = params[parameters.patSecret.slug];
|
|
101
111
|
const apiVersion = params[parameters.apiVersion.slug] || DEFAULT_API_VERSION;
|
|
102
|
-
if (!serverUrl
|
|
103
|
-
throw new Error(
|
|
104
|
-
`tableau: missing required parameters: ${parameters.serverUrl.slug}, ${parameters.siteContentUrl.slug}, ${parameters.patName.slug}, ${parameters.patSecret.slug}`
|
|
105
|
-
);
|
|
112
|
+
if (!serverUrl) {
|
|
113
|
+
throw new Error(`tableau: missing required parameter: ${parameters.serverUrl.slug}`);
|
|
106
114
|
}
|
|
107
115
|
const baseUrl = `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
|
|
108
|
-
|
|
109
|
-
async function signIn2() {
|
|
110
|
-
const res = await fetch(`${baseUrl}/auth/signin`, {
|
|
111
|
-
method: "POST",
|
|
112
|
-
headers: {
|
|
113
|
-
"Content-Type": "application/json",
|
|
114
|
-
Accept: "application/json"
|
|
115
|
-
},
|
|
116
|
-
body: JSON.stringify({
|
|
117
|
-
credentials: {
|
|
118
|
-
personalAccessTokenName: patName,
|
|
119
|
-
personalAccessTokenSecret: patSecret,
|
|
120
|
-
site: { contentUrl: siteContentUrl }
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
});
|
|
124
|
-
if (!res.ok) {
|
|
125
|
-
const body = await res.text();
|
|
126
|
-
throw new Error(`tableau: sign-in failed (${res.status}): ${body}`);
|
|
127
|
-
}
|
|
128
|
-
const data = await res.json();
|
|
129
|
-
return {
|
|
130
|
-
authToken: data.credentials.token,
|
|
131
|
-
siteId: data.credentials.site.id,
|
|
132
|
-
userId: data.credentials.user.id,
|
|
133
|
-
expiresAt: Date.now() + 30 * 60 * 1e3
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
async function ensureSession() {
|
|
137
|
-
if (session && session.expiresAt > Date.now() + 6e4) {
|
|
138
|
-
return session;
|
|
139
|
-
}
|
|
140
|
-
session = await signIn2();
|
|
141
|
-
return session;
|
|
142
|
-
}
|
|
143
|
-
async function request(path2, init) {
|
|
144
|
-
const s = await ensureSession();
|
|
145
|
-
const resolvedPath = path2.replace(/\{siteId\}/g, s.siteId).replace(/^([^/])/, "/$1");
|
|
146
|
-
const url = `${baseUrl}${resolvedPath}`;
|
|
116
|
+
function buildRequestInit(init) {
|
|
147
117
|
const headers = new Headers(init?.headers);
|
|
148
|
-
headers.set("X-Tableau-Auth", s.authToken);
|
|
149
118
|
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
150
119
|
if (!headers.has("Content-Type") && init?.body) {
|
|
151
120
|
headers.set("Content-Type", "application/json");
|
|
152
121
|
}
|
|
153
|
-
return
|
|
122
|
+
return { ...init, headers };
|
|
123
|
+
}
|
|
124
|
+
async function request(path2, init) {
|
|
125
|
+
const trimmedPath = path2.replace(/^([^/])/, "/$1");
|
|
126
|
+
const url = `${baseUrl}${trimmedPath}`;
|
|
127
|
+
return fetchFn(url, buildRequestInit(init));
|
|
154
128
|
}
|
|
155
129
|
async function getJson(path2) {
|
|
156
130
|
const res = await request(path2);
|
|
@@ -173,8 +147,19 @@ function createClient(params) {
|
|
|
173
147
|
return {
|
|
174
148
|
request,
|
|
175
149
|
async getSession() {
|
|
176
|
-
const
|
|
177
|
-
|
|
150
|
+
const res = await fetchFn(TABLEAU_SESSION_SENTINEL_URL, { method: "POST" });
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
const body = await res.text().catch(() => "(unreadable)");
|
|
153
|
+
throw new Error(
|
|
154
|
+
`tableau: failed to fetch session (${res.status}): ${body}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const data = await res.json();
|
|
158
|
+
return {
|
|
159
|
+
authToken: data.authToken,
|
|
160
|
+
siteId: data.siteId,
|
|
161
|
+
userId: data.userId
|
|
162
|
+
};
|
|
178
163
|
},
|
|
179
164
|
async listProjects(options) {
|
|
180
165
|
return getJson(
|
|
@@ -271,6 +256,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
271
256
|
tools;
|
|
272
257
|
query;
|
|
273
258
|
checkConnection;
|
|
259
|
+
/**
|
|
260
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
261
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
262
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
263
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
264
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
265
|
+
*/
|
|
266
|
+
setup;
|
|
267
|
+
/**
|
|
268
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
269
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
270
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
271
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
272
|
+
*
|
|
273
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
274
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
275
|
+
* connectionId, which doesn't exist until the row is saved
|
|
276
|
+
*
|
|
277
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
278
|
+
* the default verify-on-create behavior without opt-in.
|
|
279
|
+
*/
|
|
280
|
+
skipConnectionCheckOnCreate;
|
|
274
281
|
constructor(config) {
|
|
275
282
|
this.slug = config.slug;
|
|
276
283
|
this.authType = config.authType;
|
|
@@ -287,6 +294,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
287
294
|
this.tools = config.tools;
|
|
288
295
|
this.query = config.query;
|
|
289
296
|
this.checkConnection = config.checkConnection;
|
|
297
|
+
this.setup = config.setup;
|
|
298
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
290
299
|
}
|
|
291
300
|
get connectorKey() {
|
|
292
301
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -351,6 +360,51 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
351
360
|
}
|
|
352
361
|
};
|
|
353
362
|
|
|
363
|
+
// ../connectors/src/setup-flow.ts
|
|
364
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
365
|
+
const runtime = {
|
|
366
|
+
params,
|
|
367
|
+
language: ctx.language,
|
|
368
|
+
config
|
|
369
|
+
};
|
|
370
|
+
let state = flow.initialState();
|
|
371
|
+
let answerIdx = 0;
|
|
372
|
+
for (const step of flow.steps) {
|
|
373
|
+
const ans = ctx.answers[answerIdx];
|
|
374
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
375
|
+
state = step.applyAnswer(state, ans.answer);
|
|
376
|
+
answerIdx += 1;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (step.type === "text") {
|
|
380
|
+
return {
|
|
381
|
+
type: "nextQuestion",
|
|
382
|
+
questionSlug: step.slug,
|
|
383
|
+
question: step.question[ctx.language],
|
|
384
|
+
questionType: "text"
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
388
|
+
if (options.length === 0) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
type: "nextQuestion",
|
|
393
|
+
questionSlug: step.slug,
|
|
394
|
+
question: step.question[ctx.language],
|
|
395
|
+
questionType: step.type,
|
|
396
|
+
options
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
400
|
+
return { type: "fulfilled", dataInvestigationResult };
|
|
401
|
+
}
|
|
402
|
+
async function resolveSetupSelection(params) {
|
|
403
|
+
const { selected, allSentinel, fetchAll, limit } = params;
|
|
404
|
+
const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
|
|
405
|
+
return resolved.slice(0, limit);
|
|
406
|
+
}
|
|
407
|
+
|
|
354
408
|
// ../connectors/src/auth-types.ts
|
|
355
409
|
var AUTH_TYPES = {
|
|
356
410
|
OAUTH: "oauth",
|
|
@@ -393,60 +447,190 @@ var tableauOnboarding = new ConnectorOnboarding({
|
|
|
393
447
|
}
|
|
394
448
|
});
|
|
395
449
|
|
|
396
|
-
// ../connectors/src/connectors/tableau/
|
|
397
|
-
import { z } from "zod";
|
|
450
|
+
// ../connectors/src/connectors/tableau/utils.ts
|
|
398
451
|
var DEFAULT_API_VERSION2 = "3.28";
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
452
|
+
function buildTableauBaseUrl(params) {
|
|
453
|
+
const serverUrl = params[parameters.serverUrl.slug];
|
|
454
|
+
const apiVersion = params[parameters.apiVersion.slug] || DEFAULT_API_VERSION2;
|
|
455
|
+
if (!serverUrl) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`tableau: missing required parameter: ${parameters.serverUrl.slug}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
402
460
|
return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
|
|
403
461
|
}
|
|
404
|
-
async function
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
462
|
+
async function tableauProxyApiFetch(proxyFetch, params, path2, init) {
|
|
463
|
+
const baseUrl = buildTableauBaseUrl(params);
|
|
464
|
+
const trimmedPath = path2.replace(/^([^/])/, "/$1");
|
|
465
|
+
return proxyFetch(`${baseUrl}${trimmedPath}`, init);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ../connectors/src/connectors/tableau/setup-flow.ts
|
|
469
|
+
var ALL_PROJECTS = "__ALL_PROJECTS__";
|
|
470
|
+
var TABLEAU_SETUP_MAX_PROJECTS = 10;
|
|
471
|
+
var PAGE_SIZE = 100;
|
|
472
|
+
async function listAllProjects(rt) {
|
|
473
|
+
const all = [];
|
|
474
|
+
let pageNumber = 1;
|
|
475
|
+
while (all.length < 500) {
|
|
476
|
+
const res = await tableauProxyApiFetch(
|
|
477
|
+
rt.config.proxyFetch,
|
|
478
|
+
rt.params,
|
|
479
|
+
`/sites/{siteId}/projects?pageSize=${PAGE_SIZE}&pageNumber=${pageNumber}`
|
|
480
|
+
);
|
|
481
|
+
if (!res.ok) {
|
|
482
|
+
const body = await res.text().catch(() => res.statusText);
|
|
483
|
+
throw new Error(`tableau: listProjects failed (${res.status}): ${body}`);
|
|
411
484
|
}
|
|
412
|
-
|
|
485
|
+
const data = await res.json();
|
|
486
|
+
const batch = data.projects?.project ?? [];
|
|
487
|
+
all.push(...batch);
|
|
488
|
+
const total = Number(data.pagination?.totalAvailable ?? "0");
|
|
489
|
+
if (!batch.length || all.length >= total) break;
|
|
490
|
+
pageNumber += 1;
|
|
491
|
+
}
|
|
492
|
+
return all;
|
|
493
|
+
}
|
|
494
|
+
async function listProjectResources(rt, resource) {
|
|
495
|
+
const all = [];
|
|
496
|
+
let pageNumber = 1;
|
|
497
|
+
while (all.length < 1e3) {
|
|
498
|
+
const res = await tableauProxyApiFetch(
|
|
499
|
+
rt.config.proxyFetch,
|
|
500
|
+
rt.params,
|
|
501
|
+
`/sites/{siteId}/${resource}?pageSize=${PAGE_SIZE}&pageNumber=${pageNumber}`
|
|
502
|
+
);
|
|
503
|
+
if (!res.ok) {
|
|
504
|
+
const body = await res.text().catch(() => res.statusText);
|
|
505
|
+
throw new Error(
|
|
506
|
+
`tableau: list ${resource} failed (${res.status}): ${body}`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
const data = await res.json();
|
|
510
|
+
const container = data[resource];
|
|
511
|
+
const batch = (resource === "workbooks" ? container?.workbook : container?.datasource) ?? [];
|
|
512
|
+
all.push(...batch);
|
|
513
|
+
const total = Number(data.pagination?.totalAvailable ?? "0");
|
|
514
|
+
if (!batch.length || all.length >= total) break;
|
|
515
|
+
pageNumber += 1;
|
|
516
|
+
}
|
|
517
|
+
return all;
|
|
518
|
+
}
|
|
519
|
+
var tableauSetupFlow = {
|
|
520
|
+
initialState: () => ({}),
|
|
521
|
+
steps: [
|
|
522
|
+
{
|
|
523
|
+
slug: "projects",
|
|
524
|
+
type: "multiSelect",
|
|
525
|
+
question: {
|
|
526
|
+
ja: "\u5BFE\u8C61\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
527
|
+
en: "Select target projects (multi-select allowed)"
|
|
528
|
+
},
|
|
529
|
+
async fetchOptions(_state, rt) {
|
|
530
|
+
const projects = await listAllProjects(rt);
|
|
531
|
+
const projectOptions = projects.filter((p) => p.id && p.name).map((p) => ({ value: p.id, label: p.name }));
|
|
532
|
+
return [
|
|
533
|
+
{
|
|
534
|
+
value: ALL_PROJECTS,
|
|
535
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8" : "All projects"
|
|
536
|
+
},
|
|
537
|
+
...projectOptions
|
|
538
|
+
];
|
|
539
|
+
},
|
|
540
|
+
applyAnswer: (state, answer) => ({ ...state, projects: answer })
|
|
541
|
+
}
|
|
542
|
+
],
|
|
543
|
+
async finalize(state, rt) {
|
|
544
|
+
if (!state.projects) {
|
|
545
|
+
throw new Error("Tableau setup: incomplete state on finalize");
|
|
546
|
+
}
|
|
547
|
+
const allProjects = await listAllProjects(rt);
|
|
548
|
+
const projectById = new Map(allProjects.map((p) => [p.id, p]));
|
|
549
|
+
const targetIds = await resolveSetupSelection({
|
|
550
|
+
selected: state.projects,
|
|
551
|
+
allSentinel: ALL_PROJECTS,
|
|
552
|
+
fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
|
|
553
|
+
limit: TABLEAU_SETUP_MAX_PROJECTS
|
|
554
|
+
});
|
|
555
|
+
const sections = ["## Tableau", ""];
|
|
556
|
+
if (!targetIds.length) {
|
|
557
|
+
sections.push("_No projects selected._", "");
|
|
558
|
+
return sections.join("\n");
|
|
559
|
+
}
|
|
560
|
+
const [workbooks, datasources] = await Promise.all([
|
|
561
|
+
listProjectResources(rt, "workbooks"),
|
|
562
|
+
listProjectResources(rt, "datasources")
|
|
563
|
+
]);
|
|
564
|
+
const workbooksByProject = /* @__PURE__ */ new Map();
|
|
565
|
+
for (const wb of workbooks) {
|
|
566
|
+
const pid = wb.project?.id;
|
|
567
|
+
if (!pid) continue;
|
|
568
|
+
const bucket = workbooksByProject.get(pid) ?? [];
|
|
569
|
+
bucket.push(wb);
|
|
570
|
+
workbooksByProject.set(pid, bucket);
|
|
571
|
+
}
|
|
572
|
+
const datasourcesByProject = /* @__PURE__ */ new Map();
|
|
573
|
+
for (const ds of datasources) {
|
|
574
|
+
const pid = ds.project?.id;
|
|
575
|
+
if (!pid) continue;
|
|
576
|
+
const bucket = datasourcesByProject.get(pid) ?? [];
|
|
577
|
+
bucket.push(ds);
|
|
578
|
+
datasourcesByProject.set(pid, bucket);
|
|
579
|
+
}
|
|
580
|
+
for (const id of targetIds) {
|
|
581
|
+
const project = projectById.get(id);
|
|
582
|
+
const name = project?.name ?? id;
|
|
583
|
+
sections.push(`### Project: ${name}`, "", `- id: \`${id}\``);
|
|
584
|
+
const projectWorkbooks = workbooksByProject.get(id) ?? [];
|
|
585
|
+
sections.push(`- Workbooks (${projectWorkbooks.length}):`);
|
|
586
|
+
for (const wb of projectWorkbooks.slice(0, 25)) {
|
|
587
|
+
sections.push(` - ${wb.name ?? wb.id ?? "(unknown)"}`);
|
|
588
|
+
}
|
|
589
|
+
if (projectWorkbooks.length > 25) {
|
|
590
|
+
sections.push(` - \u2026and ${projectWorkbooks.length - 25} more`);
|
|
591
|
+
}
|
|
592
|
+
const projectDatasources = datasourcesByProject.get(id) ?? [];
|
|
593
|
+
sections.push(`- Datasources (${projectDatasources.length}):`);
|
|
594
|
+
for (const ds of projectDatasources.slice(0, 25)) {
|
|
595
|
+
sections.push(` - ${ds.name ?? ds.id ?? "(unknown)"}`);
|
|
596
|
+
}
|
|
597
|
+
if (projectDatasources.length > 25) {
|
|
598
|
+
sections.push(` - \u2026and ${projectDatasources.length - 25} more`);
|
|
599
|
+
}
|
|
600
|
+
sections.push("");
|
|
601
|
+
}
|
|
602
|
+
return sections.join("\n");
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// ../connectors/src/connectors/tableau/tools/request.ts
|
|
607
|
+
import { z } from "zod";
|
|
608
|
+
var DEFAULT_API_VERSION3 = "3.28";
|
|
609
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
610
|
+
async function fetchTableauSession(connectionId) {
|
|
611
|
+
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
612
|
+
const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
|
|
613
|
+
if (!token || !sandboxId) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
"Tableau session manager is not configured. Missing INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL or INTERNAL_SQUADBASE_SANDBOX_ID."
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
619
|
+
const url = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
413
620
|
const res = await fetch(url, {
|
|
414
621
|
method: "POST",
|
|
415
|
-
headers: {
|
|
416
|
-
"Content-Type": "application/json",
|
|
417
|
-
Accept: "application/json"
|
|
418
|
-
},
|
|
419
|
-
body: JSON.stringify(body)
|
|
622
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
420
623
|
});
|
|
421
624
|
if (!res.ok) {
|
|
422
|
-
const
|
|
625
|
+
const errBody = await res.text().catch(() => res.statusText);
|
|
423
626
|
throw new Error(
|
|
424
|
-
`Tableau
|
|
627
|
+
`Failed to fetch Tableau session from backend-api (HTTP ${res.status}): ${errBody}`
|
|
425
628
|
);
|
|
426
629
|
}
|
|
427
|
-
|
|
428
|
-
return {
|
|
429
|
-
authToken: data.credentials.token,
|
|
430
|
-
siteId: data.credentials.site.id,
|
|
431
|
-
userId: data.credentials.user.id,
|
|
432
|
-
expiresAt: Date.now() + 30 * 60 * 1e3
|
|
433
|
-
};
|
|
630
|
+
return await res.json();
|
|
434
631
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const cached = sessionCache.get(cacheKey);
|
|
438
|
-
if (cached && cached.expiresAt > Date.now() + 6e4) {
|
|
439
|
-
return cached;
|
|
440
|
-
}
|
|
441
|
-
const session = await signIn(
|
|
442
|
-
serverUrl,
|
|
443
|
-
apiVersion,
|
|
444
|
-
siteContentUrl,
|
|
445
|
-
patName,
|
|
446
|
-
patSecret
|
|
447
|
-
);
|
|
448
|
-
sessionCache.set(cacheKey, session);
|
|
449
|
-
return session;
|
|
632
|
+
function buildBaseUrl(serverUrl, apiVersion) {
|
|
633
|
+
return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
|
|
450
634
|
}
|
|
451
635
|
var inputSchema = z.object({
|
|
452
636
|
toolUseIntent: z.string().optional().describe(
|
|
@@ -480,8 +664,8 @@ var outputSchema = z.discriminatedUnion("success", [
|
|
|
480
664
|
var requestTool = new ConnectorTool({
|
|
481
665
|
name: "request",
|
|
482
666
|
description: `Send authenticated requests to the Tableau REST API.
|
|
483
|
-
The
|
|
484
|
-
All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placeholder \`{siteId}\` in the path \u2014 it is automatically substituted with the site ID
|
|
667
|
+
The X-Tableau-Auth token is issued and managed centrally by the Squadbase backend so concurrent processes (chat & deployed app) share a single PAT sign-in.
|
|
668
|
+
All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placeholder \`{siteId}\` in the path \u2014 it is automatically substituted with the signed-in site ID.
|
|
485
669
|
Accept and Content-Type headers default to application/json so list responses come back as JSON instead of Tableau's default XML.`,
|
|
486
670
|
inputSchema,
|
|
487
671
|
outputSchema,
|
|
@@ -498,52 +682,52 @@ Accept and Content-Type headers default to application/json so list responses co
|
|
|
498
682
|
);
|
|
499
683
|
try {
|
|
500
684
|
const serverUrl = parameters.serverUrl.getValue(connection2);
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
const session = await getSession(
|
|
506
|
-
serverUrl,
|
|
507
|
-
apiVersion,
|
|
508
|
-
siteContentUrl,
|
|
509
|
-
patName,
|
|
510
|
-
patSecret
|
|
511
|
-
);
|
|
512
|
-
const resolvedPath = path2.trim().replace(/\{siteId\}/g, session.siteId).replace(/^([^/])/, "/$1");
|
|
513
|
-
let url = `${buildBaseUrl(serverUrl, apiVersion)}${resolvedPath}`;
|
|
514
|
-
if (queryParams) {
|
|
515
|
-
const searchParams = new URLSearchParams(queryParams);
|
|
516
|
-
url += `?${searchParams.toString()}`;
|
|
517
|
-
}
|
|
685
|
+
const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION3;
|
|
686
|
+
const trimmedPath = path2.trim().replace(/^([^/])/, "/$1");
|
|
687
|
+
const queryString = queryParams ? `?${new URLSearchParams(queryParams).toString()}` : "";
|
|
688
|
+
const baseUrl = buildBaseUrl(serverUrl, apiVersion);
|
|
518
689
|
const controller = new AbortController();
|
|
519
690
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
520
691
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
692
|
+
let attempt = 0;
|
|
693
|
+
while (true) {
|
|
694
|
+
const session = await fetchTableauSession(connectionId);
|
|
695
|
+
const resolvedPath = trimmedPath.replace(
|
|
696
|
+
/\{siteId\}/g,
|
|
697
|
+
session.siteId
|
|
698
|
+
);
|
|
699
|
+
const url = `${baseUrl}${resolvedPath}${queryString}`;
|
|
700
|
+
const init = {
|
|
701
|
+
method,
|
|
702
|
+
headers: {
|
|
703
|
+
"X-Tableau-Auth": session.authToken,
|
|
704
|
+
Accept: "application/json",
|
|
705
|
+
"Content-Type": "application/json"
|
|
706
|
+
},
|
|
707
|
+
signal: controller.signal
|
|
708
|
+
};
|
|
709
|
+
if (body !== void 0) {
|
|
710
|
+
init.body = JSON.stringify(body);
|
|
711
|
+
}
|
|
712
|
+
const response = await fetch(url, init);
|
|
713
|
+
const text = await response.text();
|
|
714
|
+
const data = text ? (() => {
|
|
715
|
+
try {
|
|
716
|
+
return JSON.parse(text);
|
|
717
|
+
} catch {
|
|
718
|
+
return text;
|
|
719
|
+
}
|
|
720
|
+
})() : null;
|
|
721
|
+
if (response.status === 401 && attempt === 0) {
|
|
722
|
+
attempt++;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (!response.ok) {
|
|
726
|
+
const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
|
|
727
|
+
return { success: false, error: errorMessage };
|
|
540
728
|
}
|
|
541
|
-
|
|
542
|
-
if (!response.ok) {
|
|
543
|
-
const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
|
|
544
|
-
return { success: false, error: errorMessage };
|
|
729
|
+
return { success: true, status: response.status, data };
|
|
545
730
|
}
|
|
546
|
-
return { success: true, status: response.status, data };
|
|
547
731
|
} finally {
|
|
548
732
|
clearTimeout(timeout);
|
|
549
733
|
}
|
|
@@ -569,7 +753,7 @@ var tableauConnector = new ConnectorPlugin({
|
|
|
569
753
|
systemPrompt: {
|
|
570
754
|
en: `### Tools
|
|
571
755
|
|
|
572
|
-
- \`tableau_request\`: The only way to call the Tableau REST API. Use it for projects, workbooks, views, data sources, users, and permissions. The
|
|
756
|
+
- \`tableau_request\`: The only way to call the Tableau REST API. Use it for projects, workbooks, views, data sources, users, and permissions. The \`X-Tableau-Auth\` token is issued and managed centrally by the Squadbase backend so concurrent processes (chat & deployed app) share a single PAT sign-in. Use the literal \`{siteId}\` placeholder in the path \u2014 it is replaced with the session's site ID automatically.
|
|
573
757
|
|
|
574
758
|
### Business Logic
|
|
575
759
|
|
|
@@ -626,7 +810,7 @@ export default async function handler(c: Context) {
|
|
|
626
810
|
- Combine: \`filter=ownerName:eq:alice,tags:in:[finance]\``,
|
|
627
811
|
ja: `### \u30C4\u30FC\u30EB
|
|
628
812
|
|
|
629
|
-
- \`tableau_request\`: Tableau REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3001\u30D3\u30E5\u30FC\u3001\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u6A29\u9650\u306A\u3069\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\
|
|
813
|
+
- \`tableau_request\`: Tableau REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3001\u30D3\u30E5\u30FC\u3001\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u6A29\u9650\u306A\u3069\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\`X-Tableau-Auth\` \u30C8\u30FC\u30AF\u30F3\u306F Squadbase \u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3067\u4E00\u5143\u7BA1\u7406\u3055\u308C\u3001\u8907\u6570\u30D7\u30ED\u30BB\u30B9 (\u30C1\u30E3\u30C3\u30C8\u30FB\u30C7\u30D7\u30ED\u30A4\u6E08\u307F\u30A2\u30D7\u30EA) \u3067\u540C\u3058 PAT \u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u5171\u6709\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306B\u306F \`{siteId}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044 \u2014 \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B5\u30A4\u30C8 ID \u3067\u81EA\u52D5\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
|
|
630
814
|
|
|
631
815
|
### Business Logic
|
|
632
816
|
|
|
@@ -683,6 +867,7 @@ export default async function handler(c: Context) {
|
|
|
683
867
|
- \u7D44\u307F\u5408\u308F\u305B: \`filter=ownerName:eq:alice,tags:in:[finance]\``
|
|
684
868
|
},
|
|
685
869
|
tools,
|
|
870
|
+
setup: (params, ctx, config) => runSetupFlow(tableauSetupFlow, params, ctx, config),
|
|
686
871
|
async checkConnection(params) {
|
|
687
872
|
const serverUrl = params[parameters.serverUrl.slug];
|
|
688
873
|
const siteContentUrl = params[parameters.siteContentUrl.slug];
|
|
@@ -756,6 +941,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
756
941
|
import { getContext } from "hono/context-storage";
|
|
757
942
|
import { getCookie } from "hono/cookie";
|
|
758
943
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
944
|
+
var TABLEAU_SESSION_SENTINEL_URL2 = "squadbase://tableau-session/";
|
|
759
945
|
function normalizeHeaders(input) {
|
|
760
946
|
const out = {};
|
|
761
947
|
if (!input) return out;
|
|
@@ -764,6 +950,11 @@ function normalizeHeaders(input) {
|
|
|
764
950
|
});
|
|
765
951
|
return out;
|
|
766
952
|
}
|
|
953
|
+
function extractInputUrl(input) {
|
|
954
|
+
if (typeof input === "string") return input;
|
|
955
|
+
if (input instanceof URL) return input.href;
|
|
956
|
+
return input.url;
|
|
957
|
+
}
|
|
767
958
|
function createSandboxProxyFetch(connectionId) {
|
|
768
959
|
return async (input, init) => {
|
|
769
960
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -773,10 +964,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
773
964
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
774
965
|
);
|
|
775
966
|
}
|
|
776
|
-
const originalUrl =
|
|
967
|
+
const originalUrl = extractInputUrl(input);
|
|
968
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
969
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL2) {
|
|
970
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
971
|
+
return fetch(sessionUrl, {
|
|
972
|
+
method: "POST",
|
|
973
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
974
|
+
});
|
|
975
|
+
}
|
|
777
976
|
const originalMethod = init?.method ?? "GET";
|
|
778
977
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
779
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
780
978
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
781
979
|
return fetch(proxyUrl, {
|
|
782
980
|
method: "POST",
|
|
@@ -802,10 +1000,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
802
1000
|
}
|
|
803
1001
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
804
1002
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
1003
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
805
1004
|
return async (input, init) => {
|
|
806
|
-
const originalUrl =
|
|
807
|
-
const originalMethod = init?.method ?? "GET";
|
|
808
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
1005
|
+
const originalUrl = extractInputUrl(input);
|
|
809
1006
|
const c = getContext();
|
|
810
1007
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
811
1008
|
if (!appSession) {
|
|
@@ -813,6 +1010,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
813
1010
|
"No authentication method available for connection proxy."
|
|
814
1011
|
);
|
|
815
1012
|
}
|
|
1013
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL2) {
|
|
1014
|
+
return fetch(sessionUrl, {
|
|
1015
|
+
method: "POST",
|
|
1016
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
const originalMethod = init?.method ?? "GET";
|
|
1020
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
816
1021
|
return fetch(proxyUrl, {
|
|
817
1022
|
method: "POST",
|
|
818
1023
|
headers: {
|