@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.js
CHANGED
|
@@ -309,8 +309,9 @@ var PostgresAdapter = class {
|
|
|
309
309
|
const allowedSet = new Set(
|
|
310
310
|
this.allowedTables.map((t) => tableKey(t.schema, t.table))
|
|
311
311
|
);
|
|
312
|
+
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*/)");
|
|
312
313
|
const tablePattern = /(?:FROM|JOIN)\s+(?:ONLY\s+)?(?:([a-zA-Z_][a-zA-Z0-9_]*)\.)?(["']?[a-zA-Z_][a-zA-Z0-9_]*["']?)/gi;
|
|
313
|
-
const matches =
|
|
314
|
+
const matches = neutralizedSql.matchAll(tablePattern);
|
|
314
315
|
for (const match of matches) {
|
|
315
316
|
const schema = match[1] ?? this.defaultSchema;
|
|
316
317
|
const table = match[2]?.replace(/['"]/g, "");
|
|
@@ -1124,6 +1125,9 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
|
|
|
1124
1125
|
return request;
|
|
1125
1126
|
}
|
|
1126
1127
|
|
|
1128
|
+
// src/routes/modify.ts
|
|
1129
|
+
import crypto4 from "crypto";
|
|
1130
|
+
|
|
1127
1131
|
// src/routes/query.ts
|
|
1128
1132
|
import crypto3 from "crypto";
|
|
1129
1133
|
async function ask(client, queryEngine, question, options, signal) {
|
|
@@ -1135,13 +1139,26 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1135
1139
|
let previousSql = options.previousSql;
|
|
1136
1140
|
while (attempt <= maxRetry) {
|
|
1137
1141
|
console.log({ lastError, previousSql });
|
|
1142
|
+
const databaseName = options.database ?? queryEngine.getDefaultDatabase();
|
|
1143
|
+
const metadata = databaseName ? queryEngine.getDatabaseMetadata(databaseName) : void 0;
|
|
1144
|
+
let tenantSettings;
|
|
1145
|
+
if (metadata?.tenantFieldName) {
|
|
1146
|
+
tenantSettings = {
|
|
1147
|
+
tenantFieldName: metadata.tenantFieldName,
|
|
1148
|
+
tenantFieldType: metadata.tenantFieldType,
|
|
1149
|
+
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1138
1152
|
const queryResponse = await client.post(
|
|
1139
1153
|
"/query",
|
|
1140
1154
|
{
|
|
1141
1155
|
question,
|
|
1142
1156
|
...lastError ? { last_error: lastError } : {},
|
|
1143
1157
|
...previousSql ? { previous_sql: previousSql } : {},
|
|
1144
|
-
...options.maxRetry ? { max_retry: options.maxRetry } : {}
|
|
1158
|
+
...options.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1159
|
+
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1160
|
+
...databaseName ? { database: databaseName } : {},
|
|
1161
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1145
1162
|
},
|
|
1146
1163
|
tenantId,
|
|
1147
1164
|
options.userId,
|
|
@@ -1149,8 +1166,8 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1149
1166
|
signal,
|
|
1150
1167
|
sessionId
|
|
1151
1168
|
);
|
|
1152
|
-
const
|
|
1153
|
-
if (!
|
|
1169
|
+
const dbName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1170
|
+
if (!dbName) {
|
|
1154
1171
|
throw new Error(
|
|
1155
1172
|
"No database attached. Call attachPostgres/attachClickhouse first."
|
|
1156
1173
|
);
|
|
@@ -1161,7 +1178,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1161
1178
|
const execution = await queryEngine.validateAndExecute(
|
|
1162
1179
|
queryResponse.sql,
|
|
1163
1180
|
paramValues,
|
|
1164
|
-
|
|
1181
|
+
dbName,
|
|
1165
1182
|
tenantId
|
|
1166
1183
|
);
|
|
1167
1184
|
const rows = execution.rows ?? [];
|
|
@@ -1234,7 +1251,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1234
1251
|
chart,
|
|
1235
1252
|
context: queryResponse.context,
|
|
1236
1253
|
attempts: attempt + 1,
|
|
1237
|
-
target_db:
|
|
1254
|
+
target_db: dbName
|
|
1238
1255
|
};
|
|
1239
1256
|
} catch (error) {
|
|
1240
1257
|
attempt++;
|
|
@@ -1272,11 +1289,216 @@ function anonymizeResults(rows) {
|
|
|
1272
1289
|
});
|
|
1273
1290
|
}
|
|
1274
1291
|
|
|
1275
|
-
// src/routes/
|
|
1276
|
-
|
|
1277
|
-
|
|
1292
|
+
// src/routes/modify.ts
|
|
1293
|
+
function buildModifiedQuestion(originalQuestion, modifications) {
|
|
1294
|
+
const hints = [];
|
|
1295
|
+
if (modifications.timeGranularity) {
|
|
1296
|
+
hints.push(`group results by ${modifications.timeGranularity}`);
|
|
1297
|
+
}
|
|
1298
|
+
if (modifications.dateRange) {
|
|
1299
|
+
const parts = [];
|
|
1300
|
+
if (modifications.dateRange.from) {
|
|
1301
|
+
parts.push(`from ${modifications.dateRange.from}`);
|
|
1302
|
+
}
|
|
1303
|
+
if (modifications.dateRange.to) {
|
|
1304
|
+
parts.push(`to ${modifications.dateRange.to}`);
|
|
1305
|
+
}
|
|
1306
|
+
if (parts.length > 0) {
|
|
1307
|
+
hints.push(`filter date range ${parts.join(" ")}`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (modifications.additionalInstructions) {
|
|
1311
|
+
hints.push(modifications.additionalInstructions);
|
|
1312
|
+
}
|
|
1313
|
+
if (hints.length === 0) {
|
|
1314
|
+
return originalQuestion;
|
|
1315
|
+
}
|
|
1316
|
+
return `${originalQuestion} (${hints.join(", ")})`;
|
|
1317
|
+
}
|
|
1318
|
+
function buildVizHints(modifications) {
|
|
1319
|
+
const hints = {};
|
|
1320
|
+
if (modifications.chartType) {
|
|
1321
|
+
hints.chartType = modifications.chartType;
|
|
1322
|
+
}
|
|
1323
|
+
if (modifications.xAxis) {
|
|
1324
|
+
hints.xAxis = modifications.xAxis;
|
|
1325
|
+
}
|
|
1326
|
+
if (modifications.yAxis) {
|
|
1327
|
+
hints.yAxis = modifications.yAxis;
|
|
1328
|
+
}
|
|
1329
|
+
if (modifications.series) {
|
|
1330
|
+
hints.series = modifications.series;
|
|
1331
|
+
}
|
|
1332
|
+
if (modifications.stacking) {
|
|
1333
|
+
hints.stacking = modifications.stacking;
|
|
1334
|
+
}
|
|
1335
|
+
if (modifications.limit !== void 0) {
|
|
1336
|
+
hints.limit = modifications.limit;
|
|
1337
|
+
}
|
|
1338
|
+
return hints;
|
|
1339
|
+
}
|
|
1340
|
+
function resolveTenantId5(client, tenantId) {
|
|
1341
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1342
|
+
if (!resolved) {
|
|
1343
|
+
throw new Error(
|
|
1344
|
+
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
return resolved;
|
|
1348
|
+
}
|
|
1349
|
+
async function modifyChart(client, queryEngine, input, options, signal) {
|
|
1278
1350
|
const tenantId = resolveTenantId5(client, options?.tenantId);
|
|
1279
1351
|
const sessionId = crypto4.randomUUID();
|
|
1352
|
+
const chartType = options?.chartType ?? "vega-lite";
|
|
1353
|
+
const hasSqlMods = !!input.sqlModifications;
|
|
1354
|
+
const hasVizMods = !!input.vizModifications;
|
|
1355
|
+
const hasCustomSql = !!input.sqlModifications?.customSql;
|
|
1356
|
+
let finalSql = input.sql;
|
|
1357
|
+
let finalParams = input.params ?? {};
|
|
1358
|
+
let paramMetadata = [];
|
|
1359
|
+
let rationale;
|
|
1360
|
+
let queryId;
|
|
1361
|
+
let sqlChanged = false;
|
|
1362
|
+
const databaseName = input.database ?? queryEngine.getDefaultDatabase();
|
|
1363
|
+
if (!databaseName) {
|
|
1364
|
+
throw new Error(
|
|
1365
|
+
"No database specified. Provide database in input or attach a default database."
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
const metadata = queryEngine.getDatabaseMetadata(databaseName);
|
|
1369
|
+
let tenantSettings;
|
|
1370
|
+
if (metadata?.tenantFieldName) {
|
|
1371
|
+
tenantSettings = {
|
|
1372
|
+
tenantFieldName: metadata.tenantFieldName,
|
|
1373
|
+
tenantFieldType: metadata.tenantFieldType,
|
|
1374
|
+
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
if (hasCustomSql) {
|
|
1378
|
+
finalSql = input.sqlModifications.customSql;
|
|
1379
|
+
finalParams = {};
|
|
1380
|
+
paramMetadata = [];
|
|
1381
|
+
sqlChanged = true;
|
|
1382
|
+
} else if (hasSqlMods && !hasCustomSql) {
|
|
1383
|
+
const modifiedQuestion = buildModifiedQuestion(
|
|
1384
|
+
input.question,
|
|
1385
|
+
input.sqlModifications
|
|
1386
|
+
);
|
|
1387
|
+
const queryResponse = await client.post(
|
|
1388
|
+
"/query",
|
|
1389
|
+
{
|
|
1390
|
+
question: modifiedQuestion,
|
|
1391
|
+
previous_sql: input.sql,
|
|
1392
|
+
...options?.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1393
|
+
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1394
|
+
...databaseName ? { database: databaseName } : {},
|
|
1395
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1396
|
+
},
|
|
1397
|
+
tenantId,
|
|
1398
|
+
options?.userId,
|
|
1399
|
+
options?.scopes,
|
|
1400
|
+
signal,
|
|
1401
|
+
sessionId
|
|
1402
|
+
);
|
|
1403
|
+
finalSql = queryResponse.sql;
|
|
1404
|
+
paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
|
|
1405
|
+
finalParams = queryEngine.mapGeneratedParams(paramMetadata);
|
|
1406
|
+
rationale = queryResponse.rationale;
|
|
1407
|
+
queryId = queryResponse.queryId;
|
|
1408
|
+
sqlChanged = finalSql !== input.sql;
|
|
1409
|
+
}
|
|
1410
|
+
const execution = await queryEngine.validateAndExecute(
|
|
1411
|
+
finalSql,
|
|
1412
|
+
finalParams,
|
|
1413
|
+
databaseName,
|
|
1414
|
+
tenantId
|
|
1415
|
+
);
|
|
1416
|
+
const rows = execution.rows ?? [];
|
|
1417
|
+
let chart = {
|
|
1418
|
+
specType: chartType,
|
|
1419
|
+
notes: rows.length === 0 ? "Query returned no rows." : null
|
|
1420
|
+
};
|
|
1421
|
+
if (rows.length > 0) {
|
|
1422
|
+
const vizHints = hasVizMods ? buildVizHints(input.vizModifications) : {};
|
|
1423
|
+
if (chartType === "vizspec") {
|
|
1424
|
+
const vizspecResponse = await client.post(
|
|
1425
|
+
"/vizspec",
|
|
1426
|
+
{
|
|
1427
|
+
question: input.question,
|
|
1428
|
+
sql: finalSql,
|
|
1429
|
+
rationale,
|
|
1430
|
+
fields: execution.fields,
|
|
1431
|
+
rows: anonymizeResults(rows),
|
|
1432
|
+
max_retries: options?.chartMaxRetries ?? 3,
|
|
1433
|
+
query_id: queryId,
|
|
1434
|
+
// Include viz hints for the chart generator
|
|
1435
|
+
...hasVizMods ? { encoding_hints: vizHints } : {}
|
|
1436
|
+
},
|
|
1437
|
+
tenantId,
|
|
1438
|
+
options?.userId,
|
|
1439
|
+
options?.scopes,
|
|
1440
|
+
signal,
|
|
1441
|
+
sessionId
|
|
1442
|
+
);
|
|
1443
|
+
chart = {
|
|
1444
|
+
vizSpec: vizspecResponse.spec,
|
|
1445
|
+
specType: "vizspec",
|
|
1446
|
+
notes: vizspecResponse.notes
|
|
1447
|
+
};
|
|
1448
|
+
} else {
|
|
1449
|
+
const chartResponse = await client.post(
|
|
1450
|
+
"/chart",
|
|
1451
|
+
{
|
|
1452
|
+
question: input.question,
|
|
1453
|
+
sql: finalSql,
|
|
1454
|
+
rationale,
|
|
1455
|
+
fields: execution.fields,
|
|
1456
|
+
rows: anonymizeResults(rows),
|
|
1457
|
+
max_retries: options?.chartMaxRetries ?? 3,
|
|
1458
|
+
query_id: queryId,
|
|
1459
|
+
// Include viz hints for the chart generator
|
|
1460
|
+
...hasVizMods ? { encoding_hints: vizHints } : {}
|
|
1461
|
+
},
|
|
1462
|
+
tenantId,
|
|
1463
|
+
options?.userId,
|
|
1464
|
+
options?.scopes,
|
|
1465
|
+
signal,
|
|
1466
|
+
sessionId
|
|
1467
|
+
);
|
|
1468
|
+
chart = {
|
|
1469
|
+
vegaLiteSpec: chartResponse.chart ? {
|
|
1470
|
+
...chartResponse.chart,
|
|
1471
|
+
data: { values: rows }
|
|
1472
|
+
} : null,
|
|
1473
|
+
specType: "vega-lite",
|
|
1474
|
+
notes: chartResponse.notes
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
sql: finalSql,
|
|
1480
|
+
params: finalParams,
|
|
1481
|
+
paramMetadata,
|
|
1482
|
+
rationale,
|
|
1483
|
+
dialect: metadata?.dialect ?? "unknown",
|
|
1484
|
+
queryId,
|
|
1485
|
+
rows,
|
|
1486
|
+
fields: execution.fields,
|
|
1487
|
+
chart,
|
|
1488
|
+
attempts: 1,
|
|
1489
|
+
target_db: databaseName,
|
|
1490
|
+
modified: {
|
|
1491
|
+
sqlChanged,
|
|
1492
|
+
vizChanged: hasVizMods
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/routes/vizspec.ts
|
|
1498
|
+
import crypto5 from "crypto";
|
|
1499
|
+
async function generateVizSpec(client, input, options, signal) {
|
|
1500
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1501
|
+
const sessionId = crypto5.randomUUID();
|
|
1280
1502
|
const response = await client.post(
|
|
1281
1503
|
"/vizspec",
|
|
1282
1504
|
{
|
|
@@ -1296,7 +1518,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1296
1518
|
);
|
|
1297
1519
|
return response;
|
|
1298
1520
|
}
|
|
1299
|
-
function
|
|
1521
|
+
function resolveTenantId6(client, tenantId) {
|
|
1300
1522
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1301
1523
|
if (!resolved) {
|
|
1302
1524
|
throw new Error(
|
|
@@ -1336,6 +1558,7 @@ var QueryPanelSdkAPI = class {
|
|
|
1336
1558
|
description: options?.description,
|
|
1337
1559
|
tags: options?.tags,
|
|
1338
1560
|
tenantFieldName: options?.tenantFieldName,
|
|
1561
|
+
tenantFieldType: options?.tenantFieldType ?? "String",
|
|
1339
1562
|
enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
|
|
1340
1563
|
};
|
|
1341
1564
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
@@ -1352,6 +1575,30 @@ var QueryPanelSdkAPI = class {
|
|
|
1352
1575
|
const adapter = this.queryEngine.getDatabase(databaseName);
|
|
1353
1576
|
return await adapter.introspect(tables ? { tables } : void 0);
|
|
1354
1577
|
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Syncs the database schema to QueryPanel for natural language query generation.
|
|
1580
|
+
*
|
|
1581
|
+
* This method introspects your database schema and uploads it to QueryPanel's
|
|
1582
|
+
* vector store. The schema is used by the LLM to generate accurate SQL queries.
|
|
1583
|
+
* Schema embedding is skipped if no changes are detected (drift detection).
|
|
1584
|
+
*
|
|
1585
|
+
* @param databaseName - Name of the attached database to sync
|
|
1586
|
+
* @param options - Sync options including tenantId and forceReindex
|
|
1587
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1588
|
+
* @returns Response with sync status and chunk counts
|
|
1589
|
+
*
|
|
1590
|
+
* @example
|
|
1591
|
+
* ```typescript
|
|
1592
|
+
* // Basic schema sync (skips if no changes)
|
|
1593
|
+
* await qp.syncSchema("analytics", { tenantId: "tenant_123" });
|
|
1594
|
+
*
|
|
1595
|
+
* // Force re-embedding even if schema hasn't changed
|
|
1596
|
+
* await qp.syncSchema("analytics", {
|
|
1597
|
+
* tenantId: "tenant_123",
|
|
1598
|
+
* forceReindex: true,
|
|
1599
|
+
* });
|
|
1600
|
+
* ```
|
|
1601
|
+
*/
|
|
1355
1602
|
async syncSchema(databaseName, options, signal) {
|
|
1356
1603
|
return await syncSchema(
|
|
1357
1604
|
this.client,
|
|
@@ -1362,6 +1609,36 @@ var QueryPanelSdkAPI = class {
|
|
|
1362
1609
|
);
|
|
1363
1610
|
}
|
|
1364
1611
|
// Natural language query
|
|
1612
|
+
/**
|
|
1613
|
+
* Generates SQL from a natural language question and executes it.
|
|
1614
|
+
*
|
|
1615
|
+
* This is the primary method for converting user questions into data.
|
|
1616
|
+
* It handles the complete flow: SQL generation → validation → execution → chart generation.
|
|
1617
|
+
*
|
|
1618
|
+
* @param question - Natural language question (e.g., "Show revenue by country")
|
|
1619
|
+
* @param options - Query options including tenantId, database, and retry settings
|
|
1620
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1621
|
+
* @returns Response with SQL, executed data rows, and generated chart
|
|
1622
|
+
* @throws {Error} When SQL generation or execution fails after all retries
|
|
1623
|
+
*
|
|
1624
|
+
* @example
|
|
1625
|
+
* ```typescript
|
|
1626
|
+
* // Basic query
|
|
1627
|
+
* const result = await qp.ask("Top 10 customers by revenue", {
|
|
1628
|
+
* tenantId: "tenant_123",
|
|
1629
|
+
* database: "analytics",
|
|
1630
|
+
* });
|
|
1631
|
+
* console.log(result.sql); // Generated SQL
|
|
1632
|
+
* console.log(result.rows); // Query results
|
|
1633
|
+
* console.log(result.chart); // Vega-Lite chart spec
|
|
1634
|
+
*
|
|
1635
|
+
* // With automatic SQL repair on failure
|
|
1636
|
+
* const result = await qp.ask("Show monthly trends", {
|
|
1637
|
+
* tenantId: "tenant_123",
|
|
1638
|
+
* maxRetry: 3, // Retry up to 3 times if SQL fails
|
|
1639
|
+
* });
|
|
1640
|
+
* ```
|
|
1641
|
+
*/
|
|
1365
1642
|
async ask(question, options, signal) {
|
|
1366
1643
|
return await ask(
|
|
1367
1644
|
this.client,
|
|
@@ -1372,6 +1649,28 @@ var QueryPanelSdkAPI = class {
|
|
|
1372
1649
|
);
|
|
1373
1650
|
}
|
|
1374
1651
|
// VizSpec generation
|
|
1652
|
+
/**
|
|
1653
|
+
* Generates a VizSpec visualization specification from query results.
|
|
1654
|
+
*
|
|
1655
|
+
* Use this when you have raw SQL results and want to generate a chart
|
|
1656
|
+
* specification without going through the full ask() flow. Useful for
|
|
1657
|
+
* re-generating charts with different settings.
|
|
1658
|
+
*
|
|
1659
|
+
* @param input - VizSpec generation input with question, SQL, and result data
|
|
1660
|
+
* @param options - Optional settings for tenant and retries
|
|
1661
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1662
|
+
* @returns VizSpec specification for chart, table, or metric visualization
|
|
1663
|
+
*
|
|
1664
|
+
* @example
|
|
1665
|
+
* ```typescript
|
|
1666
|
+
* const vizspec = await qp.generateVizSpec({
|
|
1667
|
+
* question: "Revenue by country",
|
|
1668
|
+
* sql: "SELECT country, SUM(revenue) FROM orders GROUP BY country",
|
|
1669
|
+
* fields: ["country", "revenue"],
|
|
1670
|
+
* rows: queryResults,
|
|
1671
|
+
* }, { tenantId: "tenant_123" });
|
|
1672
|
+
* ```
|
|
1673
|
+
*/
|
|
1375
1674
|
async generateVizSpec(input, options, signal) {
|
|
1376
1675
|
return await generateVizSpec(
|
|
1377
1676
|
this.client,
|
|
@@ -1380,10 +1679,125 @@ var QueryPanelSdkAPI = class {
|
|
|
1380
1679
|
signal
|
|
1381
1680
|
);
|
|
1382
1681
|
}
|
|
1682
|
+
// Chart modification
|
|
1683
|
+
/**
|
|
1684
|
+
* Modifies a chart by regenerating SQL and/or applying visualization changes.
|
|
1685
|
+
*
|
|
1686
|
+
* This method supports three modes of operation:
|
|
1687
|
+
*
|
|
1688
|
+
* 1. **SQL Modifications**: When `sqlModifications` is provided, the SQL is
|
|
1689
|
+
* regenerated using the query endpoint with modification hints. If `customSql`
|
|
1690
|
+
* is set, it's used directly without regeneration.
|
|
1691
|
+
*
|
|
1692
|
+
* 2. **Visualization Modifications**: When only `vizModifications` is provided,
|
|
1693
|
+
* the existing SQL is re-executed and a new chart is generated with the
|
|
1694
|
+
* specified encoding preferences.
|
|
1695
|
+
*
|
|
1696
|
+
* 3. **Combined**: Both SQL and visualization modifications can be applied
|
|
1697
|
+
* together. SQL is regenerated first, then viz modifications are applied.
|
|
1698
|
+
*
|
|
1699
|
+
* @param input - Chart modification input with source data and modifications
|
|
1700
|
+
* @param options - Optional settings for tenant, user, and chart generation
|
|
1701
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1702
|
+
* @returns Modified chart response with SQL, data, and chart specification
|
|
1703
|
+
*
|
|
1704
|
+
* @example
|
|
1705
|
+
* ```typescript
|
|
1706
|
+
* // Change chart type and axis from an ask() response
|
|
1707
|
+
* const modified = await qp.modifyChart({
|
|
1708
|
+
* sql: response.sql,
|
|
1709
|
+
* question: "revenue by country",
|
|
1710
|
+
* database: "analytics",
|
|
1711
|
+
* vizModifications: {
|
|
1712
|
+
* chartType: "bar",
|
|
1713
|
+
* xAxis: { field: "country" },
|
|
1714
|
+
* yAxis: { field: "revenue", aggregate: "sum" },
|
|
1715
|
+
* },
|
|
1716
|
+
* }, { tenantId: "tenant_123" });
|
|
1717
|
+
*
|
|
1718
|
+
* // Change time granularity (triggers SQL regeneration)
|
|
1719
|
+
* const monthly = await qp.modifyChart({
|
|
1720
|
+
* sql: response.sql,
|
|
1721
|
+
* question: "revenue over time",
|
|
1722
|
+
* database: "analytics",
|
|
1723
|
+
* sqlModifications: {
|
|
1724
|
+
* timeGranularity: "month",
|
|
1725
|
+
* dateRange: { from: "2024-01-01", to: "2024-12-31" },
|
|
1726
|
+
* },
|
|
1727
|
+
* }, { tenantId: "tenant_123" });
|
|
1728
|
+
*
|
|
1729
|
+
* // Direct SQL edit with chart regeneration
|
|
1730
|
+
* const customized = await qp.modifyChart({
|
|
1731
|
+
* sql: response.sql,
|
|
1732
|
+
* question: "revenue by country",
|
|
1733
|
+
* database: "analytics",
|
|
1734
|
+
* sqlModifications: {
|
|
1735
|
+
* customSql: "SELECT country, SUM(revenue) FROM orders GROUP BY country",
|
|
1736
|
+
* },
|
|
1737
|
+
* }, { tenantId: "tenant_123" });
|
|
1738
|
+
* ```
|
|
1739
|
+
*/
|
|
1740
|
+
async modifyChart(input, options, signal) {
|
|
1741
|
+
return await modifyChart(
|
|
1742
|
+
this.client,
|
|
1743
|
+
this.queryEngine,
|
|
1744
|
+
input,
|
|
1745
|
+
options,
|
|
1746
|
+
signal
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1383
1749
|
// Chart CRUD operations
|
|
1750
|
+
/**
|
|
1751
|
+
* Saves a chart to the QueryPanel system for later retrieval.
|
|
1752
|
+
*
|
|
1753
|
+
* Charts store the SQL query, parameters, and visualization spec - never the actual data.
|
|
1754
|
+
* Data is fetched live when the chart is rendered or refreshed.
|
|
1755
|
+
*
|
|
1756
|
+
* @param body - Chart data including title, SQL, and Vega-Lite spec
|
|
1757
|
+
* @param options - Tenant, user, and scope options
|
|
1758
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1759
|
+
* @returns The saved chart with its generated ID
|
|
1760
|
+
*
|
|
1761
|
+
* @example
|
|
1762
|
+
* ```typescript
|
|
1763
|
+
* const savedChart = await qp.createChart({
|
|
1764
|
+
* title: "Revenue by Country",
|
|
1765
|
+
* sql: response.sql,
|
|
1766
|
+
* sql_params: response.params,
|
|
1767
|
+
* vega_lite_spec: response.chart.vegaLiteSpec,
|
|
1768
|
+
* target_db: "analytics",
|
|
1769
|
+
* }, { tenantId: "tenant_123", userId: "user_456" });
|
|
1770
|
+
* ```
|
|
1771
|
+
*/
|
|
1384
1772
|
async createChart(body, options, signal) {
|
|
1385
1773
|
return await createChart(this.client, body, options, signal);
|
|
1386
1774
|
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Lists saved charts with optional filtering and pagination.
|
|
1777
|
+
*
|
|
1778
|
+
* Use `includeData: true` to execute each chart's SQL and include live data.
|
|
1779
|
+
*
|
|
1780
|
+
* @param options - Filtering, pagination, and data options
|
|
1781
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1782
|
+
* @returns Paginated list of charts
|
|
1783
|
+
*
|
|
1784
|
+
* @example
|
|
1785
|
+
* ```typescript
|
|
1786
|
+
* // List charts with pagination
|
|
1787
|
+
* const charts = await qp.listCharts({
|
|
1788
|
+
* tenantId: "tenant_123",
|
|
1789
|
+
* pagination: { page: 1, limit: 10 },
|
|
1790
|
+
* sortBy: "created_at",
|
|
1791
|
+
* sortDir: "desc",
|
|
1792
|
+
* });
|
|
1793
|
+
*
|
|
1794
|
+
* // List with live data
|
|
1795
|
+
* const chartsWithData = await qp.listCharts({
|
|
1796
|
+
* tenantId: "tenant_123",
|
|
1797
|
+
* includeData: true,
|
|
1798
|
+
* });
|
|
1799
|
+
* ```
|
|
1800
|
+
*/
|
|
1387
1801
|
async listCharts(options, signal) {
|
|
1388
1802
|
return await listCharts(
|
|
1389
1803
|
this.client,
|
|
@@ -1392,6 +1806,24 @@ var QueryPanelSdkAPI = class {
|
|
|
1392
1806
|
signal
|
|
1393
1807
|
);
|
|
1394
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Retrieves a single chart by ID with live data.
|
|
1811
|
+
*
|
|
1812
|
+
* The chart's SQL is automatically executed and data is included in the response.
|
|
1813
|
+
*
|
|
1814
|
+
* @param id - Chart ID
|
|
1815
|
+
* @param options - Tenant, user, and scope options
|
|
1816
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1817
|
+
* @returns Chart with live data populated
|
|
1818
|
+
*
|
|
1819
|
+
* @example
|
|
1820
|
+
* ```typescript
|
|
1821
|
+
* const chart = await qp.getChart("chart_123", {
|
|
1822
|
+
* tenantId: "tenant_123",
|
|
1823
|
+
* });
|
|
1824
|
+
* console.log(chart.vega_lite_spec.data.values); // Live data
|
|
1825
|
+
* ```
|
|
1826
|
+
*/
|
|
1395
1827
|
async getChart(id, options, signal) {
|
|
1396
1828
|
return await getChart(
|
|
1397
1829
|
this.client,
|
|
@@ -1401,6 +1833,23 @@ var QueryPanelSdkAPI = class {
|
|
|
1401
1833
|
signal
|
|
1402
1834
|
);
|
|
1403
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Updates an existing chart's metadata or configuration.
|
|
1838
|
+
*
|
|
1839
|
+
* @param id - Chart ID to update
|
|
1840
|
+
* @param body - Fields to update (partial update supported)
|
|
1841
|
+
* @param options - Tenant, user, and scope options
|
|
1842
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1843
|
+
* @returns Updated chart
|
|
1844
|
+
*
|
|
1845
|
+
* @example
|
|
1846
|
+
* ```typescript
|
|
1847
|
+
* const updated = await qp.updateChart("chart_123", {
|
|
1848
|
+
* title: "Updated Chart Title",
|
|
1849
|
+
* description: "New description",
|
|
1850
|
+
* }, { tenantId: "tenant_123" });
|
|
1851
|
+
* ```
|
|
1852
|
+
*/
|
|
1404
1853
|
async updateChart(id, body, options, signal) {
|
|
1405
1854
|
return await updateChart(
|
|
1406
1855
|
this.client,
|
|
@@ -1410,10 +1859,42 @@ var QueryPanelSdkAPI = class {
|
|
|
1410
1859
|
signal
|
|
1411
1860
|
);
|
|
1412
1861
|
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Deletes a chart permanently.
|
|
1864
|
+
*
|
|
1865
|
+
* @param id - Chart ID to delete
|
|
1866
|
+
* @param options - Tenant, user, and scope options
|
|
1867
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1868
|
+
*
|
|
1869
|
+
* @example
|
|
1870
|
+
* ```typescript
|
|
1871
|
+
* await qp.deleteChart("chart_123", { tenantId: "tenant_123" });
|
|
1872
|
+
* ```
|
|
1873
|
+
*/
|
|
1413
1874
|
async deleteChart(id, options, signal) {
|
|
1414
1875
|
await deleteChart(this.client, id, options, signal);
|
|
1415
1876
|
}
|
|
1416
|
-
// Active Chart CRUD operations
|
|
1877
|
+
// Active Chart CRUD operations (Dashboard)
|
|
1878
|
+
/**
|
|
1879
|
+
* Pins a saved chart to the dashboard (Active Charts).
|
|
1880
|
+
*
|
|
1881
|
+
* Active Charts are used for building dashboards. Unlike the chart history,
|
|
1882
|
+
* active charts are meant to be displayed together with layout metadata.
|
|
1883
|
+
*
|
|
1884
|
+
* @param body - Active chart config with chart_id, order, and optional meta
|
|
1885
|
+
* @param options - Tenant, user, and scope options
|
|
1886
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1887
|
+
* @returns Created active chart entry
|
|
1888
|
+
*
|
|
1889
|
+
* @example
|
|
1890
|
+
* ```typescript
|
|
1891
|
+
* const pinned = await qp.createActiveChart({
|
|
1892
|
+
* chart_id: savedChart.id,
|
|
1893
|
+
* order: 1,
|
|
1894
|
+
* meta: { width: "full", variant: "dark" },
|
|
1895
|
+
* }, { tenantId: "tenant_123" });
|
|
1896
|
+
* ```
|
|
1897
|
+
*/
|
|
1417
1898
|
async createActiveChart(body, options, signal) {
|
|
1418
1899
|
return await createActiveChart(
|
|
1419
1900
|
this.client,
|
|
@@ -1422,6 +1903,30 @@ var QueryPanelSdkAPI = class {
|
|
|
1422
1903
|
signal
|
|
1423
1904
|
);
|
|
1424
1905
|
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Lists all active charts (dashboard items) with optional live data.
|
|
1908
|
+
*
|
|
1909
|
+
* Use `withData: true` to execute each chart's SQL and include results.
|
|
1910
|
+
* This is the primary method for loading a complete dashboard.
|
|
1911
|
+
*
|
|
1912
|
+
* @param options - Filtering and data options including withData
|
|
1913
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1914
|
+
* @returns Paginated list of active charts with optional live data
|
|
1915
|
+
*
|
|
1916
|
+
* @example
|
|
1917
|
+
* ```typescript
|
|
1918
|
+
* // Load dashboard with live data
|
|
1919
|
+
* const dashboard = await qp.listActiveCharts({
|
|
1920
|
+
* tenantId: "tenant_123",
|
|
1921
|
+
* withData: true,
|
|
1922
|
+
* });
|
|
1923
|
+
*
|
|
1924
|
+
* dashboard.data.forEach(item => {
|
|
1925
|
+
* console.log(item.chart?.title);
|
|
1926
|
+
* console.log(item.chart?.vega_lite_spec.data.values);
|
|
1927
|
+
* });
|
|
1928
|
+
* ```
|
|
1929
|
+
*/
|
|
1425
1930
|
async listActiveCharts(options, signal) {
|
|
1426
1931
|
return await listActiveCharts(
|
|
1427
1932
|
this.client,
|
|
@@ -1430,6 +1935,22 @@ var QueryPanelSdkAPI = class {
|
|
|
1430
1935
|
signal
|
|
1431
1936
|
);
|
|
1432
1937
|
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Retrieves a single active chart by ID.
|
|
1940
|
+
*
|
|
1941
|
+
* @param id - Active chart ID
|
|
1942
|
+
* @param options - Options including withData for live data
|
|
1943
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1944
|
+
* @returns Active chart with associated chart data
|
|
1945
|
+
*
|
|
1946
|
+
* @example
|
|
1947
|
+
* ```typescript
|
|
1948
|
+
* const activeChart = await qp.getActiveChart("active_123", {
|
|
1949
|
+
* tenantId: "tenant_123",
|
|
1950
|
+
* withData: true,
|
|
1951
|
+
* });
|
|
1952
|
+
* ```
|
|
1953
|
+
*/
|
|
1433
1954
|
async getActiveChart(id, options, signal) {
|
|
1434
1955
|
return await getActiveChart(
|
|
1435
1956
|
this.client,
|
|
@@ -1439,6 +1960,25 @@ var QueryPanelSdkAPI = class {
|
|
|
1439
1960
|
signal
|
|
1440
1961
|
);
|
|
1441
1962
|
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Updates an active chart's order or metadata.
|
|
1965
|
+
*
|
|
1966
|
+
* Use this to reorder dashboard items or update layout hints.
|
|
1967
|
+
*
|
|
1968
|
+
* @param id - Active chart ID to update
|
|
1969
|
+
* @param body - Fields to update (order, meta)
|
|
1970
|
+
* @param options - Tenant, user, and scope options
|
|
1971
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1972
|
+
* @returns Updated active chart
|
|
1973
|
+
*
|
|
1974
|
+
* @example
|
|
1975
|
+
* ```typescript
|
|
1976
|
+
* const updated = await qp.updateActiveChart("active_123", {
|
|
1977
|
+
* order: 5,
|
|
1978
|
+
* meta: { width: "half" },
|
|
1979
|
+
* }, { tenantId: "tenant_123" });
|
|
1980
|
+
* ```
|
|
1981
|
+
*/
|
|
1442
1982
|
async updateActiveChart(id, body, options, signal) {
|
|
1443
1983
|
return await updateActiveChart(
|
|
1444
1984
|
this.client,
|
|
@@ -1448,6 +1988,20 @@ var QueryPanelSdkAPI = class {
|
|
|
1448
1988
|
signal
|
|
1449
1989
|
);
|
|
1450
1990
|
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Removes a chart from the dashboard (unpins it).
|
|
1993
|
+
*
|
|
1994
|
+
* This only removes the active chart entry, not the underlying saved chart.
|
|
1995
|
+
*
|
|
1996
|
+
* @param id - Active chart ID to delete
|
|
1997
|
+
* @param options - Tenant, user, and scope options
|
|
1998
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1999
|
+
*
|
|
2000
|
+
* @example
|
|
2001
|
+
* ```typescript
|
|
2002
|
+
* await qp.deleteActiveChart("active_123", { tenantId: "tenant_123" });
|
|
2003
|
+
* ```
|
|
2004
|
+
*/
|
|
1451
2005
|
async deleteActiveChart(id, options, signal) {
|
|
1452
2006
|
await deleteActiveChart(this.client, id, options, signal);
|
|
1453
2007
|
}
|