@squadbase/vite-server 0.1.17-dev.24af54e → 0.1.17-dev.3b633bb
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 +1681 -449
- package/dist/connectors/airtable-oauth.js +28 -3
- package/dist/connectors/airtable.js +28 -3
- package/dist/connectors/amplitude.js +28 -3
- package/dist/connectors/asana.js +28 -3
- package/dist/connectors/attio.js +28 -3
- package/dist/connectors/aws-billing.js +28 -3
- package/dist/connectors/azure-sql.js +31 -6
- package/dist/connectors/backlog-api-key.js +28 -3
- package/dist/connectors/clickup.js +28 -3
- package/dist/connectors/cosmosdb.js +28 -3
- package/dist/connectors/customerio.js +29 -4
- package/dist/connectors/dbt.js +28 -3
- package/dist/connectors/freshdesk.js +28 -3
- package/dist/connectors/freshsales.js +28 -3
- package/dist/connectors/freshservice.js +28 -3
- package/dist/connectors/gamma.js +30 -5
- package/dist/connectors/github.js +28 -3
- package/dist/connectors/gmail-oauth.js +28 -3
- package/dist/connectors/gmail.js +28 -3
- package/dist/connectors/google-ads.js +28 -3
- package/dist/connectors/google-analytics-oauth.js +28 -3
- package/dist/connectors/google-analytics.js +227 -105
- package/dist/connectors/google-audit-log.js +28 -3
- package/dist/connectors/google-calendar-oauth.js +28 -3
- package/dist/connectors/google-calendar.js +28 -3
- package/dist/connectors/google-docs.js +28 -3
- package/dist/connectors/google-drive.js +28 -3
- package/dist/connectors/google-search-console-oauth.js +28 -3
- package/dist/connectors/google-sheets.js +28 -3
- package/dist/connectors/google-slides.js +28 -3
- package/dist/connectors/grafana.js +28 -3
- package/dist/connectors/hubspot-oauth.js +28 -3
- package/dist/connectors/hubspot.js +28 -3
- package/dist/connectors/influxdb.js +28 -3
- package/dist/connectors/intercom-oauth.js +28 -3
- package/dist/connectors/intercom.js +28 -3
- package/dist/connectors/jdbc.js +28 -3
- package/dist/connectors/jira-api-key.js +28 -3
- package/dist/connectors/kintone-api-token.js +28 -3
- package/dist/connectors/kintone.js +28 -3
- package/dist/connectors/linear.js +28 -3
- package/dist/connectors/linkedin-ads.js +28 -3
- package/dist/connectors/mailchimp-oauth.js +28 -3
- package/dist/connectors/mailchimp.js +28 -3
- package/dist/connectors/meta-ads-oauth.js +28 -3
- package/dist/connectors/meta-ads.js +28 -3
- package/dist/connectors/mixpanel.js +28 -3
- package/dist/connectors/monday.js +28 -3
- package/dist/connectors/mongodb.js +28 -3
- package/dist/connectors/notion-oauth.js +28 -3
- package/dist/connectors/notion.js +28 -3
- package/dist/connectors/oracle.js +54 -14
- package/dist/connectors/outlook-oauth.js +28 -3
- package/dist/connectors/powerbi-oauth.js +309 -37
- package/dist/connectors/salesforce.js +28 -3
- package/dist/connectors/semrush.js +366 -46
- package/dist/connectors/sentry.js +28 -3
- package/dist/connectors/shopify-oauth.js +28 -3
- package/dist/connectors/shopify.js +28 -3
- package/dist/connectors/sqlserver.js +31 -6
- package/dist/connectors/stripe-api-key.js +28 -3
- package/dist/connectors/stripe-oauth.js +28 -3
- package/dist/connectors/supabase.js +31 -6
- package/dist/connectors/tableau.js +246 -78
- package/dist/connectors/tiktok-ads.js +28 -3
- package/dist/connectors/wix-store.js +28 -3
- package/dist/connectors/zendesk-oauth.js +28 -3
- package/dist/connectors/zendesk.js +28 -3
- package/dist/index.js +1681 -449
- package/dist/main.js +1681 -449
- package/dist/vite-plugin.js +1681 -449
- package/package.json +1 -1
|
@@ -369,19 +369,34 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
369
369
|
};
|
|
370
370
|
let state = flow.initialState();
|
|
371
371
|
let answerIdx = 0;
|
|
372
|
+
const pendingParameterUpdates = [];
|
|
372
373
|
for (const step of flow.steps) {
|
|
373
374
|
const ans = ctx.answers[answerIdx];
|
|
374
375
|
if (ans && ans.questionSlug === step.slug) {
|
|
375
376
|
state = step.applyAnswer(state, ans.answer);
|
|
377
|
+
if (step.toParameterUpdates) {
|
|
378
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
379
|
+
}
|
|
376
380
|
answerIdx += 1;
|
|
377
381
|
continue;
|
|
378
382
|
}
|
|
383
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
379
384
|
if (step.type === "text") {
|
|
385
|
+
if (step.fetchOptions) {
|
|
386
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
387
|
+
if (options2.length === 0) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
380
391
|
return {
|
|
381
392
|
type: "nextQuestion",
|
|
382
393
|
questionSlug: step.slug,
|
|
383
394
|
question: step.question[ctx.language],
|
|
384
|
-
questionType: "text"
|
|
395
|
+
questionType: "text",
|
|
396
|
+
allowFreeText: resolvedAllowFreeText,
|
|
397
|
+
...pendingParameterUpdates.length > 0 && {
|
|
398
|
+
parameterUpdates: pendingParameterUpdates
|
|
399
|
+
}
|
|
385
400
|
};
|
|
386
401
|
}
|
|
387
402
|
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
@@ -393,11 +408,21 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
393
408
|
questionSlug: step.slug,
|
|
394
409
|
question: step.question[ctx.language],
|
|
395
410
|
questionType: step.type,
|
|
396
|
-
options
|
|
411
|
+
options,
|
|
412
|
+
allowFreeText: resolvedAllowFreeText,
|
|
413
|
+
...pendingParameterUpdates.length > 0 && {
|
|
414
|
+
parameterUpdates: pendingParameterUpdates
|
|
415
|
+
}
|
|
397
416
|
};
|
|
398
417
|
}
|
|
399
418
|
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
400
|
-
return {
|
|
419
|
+
return {
|
|
420
|
+
type: "fulfilled",
|
|
421
|
+
dataInvestigationResult,
|
|
422
|
+
...pendingParameterUpdates.length > 0 && {
|
|
423
|
+
parameterUpdates: pendingParameterUpdates
|
|
424
|
+
}
|
|
425
|
+
};
|
|
401
426
|
}
|
|
402
427
|
async function resolveSetupSelection(params) {
|
|
403
428
|
const { selected, allSentinel, fetchAll, limit } = params;
|
|
@@ -467,7 +492,11 @@ async function tableauProxyApiFetch(proxyFetch, params, path2, init) {
|
|
|
467
492
|
|
|
468
493
|
// ../connectors/src/connectors/tableau/setup-flow.ts
|
|
469
494
|
var ALL_PROJECTS = "__ALL_PROJECTS__";
|
|
495
|
+
var ALL_WORKBOOKS = "__ALL_WORKBOOKS__";
|
|
470
496
|
var TABLEAU_SETUP_MAX_PROJECTS = 10;
|
|
497
|
+
var MAX_WORKBOOKS_DETAIL = 10;
|
|
498
|
+
var MAX_VIEWS_PER_WORKBOOK = 5;
|
|
499
|
+
var MAX_SAMPLE_ROWS = 5;
|
|
471
500
|
var PAGE_SIZE = 100;
|
|
472
501
|
async function listAllProjects(rt) {
|
|
473
502
|
const all = [];
|
|
@@ -516,6 +545,57 @@ async function listProjectResources(rt, resource) {
|
|
|
516
545
|
}
|
|
517
546
|
return all;
|
|
518
547
|
}
|
|
548
|
+
async function listViewsForWorkbook(rt, workbookId) {
|
|
549
|
+
const res = await tableauProxyApiFetch(
|
|
550
|
+
rt.config.proxyFetch,
|
|
551
|
+
rt.params,
|
|
552
|
+
`/sites/{siteId}/workbooks/${encodeURIComponent(workbookId)}/views`
|
|
553
|
+
);
|
|
554
|
+
if (!res.ok) return [];
|
|
555
|
+
const data = await res.json();
|
|
556
|
+
return data.views?.view ?? [];
|
|
557
|
+
}
|
|
558
|
+
async function fetchViewDataSample(rt, viewId) {
|
|
559
|
+
try {
|
|
560
|
+
const res = await tableauProxyApiFetch(
|
|
561
|
+
rt.config.proxyFetch,
|
|
562
|
+
rt.params,
|
|
563
|
+
`/sites/{siteId}/views/${encodeURIComponent(viewId)}/data`
|
|
564
|
+
);
|
|
565
|
+
if (!res.ok) return null;
|
|
566
|
+
const csv = await res.text();
|
|
567
|
+
const lines = csv.trim().split("\n").filter(Boolean);
|
|
568
|
+
if (lines.length === 0) return null;
|
|
569
|
+
const columns = parseCsvLine(lines[0]);
|
|
570
|
+
const rows = lines.slice(1, 1 + MAX_SAMPLE_ROWS).map(parseCsvLine);
|
|
571
|
+
return { columns, rows };
|
|
572
|
+
} catch {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function parseCsvLine(line) {
|
|
577
|
+
const result = [];
|
|
578
|
+
let current = "";
|
|
579
|
+
let inQuotes = false;
|
|
580
|
+
for (let i = 0; i < line.length; i++) {
|
|
581
|
+
const ch = line[i];
|
|
582
|
+
if (ch === '"') {
|
|
583
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
584
|
+
current += '"';
|
|
585
|
+
i++;
|
|
586
|
+
} else {
|
|
587
|
+
inQuotes = !inQuotes;
|
|
588
|
+
}
|
|
589
|
+
} else if (ch === "," && !inQuotes) {
|
|
590
|
+
result.push(current.trim());
|
|
591
|
+
current = "";
|
|
592
|
+
} else {
|
|
593
|
+
current += ch;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
result.push(current.trim());
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
519
599
|
var tableauSetupFlow = {
|
|
520
600
|
initialState: () => ({}),
|
|
521
601
|
steps: [
|
|
@@ -538,6 +618,39 @@ var tableauSetupFlow = {
|
|
|
538
618
|
];
|
|
539
619
|
},
|
|
540
620
|
applyAnswer: (state, answer) => ({ ...state, projects: answer })
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
slug: "workbooks",
|
|
624
|
+
type: "multiSelect",
|
|
625
|
+
question: {
|
|
626
|
+
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",
|
|
627
|
+
en: "Select the workbooks to analyze (multi-select allowed)"
|
|
628
|
+
},
|
|
629
|
+
async fetchOptions(state, rt) {
|
|
630
|
+
if (!state.projects?.length) return [];
|
|
631
|
+
const allProjects = await listAllProjects(rt);
|
|
632
|
+
const targetIds = await resolveSetupSelection({
|
|
633
|
+
selected: state.projects,
|
|
634
|
+
allSentinel: ALL_PROJECTS,
|
|
635
|
+
fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
|
|
636
|
+
limit: TABLEAU_SETUP_MAX_PROJECTS
|
|
637
|
+
});
|
|
638
|
+
const targetSet = new Set(targetIds);
|
|
639
|
+
const workbooks = await listProjectResources(rt, "workbooks");
|
|
640
|
+
const options = workbooks.filter((wb) => wb.id && wb.project?.id && targetSet.has(wb.project.id)).map((wb) => ({
|
|
641
|
+
value: wb.id,
|
|
642
|
+
label: wb.name ?? wb.id ?? "(unknown)"
|
|
643
|
+
}));
|
|
644
|
+
if (options.length === 0) return [];
|
|
645
|
+
return [
|
|
646
|
+
{
|
|
647
|
+
value: ALL_WORKBOOKS,
|
|
648
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF" : "All workbooks"
|
|
649
|
+
},
|
|
650
|
+
...options
|
|
651
|
+
];
|
|
652
|
+
},
|
|
653
|
+
applyAnswer: (state, answer) => ({ ...state, workbooks: answer })
|
|
541
654
|
}
|
|
542
655
|
],
|
|
543
656
|
async finalize(state, rt) {
|
|
@@ -546,25 +659,33 @@ var tableauSetupFlow = {
|
|
|
546
659
|
}
|
|
547
660
|
const allProjects = await listAllProjects(rt);
|
|
548
661
|
const projectById = new Map(allProjects.map((p) => [p.id, p]));
|
|
549
|
-
const
|
|
662
|
+
const targetProjectIds = await resolveSetupSelection({
|
|
550
663
|
selected: state.projects,
|
|
551
664
|
allSentinel: ALL_PROJECTS,
|
|
552
665
|
fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
|
|
553
666
|
limit: TABLEAU_SETUP_MAX_PROJECTS
|
|
554
667
|
});
|
|
555
668
|
const sections = ["## Tableau", ""];
|
|
556
|
-
if (!
|
|
669
|
+
if (!targetProjectIds.length) {
|
|
557
670
|
sections.push("_No projects selected._", "");
|
|
558
671
|
return sections.join("\n");
|
|
559
672
|
}
|
|
560
|
-
const [
|
|
673
|
+
const [allWorkbooks, datasources] = await Promise.all([
|
|
561
674
|
listProjectResources(rt, "workbooks"),
|
|
562
675
|
listProjectResources(rt, "datasources")
|
|
563
676
|
]);
|
|
677
|
+
const projectIdSet = new Set(targetProjectIds);
|
|
678
|
+
const targetWorkbooks = await resolveSetupSelection({
|
|
679
|
+
selected: state.workbooks ?? [],
|
|
680
|
+
allSentinel: ALL_WORKBOOKS,
|
|
681
|
+
fetchAll: async () => allWorkbooks.filter((wb) => wb.id && wb.project?.id && projectIdSet.has(wb.project.id)).map((wb) => wb.id).filter(Boolean),
|
|
682
|
+
limit: MAX_WORKBOOKS_DETAIL
|
|
683
|
+
});
|
|
684
|
+
const targetWorkbookSet = new Set(targetWorkbooks);
|
|
564
685
|
const workbooksByProject = /* @__PURE__ */ new Map();
|
|
565
|
-
for (const wb of
|
|
686
|
+
for (const wb of allWorkbooks) {
|
|
566
687
|
const pid = wb.project?.id;
|
|
567
|
-
if (!pid) continue;
|
|
688
|
+
if (!pid || !projectIdSet.has(pid)) continue;
|
|
568
689
|
const bucket = workbooksByProject.get(pid) ?? [];
|
|
569
690
|
bucket.push(wb);
|
|
570
691
|
workbooksByProject.set(pid, bucket);
|
|
@@ -572,32 +693,78 @@ var tableauSetupFlow = {
|
|
|
572
693
|
const datasourcesByProject = /* @__PURE__ */ new Map();
|
|
573
694
|
for (const ds of datasources) {
|
|
574
695
|
const pid = ds.project?.id;
|
|
575
|
-
if (!pid) continue;
|
|
696
|
+
if (!pid || !projectIdSet.has(pid)) continue;
|
|
576
697
|
const bucket = datasourcesByProject.get(pid) ?? [];
|
|
577
698
|
bucket.push(ds);
|
|
578
699
|
datasourcesByProject.set(pid, bucket);
|
|
579
700
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
sections.push(`### Project: ${name}`, ""
|
|
584
|
-
const projectWorkbooks = workbooksByProject.get(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
701
|
+
const allDiscoveredColumns = [];
|
|
702
|
+
for (const pid of targetProjectIds) {
|
|
703
|
+
const project = projectById.get(pid);
|
|
704
|
+
sections.push(`### Project: ${project?.name ?? pid}`, "");
|
|
705
|
+
const projectWorkbooks = workbooksByProject.get(pid) ?? [];
|
|
706
|
+
for (const wb of projectWorkbooks) {
|
|
707
|
+
if (!wb.id) continue;
|
|
708
|
+
const isDetailed = targetWorkbookSet.has(wb.id);
|
|
709
|
+
sections.push(`#### Workbook: ${wb.name ?? wb.id}`, "");
|
|
710
|
+
if (!isDetailed) continue;
|
|
711
|
+
const views = await listViewsForWorkbook(rt, wb.id);
|
|
712
|
+
if (views.length === 0) {
|
|
713
|
+
sections.push("_No views found._", "");
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
for (const view of views.slice(0, MAX_VIEWS_PER_WORKBOOK)) {
|
|
717
|
+
if (!view.id) continue;
|
|
718
|
+
sections.push(`##### View: ${view.name ?? view.id}`, "");
|
|
719
|
+
const sample = await fetchViewDataSample(rt, view.id);
|
|
720
|
+
if (!sample || sample.columns.length === 0) {
|
|
721
|
+
sections.push("_Data not available._", "");
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
allDiscoveredColumns.push(...sample.columns);
|
|
725
|
+
sections.push(`| ${sample.columns.join(" | ")} |`);
|
|
726
|
+
sections.push(`|${sample.columns.map(() => "---").join("|")}|`);
|
|
727
|
+
for (const row of sample.rows) {
|
|
728
|
+
const cells = row.map((c) => c.replace(/\|/g, "\\|"));
|
|
729
|
+
sections.push(`| ${cells.join(" | ")} |`);
|
|
730
|
+
}
|
|
731
|
+
sections.push("");
|
|
732
|
+
}
|
|
591
733
|
}
|
|
592
|
-
const projectDatasources = datasourcesByProject.get(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
734
|
+
const projectDatasources = datasourcesByProject.get(pid) ?? [];
|
|
735
|
+
if (projectDatasources.length > 0) {
|
|
736
|
+
sections.push(`#### Datasources (${projectDatasources.length})`, "");
|
|
737
|
+
for (const ds of projectDatasources.slice(0, 25)) {
|
|
738
|
+
sections.push(`- ${ds.name ?? ds.id ?? "(unknown)"}`);
|
|
739
|
+
}
|
|
740
|
+
sections.push("");
|
|
596
741
|
}
|
|
597
|
-
|
|
598
|
-
|
|
742
|
+
}
|
|
743
|
+
if (allDiscoveredColumns.length > 0) {
|
|
744
|
+
const unique = [...new Set(allDiscoveredColumns.map((c) => c.toLowerCase()))];
|
|
745
|
+
const themes = [];
|
|
746
|
+
const hasPattern = (keywords) => unique.some((c) => keywords.some((k) => c.includes(k)));
|
|
747
|
+
if (hasPattern(["revenue", "sales", "amount", "price", "cost", "profit", "\u58F2\u4E0A", "\u91D1\u984D", "\u5229\u76CA"]))
|
|
748
|
+
themes.push("Revenue & profitability analysis (trends, breakdown by segment)");
|
|
749
|
+
if (hasPattern(["date", "month", "year", "quarter", "day", "\u65E5\u4ED8", "\u6708", "\u5E74", "\u671F"]))
|
|
750
|
+
themes.push("Time-series trend analysis (YoY, MoM comparisons)");
|
|
751
|
+
if (hasPattern(["region", "country", "city", "state", "\u5730\u57DF", "\u56FD", "\u90FD\u5E02", "\u5E02"]))
|
|
752
|
+
themes.push("Geographic/regional performance comparison");
|
|
753
|
+
if (hasPattern(["product", "category", "item", "sku", "\u5546\u54C1", "\u30AB\u30C6\u30B4\u30EA"]))
|
|
754
|
+
themes.push("Product/category mix analysis");
|
|
755
|
+
if (hasPattern(["customer", "user", "account", "\u9867\u5BA2", "\u30E6\u30FC\u30B6\u30FC"]))
|
|
756
|
+
themes.push("Customer segmentation and behavior analysis");
|
|
757
|
+
if (hasPattern(["quantity", "count", "volume", "\u6570\u91CF", "\u4EF6\u6570"]))
|
|
758
|
+
themes.push("Volume and demand pattern analysis");
|
|
759
|
+
if (hasPattern(["rate", "ratio", "percentage", "%", "\u7387"]))
|
|
760
|
+
themes.push("KPI ratio tracking and benchmarking");
|
|
761
|
+
if (themes.length > 0) {
|
|
762
|
+
sections.push("### Suggested Analysis Themes", "");
|
|
763
|
+
for (const theme of themes) {
|
|
764
|
+
sections.push(`- ${theme}`);
|
|
765
|
+
}
|
|
766
|
+
sections.push("");
|
|
599
767
|
}
|
|
600
|
-
sections.push("");
|
|
601
768
|
}
|
|
602
769
|
return sections.join("\n");
|
|
603
770
|
}
|
|
@@ -607,27 +774,36 @@ var tableauSetupFlow = {
|
|
|
607
774
|
import { z } from "zod";
|
|
608
775
|
var DEFAULT_API_VERSION3 = "3.28";
|
|
609
776
|
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
|
-
);
|
|
777
|
+
var cachedToken = null;
|
|
778
|
+
async function getProxyToken(config) {
|
|
779
|
+
if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
|
|
780
|
+
return cachedToken.token;
|
|
617
781
|
}
|
|
618
|
-
const
|
|
619
|
-
const url = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
782
|
+
const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
|
|
620
783
|
const res = await fetch(url, {
|
|
621
784
|
method: "POST",
|
|
622
|
-
headers: {
|
|
785
|
+
headers: {
|
|
786
|
+
"Content-Type": "application/json",
|
|
787
|
+
"x-api-key": config.appApiKey,
|
|
788
|
+
"project-id": config.projectId
|
|
789
|
+
},
|
|
790
|
+
body: JSON.stringify({
|
|
791
|
+
sandboxId: config.sandboxId,
|
|
792
|
+
issuedBy: "coding-agent"
|
|
793
|
+
})
|
|
623
794
|
});
|
|
624
795
|
if (!res.ok) {
|
|
625
|
-
const
|
|
796
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
626
797
|
throw new Error(
|
|
627
|
-
`Failed to
|
|
798
|
+
`Failed to get proxy token: HTTP ${res.status} ${errorText}`
|
|
628
799
|
);
|
|
629
800
|
}
|
|
630
|
-
|
|
801
|
+
const data = await res.json();
|
|
802
|
+
cachedToken = {
|
|
803
|
+
token: data.token,
|
|
804
|
+
expiresAt: new Date(data.expiresAt).getTime()
|
|
805
|
+
};
|
|
806
|
+
return data.token;
|
|
631
807
|
}
|
|
632
808
|
function buildBaseUrl(serverUrl, apiVersion) {
|
|
633
809
|
return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
|
|
@@ -669,7 +845,7 @@ All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placehol
|
|
|
669
845
|
Accept and Content-Type headers default to application/json so list responses come back as JSON instead of Tableau's default XML.`,
|
|
670
846
|
inputSchema,
|
|
671
847
|
outputSchema,
|
|
672
|
-
async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
|
|
848
|
+
async execute({ connectionId, method, path: path2, queryParams, body }, connections, config) {
|
|
673
849
|
const connection2 = connections.find((c) => c.id === connectionId);
|
|
674
850
|
if (!connection2) {
|
|
675
851
|
return {
|
|
@@ -684,50 +860,42 @@ Accept and Content-Type headers default to application/json so list responses co
|
|
|
684
860
|
const serverUrl = parameters.serverUrl.getValue(connection2);
|
|
685
861
|
const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION3;
|
|
686
862
|
const trimmedPath = path2.trim().replace(/^([^/])/, "/$1");
|
|
687
|
-
const queryString = queryParams ? `?${new URLSearchParams(queryParams).toString()}` : "";
|
|
688
863
|
const baseUrl = buildBaseUrl(serverUrl, apiVersion);
|
|
864
|
+
let url = `${baseUrl}${trimmedPath}`;
|
|
865
|
+
if (queryParams) {
|
|
866
|
+
url += `?${new URLSearchParams(queryParams).toString()}`;
|
|
867
|
+
}
|
|
868
|
+
const token = await getProxyToken(config.oauthProxy);
|
|
869
|
+
const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
689
870
|
const controller = new AbortController();
|
|
690
871
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
691
872
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const init = {
|
|
873
|
+
const response = await fetch(proxyUrl, {
|
|
874
|
+
method: "POST",
|
|
875
|
+
headers: {
|
|
876
|
+
"Content-Type": "application/json",
|
|
877
|
+
Authorization: `Bearer ${token}`
|
|
878
|
+
},
|
|
879
|
+
body: JSON.stringify({
|
|
880
|
+
url,
|
|
701
881
|
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 };
|
|
882
|
+
...body !== void 0 ? { body } : {}
|
|
883
|
+
}),
|
|
884
|
+
signal: controller.signal
|
|
885
|
+
});
|
|
886
|
+
const text = await response.text();
|
|
887
|
+
const data = text ? (() => {
|
|
888
|
+
try {
|
|
889
|
+
return JSON.parse(text);
|
|
890
|
+
} catch {
|
|
891
|
+
return text;
|
|
728
892
|
}
|
|
729
|
-
|
|
893
|
+
})() : null;
|
|
894
|
+
if (!response.ok) {
|
|
895
|
+
const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
|
|
896
|
+
return { success: false, error: errorMessage };
|
|
730
897
|
}
|
|
898
|
+
return { success: true, status: response.status, data };
|
|
731
899
|
} finally {
|
|
732
900
|
clearTimeout(timeout);
|
|
733
901
|
}
|
|
@@ -235,19 +235,34 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
235
235
|
};
|
|
236
236
|
let state = flow.initialState();
|
|
237
237
|
let answerIdx = 0;
|
|
238
|
+
const pendingParameterUpdates = [];
|
|
238
239
|
for (const step of flow.steps) {
|
|
239
240
|
const ans = ctx.answers[answerIdx];
|
|
240
241
|
if (ans && ans.questionSlug === step.slug) {
|
|
241
242
|
state = step.applyAnswer(state, ans.answer);
|
|
243
|
+
if (step.toParameterUpdates) {
|
|
244
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
245
|
+
}
|
|
242
246
|
answerIdx += 1;
|
|
243
247
|
continue;
|
|
244
248
|
}
|
|
249
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
245
250
|
if (step.type === "text") {
|
|
251
|
+
if (step.fetchOptions) {
|
|
252
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
253
|
+
if (options2.length === 0) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
246
257
|
return {
|
|
247
258
|
type: "nextQuestion",
|
|
248
259
|
questionSlug: step.slug,
|
|
249
260
|
question: step.question[ctx.language],
|
|
250
|
-
questionType: "text"
|
|
261
|
+
questionType: "text",
|
|
262
|
+
allowFreeText: resolvedAllowFreeText,
|
|
263
|
+
...pendingParameterUpdates.length > 0 && {
|
|
264
|
+
parameterUpdates: pendingParameterUpdates
|
|
265
|
+
}
|
|
251
266
|
};
|
|
252
267
|
}
|
|
253
268
|
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
@@ -259,11 +274,21 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
259
274
|
questionSlug: step.slug,
|
|
260
275
|
question: step.question[ctx.language],
|
|
261
276
|
questionType: step.type,
|
|
262
|
-
options
|
|
277
|
+
options,
|
|
278
|
+
allowFreeText: resolvedAllowFreeText,
|
|
279
|
+
...pendingParameterUpdates.length > 0 && {
|
|
280
|
+
parameterUpdates: pendingParameterUpdates
|
|
281
|
+
}
|
|
263
282
|
};
|
|
264
283
|
}
|
|
265
284
|
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
266
|
-
return {
|
|
285
|
+
return {
|
|
286
|
+
type: "fulfilled",
|
|
287
|
+
dataInvestigationResult,
|
|
288
|
+
...pendingParameterUpdates.length > 0 && {
|
|
289
|
+
parameterUpdates: pendingParameterUpdates
|
|
290
|
+
}
|
|
291
|
+
};
|
|
267
292
|
}
|
|
268
293
|
async function resolveSetupSelection(params) {
|
|
269
294
|
const { selected, allSentinel, fetchAll, limit } = params;
|
|
@@ -383,19 +383,34 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
383
383
|
};
|
|
384
384
|
let state = flow.initialState();
|
|
385
385
|
let answerIdx = 0;
|
|
386
|
+
const pendingParameterUpdates = [];
|
|
386
387
|
for (const step of flow.steps) {
|
|
387
388
|
const ans = ctx.answers[answerIdx];
|
|
388
389
|
if (ans && ans.questionSlug === step.slug) {
|
|
389
390
|
state = step.applyAnswer(state, ans.answer);
|
|
391
|
+
if (step.toParameterUpdates) {
|
|
392
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
393
|
+
}
|
|
390
394
|
answerIdx += 1;
|
|
391
395
|
continue;
|
|
392
396
|
}
|
|
397
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
393
398
|
if (step.type === "text") {
|
|
399
|
+
if (step.fetchOptions) {
|
|
400
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
401
|
+
if (options2.length === 0) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
394
405
|
return {
|
|
395
406
|
type: "nextQuestion",
|
|
396
407
|
questionSlug: step.slug,
|
|
397
408
|
question: step.question[ctx.language],
|
|
398
|
-
questionType: "text"
|
|
409
|
+
questionType: "text",
|
|
410
|
+
allowFreeText: resolvedAllowFreeText,
|
|
411
|
+
...pendingParameterUpdates.length > 0 && {
|
|
412
|
+
parameterUpdates: pendingParameterUpdates
|
|
413
|
+
}
|
|
399
414
|
};
|
|
400
415
|
}
|
|
401
416
|
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
@@ -407,11 +422,21 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
407
422
|
questionSlug: step.slug,
|
|
408
423
|
question: step.question[ctx.language],
|
|
409
424
|
questionType: step.type,
|
|
410
|
-
options
|
|
425
|
+
options,
|
|
426
|
+
allowFreeText: resolvedAllowFreeText,
|
|
427
|
+
...pendingParameterUpdates.length > 0 && {
|
|
428
|
+
parameterUpdates: pendingParameterUpdates
|
|
429
|
+
}
|
|
411
430
|
};
|
|
412
431
|
}
|
|
413
432
|
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
414
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
type: "fulfilled",
|
|
435
|
+
dataInvestigationResult,
|
|
436
|
+
...pendingParameterUpdates.length > 0 && {
|
|
437
|
+
parameterUpdates: pendingParameterUpdates
|
|
438
|
+
}
|
|
439
|
+
};
|
|
415
440
|
}
|
|
416
441
|
|
|
417
442
|
// ../connectors/src/auth-types.ts
|
|
@@ -178,19 +178,34 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
178
178
|
};
|
|
179
179
|
let state = flow.initialState();
|
|
180
180
|
let answerIdx = 0;
|
|
181
|
+
const pendingParameterUpdates = [];
|
|
181
182
|
for (const step of flow.steps) {
|
|
182
183
|
const ans = ctx.answers[answerIdx];
|
|
183
184
|
if (ans && ans.questionSlug === step.slug) {
|
|
184
185
|
state = step.applyAnswer(state, ans.answer);
|
|
186
|
+
if (step.toParameterUpdates) {
|
|
187
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
188
|
+
}
|
|
185
189
|
answerIdx += 1;
|
|
186
190
|
continue;
|
|
187
191
|
}
|
|
192
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
188
193
|
if (step.type === "text") {
|
|
194
|
+
if (step.fetchOptions) {
|
|
195
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
196
|
+
if (options2.length === 0) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
189
200
|
return {
|
|
190
201
|
type: "nextQuestion",
|
|
191
202
|
questionSlug: step.slug,
|
|
192
203
|
question: step.question[ctx.language],
|
|
193
|
-
questionType: "text"
|
|
204
|
+
questionType: "text",
|
|
205
|
+
allowFreeText: resolvedAllowFreeText,
|
|
206
|
+
...pendingParameterUpdates.length > 0 && {
|
|
207
|
+
parameterUpdates: pendingParameterUpdates
|
|
208
|
+
}
|
|
194
209
|
};
|
|
195
210
|
}
|
|
196
211
|
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
@@ -202,11 +217,21 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
202
217
|
questionSlug: step.slug,
|
|
203
218
|
question: step.question[ctx.language],
|
|
204
219
|
questionType: step.type,
|
|
205
|
-
options
|
|
220
|
+
options,
|
|
221
|
+
allowFreeText: resolvedAllowFreeText,
|
|
222
|
+
...pendingParameterUpdates.length > 0 && {
|
|
223
|
+
parameterUpdates: pendingParameterUpdates
|
|
224
|
+
}
|
|
206
225
|
};
|
|
207
226
|
}
|
|
208
227
|
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
209
|
-
return {
|
|
228
|
+
return {
|
|
229
|
+
type: "fulfilled",
|
|
230
|
+
dataInvestigationResult,
|
|
231
|
+
...pendingParameterUpdates.length > 0 && {
|
|
232
|
+
parameterUpdates: pendingParameterUpdates
|
|
233
|
+
}
|
|
234
|
+
};
|
|
210
235
|
}
|
|
211
236
|
async function resolveSetupSelection(params) {
|
|
212
237
|
const { selected, allSentinel, fetchAll, limit } = params;
|