yamchart 0.8.8 → 0.9.0
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/{advisor-IJBW56F5.js → advisor-Z7TKPPBR.js} +10 -10
- package/dist/{agent-2DFNL2UB.js → agent-KWKPAYT2.js} +2 -2
- package/dist/{chunk-34ZVFILD.js → chunk-AMHCOB4D.js} +4 -4
- package/dist/{chunk-5N3FYFBV.js → chunk-CWAWATL4.js} +34 -7
- package/dist/chunk-CWAWATL4.js.map +1 -0
- package/dist/{chunk-D3ELUYYE.js → chunk-E2QN2M7S.js} +52 -5
- package/dist/chunk-E2QN2M7S.js.map +1 -0
- package/dist/{chunk-B5ZLCKNF.js → chunk-FZFBBB7K.js} +2 -2
- package/dist/{chunk-232AMQ5T.js → chunk-G57J2WQM.js} +4 -4
- package/dist/{chunk-77U26A7F.js → chunk-ZA6AOQVZ.js} +4 -4
- package/dist/chunk-ZA6AOQVZ.js.map +1 -0
- package/dist/{connection-utils-ZLSV5OLQ.js → connection-utils-CTPN7PV3.js} +4 -4
- package/dist/{describe-VO4CEYL2.js → describe-4NME6RCB.js} +5 -5
- package/dist/{dev-JUA73B6D.js → dev-6QGAB4ZH.js} +316 -29
- package/dist/dev-6QGAB4ZH.js.map +1 -0
- package/dist/{dist-PINRLZVT.js → dist-4GUE24QV.js} +2 -2
- package/dist/{dist-GVNWQXFR.js → dist-7CRX2GIR.js} +2 -2
- package/dist/{dist-E2PVGIPT.js → dist-VNX77VV5.js} +4 -2
- package/dist/index.js +21 -21
- package/dist/public/assets/{EventManagement-DQY1Sic0.js → EventManagement-MMsAkJKj.js} +2 -2
- package/dist/public/assets/{ExplorePage-C9M-fQ3Y.js → ExplorePage-BSkSNgLT.js} +1 -1
- package/dist/public/assets/{LoginPage-XYLwfbfw.js → LoginPage-vaI1dnyL.js} +1 -1
- package/dist/public/assets/PublicViewer-B-OKj2cg.js +1 -0
- package/dist/public/assets/{SetupWizard-fle7FC4b.js → SetupWizard-DvlVX2O6.js} +1 -1
- package/dist/public/assets/{ShareManagement-b3j4jzn3.js → ShareManagement-ulvPrOAQ.js} +1 -1
- package/dist/public/assets/{UserManagement-DwZ-AaqI.js → UserManagement-CvmpNy3o.js} +1 -1
- package/dist/public/assets/{index-CRcgti3B.css → index-CfyF2Wf-.css} +1 -1
- package/dist/public/assets/index-DD59fsOk.js +195 -0
- package/dist/public/assets/{index.es-Bd2YVqtj.js → index.es-BeTaRWIv.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-CpRfbJ_H.js → jspdf.es.min-9haD1GSE.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-PXJMSDKR.js → query-Z75RKTHV.js} +4 -4
- package/dist/{sample-M24H3M73.js → sample-OIJNXQNC.js} +4 -4
- package/dist/{search-UCJS7X5D.js → search-YDCPIDZX.js} +5 -5
- package/dist/{source-resolver-RXNB7A64.js → source-resolver-4SUWXUGW.js} +5 -5
- package/dist/{sync-warehouse-S75DWVTG.js → sync-warehouse-NZFDS6WK.js} +4 -4
- package/dist/{tables-W3M3JDJO.js → tables-WJS2VI4L.js} +5 -5
- package/dist/templates/default/CLAUDE.md +1 -1
- package/dist/templates/default/docs/yamchart-reference.md +108 -2
- package/dist/{test-67AXD5PI.js → test-I4XOF7TZ.js} +5 -17
- package/dist/test-I4XOF7TZ.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-5N3FYFBV.js.map +0 -1
- package/dist/chunk-77U26A7F.js.map +0 -1
- package/dist/chunk-D3ELUYYE.js.map +0 -1
- package/dist/dev-JUA73B6D.js.map +0 -1
- package/dist/public/assets/PublicViewer-CZJYS0wF.js +0 -1
- package/dist/public/assets/index-m_Jm3sB1.js +0 -188
- package/dist/test-67AXD5PI.js.map +0 -1
- /package/dist/{advisor-IJBW56F5.js.map → advisor-Z7TKPPBR.js.map} +0 -0
- /package/dist/{agent-2DFNL2UB.js.map → agent-KWKPAYT2.js.map} +0 -0
- /package/dist/{chunk-34ZVFILD.js.map → chunk-AMHCOB4D.js.map} +0 -0
- /package/dist/{chunk-B5ZLCKNF.js.map → chunk-FZFBBB7K.js.map} +0 -0
- /package/dist/{chunk-232AMQ5T.js.map → chunk-G57J2WQM.js.map} +0 -0
- /package/dist/{connection-utils-ZLSV5OLQ.js.map → connection-utils-CTPN7PV3.js.map} +0 -0
- /package/dist/{describe-VO4CEYL2.js.map → describe-4NME6RCB.js.map} +0 -0
- /package/dist/{dist-E2PVGIPT.js.map → dist-4GUE24QV.js.map} +0 -0
- /package/dist/{dist-GVNWQXFR.js.map → dist-7CRX2GIR.js.map} +0 -0
- /package/dist/{dist-PINRLZVT.js.map → dist-VNX77VV5.js.map} +0 -0
- /package/dist/{query-PXJMSDKR.js.map → query-Z75RKTHV.js.map} +0 -0
- /package/dist/{sample-M24H3M73.js.map → sample-OIJNXQNC.js.map} +0 -0
- /package/dist/{search-UCJS7X5D.js.map → search-YDCPIDZX.js.map} +0 -0
- /package/dist/{source-resolver-RXNB7A64.js.map → source-resolver-4SUWXUGW.js.map} +0 -0
- /package/dist/{sync-warehouse-S75DWVTG.js.map → sync-warehouse-NZFDS6WK.js.map} +0 -0
- /package/dist/{tables-W3M3JDJO.js.map → tables-WJS2VI4L.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadEnvFile,
|
|
3
3
|
validateProject
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G57J2WQM.js";
|
|
5
5
|
import {
|
|
6
6
|
box,
|
|
7
7
|
detail,
|
|
@@ -23,11 +23,11 @@ import {
|
|
|
23
23
|
SemanticQuerySchema,
|
|
24
24
|
deepMerge,
|
|
25
25
|
resolveProjectConfig
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-E2QN2M7S.js";
|
|
27
27
|
import {
|
|
28
28
|
StreamingChatAgent,
|
|
29
29
|
filterCatalogEntries
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-ZA6AOQVZ.js";
|
|
31
31
|
import {
|
|
32
32
|
AuthDatabase,
|
|
33
33
|
generateSessionToken,
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
resolveMySQLAuth,
|
|
58
58
|
resolvePostgresAuth,
|
|
59
59
|
resolveSnowflakeAuth
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-CWAWATL4.js";
|
|
61
61
|
import {
|
|
62
62
|
SemanticModelBuilder,
|
|
63
63
|
SemanticQueryCompiler,
|
|
@@ -804,12 +804,18 @@ function parseTtl2(ttl) {
|
|
|
804
804
|
}
|
|
805
805
|
|
|
806
806
|
// ../server/dist/services/query-service.js
|
|
807
|
+
function quoteIdentifier(name, dialect) {
|
|
808
|
+
if (dialect === "mysql")
|
|
809
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
810
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
811
|
+
}
|
|
807
812
|
var QueryService = class {
|
|
808
813
|
compiler;
|
|
809
814
|
connector;
|
|
810
815
|
cache;
|
|
811
816
|
models;
|
|
812
817
|
refs;
|
|
818
|
+
dialect;
|
|
813
819
|
constructor(config) {
|
|
814
820
|
this.compiler = new QueryCompiler({
|
|
815
821
|
models: config.models,
|
|
@@ -819,6 +825,7 @@ var QueryService = class {
|
|
|
819
825
|
this.cache = config.cache;
|
|
820
826
|
this.models = new Map(Object.entries(config.models));
|
|
821
827
|
this.refs = config.refs;
|
|
828
|
+
this.dialect = config.dialect ?? "duckdb";
|
|
822
829
|
}
|
|
823
830
|
async executeChart(chart, params, userContext) {
|
|
824
831
|
const compiled = this.compiler.compile(chart, params, userContext);
|
|
@@ -850,6 +857,120 @@ var QueryService = class {
|
|
|
850
857
|
compiledSql: compiled.sql
|
|
851
858
|
};
|
|
852
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Execute a table chart with server-side pagination.
|
|
862
|
+
* Wraps the compiled SQL with LIMIT/OFFSET and runs a parallel COUNT(*) query.
|
|
863
|
+
* Optionally computes summary aggregations for specified columns.
|
|
864
|
+
*/
|
|
865
|
+
async executeChartPaginated(chart, params, options, userContext) {
|
|
866
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
867
|
+
const baseSql = compiled.sql;
|
|
868
|
+
const q = quoteIdentifier;
|
|
869
|
+
const d = this.dialect;
|
|
870
|
+
let orderBy = "";
|
|
871
|
+
if (options.sortField) {
|
|
872
|
+
const cleanField = options.sortField.replace(/[^\w\s]/g, "");
|
|
873
|
+
if (cleanField.length > 0) {
|
|
874
|
+
orderBy = `ORDER BY ${q(cleanField, d)} ${options.sortDirection === "desc" ? "DESC" : "ASC"}`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const offset = options.page * options.pageSize;
|
|
878
|
+
const dataSql = `SELECT * FROM (${baseSql}) _t ${orderBy} LIMIT ${options.pageSize} OFFSET ${offset}`;
|
|
879
|
+
const countCacheKey = `${compiled.cacheKey}:count`;
|
|
880
|
+
let totalCount;
|
|
881
|
+
const cachedCount = await this.cache.get(countCacheKey);
|
|
882
|
+
if (cachedCount) {
|
|
883
|
+
totalCount = cachedCount.rowCount;
|
|
884
|
+
} else {
|
|
885
|
+
const countSql = `SELECT COUNT(*) AS _count FROM (${baseSql}) _t`;
|
|
886
|
+
const countResult = await this.connector.execute(countSql);
|
|
887
|
+
totalCount = Number(countResult.rows[0]?._count ?? 0);
|
|
888
|
+
await this.cache.set(countCacheKey, {
|
|
889
|
+
columns: [],
|
|
890
|
+
rows: [],
|
|
891
|
+
rowCount: totalCount,
|
|
892
|
+
durationMs: countResult.durationMs,
|
|
893
|
+
cachedAt: Date.now()
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
let summaryValues;
|
|
897
|
+
if (options.summaryColumns && options.summaryColumns.length > 0) {
|
|
898
|
+
const summaryCacheKey = `${compiled.cacheKey}:summary`;
|
|
899
|
+
const cachedSummary = await this.cache.get(summaryCacheKey);
|
|
900
|
+
if (cachedSummary && cachedSummary.rows[0]) {
|
|
901
|
+
summaryValues = cachedSummary.rows[0];
|
|
902
|
+
} else {
|
|
903
|
+
const aggExprs = options.summaryColumns.map(({ field, summary }) => {
|
|
904
|
+
const col = q(field, d);
|
|
905
|
+
switch (summary) {
|
|
906
|
+
case "sum":
|
|
907
|
+
return `SUM(${col}) AS ${q(`_sum_${field}`, d)}`;
|
|
908
|
+
case "avg":
|
|
909
|
+
return `AVG(${col}) AS ${q(`_avg_${field}`, d)}`;
|
|
910
|
+
case "min":
|
|
911
|
+
return `MIN(${col}) AS ${q(`_min_${field}`, d)}`;
|
|
912
|
+
case "max":
|
|
913
|
+
return `MAX(${col}) AS ${q(`_max_${field}`, d)}`;
|
|
914
|
+
case "count":
|
|
915
|
+
return `COUNT(${col}) AS ${q(`_count_${field}`, d)}`;
|
|
916
|
+
default:
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
}).filter(Boolean);
|
|
920
|
+
if (aggExprs.length > 0) {
|
|
921
|
+
const summarySql = `SELECT ${aggExprs.join(", ")} FROM (${baseSql}) _t`;
|
|
922
|
+
const summaryResult = await this.connector.execute(summarySql);
|
|
923
|
+
if (summaryResult.rows[0]) {
|
|
924
|
+
summaryValues = {};
|
|
925
|
+
for (const { field, summary } of options.summaryColumns) {
|
|
926
|
+
const key = `_${summary}_${field}`;
|
|
927
|
+
summaryValues[`${summary}:${field}`] = Number(summaryResult.rows[0][key] ?? 0);
|
|
928
|
+
}
|
|
929
|
+
await this.cache.set(summaryCacheKey, {
|
|
930
|
+
columns: [],
|
|
931
|
+
rows: [summaryValues],
|
|
932
|
+
rowCount: 0,
|
|
933
|
+
durationMs: summaryResult.durationMs,
|
|
934
|
+
cachedAt: Date.now()
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const result = await this.connector.execute(dataSql);
|
|
941
|
+
return {
|
|
942
|
+
...result,
|
|
943
|
+
cached: false,
|
|
944
|
+
cacheKey: compiled.cacheKey,
|
|
945
|
+
compiledSql: dataSql,
|
|
946
|
+
totalCount,
|
|
947
|
+
page: options.page,
|
|
948
|
+
pageSize: options.pageSize,
|
|
949
|
+
summaryValues
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Execute a chart query for CSV export with a bounded row limit.
|
|
954
|
+
* Returns full QueryResult — caller streams to HTTP response.
|
|
955
|
+
*/
|
|
956
|
+
async executeChartForExport(chart, params, maxRows, userContext) {
|
|
957
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
958
|
+
const sql = `SELECT * FROM (${compiled.sql}) _t LIMIT ${maxRows}`;
|
|
959
|
+
const result = await this.connector.execute(sql);
|
|
960
|
+
return {
|
|
961
|
+
...result,
|
|
962
|
+
cached: false,
|
|
963
|
+
cacheKey: compiled.cacheKey,
|
|
964
|
+
compiledSql: sql
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Compile a chart query without executing it.
|
|
969
|
+
* Returns the compiled SQL and cache key.
|
|
970
|
+
*/
|
|
971
|
+
compileChart(chart, params, userContext) {
|
|
972
|
+
return this.compiler.compile(chart, params, userContext);
|
|
973
|
+
}
|
|
853
974
|
invalidateChart(chartName) {
|
|
854
975
|
this.cache.invalidatePattern(`${chartName}:*`);
|
|
855
976
|
}
|
|
@@ -1132,6 +1253,12 @@ function classifyError(error2) {
|
|
|
1132
1253
|
}
|
|
1133
1254
|
return "query_error";
|
|
1134
1255
|
}
|
|
1256
|
+
function escapeCSVField(value) {
|
|
1257
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
1258
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
1259
|
+
}
|
|
1260
|
+
return value;
|
|
1261
|
+
}
|
|
1135
1262
|
function getUserContext(request) {
|
|
1136
1263
|
const user = request.user;
|
|
1137
1264
|
if (!user)
|
|
@@ -1196,15 +1323,78 @@ async function chartRoutes(fastify, options) {
|
|
|
1196
1323
|
});
|
|
1197
1324
|
fastify.post("/api/charts/:name/query", async (request, reply) => {
|
|
1198
1325
|
const { name } = request.params;
|
|
1199
|
-
const
|
|
1326
|
+
const rawParams = request.body ?? {};
|
|
1200
1327
|
const includeConfig = request.query.includeConfig === "true";
|
|
1201
1328
|
const chart = configLoader.getChartByName(name);
|
|
1202
1329
|
if (!chart) {
|
|
1203
1330
|
const resp = chartNotFoundResponse(configLoader, name);
|
|
1204
1331
|
return reply.status(resp.status).send(resp.body);
|
|
1205
1332
|
}
|
|
1333
|
+
const { _page, _page_size, _sort_field, _sort_dir, _server_paginated, ...params } = rawParams;
|
|
1206
1334
|
try {
|
|
1207
|
-
const
|
|
1335
|
+
const isTable = chart.chart?.type === "table";
|
|
1336
|
+
const project = configLoader.getProject();
|
|
1337
|
+
const chartMaxRows = chart.chart?.max_rows;
|
|
1338
|
+
const maxDisplayRows = chartMaxRows ?? project?.defaults?.table?.max_display_rows ?? 1e4;
|
|
1339
|
+
const forceServerSide = chart.chart?.pagination && chart.chart.pagination?.server_side === true;
|
|
1340
|
+
if (isTable && _server_paginated) {
|
|
1341
|
+
const page = Number(_page) || 0;
|
|
1342
|
+
const pageSize = Number(_page_size) || 50;
|
|
1343
|
+
const columns = chart.chart?.columns ?? [];
|
|
1344
|
+
const summaryColumns = columns.filter((c) => c.summary).map((c) => ({ field: c.field, summary: c.summary }));
|
|
1345
|
+
const paginatedResult = await queryService.executeChartPaginated(chart, params, { page, pageSize, sortField: _sort_field, sortDirection: _sort_dir, summaryColumns }, getUserContext(request));
|
|
1346
|
+
reply.header("X-Cache", "MISS");
|
|
1347
|
+
reply.header("X-Query-Duration-Ms", paginatedResult.durationMs.toFixed(0));
|
|
1348
|
+
const response2 = {
|
|
1349
|
+
columns: paginatedResult.columns,
|
|
1350
|
+
rows: paginatedResult.rows,
|
|
1351
|
+
meta: {
|
|
1352
|
+
cached: paginatedResult.cached,
|
|
1353
|
+
durationMs: paginatedResult.durationMs,
|
|
1354
|
+
rowCount: paginatedResult.rowCount,
|
|
1355
|
+
cacheKey: paginatedResult.cacheKey,
|
|
1356
|
+
compiledSql: paginatedResult.compiledSql,
|
|
1357
|
+
serverPaginated: true,
|
|
1358
|
+
totalCount: paginatedResult.totalCount,
|
|
1359
|
+
page,
|
|
1360
|
+
pageSize
|
|
1361
|
+
},
|
|
1362
|
+
...paginatedResult.summaryValues ? { summaryValues: paginatedResult.summaryValues } : {}
|
|
1363
|
+
};
|
|
1364
|
+
if (includeConfig) {
|
|
1365
|
+
response2.config = {
|
|
1366
|
+
name: chart.name,
|
|
1367
|
+
title: chart.title,
|
|
1368
|
+
description: chart.description,
|
|
1369
|
+
parameters: chart.parameters ?? [],
|
|
1370
|
+
chart: chart.chart,
|
|
1371
|
+
drillDown: chart.drillDown
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
return response2;
|
|
1375
|
+
}
|
|
1376
|
+
let result;
|
|
1377
|
+
let serverPaginated = false;
|
|
1378
|
+
let totalCount;
|
|
1379
|
+
if (isTable) {
|
|
1380
|
+
const probeResult = await queryService.executeChartForExport(chart, params, maxDisplayRows + 1, getUserContext(request));
|
|
1381
|
+
if (probeResult.rowCount > maxDisplayRows || forceServerSide) {
|
|
1382
|
+
serverPaginated = true;
|
|
1383
|
+
if (probeResult.rowCount > maxDisplayRows) {
|
|
1384
|
+
const compiled = queryService.compileChart(chart, params, getUserContext(request));
|
|
1385
|
+
const countResult = await queryService.executeRawSql(`SELECT COUNT(*) AS _count FROM (${compiled.sql}) _t`);
|
|
1386
|
+
totalCount = Number(countResult.rows[0]?._count ?? 0);
|
|
1387
|
+
result = { ...probeResult, rows: probeResult.rows.slice(0, maxDisplayRows), rowCount: maxDisplayRows };
|
|
1388
|
+
} else {
|
|
1389
|
+
totalCount = probeResult.rowCount;
|
|
1390
|
+
result = probeResult;
|
|
1391
|
+
}
|
|
1392
|
+
} else {
|
|
1393
|
+
result = probeResult;
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
result = await queryService.executeChart(chart, params, getUserContext(request));
|
|
1397
|
+
}
|
|
1208
1398
|
let comparison = void 0;
|
|
1209
1399
|
if (chart.chart?.type === "kpi" && chart.chart?.comparison?.period === "previous") {
|
|
1210
1400
|
let startDate = params.start_date;
|
|
@@ -1247,6 +1437,54 @@ async function chartRoutes(fastify, options) {
|
|
|
1247
1437
|
}
|
|
1248
1438
|
}
|
|
1249
1439
|
}
|
|
1440
|
+
const compareConfig = chart.chart?.compare;
|
|
1441
|
+
let compareData;
|
|
1442
|
+
if (compareConfig && compareConfig.length > 0) {
|
|
1443
|
+
let startDate = params.start_date;
|
|
1444
|
+
let endDate = params.end_date;
|
|
1445
|
+
const dateRange = params.date_range;
|
|
1446
|
+
if (!startDate || !endDate) {
|
|
1447
|
+
if (isRelativeDateRange(params.date_range)) {
|
|
1448
|
+
const resolved = expandRelativeDateRange(params.date_range);
|
|
1449
|
+
startDate = resolved.start_date;
|
|
1450
|
+
endDate = resolved.end_date;
|
|
1451
|
+
} else if (isCustomDateRange(params.date_range)) {
|
|
1452
|
+
const resolved = expandCustomDateRange(params.date_range);
|
|
1453
|
+
startDate = resolved.start_date;
|
|
1454
|
+
endDate = resolved.end_date;
|
|
1455
|
+
} else if (dateRange && isDatePreset(dateRange)) {
|
|
1456
|
+
const resolved = expandDatePreset(dateRange);
|
|
1457
|
+
if (resolved) {
|
|
1458
|
+
startDate = resolved.start_date;
|
|
1459
|
+
endDate = resolved.end_date;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (startDate && endDate) {
|
|
1464
|
+
compareData = [];
|
|
1465
|
+
for (const comp of compareConfig) {
|
|
1466
|
+
try {
|
|
1467
|
+
const periodMap = {
|
|
1468
|
+
previous_week: "last_7_days",
|
|
1469
|
+
previous_month: "last_30_days",
|
|
1470
|
+
previous_quarter: "last_90_days",
|
|
1471
|
+
previous_year: "last_365_days"
|
|
1472
|
+
};
|
|
1473
|
+
const prev = computePreviousPeriod(startDate, endDate, periodMap[comp.period] ?? comp.period);
|
|
1474
|
+
const prevLabel = comp.label ?? comp.period.replace(/_/g, " ");
|
|
1475
|
+
const { date_range: _, ...restParams } = params;
|
|
1476
|
+
const prevParams = { ...restParams, start_date: prev.start_date, end_date: prev.end_date };
|
|
1477
|
+
const prevResult = await queryService.executeChart(chart, prevParams, getUserContext(request));
|
|
1478
|
+
compareData.push({
|
|
1479
|
+
period: comp.period,
|
|
1480
|
+
label: prevLabel,
|
|
1481
|
+
rows: prevResult.rows
|
|
1482
|
+
});
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1250
1488
|
reply.header("X-Cache", result.cached ? "HIT" : "MISS");
|
|
1251
1489
|
reply.header("X-Query-Duration-Ms", result.durationMs.toFixed(0));
|
|
1252
1490
|
const meta = {
|
|
@@ -1254,7 +1492,8 @@ async function chartRoutes(fastify, options) {
|
|
|
1254
1492
|
durationMs: result.durationMs,
|
|
1255
1493
|
rowCount: result.rowCount,
|
|
1256
1494
|
cacheKey: result.cacheKey,
|
|
1257
|
-
compiledSql: result.compiledSql
|
|
1495
|
+
compiledSql: result.compiledSql,
|
|
1496
|
+
...serverPaginated ? { serverPaginated: true, totalCount } : {}
|
|
1258
1497
|
};
|
|
1259
1498
|
const response = {
|
|
1260
1499
|
columns: result.columns,
|
|
@@ -1274,6 +1513,9 @@ async function chartRoutes(fastify, options) {
|
|
|
1274
1513
|
if (comparison) {
|
|
1275
1514
|
response.comparison = comparison;
|
|
1276
1515
|
}
|
|
1516
|
+
if (compareData && compareData.length > 0) {
|
|
1517
|
+
response.compareData = compareData;
|
|
1518
|
+
}
|
|
1277
1519
|
let goalData = void 0;
|
|
1278
1520
|
const goals = chart.chart?.goals;
|
|
1279
1521
|
const modelGoal = goals?.find((g) => g.type === "model" && g.source?.model);
|
|
@@ -1331,6 +1573,38 @@ async function chartRoutes(fastify, options) {
|
|
|
1331
1573
|
return reply.status(500).send({ error: message, errorType: classifyError(error2) });
|
|
1332
1574
|
}
|
|
1333
1575
|
});
|
|
1576
|
+
fastify.get("/api/charts/:name/export", async (request, reply) => {
|
|
1577
|
+
const { name } = request.params;
|
|
1578
|
+
const params = { ...request.query };
|
|
1579
|
+
const chart = configLoader.getChartByName(name);
|
|
1580
|
+
if (!chart) {
|
|
1581
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
1582
|
+
return reply.status(resp.status).send(resp.body);
|
|
1583
|
+
}
|
|
1584
|
+
const project = configLoader.getProject();
|
|
1585
|
+
const chartExportConfig = chart.chart?.export;
|
|
1586
|
+
if (chartExportConfig?.enabled === false) {
|
|
1587
|
+
return reply.status(403).send({ error: "Export is disabled for this chart", errorType: "validation_error" });
|
|
1588
|
+
}
|
|
1589
|
+
const maxExportRows = chartExportConfig?.max_rows ?? project?.defaults?.table?.max_export_rows ?? 5e5;
|
|
1590
|
+
try {
|
|
1591
|
+
const result = await queryService.executeChartForExport(chart, params, maxExportRows, getUserContext(request));
|
|
1592
|
+
reply.header("Content-Type", "text/csv; charset=utf-8");
|
|
1593
|
+
reply.header("Content-Disposition", `attachment; filename="${name}.csv"`);
|
|
1594
|
+
reply.header("X-Total-Rows", String(result.rowCount));
|
|
1595
|
+
const columns = result.columns.map((c) => c.name);
|
|
1596
|
+
const headerLine = columns.map(escapeCSVField).join(",");
|
|
1597
|
+
const lines = [headerLine];
|
|
1598
|
+
for (const row of result.rows) {
|
|
1599
|
+
const line = columns.map((col) => escapeCSVField(String(row[col] ?? ""))).join(",");
|
|
1600
|
+
lines.push(line);
|
|
1601
|
+
}
|
|
1602
|
+
return reply.send(lines.join("\n"));
|
|
1603
|
+
} catch (error2) {
|
|
1604
|
+
const message = error2 instanceof Error ? error2.message : "Export failed";
|
|
1605
|
+
return reply.status(500).send({ error: message, errorType: classifyError(error2) });
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1334
1608
|
fastify.post("/api/charts/batch", async (request, reply) => {
|
|
1335
1609
|
const { charts, includeConfig } = request.body;
|
|
1336
1610
|
if (!charts || !Array.isArray(charts)) {
|
|
@@ -14317,7 +14591,7 @@ async function chatRoutes(fastify, options) {
|
|
|
14317
14591
|
systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
|
|
14318
14592
|
}
|
|
14319
14593
|
const systemPrompt = systemParts.join("\n");
|
|
14320
|
-
const { AnthropicProvider } = await import("./dist-
|
|
14594
|
+
const { AnthropicProvider } = await import("./dist-7CRX2GIR.js");
|
|
14321
14595
|
const provider = new AnthropicProvider(apiKey);
|
|
14322
14596
|
const response = await provider.chat({
|
|
14323
14597
|
system: systemPrompt,
|
|
@@ -14653,7 +14927,8 @@ async function createServer(options) {
|
|
|
14653
14927
|
connector,
|
|
14654
14928
|
cache,
|
|
14655
14929
|
models,
|
|
14656
|
-
refs
|
|
14930
|
+
refs,
|
|
14931
|
+
dialect: defaultConnection.type
|
|
14657
14932
|
});
|
|
14658
14933
|
const semanticService = new SemanticService({
|
|
14659
14934
|
projectDir,
|
|
@@ -14891,7 +15166,7 @@ async function runDevServer(projectDir, options) {
|
|
|
14891
15166
|
try {
|
|
14892
15167
|
const { readFileSync: readFileSync2 } = await import("fs");
|
|
14893
15168
|
const { parse: parse2 } = await import("yaml");
|
|
14894
|
-
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-
|
|
15169
|
+
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-4GUE24QV.js");
|
|
14895
15170
|
const raw = readFileSync2(resolve2(projectDir, "yamchart.yaml"), "utf-8");
|
|
14896
15171
|
const rawConfig = parse2(raw);
|
|
14897
15172
|
const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
|
|
@@ -14931,21 +15206,33 @@ async function runDevServer(projectDir, options) {
|
|
|
14931
15206
|
}
|
|
14932
15207
|
const spinner2 = spinner("Starting server...");
|
|
14933
15208
|
let server;
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
15209
|
+
let actualPort = options.port;
|
|
15210
|
+
const MAX_PORT_ATTEMPTS = 10;
|
|
15211
|
+
for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
|
|
15212
|
+
try {
|
|
15213
|
+
server = await createServer({
|
|
15214
|
+
projectDir,
|
|
15215
|
+
port: actualPort,
|
|
15216
|
+
watch: true,
|
|
15217
|
+
serveStatic: !options.apiOnly,
|
|
15218
|
+
localAuth,
|
|
15219
|
+
env: options.env
|
|
15220
|
+
});
|
|
15221
|
+
await server.start();
|
|
15222
|
+
spinner2.stop();
|
|
15223
|
+
if (actualPort !== options.port) {
|
|
15224
|
+
info(`Port ${options.port} is in use, starting on port ${actualPort}`);
|
|
15225
|
+
}
|
|
15226
|
+
break;
|
|
15227
|
+
} catch (err) {
|
|
15228
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS - 1) {
|
|
15229
|
+
actualPort++;
|
|
15230
|
+
continue;
|
|
15231
|
+
}
|
|
15232
|
+
spinner2.fail("Failed to start server");
|
|
15233
|
+
error(err instanceof Error ? err.message : "Unknown error");
|
|
15234
|
+
process.exit(1);
|
|
15235
|
+
}
|
|
14949
15236
|
}
|
|
14950
15237
|
const project = server.configLoader.getProject();
|
|
14951
15238
|
const charts = server.configLoader.getCharts();
|
|
@@ -14954,8 +15241,8 @@ async function runDevServer(projectDir, options) {
|
|
|
14954
15241
|
box([
|
|
14955
15242
|
`Yamchart v${options.version}`,
|
|
14956
15243
|
``,
|
|
14957
|
-
`Dashboard: http://localhost:${
|
|
14958
|
-
`API: http://localhost:${
|
|
15244
|
+
`Dashboard: http://localhost:${actualPort}`,
|
|
15245
|
+
`API: http://localhost:${actualPort}/api`,
|
|
14959
15246
|
``,
|
|
14960
15247
|
`Project: ${project.name}`,
|
|
14961
15248
|
`Charts: ${charts.length} loaded`,
|
|
@@ -14966,7 +15253,7 @@ async function runDevServer(projectDir, options) {
|
|
|
14966
15253
|
]);
|
|
14967
15254
|
newline();
|
|
14968
15255
|
if (options.open && !options.apiOnly) {
|
|
14969
|
-
const url = `http://localhost:${
|
|
15256
|
+
const url = `http://localhost:${actualPort}`;
|
|
14970
15257
|
const { exec } = await import("child_process");
|
|
14971
15258
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
14972
15259
|
exec(`${command} ${url}`);
|
|
@@ -14983,4 +15270,4 @@ async function runDevServer(projectDir, options) {
|
|
|
14983
15270
|
export {
|
|
14984
15271
|
runDevServer
|
|
14985
15272
|
};
|
|
14986
|
-
//# sourceMappingURL=dev-
|
|
15273
|
+
//# sourceMappingURL=dev-6QGAB4ZH.js.map
|