@squadbase/vite-server 0.1.12-dev.8860f37 → 0.1.12-dev.a9ac647
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 +139 -527
- package/dist/connectors/outlook-oauth.js +1 -1
- package/dist/connectors/powerbi-oauth.js +2 -2
- package/dist/connectors/tableau.js +2 -2
- package/dist/index.js +139 -527
- package/dist/main.js +139 -527
- package/dist/vite-plugin.js +139 -527
- package/package.json +1 -5
- package/dist/connectors/powerbi.d.ts +0 -5
- package/dist/connectors/powerbi.js +0 -869
|
@@ -1,869 +0,0 @@
|
|
|
1
|
-
// ../connectors/src/parameter-definition.ts
|
|
2
|
-
var ParameterDefinition = class {
|
|
3
|
-
slug;
|
|
4
|
-
name;
|
|
5
|
-
description;
|
|
6
|
-
envVarBaseKey;
|
|
7
|
-
type;
|
|
8
|
-
secret;
|
|
9
|
-
required;
|
|
10
|
-
constructor(config) {
|
|
11
|
-
this.slug = config.slug;
|
|
12
|
-
this.name = config.name;
|
|
13
|
-
this.description = config.description;
|
|
14
|
-
this.envVarBaseKey = config.envVarBaseKey;
|
|
15
|
-
this.type = config.type;
|
|
16
|
-
this.secret = config.secret;
|
|
17
|
-
this.required = config.required;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Get the parameter value from a ConnectorConnectionObject.
|
|
21
|
-
*/
|
|
22
|
-
getValue(connection2) {
|
|
23
|
-
const param = connection2.parameters.find(
|
|
24
|
-
(p) => p.parameterSlug === this.slug
|
|
25
|
-
);
|
|
26
|
-
if (!param || param.value == null) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return param.value;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
35
|
-
*/
|
|
36
|
-
tryGetValue(connection2) {
|
|
37
|
-
const param = connection2.parameters.find(
|
|
38
|
-
(p) => p.parameterSlug === this.slug
|
|
39
|
-
);
|
|
40
|
-
if (!param || param.value == null) return void 0;
|
|
41
|
-
return param.value;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// ../connectors/src/connectors/powerbi/parameters.ts
|
|
46
|
-
var parameters = {
|
|
47
|
-
tenantId: new ParameterDefinition({
|
|
48
|
-
slug: "tenant-id",
|
|
49
|
-
name: "Azure Tenant ID",
|
|
50
|
-
description: "The Microsoft Entra ID (Azure AD) tenant ID that owns the Service Principal. Found in Azure Portal > Microsoft Entra ID > Overview > Tenant ID.",
|
|
51
|
-
envVarBaseKey: "POWERBI_TENANT_ID",
|
|
52
|
-
type: "text",
|
|
53
|
-
secret: false,
|
|
54
|
-
required: true
|
|
55
|
-
}),
|
|
56
|
-
clientId: new ParameterDefinition({
|
|
57
|
-
slug: "client-id",
|
|
58
|
-
name: "Application (Client) ID",
|
|
59
|
-
description: "The Application (client) ID of the Microsoft Entra app registration used as the Power BI Service Principal. Found in Azure Portal > App registrations > Overview.",
|
|
60
|
-
envVarBaseKey: "POWERBI_CLIENT_ID",
|
|
61
|
-
type: "text",
|
|
62
|
-
secret: false,
|
|
63
|
-
required: true
|
|
64
|
-
}),
|
|
65
|
-
clientSecret: new ParameterDefinition({
|
|
66
|
-
slug: "client-secret",
|
|
67
|
-
name: "Client Secret",
|
|
68
|
-
description: "A client secret value for the app registration. Generated under App registrations > Certificates & secrets > Client secrets. The Service Principal must be granted Power BI tenant access (via 'Allow service principals to use Power BI APIs' admin setting) and added to each workspace as a Member/Admin.",
|
|
69
|
-
envVarBaseKey: "POWERBI_CLIENT_SECRET",
|
|
70
|
-
type: "text",
|
|
71
|
-
secret: true,
|
|
72
|
-
required: true
|
|
73
|
-
})
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// ../connectors/src/connectors/powerbi/sdk/index.ts
|
|
77
|
-
var BASE_URL = "https://api.powerbi.com/v1.0/myorg";
|
|
78
|
-
var TOKEN_SCOPE = "https://analysis.windows.net/powerbi/api/.default";
|
|
79
|
-
function createClient(params) {
|
|
80
|
-
const tenantId = params[parameters.tenantId.slug];
|
|
81
|
-
const clientId = params[parameters.clientId.slug];
|
|
82
|
-
const clientSecret = params[parameters.clientSecret.slug];
|
|
83
|
-
if (!tenantId || !clientId || !clientSecret) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`powerbi: missing required parameters: ${parameters.tenantId.slug}, ${parameters.clientId.slug}, ${parameters.clientSecret.slug}`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
let cachedToken = null;
|
|
89
|
-
async function getAccessToken2() {
|
|
90
|
-
if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
|
|
91
|
-
return cachedToken.token;
|
|
92
|
-
}
|
|
93
|
-
const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
|
|
94
|
-
const body = new URLSearchParams({
|
|
95
|
-
grant_type: "client_credentials",
|
|
96
|
-
client_id: clientId,
|
|
97
|
-
client_secret: clientSecret,
|
|
98
|
-
scope: TOKEN_SCOPE
|
|
99
|
-
});
|
|
100
|
-
const res = await fetch(tokenUrl, {
|
|
101
|
-
method: "POST",
|
|
102
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
103
|
-
body: body.toString()
|
|
104
|
-
});
|
|
105
|
-
if (!res.ok) {
|
|
106
|
-
const errorText = await res.text().catch(() => res.statusText);
|
|
107
|
-
throw new Error(
|
|
108
|
-
`powerbi: token request failed (${res.status}): ${errorText}`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
const data = await res.json();
|
|
112
|
-
cachedToken = {
|
|
113
|
-
token: data.access_token,
|
|
114
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
115
|
-
};
|
|
116
|
-
return data.access_token;
|
|
117
|
-
}
|
|
118
|
-
async function request(path2, init) {
|
|
119
|
-
const token = await getAccessToken2();
|
|
120
|
-
const headers = new Headers(init?.headers);
|
|
121
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
122
|
-
if (!headers.has("Content-Type") && init?.body) {
|
|
123
|
-
headers.set("Content-Type", "application/json");
|
|
124
|
-
}
|
|
125
|
-
if (!headers.has("Accept")) {
|
|
126
|
-
headers.set("Accept", "application/json");
|
|
127
|
-
}
|
|
128
|
-
const url = `${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
129
|
-
return fetch(url, { ...init, headers });
|
|
130
|
-
}
|
|
131
|
-
async function getJson(path2) {
|
|
132
|
-
const res = await request(path2);
|
|
133
|
-
if (!res.ok) {
|
|
134
|
-
const body = await res.text();
|
|
135
|
-
throw new Error(`powerbi: GET ${path2} failed (${res.status}): ${body}`);
|
|
136
|
-
}
|
|
137
|
-
return await res.json();
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
request,
|
|
141
|
-
async listGroups(options) {
|
|
142
|
-
const qs = new URLSearchParams();
|
|
143
|
-
if (options?.top != null) qs.set("$top", String(options.top));
|
|
144
|
-
if (options?.skip != null) qs.set("$skip", String(options.skip));
|
|
145
|
-
if (options?.filter) qs.set("$filter", options.filter);
|
|
146
|
-
const query = qs.toString();
|
|
147
|
-
return getJson(
|
|
148
|
-
`/groups${query ? `?${query}` : ""}`
|
|
149
|
-
);
|
|
150
|
-
},
|
|
151
|
-
async listDatasets(groupId) {
|
|
152
|
-
return getJson(
|
|
153
|
-
`/groups/${encodeURIComponent(groupId)}/datasets`
|
|
154
|
-
);
|
|
155
|
-
},
|
|
156
|
-
async listReports(groupId) {
|
|
157
|
-
return getJson(
|
|
158
|
-
`/groups/${encodeURIComponent(groupId)}/reports`
|
|
159
|
-
);
|
|
160
|
-
},
|
|
161
|
-
async getDataset(groupId, datasetId) {
|
|
162
|
-
return getJson(
|
|
163
|
-
`/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}`
|
|
164
|
-
);
|
|
165
|
-
},
|
|
166
|
-
async executeQueries(groupId, datasetId, queries, options) {
|
|
167
|
-
const res = await request(
|
|
168
|
-
`/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}/executeQueries`,
|
|
169
|
-
{
|
|
170
|
-
method: "POST",
|
|
171
|
-
body: JSON.stringify({
|
|
172
|
-
queries: queries.map((q) => ({ query: q })),
|
|
173
|
-
serializerSettings: { includeNulls: options?.includeNulls ?? true },
|
|
174
|
-
...options?.impersonatedUserName ? {
|
|
175
|
-
impersonatedUserName: options.impersonatedUserName
|
|
176
|
-
} : {}
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
);
|
|
180
|
-
if (!res.ok) {
|
|
181
|
-
const body = await res.text();
|
|
182
|
-
throw new Error(
|
|
183
|
-
`powerbi: executeQueries failed (${res.status}): ${body}`
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return await res.json();
|
|
187
|
-
},
|
|
188
|
-
async refreshDataset(groupId, datasetId, options) {
|
|
189
|
-
const res = await request(
|
|
190
|
-
`/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}/refreshes`,
|
|
191
|
-
{
|
|
192
|
-
method: "POST",
|
|
193
|
-
body: JSON.stringify({
|
|
194
|
-
notifyOption: options?.notifyOption ?? "NoNotification"
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
);
|
|
198
|
-
if (!res.ok) {
|
|
199
|
-
const body = await res.text();
|
|
200
|
-
throw new Error(
|
|
201
|
-
`powerbi: refreshDataset failed (${res.status}): ${body}`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// ../connectors/src/connector-onboarding.ts
|
|
209
|
-
var ConnectorOnboarding = class {
|
|
210
|
-
/** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
|
|
211
|
-
connectionSetupInstructions;
|
|
212
|
-
/** Phase 2: Data overview instructions */
|
|
213
|
-
dataOverviewInstructions;
|
|
214
|
-
constructor(config) {
|
|
215
|
-
this.connectionSetupInstructions = config.connectionSetupInstructions;
|
|
216
|
-
this.dataOverviewInstructions = config.dataOverviewInstructions;
|
|
217
|
-
}
|
|
218
|
-
getConnectionSetupPrompt(language) {
|
|
219
|
-
return this.connectionSetupInstructions?.[language] ?? null;
|
|
220
|
-
}
|
|
221
|
-
getDataOverviewInstructions(language) {
|
|
222
|
-
return this.dataOverviewInstructions[language];
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// ../connectors/src/connector-tool.ts
|
|
227
|
-
var ConnectorTool = class {
|
|
228
|
-
name;
|
|
229
|
-
description;
|
|
230
|
-
inputSchema;
|
|
231
|
-
outputSchema;
|
|
232
|
-
_execute;
|
|
233
|
-
constructor(config) {
|
|
234
|
-
this.name = config.name;
|
|
235
|
-
this.description = config.description;
|
|
236
|
-
this.inputSchema = config.inputSchema;
|
|
237
|
-
this.outputSchema = config.outputSchema;
|
|
238
|
-
this._execute = config.execute;
|
|
239
|
-
}
|
|
240
|
-
createTool(connections, config) {
|
|
241
|
-
return {
|
|
242
|
-
description: this.description,
|
|
243
|
-
inputSchema: this.inputSchema,
|
|
244
|
-
outputSchema: this.outputSchema,
|
|
245
|
-
execute: (input) => this._execute(input, connections, config)
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// ../connectors/src/connector-plugin.ts
|
|
251
|
-
var ConnectorPlugin = class _ConnectorPlugin {
|
|
252
|
-
slug;
|
|
253
|
-
authType;
|
|
254
|
-
name;
|
|
255
|
-
description;
|
|
256
|
-
iconUrl;
|
|
257
|
-
parameters;
|
|
258
|
-
releaseFlag;
|
|
259
|
-
proxyPolicy;
|
|
260
|
-
experimentalAttributes;
|
|
261
|
-
categories;
|
|
262
|
-
onboarding;
|
|
263
|
-
systemPrompt;
|
|
264
|
-
tools;
|
|
265
|
-
query;
|
|
266
|
-
checkConnection;
|
|
267
|
-
constructor(config) {
|
|
268
|
-
this.slug = config.slug;
|
|
269
|
-
this.authType = config.authType;
|
|
270
|
-
this.name = config.name;
|
|
271
|
-
this.description = config.description;
|
|
272
|
-
this.iconUrl = config.iconUrl;
|
|
273
|
-
this.parameters = config.parameters;
|
|
274
|
-
this.releaseFlag = config.releaseFlag;
|
|
275
|
-
this.proxyPolicy = config.proxyPolicy;
|
|
276
|
-
this.experimentalAttributes = config.experimentalAttributes;
|
|
277
|
-
this.categories = config.categories ?? [];
|
|
278
|
-
this.onboarding = config.onboarding;
|
|
279
|
-
this.systemPrompt = config.systemPrompt;
|
|
280
|
-
this.tools = config.tools;
|
|
281
|
-
this.query = config.query;
|
|
282
|
-
this.checkConnection = config.checkConnection;
|
|
283
|
-
}
|
|
284
|
-
get connectorKey() {
|
|
285
|
-
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Create tools for connections that belong to this connector.
|
|
289
|
-
* Filters connections by connectorKey internally.
|
|
290
|
-
* Returns tools keyed as `${connectorKey}_${toolName}`.
|
|
291
|
-
*/
|
|
292
|
-
createTools(connections, config, opts) {
|
|
293
|
-
const myConnections = connections.filter(
|
|
294
|
-
(c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
|
|
295
|
-
);
|
|
296
|
-
const result = {};
|
|
297
|
-
for (const t of Object.values(this.tools)) {
|
|
298
|
-
const tool = t.createTool(myConnections, config);
|
|
299
|
-
const originalToModelOutput = tool.toModelOutput;
|
|
300
|
-
result[`${this.connectorKey}_${t.name}`] = {
|
|
301
|
-
...tool,
|
|
302
|
-
toModelOutput: async (options) => {
|
|
303
|
-
if (!originalToModelOutput) {
|
|
304
|
-
return opts.truncateOutput(options.output);
|
|
305
|
-
}
|
|
306
|
-
const modelOutput = await originalToModelOutput(options);
|
|
307
|
-
if (modelOutput.type === "text" || modelOutput.type === "json") {
|
|
308
|
-
return opts.truncateOutput(modelOutput.value);
|
|
309
|
-
}
|
|
310
|
-
return modelOutput;
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
static deriveKey(slug, authType) {
|
|
317
|
-
if (authType) return `${slug}-${authType}`;
|
|
318
|
-
const LEGACY_NULL_AUTH_TYPE_MAP = {
|
|
319
|
-
// user-password
|
|
320
|
-
"postgresql": "user-password",
|
|
321
|
-
"mysql": "user-password",
|
|
322
|
-
"clickhouse": "user-password",
|
|
323
|
-
"kintone": "user-password",
|
|
324
|
-
"squadbase-db": "user-password",
|
|
325
|
-
// service-account
|
|
326
|
-
"snowflake": "service-account",
|
|
327
|
-
"bigquery": "service-account",
|
|
328
|
-
"google-analytics": "service-account",
|
|
329
|
-
"google-calendar": "service-account",
|
|
330
|
-
"aws-athena": "service-account",
|
|
331
|
-
"redshift": "service-account",
|
|
332
|
-
// api-key
|
|
333
|
-
"databricks": "api-key",
|
|
334
|
-
"dbt": "api-key",
|
|
335
|
-
"airtable": "api-key",
|
|
336
|
-
"openai": "api-key",
|
|
337
|
-
"gemini": "api-key",
|
|
338
|
-
"anthropic": "api-key",
|
|
339
|
-
"wix-store": "api-key"
|
|
340
|
-
};
|
|
341
|
-
const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
|
|
342
|
-
if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
|
|
343
|
-
return slug;
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
// ../connectors/src/auth-types.ts
|
|
348
|
-
var AUTH_TYPES = {
|
|
349
|
-
OAUTH: "oauth",
|
|
350
|
-
API_KEY: "api-key",
|
|
351
|
-
JWT: "jwt",
|
|
352
|
-
SERVICE_ACCOUNT: "service-account",
|
|
353
|
-
PAT: "pat",
|
|
354
|
-
USER_PASSWORD: "user-password"
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// ../connectors/src/lib/normalize-path.ts
|
|
358
|
-
function normalizeRequestPath(path2, basePathSegment) {
|
|
359
|
-
let p = path2.trim();
|
|
360
|
-
if (!p.startsWith("/")) p = "/" + p;
|
|
361
|
-
if (p === basePathSegment || p.startsWith(basePathSegment + "/")) {
|
|
362
|
-
p = p.slice(basePathSegment.length) || "/";
|
|
363
|
-
}
|
|
364
|
-
return p;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// ../connectors/src/connectors/powerbi/setup.ts
|
|
368
|
-
var powerbiOnboarding = new ConnectorOnboarding({
|
|
369
|
-
connectionSetupInstructions: {
|
|
370
|
-
en: `Follow these steps to verify the Power BI Service Principal connection.
|
|
371
|
-
|
|
372
|
-
1. Call \`powerbi_request\` with \`method: "GET"\` and \`path: "/groups"\` to list workspaces the Service Principal can access
|
|
373
|
-
2. If the response is an empty \`value\` array, ask the user to add the Service Principal as Member/Admin to at least one workspace (Power BI Service > Workspace > Access). The 'Allow service principals to use Power BI APIs' tenant setting must also be enabled
|
|
374
|
-
|
|
375
|
-
#### Constraints
|
|
376
|
-
- **Do NOT execute DAX queries or refresh datasets during setup**. Only the workspace listing above is allowed
|
|
377
|
-
- Service Principals cannot access "My workspace" \u2014 always work through workspace (group) IDs`,
|
|
378
|
-
ja: `Power BI Service Principal \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
|
|
379
|
-
|
|
380
|
-
1. \`powerbi_request\` \u3092 \`method: "GET"\`\u3001\`path: "/groups"\` \u3067\u547C\u3073\u51FA\u3057\u3001Service Principal \u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B
|
|
381
|
-
2. \`value\` \u304C\u7A7A\u914D\u5217\u3067\u8FD4\u3063\u3066\u304D\u305F\u5834\u5408\u306F\u3001Power BI Service > \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 > \u30A2\u30AF\u30BB\u30B9 \u304B\u3089\u8A72\u5F53\u306E Service Principal \u3092 Member/Admin \u3068\u3057\u3066\u8FFD\u52A0\u3059\u308B\u3088\u3046\u30E6\u30FC\u30B6\u30FC\u306B\u4F1D\u3048\u308B\u3002\u30C6\u30CA\u30F3\u30C8\u7BA1\u7406\u8A2D\u5B9A\u3067\u300C\u30B5\u30FC\u30D3\u30B9 \u30D7\u30EA\u30F3\u30B7\u30D1\u30EB\u306B Power BI API \u306E\u4F7F\u7528\u3092\u8A31\u53EF\u3059\u308B\u300D\u3082\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308B
|
|
382
|
-
|
|
383
|
-
#### \u5236\u7D04
|
|
384
|
-
- **\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306B DAX \u30AF\u30A8\u30EA\u306E\u5B9F\u884C\u3084\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306E\u66F4\u65B0\u3092\u884C\u308F\u306A\u3044\u3053\u3068**\u3002\u4E0A\u8A18\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u53D6\u5F97\u306E\u307F\u8A31\u53EF
|
|
385
|
-
- Service Principal \u306F\u300C\u30DE\u30A4 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u300D\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u306A\u3044 \u2014 \u5FC5\u305A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 (group) ID \u7D4C\u7531\u3067\u64CD\u4F5C\u3059\u308B\u3053\u3068`
|
|
386
|
-
},
|
|
387
|
-
dataOverviewInstructions: {
|
|
388
|
-
en: `1. Call powerbi_request with GET /groups to list accessible workspaces
|
|
389
|
-
2. For a target workspace, call powerbi_request with GET /groups/{groupId}/datasets to list datasets
|
|
390
|
-
3. Call powerbi_request with GET /groups/{groupId}/reports to list reports
|
|
391
|
-
4. For each interesting dataset call powerbi_request with GET /groups/{groupId}/datasets/{datasetId}/tables to inspect tables (requires the dataset to allow XMLA / metadata reads)`,
|
|
392
|
-
ja: `1. powerbi_request \u3067 GET /groups \u3092\u547C\u3073\u51FA\u3057\u3001\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97
|
|
393
|
-
2. \u5BFE\u8C61\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306B\u5BFE\u3057\u3066 powerbi_request \u3067 GET /groups/{groupId}/datasets \u3092\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
|
|
394
|
-
3. powerbi_request \u3067 GET /groups/{groupId}/reports \u3092\u547C\u3073\u51FA\u3057\u3001\u30EC\u30DD\u30FC\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
|
|
395
|
-
4. \u8208\u5473\u306E\u3042\u308B\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306B\u3064\u3044\u3066 powerbi_request \u3067 GET /groups/{groupId}/datasets/{datasetId}/tables \u3092\u547C\u3073\u51FA\u3057\u3001\u30C6\u30FC\u30D6\u30EB\u69CB\u9020\u3092\u78BA\u8A8D\uFF08XMLA / \u30E1\u30BF\u30C7\u30FC\u30BF\u8AAD\u307F\u53D6\u308A\u304C\u8A31\u53EF\u3055\u308C\u3066\u3044\u308B\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306B\u9650\u308B\uFF09`
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// ../connectors/src/connectors/powerbi/tools/request.ts
|
|
400
|
-
import { z } from "zod";
|
|
401
|
-
var BASE_HOST = "https://api.powerbi.com";
|
|
402
|
-
var BASE_PATH_SEGMENT = "/v1.0/myorg";
|
|
403
|
-
var BASE_URL2 = `${BASE_HOST}${BASE_PATH_SEGMENT}`;
|
|
404
|
-
var TOKEN_SCOPE2 = "https://analysis.windows.net/powerbi/api/.default";
|
|
405
|
-
var REQUEST_TIMEOUT_MS = 6e4;
|
|
406
|
-
var tokenCache = /* @__PURE__ */ new Map();
|
|
407
|
-
async function getAccessToken(tenantId, clientId, clientSecret) {
|
|
408
|
-
const cacheKey = `${tenantId}:${clientId}`;
|
|
409
|
-
const cached = tokenCache.get(cacheKey);
|
|
410
|
-
if (cached && cached.expiresAt > Date.now() + 6e4) {
|
|
411
|
-
return cached.token;
|
|
412
|
-
}
|
|
413
|
-
const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
|
|
414
|
-
const body = new URLSearchParams({
|
|
415
|
-
grant_type: "client_credentials",
|
|
416
|
-
client_id: clientId,
|
|
417
|
-
client_secret: clientSecret,
|
|
418
|
-
scope: TOKEN_SCOPE2
|
|
419
|
-
});
|
|
420
|
-
const res = await fetch(tokenUrl, {
|
|
421
|
-
method: "POST",
|
|
422
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
423
|
-
body: body.toString()
|
|
424
|
-
});
|
|
425
|
-
if (!res.ok) {
|
|
426
|
-
const errorText = await res.text().catch(() => res.statusText);
|
|
427
|
-
throw new Error(
|
|
428
|
-
`Power BI token request failed: HTTP ${res.status} ${errorText}`
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
const data = await res.json();
|
|
432
|
-
tokenCache.set(cacheKey, {
|
|
433
|
-
token: data.access_token,
|
|
434
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
435
|
-
});
|
|
436
|
-
return data.access_token;
|
|
437
|
-
}
|
|
438
|
-
var inputSchema = z.object({
|
|
439
|
-
toolUseIntent: z.string().optional().describe(
|
|
440
|
-
"Brief description of what you intend to accomplish with this tool call"
|
|
441
|
-
),
|
|
442
|
-
connectionId: z.string().describe("ID of the Power BI connection to use"),
|
|
443
|
-
method: z.enum(["GET", "POST", "PATCH", "PUT", "DELETE"]).describe(
|
|
444
|
-
"HTTP method. Use GET for reading workspaces/datasets/reports, POST for execute queries / refresh, PATCH/PUT for updates, DELETE for removal."
|
|
445
|
-
),
|
|
446
|
-
path: z.string().describe(
|
|
447
|
-
"API path appended to https://api.powerbi.com/v1.0/myorg (e.g., '/groups', '/groups/{groupId}/datasets', '/groups/{groupId}/reports/{reportId}'). Service Principal cannot access 'My workspace' \u2014 always work through workspace (group) IDs."
|
|
448
|
-
),
|
|
449
|
-
queryParams: z.record(z.string(), z.string()).optional().describe(
|
|
450
|
-
`Query parameters to append to the URL (e.g., { $top: '50', $filter: "name eq 'Sales'" }). OData operators ($filter, $top, $skip, $expand) are supported on many list endpoints.`
|
|
451
|
-
),
|
|
452
|
-
body: z.record(z.string(), z.unknown()).optional().describe(
|
|
453
|
-
"JSON request body for POST/PATCH/PUT/DELETE. Example: executeQueries body { queries: [{ query: 'EVALUATE TOPN(10, Sales)' }], serializerSettings: { includeNulls: true } }."
|
|
454
|
-
)
|
|
455
|
-
});
|
|
456
|
-
var outputSchema = z.discriminatedUnion("success", [
|
|
457
|
-
z.object({
|
|
458
|
-
success: z.literal(true),
|
|
459
|
-
status: z.number(),
|
|
460
|
-
data: z.unknown()
|
|
461
|
-
}),
|
|
462
|
-
z.object({
|
|
463
|
-
success: z.literal(false),
|
|
464
|
-
error: z.string()
|
|
465
|
-
})
|
|
466
|
-
]);
|
|
467
|
-
var requestTool = new ConnectorTool({
|
|
468
|
-
name: "request",
|
|
469
|
-
description: `Send authenticated requests to the Power BI REST API v1.0.
|
|
470
|
-
Authentication uses a Microsoft Entra Service Principal (client_credentials) \u2014 the token is acquired and cached automatically.
|
|
471
|
-
All paths are relative to https://api.powerbi.com/v1.0/myorg. Service Principals cannot access 'My workspace'; always operate on workspaces ('groups') the SP has been added to.
|
|
472
|
-
Use the executeQueries endpoint (POST /groups/{groupId}/datasets/{datasetId}/executeQueries) to run DAX against a dataset.`,
|
|
473
|
-
inputSchema,
|
|
474
|
-
outputSchema,
|
|
475
|
-
async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
|
|
476
|
-
const connection2 = connections.find((c) => c.id === connectionId);
|
|
477
|
-
if (!connection2) {
|
|
478
|
-
return {
|
|
479
|
-
success: false,
|
|
480
|
-
error: `Connection ${connectionId} not found`
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
console.log(
|
|
484
|
-
`[connector-request] powerbi/${connection2.name}: ${method} ${path2}`
|
|
485
|
-
);
|
|
486
|
-
try {
|
|
487
|
-
const tenantId = parameters.tenantId.getValue(connection2);
|
|
488
|
-
const clientId = parameters.clientId.getValue(connection2);
|
|
489
|
-
const clientSecret = parameters.clientSecret.getValue(connection2);
|
|
490
|
-
const normalizedPath = normalizeRequestPath(path2, BASE_PATH_SEGMENT);
|
|
491
|
-
let url = `${BASE_URL2}${normalizedPath}`;
|
|
492
|
-
if (queryParams) {
|
|
493
|
-
const searchParams = new URLSearchParams(queryParams);
|
|
494
|
-
url += `?${searchParams.toString()}`;
|
|
495
|
-
}
|
|
496
|
-
const token = await getAccessToken(tenantId, clientId, clientSecret);
|
|
497
|
-
const controller = new AbortController();
|
|
498
|
-
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
499
|
-
try {
|
|
500
|
-
const init = {
|
|
501
|
-
method,
|
|
502
|
-
headers: {
|
|
503
|
-
Authorization: `Bearer ${token}`,
|
|
504
|
-
"Content-Type": "application/json",
|
|
505
|
-
Accept: "application/json"
|
|
506
|
-
},
|
|
507
|
-
signal: controller.signal
|
|
508
|
-
};
|
|
509
|
-
if (body !== void 0) {
|
|
510
|
-
init.body = JSON.stringify(body);
|
|
511
|
-
}
|
|
512
|
-
const response = await fetch(url, init);
|
|
513
|
-
const text = await response.text();
|
|
514
|
-
const data = text ? (() => {
|
|
515
|
-
try {
|
|
516
|
-
return JSON.parse(text);
|
|
517
|
-
} catch {
|
|
518
|
-
return text;
|
|
519
|
-
}
|
|
520
|
-
})() : null;
|
|
521
|
-
if (!response.ok) {
|
|
522
|
-
const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
|
|
523
|
-
return { success: false, error: errorMessage };
|
|
524
|
-
}
|
|
525
|
-
return { success: true, status: response.status, data };
|
|
526
|
-
} finally {
|
|
527
|
-
clearTimeout(timeout);
|
|
528
|
-
}
|
|
529
|
-
} catch (err) {
|
|
530
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
531
|
-
return { success: false, error: msg };
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// ../connectors/src/connectors/powerbi/index.ts
|
|
537
|
-
var tools = { request: requestTool };
|
|
538
|
-
var powerbiConnector = new ConnectorPlugin({
|
|
539
|
-
slug: "powerbi",
|
|
540
|
-
authType: AUTH_TYPES.API_KEY,
|
|
541
|
-
name: "Power BI",
|
|
542
|
-
description: "Connect to Microsoft Power BI via a Service Principal (Microsoft Entra ID app + tenant ID / client ID / client secret). Use it to enumerate workspaces, datasets, and reports, and to run DAX queries.",
|
|
543
|
-
iconUrl: "https://upload.wikimedia.org/wikipedia/commons/c/cf/New_Power_BI_Logo.svg",
|
|
544
|
-
parameters,
|
|
545
|
-
releaseFlag: { dev1: true, dev2: false, prod: false },
|
|
546
|
-
categories: ["other"],
|
|
547
|
-
onboarding: powerbiOnboarding,
|
|
548
|
-
systemPrompt: {
|
|
549
|
-
en: `### Tools
|
|
550
|
-
|
|
551
|
-
- \`powerbi_request\`: The only way to call the Power BI REST API v1.0. Use it to list workspaces (\`/groups\`), datasets, reports, dashboards, and to run DAX via the \`executeQueries\` endpoint. Service Principal authentication (\`client_credentials\`) is handled automatically and the bearer token is cached.
|
|
552
|
-
|
|
553
|
-
### Business Logic
|
|
554
|
-
|
|
555
|
-
The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
|
|
556
|
-
|
|
557
|
-
SDK methods (client created via \`connection(connectionId)\`):
|
|
558
|
-
- \`client.request(path, init?)\` \u2014 low-level authenticated fetch (path appended to \`https://api.powerbi.com/v1.0/myorg\`)
|
|
559
|
-
- \`client.listGroups(options?)\` \u2014 list workspaces (\`$top\`, \`$skip\`, \`$filter\`)
|
|
560
|
-
- \`client.listDatasets(groupId)\` / \`client.listReports(groupId)\` \u2014 list datasets / reports in a workspace
|
|
561
|
-
- \`client.getDataset(groupId, datasetId)\` \u2014 fetch a single dataset
|
|
562
|
-
- \`client.executeQueries(groupId, datasetId, queries, options?)\` \u2014 run DAX queries against a dataset
|
|
563
|
-
- \`client.refreshDataset(groupId, datasetId, options?)\` \u2014 trigger an async refresh
|
|
564
|
-
|
|
565
|
-
\`\`\`ts
|
|
566
|
-
import type { Context } from "hono";
|
|
567
|
-
import { connection } from "@squadbase/vite-server/connectors/powerbi";
|
|
568
|
-
|
|
569
|
-
const powerbi = connection("<connectionId>");
|
|
570
|
-
|
|
571
|
-
export default async function handler(c: Context) {
|
|
572
|
-
const { groupId, datasetId, dax } = await c.req.json<{
|
|
573
|
-
groupId: string;
|
|
574
|
-
datasetId: string;
|
|
575
|
-
dax: string;
|
|
576
|
-
}>();
|
|
577
|
-
|
|
578
|
-
const result = await powerbi.executeQueries(groupId, datasetId, [dax]);
|
|
579
|
-
const rows = result.results[0]?.tables[0]?.rows ?? [];
|
|
580
|
-
return c.json({ rows });
|
|
581
|
-
}
|
|
582
|
-
\`\`\`
|
|
583
|
-
|
|
584
|
-
### Power BI REST API Reference
|
|
585
|
-
|
|
586
|
-
- Base URL: \`https://api.powerbi.com/v1.0/myorg\`
|
|
587
|
-
- Authentication: Microsoft Entra Service Principal via OAuth2 \`client_credentials\` grant against \`https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token\` with scope \`https://analysis.windows.net/powerbi/api/.default\` (handled automatically)
|
|
588
|
-
- Service Principals cannot access "My workspace"; the SP must be added as Member/Admin to each workspace
|
|
589
|
-
- The tenant admin must enable "Allow service principals to use Power BI APIs" in the Power BI admin portal
|
|
590
|
-
|
|
591
|
-
#### Common Endpoints
|
|
592
|
-
- GET \`/groups\` \u2014 List workspaces accessible to the Service Principal
|
|
593
|
-
- GET \`/groups/{groupId}/datasets\` \u2014 List datasets in a workspace
|
|
594
|
-
- GET \`/groups/{groupId}/datasets/{datasetId}\` \u2014 Get dataset metadata
|
|
595
|
-
- GET \`/groups/{groupId}/datasets/{datasetId}/tables\` \u2014 List tables (requires XMLA / metadata reads enabled)
|
|
596
|
-
- GET \`/groups/{groupId}/reports\` \u2014 List reports
|
|
597
|
-
- GET \`/groups/{groupId}/dashboards\` \u2014 List dashboards
|
|
598
|
-
- POST \`/groups/{groupId}/datasets/{datasetId}/executeQueries\` \u2014 Run DAX (body: \`{ queries: [{ query: "EVALUATE ..." }], serializerSettings: { includeNulls: true } }\`)
|
|
599
|
-
- POST \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 Trigger a dataset refresh
|
|
600
|
-
- GET \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 List recent refresh history
|
|
601
|
-
|
|
602
|
-
#### DAX Tips
|
|
603
|
-
- \`EVALUATE TOPN(10, 'TableName')\` \u2014 sample 10 rows from a table
|
|
604
|
-
- \`EVALUATE SUMMARIZECOLUMNS('Date'[Year], "Sales", SUM('Sales'[Amount]))\` \u2014 aggregate
|
|
605
|
-
- Each \`executeQueries\` call accepts one query in the \`queries\` array (per current Power BI API limits); large result sets are truncated server-side (100k rows max)`,
|
|
606
|
-
ja: `### \u30C4\u30FC\u30EB
|
|
607
|
-
|
|
608
|
-
- \`powerbi_request\`: Power BI REST API v1.0 \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 (\`/groups\`)\u3001\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u3001\u30EC\u30DD\u30FC\u30C8\u3001\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u306E\u4E00\u89A7\u53D6\u5F97\u3084\u3001\`executeQueries\` \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306B\u3088\u308B DAX \u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002Service Principal \u8A8D\u8A3C (\`client_credentials\`) \u306F\u81EA\u52D5\u3067\u51E6\u7406\u3055\u308C\u3001Bearer \u30C8\u30FC\u30AF\u30F3\u306F\u30AD\u30E3\u30C3\u30B7\u30E5\u3055\u308C\u307E\u3059\u3002
|
|
609
|
-
|
|
610
|
-
### Business Logic
|
|
611
|
-
|
|
612
|
-
\u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BF SDK \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
|
|
613
|
-
|
|
614
|
-
SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
|
|
615
|
-
- \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u8A8D\u8A3C\u4ED8\u304D fetch (path \u306F \`https://api.powerbi.com/v1.0/myorg\` \u306B\u8FFD\u52A0\u3055\u308C\u307E\u3059)
|
|
616
|
-
- \`client.listGroups(options?)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7 (\`$top\`, \`$skip\`, \`$filter\`)
|
|
617
|
-
- \`client.listDatasets(groupId)\` / \`client.listReports(groupId)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u5185\u306E\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8/\u30EC\u30DD\u30FC\u30C8\u4E00\u89A7
|
|
618
|
-
- \`client.getDataset(groupId, datasetId)\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u5358\u4F53\u53D6\u5F97
|
|
619
|
-
- \`client.executeQueries(groupId, datasetId, queries, options?)\` \u2014 DAX \u30AF\u30A8\u30EA\u5B9F\u884C
|
|
620
|
-
- \`client.refreshDataset(groupId, datasetId, options?)\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u66F4\u65B0\u3092\u975E\u540C\u671F\u30C8\u30EA\u30AC\u30FC
|
|
621
|
-
|
|
622
|
-
\`\`\`ts
|
|
623
|
-
import type { Context } from "hono";
|
|
624
|
-
import { connection } from "@squadbase/vite-server/connectors/powerbi";
|
|
625
|
-
|
|
626
|
-
const powerbi = connection("<connectionId>");
|
|
627
|
-
|
|
628
|
-
export default async function handler(c: Context) {
|
|
629
|
-
const { groupId, datasetId, dax } = await c.req.json<{
|
|
630
|
-
groupId: string;
|
|
631
|
-
datasetId: string;
|
|
632
|
-
dax: string;
|
|
633
|
-
}>();
|
|
634
|
-
|
|
635
|
-
const result = await powerbi.executeQueries(groupId, datasetId, [dax]);
|
|
636
|
-
const rows = result.results[0]?.tables[0]?.rows ?? [];
|
|
637
|
-
return c.json({ rows });
|
|
638
|
-
}
|
|
639
|
-
\`\`\`
|
|
640
|
-
|
|
641
|
-
### Power BI REST API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
|
|
642
|
-
|
|
643
|
-
- \u30D9\u30FC\u30B9 URL: \`https://api.powerbi.com/v1.0/myorg\`
|
|
644
|
-
- \u8A8D\u8A3C: Microsoft Entra Service Principal \u306B\u3088\u308B OAuth2 \`client_credentials\` \u30B0\u30E9\u30F3\u30C8\u3002\`https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token\` \u306B\u5BFE\u3057\u30B9\u30B3\u30FC\u30D7 \`https://analysis.windows.net/powerbi/api/.default\` \u3067\u30C8\u30FC\u30AF\u30F3\u53D6\u5F97 (\u81EA\u52D5)
|
|
645
|
-
- Service Principal \u306F\u300C\u30DE\u30A4 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u300D\u306B\u30A2\u30AF\u30BB\u30B9\u4E0D\u53EF\u3002\u5404\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306B Member/Admin \u3068\u3057\u3066\u8FFD\u52A0\u304C\u5FC5\u8981
|
|
646
|
-
- \u30C6\u30CA\u30F3\u30C8\u7BA1\u7406\u8005\u304C Power BI \u7BA1\u7406\u30DD\u30FC\u30BF\u30EB\u3067\u300C\u30B5\u30FC\u30D3\u30B9 \u30D7\u30EA\u30F3\u30B7\u30D1\u30EB\u306B Power BI API \u306E\u4F7F\u7528\u3092\u8A31\u53EF\u3059\u308B\u300D\u3092\u6709\u52B9\u306B\u3057\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
|
|
647
|
-
|
|
648
|
-
#### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
|
|
649
|
-
- GET \`/groups\` \u2014 Service Principal \u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7
|
|
650
|
-
- GET \`/groups/{groupId}/datasets\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u5185\u306E\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u4E00\u89A7
|
|
651
|
-
- GET \`/groups/{groupId}/datasets/{datasetId}\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8 \u30E1\u30BF\u30C7\u30FC\u30BF
|
|
652
|
-
- GET \`/groups/{groupId}/datasets/{datasetId}/tables\` \u2014 \u30C6\u30FC\u30D6\u30EB\u4E00\u89A7 (XMLA / \u30E1\u30BF\u30C7\u30FC\u30BF\u8AAD\u307F\u53D6\u308A\u304C\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u3042\u308A)
|
|
653
|
-
- GET \`/groups/{groupId}/reports\` \u2014 \u30EC\u30DD\u30FC\u30C8\u4E00\u89A7
|
|
654
|
-
- GET \`/groups/{groupId}/dashboards\` \u2014 \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u4E00\u89A7
|
|
655
|
-
- POST \`/groups/{groupId}/datasets/{datasetId}/executeQueries\` \u2014 DAX \u5B9F\u884C (body: \`{ queries: [{ query: "EVALUATE ..." }], serializerSettings: { includeNulls: true } }\`)
|
|
656
|
-
- POST \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u66F4\u65B0\u30C8\u30EA\u30AC\u30FC
|
|
657
|
-
- GET \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 \u76F4\u8FD1\u306E\u66F4\u65B0\u5C65\u6B74
|
|
658
|
-
|
|
659
|
-
#### DAX Tips
|
|
660
|
-
- \`EVALUATE TOPN(10, 'TableName')\` \u2014 \u30C6\u30FC\u30D6\u30EB\u304B\u3089 10 \u884C\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0
|
|
661
|
-
- \`EVALUATE SUMMARIZECOLUMNS('Date'[Year], "Sales", SUM('Sales'[Amount]))\` \u2014 \u96C6\u8A08
|
|
662
|
-
- \`executeQueries\` \u306F\u73FE\u72B6 1 \u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u3064\u304D 1 \u30AF\u30A8\u30EA\u306E\u307F\u3002\u7D50\u679C\u306F\u6700\u5927 10 \u4E07\u884C\u307E\u3067\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u5207\u308A\u8A70\u3081\u3089\u308C\u307E\u3059`
|
|
663
|
-
},
|
|
664
|
-
tools,
|
|
665
|
-
async checkConnection(params) {
|
|
666
|
-
const tenantId = params[parameters.tenantId.slug];
|
|
667
|
-
const clientId = params[parameters.clientId.slug];
|
|
668
|
-
const clientSecret = params[parameters.clientSecret.slug];
|
|
669
|
-
if (!tenantId || !clientId || !clientSecret) {
|
|
670
|
-
return {
|
|
671
|
-
success: false,
|
|
672
|
-
error: "Missing required parameters: tenant-id, client-id, client-secret"
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
try {
|
|
676
|
-
const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
|
|
677
|
-
const body = new URLSearchParams({
|
|
678
|
-
grant_type: "client_credentials",
|
|
679
|
-
client_id: clientId,
|
|
680
|
-
client_secret: clientSecret,
|
|
681
|
-
scope: "https://analysis.windows.net/powerbi/api/.default"
|
|
682
|
-
});
|
|
683
|
-
const tokenRes = await fetch(tokenUrl, {
|
|
684
|
-
method: "POST",
|
|
685
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
686
|
-
body: body.toString()
|
|
687
|
-
});
|
|
688
|
-
if (!tokenRes.ok) {
|
|
689
|
-
const errorText = await tokenRes.text().catch(() => tokenRes.statusText);
|
|
690
|
-
return {
|
|
691
|
-
success: false,
|
|
692
|
-
error: `Power BI token request failed: HTTP ${tokenRes.status} ${errorText}`
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
const { access_token } = await tokenRes.json();
|
|
696
|
-
const apiRes = await fetch(
|
|
697
|
-
"https://api.powerbi.com/v1.0/myorg/groups?$top=1",
|
|
698
|
-
{
|
|
699
|
-
method: "GET",
|
|
700
|
-
headers: {
|
|
701
|
-
Authorization: `Bearer ${access_token}`,
|
|
702
|
-
Accept: "application/json"
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
);
|
|
706
|
-
if (!apiRes.ok) {
|
|
707
|
-
const errorText = await apiRes.text().catch(() => apiRes.statusText);
|
|
708
|
-
return {
|
|
709
|
-
success: false,
|
|
710
|
-
error: `Power BI API failed: HTTP ${apiRes.status} ${errorText}`
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
return { success: true };
|
|
714
|
-
} catch (error) {
|
|
715
|
-
return {
|
|
716
|
-
success: false,
|
|
717
|
-
error: error instanceof Error ? error.message : String(error)
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// src/connectors/create-connector-sdk.ts
|
|
724
|
-
import { readFileSync } from "fs";
|
|
725
|
-
import path from "path";
|
|
726
|
-
|
|
727
|
-
// src/connector-client/env.ts
|
|
728
|
-
function resolveEnvVar(entry, key, connectionId) {
|
|
729
|
-
const envVarName = entry.envVars[key];
|
|
730
|
-
if (!envVarName) {
|
|
731
|
-
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
732
|
-
}
|
|
733
|
-
const value = process.env[envVarName];
|
|
734
|
-
if (!value) {
|
|
735
|
-
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
736
|
-
}
|
|
737
|
-
return value;
|
|
738
|
-
}
|
|
739
|
-
function resolveEnvVarOptional(entry, key) {
|
|
740
|
-
const envVarName = entry.envVars[key];
|
|
741
|
-
if (!envVarName) return void 0;
|
|
742
|
-
return process.env[envVarName] || void 0;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// src/connector-client/proxy-fetch.ts
|
|
746
|
-
import { getContext } from "hono/context-storage";
|
|
747
|
-
import { getCookie } from "hono/cookie";
|
|
748
|
-
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
749
|
-
function normalizeHeaders(input) {
|
|
750
|
-
const out = {};
|
|
751
|
-
if (!input) return out;
|
|
752
|
-
new Headers(input).forEach((value, key) => {
|
|
753
|
-
out[key] = value;
|
|
754
|
-
});
|
|
755
|
-
return out;
|
|
756
|
-
}
|
|
757
|
-
function createSandboxProxyFetch(connectionId) {
|
|
758
|
-
return async (input, init) => {
|
|
759
|
-
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
760
|
-
const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
|
|
761
|
-
if (!token || !sandboxId) {
|
|
762
|
-
throw new Error(
|
|
763
|
-
"Connection proxy is not configured. Please check your deployment settings."
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
767
|
-
const originalMethod = init?.method ?? "GET";
|
|
768
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
769
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
770
|
-
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
771
|
-
return fetch(proxyUrl, {
|
|
772
|
-
method: "POST",
|
|
773
|
-
headers: {
|
|
774
|
-
"Content-Type": "application/json",
|
|
775
|
-
Authorization: `Bearer ${token}`
|
|
776
|
-
},
|
|
777
|
-
body: JSON.stringify({
|
|
778
|
-
url: originalUrl,
|
|
779
|
-
method: originalMethod,
|
|
780
|
-
headers: normalizeHeaders(init?.headers),
|
|
781
|
-
body: originalBody
|
|
782
|
-
})
|
|
783
|
-
});
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function createDeployedAppProxyFetch(connectionId) {
|
|
787
|
-
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
788
|
-
if (!projectId) {
|
|
789
|
-
throw new Error(
|
|
790
|
-
"Connection proxy is not configured. Please check your deployment settings."
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
794
|
-
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
795
|
-
return async (input, init) => {
|
|
796
|
-
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
797
|
-
const originalMethod = init?.method ?? "GET";
|
|
798
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
799
|
-
const c = getContext();
|
|
800
|
-
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
801
|
-
if (!appSession) {
|
|
802
|
-
throw new Error(
|
|
803
|
-
"No authentication method available for connection proxy."
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
return fetch(proxyUrl, {
|
|
807
|
-
method: "POST",
|
|
808
|
-
headers: {
|
|
809
|
-
"Content-Type": "application/json",
|
|
810
|
-
Authorization: `Bearer ${appSession}`
|
|
811
|
-
},
|
|
812
|
-
body: JSON.stringify({
|
|
813
|
-
url: originalUrl,
|
|
814
|
-
method: originalMethod,
|
|
815
|
-
headers: normalizeHeaders(init?.headers),
|
|
816
|
-
body: originalBody
|
|
817
|
-
})
|
|
818
|
-
});
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
function createProxyFetch(connectionId) {
|
|
822
|
-
if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
|
|
823
|
-
return createSandboxProxyFetch(connectionId);
|
|
824
|
-
}
|
|
825
|
-
return createDeployedAppProxyFetch(connectionId);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// src/connectors/create-connector-sdk.ts
|
|
829
|
-
function loadConnectionsSync() {
|
|
830
|
-
const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
831
|
-
try {
|
|
832
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
833
|
-
return JSON.parse(raw);
|
|
834
|
-
} catch {
|
|
835
|
-
return {};
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
function createConnectorSdk(plugin, createClient2) {
|
|
839
|
-
return (connectionId) => {
|
|
840
|
-
const connections = loadConnectionsSync();
|
|
841
|
-
const entry = connections[connectionId];
|
|
842
|
-
if (!entry) {
|
|
843
|
-
throw new Error(
|
|
844
|
-
`Connection "${connectionId}" not found in .squadbase/connections.json`
|
|
845
|
-
);
|
|
846
|
-
}
|
|
847
|
-
if (entry.connector.slug !== plugin.slug) {
|
|
848
|
-
throw new Error(
|
|
849
|
-
`Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
|
|
850
|
-
);
|
|
851
|
-
}
|
|
852
|
-
const params = {};
|
|
853
|
-
for (const param of Object.values(plugin.parameters)) {
|
|
854
|
-
if (param.required) {
|
|
855
|
-
params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
|
|
856
|
-
} else {
|
|
857
|
-
const val = resolveEnvVarOptional(entry, param.slug);
|
|
858
|
-
if (val !== void 0) params[param.slug] = val;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return createClient2(params, createProxyFetch(connectionId));
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// src/connectors/entries/powerbi.ts
|
|
866
|
-
var connection = createConnectorSdk(powerbiConnector, createClient);
|
|
867
|
-
export {
|
|
868
|
-
connection
|
|
869
|
-
};
|