@querypanel/node-sdk 1.0.41 → 1.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -8
- package/dist/index.cjs +565 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +476 -1
- package/dist/index.d.ts +476 -1
- package/dist/index.js +565 -11
- package/dist/index.js.map +1 -1
- package/package.json +71 -71
package/dist/index.cjs
CHANGED
|
@@ -348,8 +348,9 @@ var PostgresAdapter = class {
|
|
|
348
348
|
const allowedSet = new Set(
|
|
349
349
|
this.allowedTables.map((t) => tableKey(t.schema, t.table))
|
|
350
350
|
);
|
|
351
|
+
const neutralizedSql = sql.replace(/EXTRACT\s*\([^)]*FROM\s+[^)]+\)/gi, "EXTRACT(/*neutralized*/)").replace(/SUBSTRING\s*\([^)]*FROM\s+[^)]+\)/gi, "SUBSTRING(/*neutralized*/)").replace(/TRIM\s*\([^)]*FROM\s+[^)]+\)/gi, "TRIM(/*neutralized*/)").replace(/POSITION\s*\([^)]*FROM\s+[^)]+\)/gi, "POSITION(/*neutralized*/)");
|
|
351
352
|
const tablePattern = /(?:FROM|JOIN)\s+(?:ONLY\s+)?(?:([a-zA-Z_][a-zA-Z0-9_]*)\.)?(["']?[a-zA-Z_][a-zA-Z0-9_]*["']?)/gi;
|
|
352
|
-
const matches =
|
|
353
|
+
const matches = neutralizedSql.matchAll(tablePattern);
|
|
353
354
|
for (const match of matches) {
|
|
354
355
|
const schema = match[1] ?? this.defaultSchema;
|
|
355
356
|
const table = match[2]?.replace(/['"]/g, "");
|
|
@@ -1163,6 +1164,9 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
|
|
|
1163
1164
|
return request;
|
|
1164
1165
|
}
|
|
1165
1166
|
|
|
1167
|
+
// src/routes/modify.ts
|
|
1168
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
1169
|
+
|
|
1166
1170
|
// src/routes/query.ts
|
|
1167
1171
|
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
1168
1172
|
async function ask(client, queryEngine, question, options, signal) {
|
|
@@ -1174,13 +1178,26 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1174
1178
|
let previousSql = options.previousSql;
|
|
1175
1179
|
while (attempt <= maxRetry) {
|
|
1176
1180
|
console.log({ lastError, previousSql });
|
|
1181
|
+
const databaseName = options.database ?? queryEngine.getDefaultDatabase();
|
|
1182
|
+
const metadata = databaseName ? queryEngine.getDatabaseMetadata(databaseName) : void 0;
|
|
1183
|
+
let tenantSettings;
|
|
1184
|
+
if (metadata?.tenantFieldName) {
|
|
1185
|
+
tenantSettings = {
|
|
1186
|
+
tenantFieldName: metadata.tenantFieldName,
|
|
1187
|
+
tenantFieldType: metadata.tenantFieldType,
|
|
1188
|
+
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1177
1191
|
const queryResponse = await client.post(
|
|
1178
1192
|
"/query",
|
|
1179
1193
|
{
|
|
1180
1194
|
question,
|
|
1181
1195
|
...lastError ? { last_error: lastError } : {},
|
|
1182
1196
|
...previousSql ? { previous_sql: previousSql } : {},
|
|
1183
|
-
...options.maxRetry ? { max_retry: options.maxRetry } : {}
|
|
1197
|
+
...options.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1198
|
+
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1199
|
+
...databaseName ? { database: databaseName } : {},
|
|
1200
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1184
1201
|
},
|
|
1185
1202
|
tenantId,
|
|
1186
1203
|
options.userId,
|
|
@@ -1188,8 +1205,8 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1188
1205
|
signal,
|
|
1189
1206
|
sessionId
|
|
1190
1207
|
);
|
|
1191
|
-
const
|
|
1192
|
-
if (!
|
|
1208
|
+
const dbName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1209
|
+
if (!dbName) {
|
|
1193
1210
|
throw new Error(
|
|
1194
1211
|
"No database attached. Call attachPostgres/attachClickhouse first."
|
|
1195
1212
|
);
|
|
@@ -1200,7 +1217,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1200
1217
|
const execution = await queryEngine.validateAndExecute(
|
|
1201
1218
|
queryResponse.sql,
|
|
1202
1219
|
paramValues,
|
|
1203
|
-
|
|
1220
|
+
dbName,
|
|
1204
1221
|
tenantId
|
|
1205
1222
|
);
|
|
1206
1223
|
const rows = execution.rows ?? [];
|
|
@@ -1273,7 +1290,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1273
1290
|
chart,
|
|
1274
1291
|
context: queryResponse.context,
|
|
1275
1292
|
attempts: attempt + 1,
|
|
1276
|
-
target_db:
|
|
1293
|
+
target_db: dbName
|
|
1277
1294
|
};
|
|
1278
1295
|
} catch (error) {
|
|
1279
1296
|
attempt++;
|
|
@@ -1311,11 +1328,216 @@ function anonymizeResults(rows) {
|
|
|
1311
1328
|
});
|
|
1312
1329
|
}
|
|
1313
1330
|
|
|
1314
|
-
// src/routes/
|
|
1315
|
-
|
|
1316
|
-
|
|
1331
|
+
// src/routes/modify.ts
|
|
1332
|
+
function buildModifiedQuestion(originalQuestion, modifications) {
|
|
1333
|
+
const hints = [];
|
|
1334
|
+
if (modifications.timeGranularity) {
|
|
1335
|
+
hints.push(`group results by ${modifications.timeGranularity}`);
|
|
1336
|
+
}
|
|
1337
|
+
if (modifications.dateRange) {
|
|
1338
|
+
const parts = [];
|
|
1339
|
+
if (modifications.dateRange.from) {
|
|
1340
|
+
parts.push(`from ${modifications.dateRange.from}`);
|
|
1341
|
+
}
|
|
1342
|
+
if (modifications.dateRange.to) {
|
|
1343
|
+
parts.push(`to ${modifications.dateRange.to}`);
|
|
1344
|
+
}
|
|
1345
|
+
if (parts.length > 0) {
|
|
1346
|
+
hints.push(`filter date range ${parts.join(" ")}`);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (modifications.additionalInstructions) {
|
|
1350
|
+
hints.push(modifications.additionalInstructions);
|
|
1351
|
+
}
|
|
1352
|
+
if (hints.length === 0) {
|
|
1353
|
+
return originalQuestion;
|
|
1354
|
+
}
|
|
1355
|
+
return `${originalQuestion} (${hints.join(", ")})`;
|
|
1356
|
+
}
|
|
1357
|
+
function buildVizHints(modifications) {
|
|
1358
|
+
const hints = {};
|
|
1359
|
+
if (modifications.chartType) {
|
|
1360
|
+
hints.chartType = modifications.chartType;
|
|
1361
|
+
}
|
|
1362
|
+
if (modifications.xAxis) {
|
|
1363
|
+
hints.xAxis = modifications.xAxis;
|
|
1364
|
+
}
|
|
1365
|
+
if (modifications.yAxis) {
|
|
1366
|
+
hints.yAxis = modifications.yAxis;
|
|
1367
|
+
}
|
|
1368
|
+
if (modifications.series) {
|
|
1369
|
+
hints.series = modifications.series;
|
|
1370
|
+
}
|
|
1371
|
+
if (modifications.stacking) {
|
|
1372
|
+
hints.stacking = modifications.stacking;
|
|
1373
|
+
}
|
|
1374
|
+
if (modifications.limit !== void 0) {
|
|
1375
|
+
hints.limit = modifications.limit;
|
|
1376
|
+
}
|
|
1377
|
+
return hints;
|
|
1378
|
+
}
|
|
1379
|
+
function resolveTenantId5(client, tenantId) {
|
|
1380
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1381
|
+
if (!resolved) {
|
|
1382
|
+
throw new Error(
|
|
1383
|
+
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
return resolved;
|
|
1387
|
+
}
|
|
1388
|
+
async function modifyChart(client, queryEngine, input, options, signal) {
|
|
1317
1389
|
const tenantId = resolveTenantId5(client, options?.tenantId);
|
|
1318
1390
|
const sessionId = import_node_crypto4.default.randomUUID();
|
|
1391
|
+
const chartType = options?.chartType ?? "vega-lite";
|
|
1392
|
+
const hasSqlMods = !!input.sqlModifications;
|
|
1393
|
+
const hasVizMods = !!input.vizModifications;
|
|
1394
|
+
const hasCustomSql = !!input.sqlModifications?.customSql;
|
|
1395
|
+
let finalSql = input.sql;
|
|
1396
|
+
let finalParams = input.params ?? {};
|
|
1397
|
+
let paramMetadata = [];
|
|
1398
|
+
let rationale;
|
|
1399
|
+
let queryId;
|
|
1400
|
+
let sqlChanged = false;
|
|
1401
|
+
const databaseName = input.database ?? queryEngine.getDefaultDatabase();
|
|
1402
|
+
if (!databaseName) {
|
|
1403
|
+
throw new Error(
|
|
1404
|
+
"No database specified. Provide database in input or attach a default database."
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
const metadata = queryEngine.getDatabaseMetadata(databaseName);
|
|
1408
|
+
let tenantSettings;
|
|
1409
|
+
if (metadata?.tenantFieldName) {
|
|
1410
|
+
tenantSettings = {
|
|
1411
|
+
tenantFieldName: metadata.tenantFieldName,
|
|
1412
|
+
tenantFieldType: metadata.tenantFieldType,
|
|
1413
|
+
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
if (hasCustomSql) {
|
|
1417
|
+
finalSql = input.sqlModifications.customSql;
|
|
1418
|
+
finalParams = {};
|
|
1419
|
+
paramMetadata = [];
|
|
1420
|
+
sqlChanged = true;
|
|
1421
|
+
} else if (hasSqlMods && !hasCustomSql) {
|
|
1422
|
+
const modifiedQuestion = buildModifiedQuestion(
|
|
1423
|
+
input.question,
|
|
1424
|
+
input.sqlModifications
|
|
1425
|
+
);
|
|
1426
|
+
const queryResponse = await client.post(
|
|
1427
|
+
"/query",
|
|
1428
|
+
{
|
|
1429
|
+
question: modifiedQuestion,
|
|
1430
|
+
previous_sql: input.sql,
|
|
1431
|
+
...options?.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1432
|
+
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1433
|
+
...databaseName ? { database: databaseName } : {},
|
|
1434
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1435
|
+
},
|
|
1436
|
+
tenantId,
|
|
1437
|
+
options?.userId,
|
|
1438
|
+
options?.scopes,
|
|
1439
|
+
signal,
|
|
1440
|
+
sessionId
|
|
1441
|
+
);
|
|
1442
|
+
finalSql = queryResponse.sql;
|
|
1443
|
+
paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
|
|
1444
|
+
finalParams = queryEngine.mapGeneratedParams(paramMetadata);
|
|
1445
|
+
rationale = queryResponse.rationale;
|
|
1446
|
+
queryId = queryResponse.queryId;
|
|
1447
|
+
sqlChanged = finalSql !== input.sql;
|
|
1448
|
+
}
|
|
1449
|
+
const execution = await queryEngine.validateAndExecute(
|
|
1450
|
+
finalSql,
|
|
1451
|
+
finalParams,
|
|
1452
|
+
databaseName,
|
|
1453
|
+
tenantId
|
|
1454
|
+
);
|
|
1455
|
+
const rows = execution.rows ?? [];
|
|
1456
|
+
let chart = {
|
|
1457
|
+
specType: chartType,
|
|
1458
|
+
notes: rows.length === 0 ? "Query returned no rows." : null
|
|
1459
|
+
};
|
|
1460
|
+
if (rows.length > 0) {
|
|
1461
|
+
const vizHints = hasVizMods ? buildVizHints(input.vizModifications) : {};
|
|
1462
|
+
if (chartType === "vizspec") {
|
|
1463
|
+
const vizspecResponse = await client.post(
|
|
1464
|
+
"/vizspec",
|
|
1465
|
+
{
|
|
1466
|
+
question: input.question,
|
|
1467
|
+
sql: finalSql,
|
|
1468
|
+
rationale,
|
|
1469
|
+
fields: execution.fields,
|
|
1470
|
+
rows: anonymizeResults(rows),
|
|
1471
|
+
max_retries: options?.chartMaxRetries ?? 3,
|
|
1472
|
+
query_id: queryId,
|
|
1473
|
+
// Include viz hints for the chart generator
|
|
1474
|
+
...hasVizMods ? { encoding_hints: vizHints } : {}
|
|
1475
|
+
},
|
|
1476
|
+
tenantId,
|
|
1477
|
+
options?.userId,
|
|
1478
|
+
options?.scopes,
|
|
1479
|
+
signal,
|
|
1480
|
+
sessionId
|
|
1481
|
+
);
|
|
1482
|
+
chart = {
|
|
1483
|
+
vizSpec: vizspecResponse.spec,
|
|
1484
|
+
specType: "vizspec",
|
|
1485
|
+
notes: vizspecResponse.notes
|
|
1486
|
+
};
|
|
1487
|
+
} else {
|
|
1488
|
+
const chartResponse = await client.post(
|
|
1489
|
+
"/chart",
|
|
1490
|
+
{
|
|
1491
|
+
question: input.question,
|
|
1492
|
+
sql: finalSql,
|
|
1493
|
+
rationale,
|
|
1494
|
+
fields: execution.fields,
|
|
1495
|
+
rows: anonymizeResults(rows),
|
|
1496
|
+
max_retries: options?.chartMaxRetries ?? 3,
|
|
1497
|
+
query_id: queryId,
|
|
1498
|
+
// Include viz hints for the chart generator
|
|
1499
|
+
...hasVizMods ? { encoding_hints: vizHints } : {}
|
|
1500
|
+
},
|
|
1501
|
+
tenantId,
|
|
1502
|
+
options?.userId,
|
|
1503
|
+
options?.scopes,
|
|
1504
|
+
signal,
|
|
1505
|
+
sessionId
|
|
1506
|
+
);
|
|
1507
|
+
chart = {
|
|
1508
|
+
vegaLiteSpec: chartResponse.chart ? {
|
|
1509
|
+
...chartResponse.chart,
|
|
1510
|
+
data: { values: rows }
|
|
1511
|
+
} : null,
|
|
1512
|
+
specType: "vega-lite",
|
|
1513
|
+
notes: chartResponse.notes
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return {
|
|
1518
|
+
sql: finalSql,
|
|
1519
|
+
params: finalParams,
|
|
1520
|
+
paramMetadata,
|
|
1521
|
+
rationale,
|
|
1522
|
+
dialect: metadata?.dialect ?? "unknown",
|
|
1523
|
+
queryId,
|
|
1524
|
+
rows,
|
|
1525
|
+
fields: execution.fields,
|
|
1526
|
+
chart,
|
|
1527
|
+
attempts: 1,
|
|
1528
|
+
target_db: databaseName,
|
|
1529
|
+
modified: {
|
|
1530
|
+
sqlChanged,
|
|
1531
|
+
vizChanged: hasVizMods
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// src/routes/vizspec.ts
|
|
1537
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
1538
|
+
async function generateVizSpec(client, input, options, signal) {
|
|
1539
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1540
|
+
const sessionId = import_node_crypto5.default.randomUUID();
|
|
1319
1541
|
const response = await client.post(
|
|
1320
1542
|
"/vizspec",
|
|
1321
1543
|
{
|
|
@@ -1335,7 +1557,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1335
1557
|
);
|
|
1336
1558
|
return response;
|
|
1337
1559
|
}
|
|
1338
|
-
function
|
|
1560
|
+
function resolveTenantId6(client, tenantId) {
|
|
1339
1561
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1340
1562
|
if (!resolved) {
|
|
1341
1563
|
throw new Error(
|
|
@@ -1375,6 +1597,7 @@ var QueryPanelSdkAPI = class {
|
|
|
1375
1597
|
description: options?.description,
|
|
1376
1598
|
tags: options?.tags,
|
|
1377
1599
|
tenantFieldName: options?.tenantFieldName,
|
|
1600
|
+
tenantFieldType: options?.tenantFieldType ?? "String",
|
|
1378
1601
|
enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
|
|
1379
1602
|
};
|
|
1380
1603
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
@@ -1391,6 +1614,30 @@ var QueryPanelSdkAPI = class {
|
|
|
1391
1614
|
const adapter = this.queryEngine.getDatabase(databaseName);
|
|
1392
1615
|
return await adapter.introspect(tables ? { tables } : void 0);
|
|
1393
1616
|
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Syncs the database schema to QueryPanel for natural language query generation.
|
|
1619
|
+
*
|
|
1620
|
+
* This method introspects your database schema and uploads it to QueryPanel's
|
|
1621
|
+
* vector store. The schema is used by the LLM to generate accurate SQL queries.
|
|
1622
|
+
* Schema embedding is skipped if no changes are detected (drift detection).
|
|
1623
|
+
*
|
|
1624
|
+
* @param databaseName - Name of the attached database to sync
|
|
1625
|
+
* @param options - Sync options including tenantId and forceReindex
|
|
1626
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1627
|
+
* @returns Response with sync status and chunk counts
|
|
1628
|
+
*
|
|
1629
|
+
* @example
|
|
1630
|
+
* ```typescript
|
|
1631
|
+
* // Basic schema sync (skips if no changes)
|
|
1632
|
+
* await qp.syncSchema("analytics", { tenantId: "tenant_123" });
|
|
1633
|
+
*
|
|
1634
|
+
* // Force re-embedding even if schema hasn't changed
|
|
1635
|
+
* await qp.syncSchema("analytics", {
|
|
1636
|
+
* tenantId: "tenant_123",
|
|
1637
|
+
* forceReindex: true,
|
|
1638
|
+
* });
|
|
1639
|
+
* ```
|
|
1640
|
+
*/
|
|
1394
1641
|
async syncSchema(databaseName, options, signal) {
|
|
1395
1642
|
return await syncSchema(
|
|
1396
1643
|
this.client,
|
|
@@ -1401,6 +1648,36 @@ var QueryPanelSdkAPI = class {
|
|
|
1401
1648
|
);
|
|
1402
1649
|
}
|
|
1403
1650
|
// Natural language query
|
|
1651
|
+
/**
|
|
1652
|
+
* Generates SQL from a natural language question and executes it.
|
|
1653
|
+
*
|
|
1654
|
+
* This is the primary method for converting user questions into data.
|
|
1655
|
+
* It handles the complete flow: SQL generation → validation → execution → chart generation.
|
|
1656
|
+
*
|
|
1657
|
+
* @param question - Natural language question (e.g., "Show revenue by country")
|
|
1658
|
+
* @param options - Query options including tenantId, database, and retry settings
|
|
1659
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1660
|
+
* @returns Response with SQL, executed data rows, and generated chart
|
|
1661
|
+
* @throws {Error} When SQL generation or execution fails after all retries
|
|
1662
|
+
*
|
|
1663
|
+
* @example
|
|
1664
|
+
* ```typescript
|
|
1665
|
+
* // Basic query
|
|
1666
|
+
* const result = await qp.ask("Top 10 customers by revenue", {
|
|
1667
|
+
* tenantId: "tenant_123",
|
|
1668
|
+
* database: "analytics",
|
|
1669
|
+
* });
|
|
1670
|
+
* console.log(result.sql); // Generated SQL
|
|
1671
|
+
* console.log(result.rows); // Query results
|
|
1672
|
+
* console.log(result.chart); // Vega-Lite chart spec
|
|
1673
|
+
*
|
|
1674
|
+
* // With automatic SQL repair on failure
|
|
1675
|
+
* const result = await qp.ask("Show monthly trends", {
|
|
1676
|
+
* tenantId: "tenant_123",
|
|
1677
|
+
* maxRetry: 3, // Retry up to 3 times if SQL fails
|
|
1678
|
+
* });
|
|
1679
|
+
* ```
|
|
1680
|
+
*/
|
|
1404
1681
|
async ask(question, options, signal) {
|
|
1405
1682
|
return await ask(
|
|
1406
1683
|
this.client,
|
|
@@ -1411,6 +1688,28 @@ var QueryPanelSdkAPI = class {
|
|
|
1411
1688
|
);
|
|
1412
1689
|
}
|
|
1413
1690
|
// VizSpec generation
|
|
1691
|
+
/**
|
|
1692
|
+
* Generates a VizSpec visualization specification from query results.
|
|
1693
|
+
*
|
|
1694
|
+
* Use this when you have raw SQL results and want to generate a chart
|
|
1695
|
+
* specification without going through the full ask() flow. Useful for
|
|
1696
|
+
* re-generating charts with different settings.
|
|
1697
|
+
*
|
|
1698
|
+
* @param input - VizSpec generation input with question, SQL, and result data
|
|
1699
|
+
* @param options - Optional settings for tenant and retries
|
|
1700
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1701
|
+
* @returns VizSpec specification for chart, table, or metric visualization
|
|
1702
|
+
*
|
|
1703
|
+
* @example
|
|
1704
|
+
* ```typescript
|
|
1705
|
+
* const vizspec = await qp.generateVizSpec({
|
|
1706
|
+
* question: "Revenue by country",
|
|
1707
|
+
* sql: "SELECT country, SUM(revenue) FROM orders GROUP BY country",
|
|
1708
|
+
* fields: ["country", "revenue"],
|
|
1709
|
+
* rows: queryResults,
|
|
1710
|
+
* }, { tenantId: "tenant_123" });
|
|
1711
|
+
* ```
|
|
1712
|
+
*/
|
|
1414
1713
|
async generateVizSpec(input, options, signal) {
|
|
1415
1714
|
return await generateVizSpec(
|
|
1416
1715
|
this.client,
|
|
@@ -1419,10 +1718,125 @@ var QueryPanelSdkAPI = class {
|
|
|
1419
1718
|
signal
|
|
1420
1719
|
);
|
|
1421
1720
|
}
|
|
1721
|
+
// Chart modification
|
|
1722
|
+
/**
|
|
1723
|
+
* Modifies a chart by regenerating SQL and/or applying visualization changes.
|
|
1724
|
+
*
|
|
1725
|
+
* This method supports three modes of operation:
|
|
1726
|
+
*
|
|
1727
|
+
* 1. **SQL Modifications**: When `sqlModifications` is provided, the SQL is
|
|
1728
|
+
* regenerated using the query endpoint with modification hints. If `customSql`
|
|
1729
|
+
* is set, it's used directly without regeneration.
|
|
1730
|
+
*
|
|
1731
|
+
* 2. **Visualization Modifications**: When only `vizModifications` is provided,
|
|
1732
|
+
* the existing SQL is re-executed and a new chart is generated with the
|
|
1733
|
+
* specified encoding preferences.
|
|
1734
|
+
*
|
|
1735
|
+
* 3. **Combined**: Both SQL and visualization modifications can be applied
|
|
1736
|
+
* together. SQL is regenerated first, then viz modifications are applied.
|
|
1737
|
+
*
|
|
1738
|
+
* @param input - Chart modification input with source data and modifications
|
|
1739
|
+
* @param options - Optional settings for tenant, user, and chart generation
|
|
1740
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1741
|
+
* @returns Modified chart response with SQL, data, and chart specification
|
|
1742
|
+
*
|
|
1743
|
+
* @example
|
|
1744
|
+
* ```typescript
|
|
1745
|
+
* // Change chart type and axis from an ask() response
|
|
1746
|
+
* const modified = await qp.modifyChart({
|
|
1747
|
+
* sql: response.sql,
|
|
1748
|
+
* question: "revenue by country",
|
|
1749
|
+
* database: "analytics",
|
|
1750
|
+
* vizModifications: {
|
|
1751
|
+
* chartType: "bar",
|
|
1752
|
+
* xAxis: { field: "country" },
|
|
1753
|
+
* yAxis: { field: "revenue", aggregate: "sum" },
|
|
1754
|
+
* },
|
|
1755
|
+
* }, { tenantId: "tenant_123" });
|
|
1756
|
+
*
|
|
1757
|
+
* // Change time granularity (triggers SQL regeneration)
|
|
1758
|
+
* const monthly = await qp.modifyChart({
|
|
1759
|
+
* sql: response.sql,
|
|
1760
|
+
* question: "revenue over time",
|
|
1761
|
+
* database: "analytics",
|
|
1762
|
+
* sqlModifications: {
|
|
1763
|
+
* timeGranularity: "month",
|
|
1764
|
+
* dateRange: { from: "2024-01-01", to: "2024-12-31" },
|
|
1765
|
+
* },
|
|
1766
|
+
* }, { tenantId: "tenant_123" });
|
|
1767
|
+
*
|
|
1768
|
+
* // Direct SQL edit with chart regeneration
|
|
1769
|
+
* const customized = await qp.modifyChart({
|
|
1770
|
+
* sql: response.sql,
|
|
1771
|
+
* question: "revenue by country",
|
|
1772
|
+
* database: "analytics",
|
|
1773
|
+
* sqlModifications: {
|
|
1774
|
+
* customSql: "SELECT country, SUM(revenue) FROM orders GROUP BY country",
|
|
1775
|
+
* },
|
|
1776
|
+
* }, { tenantId: "tenant_123" });
|
|
1777
|
+
* ```
|
|
1778
|
+
*/
|
|
1779
|
+
async modifyChart(input, options, signal) {
|
|
1780
|
+
return await modifyChart(
|
|
1781
|
+
this.client,
|
|
1782
|
+
this.queryEngine,
|
|
1783
|
+
input,
|
|
1784
|
+
options,
|
|
1785
|
+
signal
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1422
1788
|
// Chart CRUD operations
|
|
1789
|
+
/**
|
|
1790
|
+
* Saves a chart to the QueryPanel system for later retrieval.
|
|
1791
|
+
*
|
|
1792
|
+
* Charts store the SQL query, parameters, and visualization spec - never the actual data.
|
|
1793
|
+
* Data is fetched live when the chart is rendered or refreshed.
|
|
1794
|
+
*
|
|
1795
|
+
* @param body - Chart data including title, SQL, and Vega-Lite spec
|
|
1796
|
+
* @param options - Tenant, user, and scope options
|
|
1797
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1798
|
+
* @returns The saved chart with its generated ID
|
|
1799
|
+
*
|
|
1800
|
+
* @example
|
|
1801
|
+
* ```typescript
|
|
1802
|
+
* const savedChart = await qp.createChart({
|
|
1803
|
+
* title: "Revenue by Country",
|
|
1804
|
+
* sql: response.sql,
|
|
1805
|
+
* sql_params: response.params,
|
|
1806
|
+
* vega_lite_spec: response.chart.vegaLiteSpec,
|
|
1807
|
+
* target_db: "analytics",
|
|
1808
|
+
* }, { tenantId: "tenant_123", userId: "user_456" });
|
|
1809
|
+
* ```
|
|
1810
|
+
*/
|
|
1423
1811
|
async createChart(body, options, signal) {
|
|
1424
1812
|
return await createChart(this.client, body, options, signal);
|
|
1425
1813
|
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Lists saved charts with optional filtering and pagination.
|
|
1816
|
+
*
|
|
1817
|
+
* Use `includeData: true` to execute each chart's SQL and include live data.
|
|
1818
|
+
*
|
|
1819
|
+
* @param options - Filtering, pagination, and data options
|
|
1820
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1821
|
+
* @returns Paginated list of charts
|
|
1822
|
+
*
|
|
1823
|
+
* @example
|
|
1824
|
+
* ```typescript
|
|
1825
|
+
* // List charts with pagination
|
|
1826
|
+
* const charts = await qp.listCharts({
|
|
1827
|
+
* tenantId: "tenant_123",
|
|
1828
|
+
* pagination: { page: 1, limit: 10 },
|
|
1829
|
+
* sortBy: "created_at",
|
|
1830
|
+
* sortDir: "desc",
|
|
1831
|
+
* });
|
|
1832
|
+
*
|
|
1833
|
+
* // List with live data
|
|
1834
|
+
* const chartsWithData = await qp.listCharts({
|
|
1835
|
+
* tenantId: "tenant_123",
|
|
1836
|
+
* includeData: true,
|
|
1837
|
+
* });
|
|
1838
|
+
* ```
|
|
1839
|
+
*/
|
|
1426
1840
|
async listCharts(options, signal) {
|
|
1427
1841
|
return await listCharts(
|
|
1428
1842
|
this.client,
|
|
@@ -1431,6 +1845,24 @@ var QueryPanelSdkAPI = class {
|
|
|
1431
1845
|
signal
|
|
1432
1846
|
);
|
|
1433
1847
|
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Retrieves a single chart by ID with live data.
|
|
1850
|
+
*
|
|
1851
|
+
* The chart's SQL is automatically executed and data is included in the response.
|
|
1852
|
+
*
|
|
1853
|
+
* @param id - Chart ID
|
|
1854
|
+
* @param options - Tenant, user, and scope options
|
|
1855
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1856
|
+
* @returns Chart with live data populated
|
|
1857
|
+
*
|
|
1858
|
+
* @example
|
|
1859
|
+
* ```typescript
|
|
1860
|
+
* const chart = await qp.getChart("chart_123", {
|
|
1861
|
+
* tenantId: "tenant_123",
|
|
1862
|
+
* });
|
|
1863
|
+
* console.log(chart.vega_lite_spec.data.values); // Live data
|
|
1864
|
+
* ```
|
|
1865
|
+
*/
|
|
1434
1866
|
async getChart(id, options, signal) {
|
|
1435
1867
|
return await getChart(
|
|
1436
1868
|
this.client,
|
|
@@ -1440,6 +1872,23 @@ var QueryPanelSdkAPI = class {
|
|
|
1440
1872
|
signal
|
|
1441
1873
|
);
|
|
1442
1874
|
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Updates an existing chart's metadata or configuration.
|
|
1877
|
+
*
|
|
1878
|
+
* @param id - Chart ID to update
|
|
1879
|
+
* @param body - Fields to update (partial update supported)
|
|
1880
|
+
* @param options - Tenant, user, and scope options
|
|
1881
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1882
|
+
* @returns Updated chart
|
|
1883
|
+
*
|
|
1884
|
+
* @example
|
|
1885
|
+
* ```typescript
|
|
1886
|
+
* const updated = await qp.updateChart("chart_123", {
|
|
1887
|
+
* title: "Updated Chart Title",
|
|
1888
|
+
* description: "New description",
|
|
1889
|
+
* }, { tenantId: "tenant_123" });
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1443
1892
|
async updateChart(id, body, options, signal) {
|
|
1444
1893
|
return await updateChart(
|
|
1445
1894
|
this.client,
|
|
@@ -1449,10 +1898,42 @@ var QueryPanelSdkAPI = class {
|
|
|
1449
1898
|
signal
|
|
1450
1899
|
);
|
|
1451
1900
|
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Deletes a chart permanently.
|
|
1903
|
+
*
|
|
1904
|
+
* @param id - Chart ID to delete
|
|
1905
|
+
* @param options - Tenant, user, and scope options
|
|
1906
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1907
|
+
*
|
|
1908
|
+
* @example
|
|
1909
|
+
* ```typescript
|
|
1910
|
+
* await qp.deleteChart("chart_123", { tenantId: "tenant_123" });
|
|
1911
|
+
* ```
|
|
1912
|
+
*/
|
|
1452
1913
|
async deleteChart(id, options, signal) {
|
|
1453
1914
|
await deleteChart(this.client, id, options, signal);
|
|
1454
1915
|
}
|
|
1455
|
-
// Active Chart CRUD operations
|
|
1916
|
+
// Active Chart CRUD operations (Dashboard)
|
|
1917
|
+
/**
|
|
1918
|
+
* Pins a saved chart to the dashboard (Active Charts).
|
|
1919
|
+
*
|
|
1920
|
+
* Active Charts are used for building dashboards. Unlike the chart history,
|
|
1921
|
+
* active charts are meant to be displayed together with layout metadata.
|
|
1922
|
+
*
|
|
1923
|
+
* @param body - Active chart config with chart_id, order, and optional meta
|
|
1924
|
+
* @param options - Tenant, user, and scope options
|
|
1925
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1926
|
+
* @returns Created active chart entry
|
|
1927
|
+
*
|
|
1928
|
+
* @example
|
|
1929
|
+
* ```typescript
|
|
1930
|
+
* const pinned = await qp.createActiveChart({
|
|
1931
|
+
* chart_id: savedChart.id,
|
|
1932
|
+
* order: 1,
|
|
1933
|
+
* meta: { width: "full", variant: "dark" },
|
|
1934
|
+
* }, { tenantId: "tenant_123" });
|
|
1935
|
+
* ```
|
|
1936
|
+
*/
|
|
1456
1937
|
async createActiveChart(body, options, signal) {
|
|
1457
1938
|
return await createActiveChart(
|
|
1458
1939
|
this.client,
|
|
@@ -1461,6 +1942,30 @@ var QueryPanelSdkAPI = class {
|
|
|
1461
1942
|
signal
|
|
1462
1943
|
);
|
|
1463
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Lists all active charts (dashboard items) with optional live data.
|
|
1947
|
+
*
|
|
1948
|
+
* Use `withData: true` to execute each chart's SQL and include results.
|
|
1949
|
+
* This is the primary method for loading a complete dashboard.
|
|
1950
|
+
*
|
|
1951
|
+
* @param options - Filtering and data options including withData
|
|
1952
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1953
|
+
* @returns Paginated list of active charts with optional live data
|
|
1954
|
+
*
|
|
1955
|
+
* @example
|
|
1956
|
+
* ```typescript
|
|
1957
|
+
* // Load dashboard with live data
|
|
1958
|
+
* const dashboard = await qp.listActiveCharts({
|
|
1959
|
+
* tenantId: "tenant_123",
|
|
1960
|
+
* withData: true,
|
|
1961
|
+
* });
|
|
1962
|
+
*
|
|
1963
|
+
* dashboard.data.forEach(item => {
|
|
1964
|
+
* console.log(item.chart?.title);
|
|
1965
|
+
* console.log(item.chart?.vega_lite_spec.data.values);
|
|
1966
|
+
* });
|
|
1967
|
+
* ```
|
|
1968
|
+
*/
|
|
1464
1969
|
async listActiveCharts(options, signal) {
|
|
1465
1970
|
return await listActiveCharts(
|
|
1466
1971
|
this.client,
|
|
@@ -1469,6 +1974,22 @@ var QueryPanelSdkAPI = class {
|
|
|
1469
1974
|
signal
|
|
1470
1975
|
);
|
|
1471
1976
|
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Retrieves a single active chart by ID.
|
|
1979
|
+
*
|
|
1980
|
+
* @param id - Active chart ID
|
|
1981
|
+
* @param options - Options including withData for live data
|
|
1982
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1983
|
+
* @returns Active chart with associated chart data
|
|
1984
|
+
*
|
|
1985
|
+
* @example
|
|
1986
|
+
* ```typescript
|
|
1987
|
+
* const activeChart = await qp.getActiveChart("active_123", {
|
|
1988
|
+
* tenantId: "tenant_123",
|
|
1989
|
+
* withData: true,
|
|
1990
|
+
* });
|
|
1991
|
+
* ```
|
|
1992
|
+
*/
|
|
1472
1993
|
async getActiveChart(id, options, signal) {
|
|
1473
1994
|
return await getActiveChart(
|
|
1474
1995
|
this.client,
|
|
@@ -1478,6 +1999,25 @@ var QueryPanelSdkAPI = class {
|
|
|
1478
1999
|
signal
|
|
1479
2000
|
);
|
|
1480
2001
|
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Updates an active chart's order or metadata.
|
|
2004
|
+
*
|
|
2005
|
+
* Use this to reorder dashboard items or update layout hints.
|
|
2006
|
+
*
|
|
2007
|
+
* @param id - Active chart ID to update
|
|
2008
|
+
* @param body - Fields to update (order, meta)
|
|
2009
|
+
* @param options - Tenant, user, and scope options
|
|
2010
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2011
|
+
* @returns Updated active chart
|
|
2012
|
+
*
|
|
2013
|
+
* @example
|
|
2014
|
+
* ```typescript
|
|
2015
|
+
* const updated = await qp.updateActiveChart("active_123", {
|
|
2016
|
+
* order: 5,
|
|
2017
|
+
* meta: { width: "half" },
|
|
2018
|
+
* }, { tenantId: "tenant_123" });
|
|
2019
|
+
* ```
|
|
2020
|
+
*/
|
|
1481
2021
|
async updateActiveChart(id, body, options, signal) {
|
|
1482
2022
|
return await updateActiveChart(
|
|
1483
2023
|
this.client,
|
|
@@ -1487,6 +2027,20 @@ var QueryPanelSdkAPI = class {
|
|
|
1487
2027
|
signal
|
|
1488
2028
|
);
|
|
1489
2029
|
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Removes a chart from the dashboard (unpins it).
|
|
2032
|
+
*
|
|
2033
|
+
* This only removes the active chart entry, not the underlying saved chart.
|
|
2034
|
+
*
|
|
2035
|
+
* @param id - Active chart ID to delete
|
|
2036
|
+
* @param options - Tenant, user, and scope options
|
|
2037
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2038
|
+
*
|
|
2039
|
+
* @example
|
|
2040
|
+
* ```typescript
|
|
2041
|
+
* await qp.deleteActiveChart("active_123", { tenantId: "tenant_123" });
|
|
2042
|
+
* ```
|
|
2043
|
+
*/
|
|
1490
2044
|
async deleteActiveChart(id, options, signal) {
|
|
1491
2045
|
await deleteActiveChart(this.client, id, options, signal);
|
|
1492
2046
|
}
|