@squadbase/vite-server 0.1.17-dev.24af54e → 0.1.17-dev.423ee34
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 +4873 -1073
- package/dist/connectors/airtable-oauth.js +78 -11
- package/dist/connectors/airtable.js +74 -11
- package/dist/connectors/amplitude.js +38 -11
- package/dist/connectors/anthropic.js +4 -2
- package/dist/connectors/asana.js +67 -13
- package/dist/connectors/attio.js +60 -16
- package/dist/connectors/aws-billing.js +38 -11
- package/dist/connectors/azure-sql.js +64 -13
- package/dist/connectors/backlog-api-key.js +70 -18
- package/dist/connectors/clickup.js +80 -13
- package/dist/connectors/cosmosdb.js +42 -15
- package/dist/connectors/customerio.js +39 -12
- package/dist/connectors/dbt.js +716 -28
- package/dist/connectors/freshdesk.js +112 -11
- package/dist/connectors/freshsales.js +38 -11
- package/dist/connectors/freshservice.js +38 -11
- package/dist/connectors/gamma.js +47 -20
- package/dist/connectors/gemini.js +4 -2
- package/dist/connectors/github.js +42 -15
- package/dist/connectors/gmail-oauth.js +38 -13
- package/dist/connectors/gmail.js +34 -7
- package/dist/connectors/google-ads.js +38 -11
- package/dist/connectors/google-analytics-oauth.js +182 -28
- package/dist/connectors/google-analytics.js +653 -104
- package/dist/connectors/google-audit-log.js +34 -7
- package/dist/connectors/google-calendar-oauth.js +91 -18
- package/dist/connectors/google-calendar.js +91 -14
- package/dist/connectors/google-docs.js +38 -13
- package/dist/connectors/google-drive.js +60 -13
- package/dist/connectors/google-search-console-oauth.js +156 -20
- package/dist/connectors/google-sheets.js +36 -9
- package/dist/connectors/google-slides.js +38 -13
- package/dist/connectors/grafana.js +75 -13
- package/dist/connectors/hubspot-oauth.js +69 -12
- package/dist/connectors/hubspot.js +55 -12
- package/dist/connectors/influxdb.js +38 -11
- package/dist/connectors/intercom-oauth.js +100 -15
- package/dist/connectors/intercom.js +42 -15
- package/dist/connectors/jdbc.js +36 -9
- package/dist/connectors/jira-api-key.js +98 -14
- package/dist/connectors/kintone-api-token.js +96 -21
- package/dist/connectors/kintone.js +84 -14
- package/dist/connectors/linear.js +84 -15
- package/dist/connectors/linkedin-ads.js +71 -17
- package/dist/connectors/mailchimp-oauth.js +36 -9
- package/dist/connectors/mailchimp.js +36 -9
- package/dist/connectors/meta-ads-oauth.js +63 -17
- package/dist/connectors/meta-ads.js +65 -17
- package/dist/connectors/mixpanel.js +38 -11
- package/dist/connectors/monday.js +39 -12
- package/dist/connectors/mongodb.js +38 -11
- package/dist/connectors/notion-oauth.js +88 -14
- package/dist/connectors/notion.js +90 -14
- package/dist/connectors/openai.js +4 -2
- package/dist/connectors/oracle.js +78 -20
- package/dist/connectors/outlook-oauth.js +48 -23
- package/dist/connectors/powerbi-oauth.js +321 -49
- package/dist/connectors/salesforce.js +72 -12
- package/dist/connectors/semrush.js +374 -52
- package/dist/connectors/sentry.js +66 -13
- package/dist/connectors/shopify-oauth.js +71 -13
- package/dist/connectors/shopify.js +38 -11
- package/dist/connectors/sqlserver.js +64 -13
- package/dist/connectors/stripe-api-key.js +96 -18
- package/dist/connectors/stripe-oauth.js +98 -22
- package/dist/connectors/supabase.js +55 -11
- package/dist/connectors/tableau.js +262 -92
- package/dist/connectors/tiktok-ads.js +67 -19
- package/dist/connectors/wix-store.js +38 -11
- package/dist/connectors/zendesk-oauth.js +83 -15
- package/dist/connectors/zendesk.js +42 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4902 -1077
- package/dist/main.js +4891 -1071
- package/dist/vite-plugin.js +4871 -1071
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ var init_parameter_definition = __esm({
|
|
|
16
16
|
type;
|
|
17
17
|
secret;
|
|
18
18
|
required;
|
|
19
|
+
isDeprecated;
|
|
19
20
|
constructor(config) {
|
|
20
21
|
this.slug = config.slug;
|
|
21
22
|
this.name = config.name;
|
|
@@ -24,6 +25,7 @@ var init_parameter_definition = __esm({
|
|
|
24
25
|
this.type = config.type;
|
|
25
26
|
this.secret = config.secret;
|
|
26
27
|
this.required = config.required;
|
|
28
|
+
this.isDeprecated = config.isDeprecated ?? false;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Get the parameter value from a ConnectorConnectionObject.
|
|
@@ -303,7 +305,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
303
305
|
/**
|
|
304
306
|
* Create tools for connections that belong to this connector.
|
|
305
307
|
* Filters connections by connectorKey internally.
|
|
306
|
-
* Returns tools keyed as
|
|
308
|
+
* Returns tools keyed as `connector_${connectorKey}_${toolName}`.
|
|
307
309
|
*/
|
|
308
310
|
createTools(connections, config, opts) {
|
|
309
311
|
const myConnections = connections.filter(
|
|
@@ -313,7 +315,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
313
315
|
for (const t of Object.values(this.tools)) {
|
|
314
316
|
const tool = t.createTool(myConnections, config);
|
|
315
317
|
const originalToModelOutput = tool.toModelOutput;
|
|
316
|
-
result[
|
|
318
|
+
result[`connector_${this.connectorKey}_${t.name}`] = {
|
|
317
319
|
...tool,
|
|
318
320
|
toModelOutput: async (options) => {
|
|
319
321
|
if (!originalToModelOutput) {
|
|
@@ -369,19 +371,34 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
369
371
|
};
|
|
370
372
|
let state = flow.initialState();
|
|
371
373
|
let answerIdx = 0;
|
|
374
|
+
const pendingParameterUpdates = [];
|
|
372
375
|
for (const step of flow.steps) {
|
|
373
376
|
const ans = ctx.answers[answerIdx];
|
|
374
377
|
if (ans && ans.questionSlug === step.slug) {
|
|
375
378
|
state = step.applyAnswer(state, ans.answer);
|
|
379
|
+
if (step.toParameterUpdates) {
|
|
380
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
381
|
+
}
|
|
376
382
|
answerIdx += 1;
|
|
377
383
|
continue;
|
|
378
384
|
}
|
|
385
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
379
386
|
if (step.type === "text") {
|
|
387
|
+
if (step.fetchOptions) {
|
|
388
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
389
|
+
if (options2.length === 0) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
380
393
|
return {
|
|
381
394
|
type: "nextQuestion",
|
|
382
395
|
questionSlug: step.slug,
|
|
383
396
|
question: step.question[ctx.language],
|
|
384
|
-
questionType: "text"
|
|
397
|
+
questionType: "text",
|
|
398
|
+
allowFreeText: resolvedAllowFreeText,
|
|
399
|
+
...pendingParameterUpdates.length > 0 && {
|
|
400
|
+
parameterUpdates: pendingParameterUpdates
|
|
401
|
+
}
|
|
385
402
|
};
|
|
386
403
|
}
|
|
387
404
|
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
@@ -393,11 +410,21 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
393
410
|
questionSlug: step.slug,
|
|
394
411
|
question: step.question[ctx.language],
|
|
395
412
|
questionType: step.type,
|
|
396
|
-
options
|
|
413
|
+
options,
|
|
414
|
+
allowFreeText: resolvedAllowFreeText,
|
|
415
|
+
...pendingParameterUpdates.length > 0 && {
|
|
416
|
+
parameterUpdates: pendingParameterUpdates
|
|
417
|
+
}
|
|
397
418
|
};
|
|
398
419
|
}
|
|
399
420
|
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
400
|
-
return {
|
|
421
|
+
return {
|
|
422
|
+
type: "fulfilled",
|
|
423
|
+
dataInvestigationResult,
|
|
424
|
+
...pendingParameterUpdates.length > 0 && {
|
|
425
|
+
parameterUpdates: pendingParameterUpdates
|
|
426
|
+
}
|
|
427
|
+
};
|
|
401
428
|
}
|
|
402
429
|
async function resolveSetupSelection(params) {
|
|
403
430
|
const { selected, allSentinel, fetchAll, limit } = params;
|
|
@@ -420,7 +447,7 @@ var tableauOnboarding = new ConnectorOnboarding({
|
|
|
420
447
|
connectionSetupInstructions: {
|
|
421
448
|
en: `Follow these steps to verify the Tableau PAT connection.
|
|
422
449
|
|
|
423
|
-
1. Call \`
|
|
450
|
+
1. Call \`connector_tableau_request\` with \`method: "GET"\` and \`path: "/sites/{siteId}/projects?pageSize=5"\` to list a few projects on the signed-in site
|
|
424
451
|
2. If the call fails with HTTP 401, ask the user to confirm the PAT name/secret and that the Site Content URL matches the site where the PAT was issued (Tableau Cloud sites are case-sensitive)
|
|
425
452
|
|
|
426
453
|
#### Constraints
|
|
@@ -428,7 +455,7 @@ var tableauOnboarding = new ConnectorOnboarding({
|
|
|
428
455
|
- The literal placeholder \`{siteId}\` in the path is automatically replaced with the session's site ID \u2014 do NOT hardcode the ID`,
|
|
429
456
|
ja: `Tableau PAT \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u306F\u4EE5\u4E0B\u306E\u624B\u9806\u3067\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
430
457
|
|
|
431
|
-
1. \`
|
|
458
|
+
1. \`connector_tableau_request\` \u3092 \`method: "GET"\`\u3001\`path: "/sites/{siteId}/projects?pageSize=5"\` \u3067\u547C\u3073\u51FA\u3057\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u5148\u30B5\u30A4\u30C8\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u3044\u304F\u3064\u304B\u53D6\u5F97\u3059\u308B
|
|
432
459
|
2. HTTP 401 \u3067\u5931\u6557\u3057\u305F\u5834\u5408\u306F\u3001PAT \u540D/\u30B7\u30FC\u30AF\u30EC\u30C3\u30C8\u304A\u3088\u3073 Site Content URL \u304C PAT \u767A\u884C\u30B5\u30A4\u30C8\u3068\u4E00\u81F4\u3057\u3066\u3044\u308B\u304B\u3092\u30E6\u30FC\u30B6\u30FC\u306B\u78BA\u8A8D\u3059\u308B\u3088\u3046\u4F1D\u3048\u308B\uFF08Tableau Cloud \u306E\u30B5\u30A4\u30C8\u540D\u306F\u5927\u6587\u5B57\u5C0F\u6587\u5B57\u3092\u533A\u5225\uFF09
|
|
433
460
|
|
|
434
461
|
#### \u5236\u7D04
|
|
@@ -436,14 +463,14 @@ var tableauOnboarding = new ConnectorOnboarding({
|
|
|
436
463
|
- \u30D1\u30B9\u5185\u306E\u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC \`{siteId}\` \u306F\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B5\u30A4\u30C8 ID \u3067\u81EA\u52D5\u7F6E\u63DB\u3055\u308C\u307E\u3059 \u2014 ID \u3092\u30CF\u30FC\u30C9\u30B3\u30FC\u30C9\u3057\u306A\u3044\u3053\u3068`
|
|
437
464
|
},
|
|
438
465
|
dataOverviewInstructions: {
|
|
439
|
-
en: `1. Call
|
|
440
|
-
2. Call
|
|
441
|
-
3. For a target workbook, call
|
|
442
|
-
4. Call
|
|
443
|
-
ja: `1.
|
|
444
|
-
2.
|
|
445
|
-
3. \u5BFE\u8C61\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u306B\u3064\u3044\u3066
|
|
446
|
-
4.
|
|
466
|
+
en: `1. Call connector_tableau_request with GET /sites/{siteId}/projects to list projects
|
|
467
|
+
2. Call connector_tableau_request with GET /sites/{siteId}/workbooks?pageSize=20 to list workbooks
|
|
468
|
+
3. For a target workbook, call connector_tableau_request with GET /sites/{siteId}/workbooks/{workbookId}/views to list its views
|
|
469
|
+
4. Call connector_tableau_request with GET /sites/{siteId}/datasources?pageSize=20 to list published data sources`,
|
|
470
|
+
ja: `1. connector_tableau_request \u3067 GET /sites/{siteId}/projects \u3092\u547C\u3073\u51FA\u3057\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
|
|
471
|
+
2. connector_tableau_request \u3067 GET /sites/{siteId}/workbooks?pageSize=20 \u3092\u547C\u3073\u51FA\u3057\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u4E00\u89A7\u3092\u53D6\u5F97
|
|
472
|
+
3. \u5BFE\u8C61\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u306B\u3064\u3044\u3066 connector_tableau_request \u3067 GET /sites/{siteId}/workbooks/{workbookId}/views \u3092\u547C\u3073\u51FA\u3057\u3001\u30D3\u30E5\u30FC\u4E00\u89A7\u3092\u53D6\u5F97
|
|
473
|
+
4. connector_tableau_request \u3067 GET /sites/{siteId}/datasources?pageSize=20 \u3092\u547C\u3073\u51FA\u3057\u3001\u516C\u958B\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97`
|
|
447
474
|
}
|
|
448
475
|
});
|
|
449
476
|
|
|
@@ -467,7 +494,11 @@ async function tableauProxyApiFetch(proxyFetch, params, path2, init) {
|
|
|
467
494
|
|
|
468
495
|
// ../connectors/src/connectors/tableau/setup-flow.ts
|
|
469
496
|
var ALL_PROJECTS = "__ALL_PROJECTS__";
|
|
497
|
+
var ALL_WORKBOOKS = "__ALL_WORKBOOKS__";
|
|
470
498
|
var TABLEAU_SETUP_MAX_PROJECTS = 10;
|
|
499
|
+
var MAX_WORKBOOKS_DETAIL = 10;
|
|
500
|
+
var MAX_VIEWS_PER_WORKBOOK = 5;
|
|
501
|
+
var MAX_SAMPLE_ROWS = 5;
|
|
471
502
|
var PAGE_SIZE = 100;
|
|
472
503
|
async function listAllProjects(rt) {
|
|
473
504
|
const all = [];
|
|
@@ -516,6 +547,57 @@ async function listProjectResources(rt, resource) {
|
|
|
516
547
|
}
|
|
517
548
|
return all;
|
|
518
549
|
}
|
|
550
|
+
async function listViewsForWorkbook(rt, workbookId) {
|
|
551
|
+
const res = await tableauProxyApiFetch(
|
|
552
|
+
rt.config.proxyFetch,
|
|
553
|
+
rt.params,
|
|
554
|
+
`/sites/{siteId}/workbooks/${encodeURIComponent(workbookId)}/views`
|
|
555
|
+
);
|
|
556
|
+
if (!res.ok) return [];
|
|
557
|
+
const data = await res.json();
|
|
558
|
+
return data.views?.view ?? [];
|
|
559
|
+
}
|
|
560
|
+
async function fetchViewDataSample(rt, viewId) {
|
|
561
|
+
try {
|
|
562
|
+
const res = await tableauProxyApiFetch(
|
|
563
|
+
rt.config.proxyFetch,
|
|
564
|
+
rt.params,
|
|
565
|
+
`/sites/{siteId}/views/${encodeURIComponent(viewId)}/data`
|
|
566
|
+
);
|
|
567
|
+
if (!res.ok) return null;
|
|
568
|
+
const csv = await res.text();
|
|
569
|
+
const lines = csv.trim().split("\n").filter(Boolean);
|
|
570
|
+
if (lines.length === 0) return null;
|
|
571
|
+
const columns = parseCsvLine(lines[0]);
|
|
572
|
+
const rows = lines.slice(1, 1 + MAX_SAMPLE_ROWS).map(parseCsvLine);
|
|
573
|
+
return { columns, rows };
|
|
574
|
+
} catch {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function parseCsvLine(line) {
|
|
579
|
+
const result = [];
|
|
580
|
+
let current = "";
|
|
581
|
+
let inQuotes = false;
|
|
582
|
+
for (let i = 0; i < line.length; i++) {
|
|
583
|
+
const ch = line[i];
|
|
584
|
+
if (ch === '"') {
|
|
585
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
586
|
+
current += '"';
|
|
587
|
+
i++;
|
|
588
|
+
} else {
|
|
589
|
+
inQuotes = !inQuotes;
|
|
590
|
+
}
|
|
591
|
+
} else if (ch === "," && !inQuotes) {
|
|
592
|
+
result.push(current.trim());
|
|
593
|
+
current = "";
|
|
594
|
+
} else {
|
|
595
|
+
current += ch;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
result.push(current.trim());
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
519
601
|
var tableauSetupFlow = {
|
|
520
602
|
initialState: () => ({}),
|
|
521
603
|
steps: [
|
|
@@ -538,6 +620,39 @@ var tableauSetupFlow = {
|
|
|
538
620
|
];
|
|
539
621
|
},
|
|
540
622
|
applyAnswer: (state, answer) => ({ ...state, projects: answer })
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
slug: "workbooks",
|
|
626
|
+
type: "multiSelect",
|
|
627
|
+
question: {
|
|
628
|
+
ja: "\u5206\u6790\u5BFE\u8C61\u306E\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
629
|
+
en: "Select the workbooks to analyze (multi-select allowed)"
|
|
630
|
+
},
|
|
631
|
+
async fetchOptions(state, rt) {
|
|
632
|
+
if (!state.projects?.length) return [];
|
|
633
|
+
const allProjects = await listAllProjects(rt);
|
|
634
|
+
const targetIds = await resolveSetupSelection({
|
|
635
|
+
selected: state.projects,
|
|
636
|
+
allSentinel: ALL_PROJECTS,
|
|
637
|
+
fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
|
|
638
|
+
limit: TABLEAU_SETUP_MAX_PROJECTS
|
|
639
|
+
});
|
|
640
|
+
const targetSet = new Set(targetIds);
|
|
641
|
+
const workbooks = await listProjectResources(rt, "workbooks");
|
|
642
|
+
const options = workbooks.filter((wb) => wb.id && wb.project?.id && targetSet.has(wb.project.id)).map((wb) => ({
|
|
643
|
+
value: wb.id,
|
|
644
|
+
label: wb.name ?? wb.id ?? "(unknown)"
|
|
645
|
+
}));
|
|
646
|
+
if (options.length === 0) return [];
|
|
647
|
+
return [
|
|
648
|
+
{
|
|
649
|
+
value: ALL_WORKBOOKS,
|
|
650
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF" : "All workbooks"
|
|
651
|
+
},
|
|
652
|
+
...options
|
|
653
|
+
];
|
|
654
|
+
},
|
|
655
|
+
applyAnswer: (state, answer) => ({ ...state, workbooks: answer })
|
|
541
656
|
}
|
|
542
657
|
],
|
|
543
658
|
async finalize(state, rt) {
|
|
@@ -546,25 +661,33 @@ var tableauSetupFlow = {
|
|
|
546
661
|
}
|
|
547
662
|
const allProjects = await listAllProjects(rt);
|
|
548
663
|
const projectById = new Map(allProjects.map((p) => [p.id, p]));
|
|
549
|
-
const
|
|
664
|
+
const targetProjectIds = await resolveSetupSelection({
|
|
550
665
|
selected: state.projects,
|
|
551
666
|
allSentinel: ALL_PROJECTS,
|
|
552
667
|
fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
|
|
553
668
|
limit: TABLEAU_SETUP_MAX_PROJECTS
|
|
554
669
|
});
|
|
555
670
|
const sections = ["## Tableau", ""];
|
|
556
|
-
if (!
|
|
671
|
+
if (!targetProjectIds.length) {
|
|
557
672
|
sections.push("_No projects selected._", "");
|
|
558
673
|
return sections.join("\n");
|
|
559
674
|
}
|
|
560
|
-
const [
|
|
675
|
+
const [allWorkbooks, datasources] = await Promise.all([
|
|
561
676
|
listProjectResources(rt, "workbooks"),
|
|
562
677
|
listProjectResources(rt, "datasources")
|
|
563
678
|
]);
|
|
679
|
+
const projectIdSet = new Set(targetProjectIds);
|
|
680
|
+
const targetWorkbooks = await resolveSetupSelection({
|
|
681
|
+
selected: state.workbooks ?? [],
|
|
682
|
+
allSentinel: ALL_WORKBOOKS,
|
|
683
|
+
fetchAll: async () => allWorkbooks.filter((wb) => wb.id && wb.project?.id && projectIdSet.has(wb.project.id)).map((wb) => wb.id).filter(Boolean),
|
|
684
|
+
limit: MAX_WORKBOOKS_DETAIL
|
|
685
|
+
});
|
|
686
|
+
const targetWorkbookSet = new Set(targetWorkbooks);
|
|
564
687
|
const workbooksByProject = /* @__PURE__ */ new Map();
|
|
565
|
-
for (const wb of
|
|
688
|
+
for (const wb of allWorkbooks) {
|
|
566
689
|
const pid = wb.project?.id;
|
|
567
|
-
if (!pid) continue;
|
|
690
|
+
if (!pid || !projectIdSet.has(pid)) continue;
|
|
568
691
|
const bucket = workbooksByProject.get(pid) ?? [];
|
|
569
692
|
bucket.push(wb);
|
|
570
693
|
workbooksByProject.set(pid, bucket);
|
|
@@ -572,32 +695,78 @@ var tableauSetupFlow = {
|
|
|
572
695
|
const datasourcesByProject = /* @__PURE__ */ new Map();
|
|
573
696
|
for (const ds of datasources) {
|
|
574
697
|
const pid = ds.project?.id;
|
|
575
|
-
if (!pid) continue;
|
|
698
|
+
if (!pid || !projectIdSet.has(pid)) continue;
|
|
576
699
|
const bucket = datasourcesByProject.get(pid) ?? [];
|
|
577
700
|
bucket.push(ds);
|
|
578
701
|
datasourcesByProject.set(pid, bucket);
|
|
579
702
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
sections.push(`### Project: ${name}`, ""
|
|
584
|
-
const projectWorkbooks = workbooksByProject.get(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
703
|
+
const allDiscoveredColumns = [];
|
|
704
|
+
for (const pid of targetProjectIds) {
|
|
705
|
+
const project = projectById.get(pid);
|
|
706
|
+
sections.push(`### Project: ${project?.name ?? pid}`, "");
|
|
707
|
+
const projectWorkbooks = workbooksByProject.get(pid) ?? [];
|
|
708
|
+
for (const wb of projectWorkbooks) {
|
|
709
|
+
if (!wb.id) continue;
|
|
710
|
+
const isDetailed = targetWorkbookSet.has(wb.id);
|
|
711
|
+
sections.push(`#### Workbook: ${wb.name ?? wb.id}`, "");
|
|
712
|
+
if (!isDetailed) continue;
|
|
713
|
+
const views = await listViewsForWorkbook(rt, wb.id);
|
|
714
|
+
if (views.length === 0) {
|
|
715
|
+
sections.push("_No views found._", "");
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
for (const view of views.slice(0, MAX_VIEWS_PER_WORKBOOK)) {
|
|
719
|
+
if (!view.id) continue;
|
|
720
|
+
sections.push(`##### View: ${view.name ?? view.id}`, "");
|
|
721
|
+
const sample = await fetchViewDataSample(rt, view.id);
|
|
722
|
+
if (!sample || sample.columns.length === 0) {
|
|
723
|
+
sections.push("_Data not available._", "");
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
allDiscoveredColumns.push(...sample.columns);
|
|
727
|
+
sections.push(`| ${sample.columns.join(" | ")} |`);
|
|
728
|
+
sections.push(`|${sample.columns.map(() => "---").join("|")}|`);
|
|
729
|
+
for (const row of sample.rows) {
|
|
730
|
+
const cells = row.map((c) => c.replace(/\|/g, "\\|"));
|
|
731
|
+
sections.push(`| ${cells.join(" | ")} |`);
|
|
732
|
+
}
|
|
733
|
+
sections.push("");
|
|
734
|
+
}
|
|
591
735
|
}
|
|
592
|
-
const projectDatasources = datasourcesByProject.get(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
736
|
+
const projectDatasources = datasourcesByProject.get(pid) ?? [];
|
|
737
|
+
if (projectDatasources.length > 0) {
|
|
738
|
+
sections.push(`#### Datasources (${projectDatasources.length})`, "");
|
|
739
|
+
for (const ds of projectDatasources.slice(0, 25)) {
|
|
740
|
+
sections.push(`- ${ds.name ?? ds.id ?? "(unknown)"}`);
|
|
741
|
+
}
|
|
742
|
+
sections.push("");
|
|
596
743
|
}
|
|
597
|
-
|
|
598
|
-
|
|
744
|
+
}
|
|
745
|
+
if (allDiscoveredColumns.length > 0) {
|
|
746
|
+
const unique = [...new Set(allDiscoveredColumns.map((c) => c.toLowerCase()))];
|
|
747
|
+
const themes = [];
|
|
748
|
+
const hasPattern = (keywords) => unique.some((c) => keywords.some((k) => c.includes(k)));
|
|
749
|
+
if (hasPattern(["revenue", "sales", "amount", "price", "cost", "profit", "\u58F2\u4E0A", "\u91D1\u984D", "\u5229\u76CA"]))
|
|
750
|
+
themes.push("Revenue & profitability analysis (trends, breakdown by segment)");
|
|
751
|
+
if (hasPattern(["date", "month", "year", "quarter", "day", "\u65E5\u4ED8", "\u6708", "\u5E74", "\u671F"]))
|
|
752
|
+
themes.push("Time-series trend analysis (YoY, MoM comparisons)");
|
|
753
|
+
if (hasPattern(["region", "country", "city", "state", "\u5730\u57DF", "\u56FD", "\u90FD\u5E02", "\u5E02"]))
|
|
754
|
+
themes.push("Geographic/regional performance comparison");
|
|
755
|
+
if (hasPattern(["product", "category", "item", "sku", "\u5546\u54C1", "\u30AB\u30C6\u30B4\u30EA"]))
|
|
756
|
+
themes.push("Product/category mix analysis");
|
|
757
|
+
if (hasPattern(["customer", "user", "account", "\u9867\u5BA2", "\u30E6\u30FC\u30B6\u30FC"]))
|
|
758
|
+
themes.push("Customer segmentation and behavior analysis");
|
|
759
|
+
if (hasPattern(["quantity", "count", "volume", "\u6570\u91CF", "\u4EF6\u6570"]))
|
|
760
|
+
themes.push("Volume and demand pattern analysis");
|
|
761
|
+
if (hasPattern(["rate", "ratio", "percentage", "%", "\u7387"]))
|
|
762
|
+
themes.push("KPI ratio tracking and benchmarking");
|
|
763
|
+
if (themes.length > 0) {
|
|
764
|
+
sections.push("### Suggested Analysis Themes", "");
|
|
765
|
+
for (const theme of themes) {
|
|
766
|
+
sections.push(`- ${theme}`);
|
|
767
|
+
}
|
|
768
|
+
sections.push("");
|
|
599
769
|
}
|
|
600
|
-
sections.push("");
|
|
601
770
|
}
|
|
602
771
|
return sections.join("\n");
|
|
603
772
|
}
|
|
@@ -607,27 +776,36 @@ var tableauSetupFlow = {
|
|
|
607
776
|
import { z } from "zod";
|
|
608
777
|
var DEFAULT_API_VERSION3 = "3.28";
|
|
609
778
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
throw new Error(
|
|
615
|
-
"Tableau session manager is not configured. Missing INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL or INTERNAL_SQUADBASE_SANDBOX_ID."
|
|
616
|
-
);
|
|
779
|
+
var cachedToken = null;
|
|
780
|
+
async function getProxyToken(config) {
|
|
781
|
+
if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
|
|
782
|
+
return cachedToken.token;
|
|
617
783
|
}
|
|
618
|
-
const
|
|
619
|
-
const url = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
784
|
+
const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
|
|
620
785
|
const res = await fetch(url, {
|
|
621
786
|
method: "POST",
|
|
622
|
-
headers: {
|
|
787
|
+
headers: {
|
|
788
|
+
"Content-Type": "application/json",
|
|
789
|
+
"x-api-key": config.appApiKey,
|
|
790
|
+
"project-id": config.projectId
|
|
791
|
+
},
|
|
792
|
+
body: JSON.stringify({
|
|
793
|
+
sandboxId: config.sandboxId,
|
|
794
|
+
issuedBy: "coding-agent"
|
|
795
|
+
})
|
|
623
796
|
});
|
|
624
797
|
if (!res.ok) {
|
|
625
|
-
const
|
|
798
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
626
799
|
throw new Error(
|
|
627
|
-
`Failed to
|
|
800
|
+
`Failed to get proxy token: HTTP ${res.status} ${errorText}`
|
|
628
801
|
);
|
|
629
802
|
}
|
|
630
|
-
|
|
803
|
+
const data = await res.json();
|
|
804
|
+
cachedToken = {
|
|
805
|
+
token: data.token,
|
|
806
|
+
expiresAt: new Date(data.expiresAt).getTime()
|
|
807
|
+
};
|
|
808
|
+
return data.token;
|
|
631
809
|
}
|
|
632
810
|
function buildBaseUrl(serverUrl, apiVersion) {
|
|
633
811
|
return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
|
|
@@ -669,7 +847,7 @@ All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placehol
|
|
|
669
847
|
Accept and Content-Type headers default to application/json so list responses come back as JSON instead of Tableau's default XML.`,
|
|
670
848
|
inputSchema,
|
|
671
849
|
outputSchema,
|
|
672
|
-
async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
|
|
850
|
+
async execute({ connectionId, method, path: path2, queryParams, body }, connections, config) {
|
|
673
851
|
const connection2 = connections.find((c) => c.id === connectionId);
|
|
674
852
|
if (!connection2) {
|
|
675
853
|
return {
|
|
@@ -684,50 +862,42 @@ Accept and Content-Type headers default to application/json so list responses co
|
|
|
684
862
|
const serverUrl = parameters.serverUrl.getValue(connection2);
|
|
685
863
|
const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION3;
|
|
686
864
|
const trimmedPath = path2.trim().replace(/^([^/])/, "/$1");
|
|
687
|
-
const queryString = queryParams ? `?${new URLSearchParams(queryParams).toString()}` : "";
|
|
688
865
|
const baseUrl = buildBaseUrl(serverUrl, apiVersion);
|
|
866
|
+
let url = `${baseUrl}${trimmedPath}`;
|
|
867
|
+
if (queryParams) {
|
|
868
|
+
url += `?${new URLSearchParams(queryParams).toString()}`;
|
|
869
|
+
}
|
|
870
|
+
const token = await getProxyToken(config.oauthProxy);
|
|
871
|
+
const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
689
872
|
const controller = new AbortController();
|
|
690
873
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
691
874
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const init = {
|
|
875
|
+
const response = await fetch(proxyUrl, {
|
|
876
|
+
method: "POST",
|
|
877
|
+
headers: {
|
|
878
|
+
"Content-Type": "application/json",
|
|
879
|
+
Authorization: `Bearer ${token}`
|
|
880
|
+
},
|
|
881
|
+
body: JSON.stringify({
|
|
882
|
+
url,
|
|
701
883
|
method,
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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 };
|
|
884
|
+
...body !== void 0 ? { body } : {}
|
|
885
|
+
}),
|
|
886
|
+
signal: controller.signal
|
|
887
|
+
});
|
|
888
|
+
const text = await response.text();
|
|
889
|
+
const data = text ? (() => {
|
|
890
|
+
try {
|
|
891
|
+
return JSON.parse(text);
|
|
892
|
+
} catch {
|
|
893
|
+
return text;
|
|
728
894
|
}
|
|
729
|
-
|
|
895
|
+
})() : null;
|
|
896
|
+
if (!response.ok) {
|
|
897
|
+
const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
|
|
898
|
+
return { success: false, error: errorMessage };
|
|
730
899
|
}
|
|
900
|
+
return { success: true, status: response.status, data };
|
|
731
901
|
} finally {
|
|
732
902
|
clearTimeout(timeout);
|
|
733
903
|
}
|
|
@@ -753,7 +923,7 @@ var tableauConnector = new ConnectorPlugin({
|
|
|
753
923
|
systemPrompt: {
|
|
754
924
|
en: `### Tools
|
|
755
925
|
|
|
756
|
-
- \`
|
|
926
|
+
- \`connector_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.
|
|
757
927
|
|
|
758
928
|
### Business Logic
|
|
759
929
|
|
|
@@ -810,7 +980,7 @@ export default async function handler(c: Context) {
|
|
|
810
980
|
- Combine: \`filter=ownerName:eq:alice,tags:in:[finance]\``,
|
|
811
981
|
ja: `### \u30C4\u30FC\u30EB
|
|
812
982
|
|
|
813
|
-
- \`
|
|
983
|
+
- \`connector_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
|
|
814
984
|
|
|
815
985
|
### Business Logic
|
|
816
986
|
|