@bitcobblers/wod-wiki-wql 0.51.2196 → 0.53.2203
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/index.cjs +2933 -801
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +457 -91
- package/dist/index.d.ts +457 -91
- package/dist/index.mjs +2914 -802
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ var wodWikiCore = require('@bitcobblers/wod-wiki-core');
|
|
|
4
4
|
var lr = require('@lezer/lr');
|
|
5
5
|
var language = require('@codemirror/language');
|
|
6
6
|
var autocomplete = require('@codemirror/autocomplete');
|
|
7
|
+
var state = require('@codemirror/state');
|
|
7
8
|
var highlight = require('@lezer/highlight');
|
|
8
9
|
|
|
9
10
|
var __defProp = Object.defineProperty;
|
|
@@ -59,7 +60,7 @@ var WQL_CALC_TARGETS = [
|
|
|
59
60
|
];
|
|
60
61
|
var WQL_INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
61
62
|
var WQL_GRAINS = ["summary", "event"];
|
|
62
|
-
var WQL_FIND_TARGETS = ["note", "block", "effort", "session", "segment", "event", "notebook", "page", "tag", "equipment"];
|
|
63
|
+
var WQL_FIND_TARGETS = ["note", "block", "effort", "session", "segment", "event", "notebook", "page", "tag", "equipment", "feed"];
|
|
63
64
|
var WQL_SOURCE_VALUES = ["journal", "notebooks", "guides", "playground", "dashboards", "efforts", "equipment"];
|
|
64
65
|
var WQL_SOURCE_HEADS = [
|
|
65
66
|
"note",
|
|
@@ -76,6 +77,8 @@ var WQL_SOURCE_HEADS = [
|
|
|
76
77
|
"page",
|
|
77
78
|
"tag",
|
|
78
79
|
"equipment",
|
|
80
|
+
"feed",
|
|
81
|
+
"feeds",
|
|
79
82
|
"dashboard"
|
|
80
83
|
];
|
|
81
84
|
var WQL_SOURCE_HEAD_SCOPES = {
|
|
@@ -89,8 +92,8 @@ var WQL_TYPE_HEADS = {
|
|
|
89
92
|
};
|
|
90
93
|
var WQL_NOTE_DEFAULT_SOURCES = [];
|
|
91
94
|
var WQL_FUNCTION_HEADS = WQL_AGGREGATORS;
|
|
92
|
-
var WQL_CHART_HEADS = ["timeseries", "bar", "table", "donut", "toplist", "value"];
|
|
93
|
-
var WQL_STANDARD_DATASETS = ["@session", "@today"];
|
|
95
|
+
var WQL_CHART_HEADS = ["timeseries", "bar", "stacked-bar", "table", "list", "donut", "toplist", "value", "goal-rings", "zone-distribution"];
|
|
96
|
+
var WQL_STANDARD_DATASETS = ["@session", "@today", "@note"];
|
|
94
97
|
var WQL_RESULT_PLANES = wodWikiCore.KNOWN_OUTPUT_TYPES;
|
|
95
98
|
var WQL_CONTENT_FILTER_KEYS = ["type", "text", "source", "notebook", "page", "contributes", "feed", "subscribed", ...WQL_TAG_KEYS];
|
|
96
99
|
var WQL_SOURCES = WQL_FIND_TARGETS;
|
|
@@ -147,10 +150,11 @@ var TARGET_TYPES = {
|
|
|
147
150
|
// return flat rows (no `by {}` grouping), so groupby stays unadvertised.
|
|
148
151
|
page: /* @__PURE__ */ new Set(["page", "notebook", "text", "note", "time"]),
|
|
149
152
|
tag: /* @__PURE__ */ new Set(["tag", "type", "text", "contributes", "time"]),
|
|
150
|
-
equipment: /* @__PURE__ */ new Set(["equipment", "tags", "text", "notebook", "time"])
|
|
153
|
+
equipment: /* @__PURE__ */ new Set(["equipment", "tags", "text", "notebook", "time"]),
|
|
154
|
+
feed: /* @__PURE__ */ new Set(["feed", "tag", "text", "subscribed", "groupby"])
|
|
151
155
|
};
|
|
152
156
|
var EMPTY_TYPES = /* @__PURE__ */ new Set();
|
|
153
|
-
var AGGREGATE_KEYS = WQL_TAG_KEYS;
|
|
157
|
+
var AGGREGATE_KEYS = ["metric", ...WQL_TAG_KEYS.filter((key) => key !== "metric")];
|
|
154
158
|
var TARGET_KEYS = Object.fromEntries(
|
|
155
159
|
Object.entries(TARGET_TYPES).map(([target, types]) => [
|
|
156
160
|
target,
|
|
@@ -179,14 +183,14 @@ function supportsWqlFilterKey(target, family, rawKey) {
|
|
|
179
183
|
}
|
|
180
184
|
var parser = lr.LRParser.deserialize({
|
|
181
185
|
version: 14,
|
|
182
|
-
states: "
|
|
183
|
-
stateData: ")
|
|
184
|
-
goto: "
|
|
185
|
-
nodeNames: "\u26A0 Query Pipeline Stage Head Word Aggregator Metric Filters Filter Negate TagKey TagValue Value Star Quoted DatasetReference At Single GroupBy By Dimension Rollup RollupDot Int",
|
|
186
|
-
maxTerm:
|
|
186
|
+
states: "+|OVQPOOOOQO'#Cb'#CbObQPO'#C`OgQPO'#C`OlQPO'#CnOqQQO'#C_OOQO'#C_'#C_O!SQQO'#CpQOQPOOOOQO,58z,58zO!bQPO,58zOOQO,59Y,59YO!jQPO'#CfOOQO,58y,58yOVQPO,58xO!xQPO'#CqO!}QPO'#CtOOQO,59[,59[O#SQPO,59[O#[QPO'#CdO#aQQO'#CcOOQO'#Cc'#CcOOQO1G.f1G.fOOQO'#Ci'#CiO#uQPO'#CgO#zQPO'#CgOOQO'#Cg'#CgOOQO,59Q,59QO$PQPO,59QO$XQPO'#C_O$dQPO1G.dO$lQPO,59]O$tQQO,59`OOQO1G.v1G.vOOQO,59O,59OO$yQPO'#CwO%OQQO,58}O%dQPO,59RO%rQPO,59RO%wQPO'#CzO&SQPO1G.lOOQO1G.l1G.lOVQPO'#C{O&[QPO7+$OOOQO'#Cs'#CsOOQO1G.w1G.wO&dQPO1G.wO&lQPO1G.zOOQO,59c,59cOOQO-E6u-E6uOOQO'#Ck'#CkO&qQPO'#CkO'VQPO'#CkO'eQPO'#CjOOQO1G.m1G.mO%dQPO1G.mOOQO,59f,59fOOQO-E6x-E6xOOQO7+$W7+$WO'pQPO'#CcOOQO,59g,59gOOQO-E6y-E6yO(OQPO'#C|O(TQPO7+$cOOQO7+$c7+$cO(]QPO7+$fO(bQPO'#CxO(gQPO,59VOOQO,59V,59VO%dQPO'#CyO({QPO,59UOOQO7+$X7+$XO)WQPO,58}OOQO,59h,59hOOQO-E6z-E6zOOQO<<G}<<G}OOQO<<HQ<<HQOOQO,59d,59dOOQO-E6v-E6vOOQO1G.q1G.qOOQO,59e,59eOOQO-E6w-E6wO)fQPO,58zO)nQPO'#C`",
|
|
187
|
+
stateData: ")s~OsOS~OTPOcSOtQO~OTXO~OtYO~OTZO~Ov[OfRXiRXqRXwRX~Of_Oi`Ow^OqdX~OTdOXcO~OTgOXcO[iOykO~OvoO~OzpO~Oi`Oqda~OTrO~OusOfVXiVXqVXvVXwVX~OtuO~OTgO~OxwOyyO~Ov[OqRXwRX~OwzOqQi~OT|Oy}O~Oj!PO~OT!QO~OusOfVaiVaqVavVawVa~OT!TOXcO`!SOa!UO~Ot!XO~OTgOXcO[iO~OxwOy![O~OwzOqQq~Ox!`Oy!bO~OT!cO~O`!fOt!dOu!dOw_Xx_Xy_X~O`!fOw_Xx_Xy_X~Ow!gOx^Xy^X~OusOqVXvVXwVX~OT|O~Ox!`Oy!mO~O{!nO~OT!oO~O`!qOt!dOu!dOw_ax_ay_a~Ow!gOx^ay^a~OusOqVavVawVa~OT!]OXcO~Ot!tO~O",
|
|
188
|
+
goto: "$mqPPru!OP!V!^!bP!o!sP!y#Q#WPP#_Pr#dP#g#mPP#s#}$T$Z$a$gRWOQVOQn^R!^zQTOTm^zQROT!u^zTfY!tSeY!tSj[wV!Su!X!gT]TmQl[R!YwSh[wRviQ!WuR!i!XS!Vu!XR!r!gVUO^zRbVQ!OoR!k!`QaVRqbQtdS!Rt!jR!j!]Q!e!TR!p!eQ!h!VR!s!hQxlR!ZxQ{nR!_{Q!a!OR!l!a",
|
|
189
|
+
nodeNames: "\u26A0 Query Pipeline Stage Head Word Aggregator Metric ParamRef Dollar Filters Filter Negate TagKey TagValue Value Star Quoted DatasetReference At Single GroupBy By Dimension Rollup RollupDot Int",
|
|
190
|
+
maxTerm: 43,
|
|
187
191
|
skippedNodes: [0],
|
|
188
192
|
repeatNodeCount: 6,
|
|
189
|
-
tokenData: "'
|
|
193
|
+
tokenData: "'q~RhXY!mYZ!m]^!mpq!mqr#Ors#Ttu#rxy#wyz#|z{$R|}$W}!O$]!O!P$q!Q![%n![!]&U!b!c&Z!c!}$]#R#S$]#T#U$]#U#V&`#V#o$]#o#p'b#p#q'g#q#r'l~!rSs~XY!mYZ!m]^!mpq!m~#TO[~~#WTOr#Trs#gs;'S#T;'S;=`#l<%lO#T~#lOa~~#oP;=`<%l#T~#wOX~~#|Oz~~$RO{~~$WO`~~$]Ox~P$bTTP}!O$]!Q![$]!c!}$]#R#S$]#T#o$]~$vPu~#f#g$y~$|P#c#d%P~%SP#`#a%V~%YP#`#a%]~%`P#i#j%c~%fP#d#e%i~%nOi~R%uTjQTP}!O$]!Q![%n!c!}$]#R#S$]#T#o$]~&ZOt~~&`Oc~R&eVTP}!O$]!Q![$]!c!}$]#R#S$]#T#m$]#m#n&z#n#o$]R'RTfQTP}!O$]!Q![$]!c!}$]#R#S$]#T#o$]~'gOv~~'lOw~~'qOy~",
|
|
190
194
|
tokenizers: [0, 1],
|
|
191
195
|
topRules: { "Query": [0, 1] },
|
|
192
196
|
tokenPrec: 0
|
|
@@ -200,6 +204,7 @@ __export(wql_parser_terms_exports, {
|
|
|
200
204
|
By: () => By,
|
|
201
205
|
DatasetReference: () => DatasetReference,
|
|
202
206
|
Dimension: () => Dimension,
|
|
207
|
+
Dollar: () => Dollar,
|
|
203
208
|
Filter: () => Filter,
|
|
204
209
|
Filters: () => Filters,
|
|
205
210
|
GroupBy: () => GroupBy,
|
|
@@ -207,6 +212,7 @@ __export(wql_parser_terms_exports, {
|
|
|
207
212
|
Int: () => Int,
|
|
208
213
|
Metric: () => Metric,
|
|
209
214
|
Negate: () => Negate,
|
|
215
|
+
ParamRef: () => ParamRef,
|
|
210
216
|
Pipeline: () => Pipeline,
|
|
211
217
|
Query: () => Query,
|
|
212
218
|
Quoted: () => Quoted,
|
|
@@ -227,23 +233,25 @@ var Head = 4;
|
|
|
227
233
|
var Word = 5;
|
|
228
234
|
var Aggregator = 6;
|
|
229
235
|
var Metric = 7;
|
|
230
|
-
var
|
|
231
|
-
var
|
|
232
|
-
var
|
|
233
|
-
var
|
|
234
|
-
var
|
|
235
|
-
var
|
|
236
|
-
var
|
|
237
|
-
var
|
|
238
|
-
var
|
|
239
|
-
var
|
|
240
|
-
var
|
|
241
|
-
var
|
|
242
|
-
var
|
|
243
|
-
var
|
|
244
|
-
var
|
|
245
|
-
var
|
|
246
|
-
var
|
|
236
|
+
var ParamRef = 8;
|
|
237
|
+
var Dollar = 9;
|
|
238
|
+
var Filters = 10;
|
|
239
|
+
var Filter = 11;
|
|
240
|
+
var Negate = 12;
|
|
241
|
+
var TagKey = 13;
|
|
242
|
+
var TagValue = 14;
|
|
243
|
+
var Value = 15;
|
|
244
|
+
var Star = 16;
|
|
245
|
+
var Quoted = 17;
|
|
246
|
+
var DatasetReference = 18;
|
|
247
|
+
var At = 19;
|
|
248
|
+
var Single = 20;
|
|
249
|
+
var GroupBy = 21;
|
|
250
|
+
var By = 22;
|
|
251
|
+
var Dimension = 23;
|
|
252
|
+
var Rollup = 24;
|
|
253
|
+
var RollupDot = 25;
|
|
254
|
+
var Int = 26;
|
|
247
255
|
|
|
248
256
|
// src/wqlSuffix.ts
|
|
249
257
|
function splitAtWhere(text) {
|
|
@@ -265,6 +273,7 @@ var DISPLAY_UNIT_RE = /\s+in\s+([a-zA-Z0-9_-]+)\s*$/;
|
|
|
265
273
|
var ROLLUP_RE = /\.rollup\((\d+)?([a-zA-Z]*)\)\s*$/;
|
|
266
274
|
var BY_RE = /\s+by\s+\{([^}]*)\}\s*$/;
|
|
267
275
|
var LAST_RE = /\s+last\s+(\d+)([dw])\s*$/i;
|
|
276
|
+
var LAST_PARAM_RE = /\s+last\s+\$([a-zA-Z_][\w-]*)(?:\s+([dw]))?\s*$/i;
|
|
268
277
|
var FROM_TO_RE = /\s+from\s+(\d{4}-\d{2}-\d{2})(?:\s+to\s+(\d{4}-\d{2}-\d{2}))?\s*$/i;
|
|
269
278
|
function parseWqlSuffixes(raw) {
|
|
270
279
|
const { primary, where } = splitAtWhere(raw);
|
|
@@ -305,6 +314,7 @@ function parseWqlSuffixes(raw) {
|
|
|
305
314
|
storedAs = "@" + storeMatches[storeMatches.length - 1][1];
|
|
306
315
|
}
|
|
307
316
|
const froms = stripRepeated(FROM_TO_RE);
|
|
317
|
+
const lastParams = stripRepeated(LAST_PARAM_RE);
|
|
308
318
|
const lasts = stripRepeated(LAST_RE);
|
|
309
319
|
{
|
|
310
320
|
const windows = [
|
|
@@ -315,6 +325,13 @@ function parseWqlSuffixes(raw) {
|
|
|
315
325
|
raw: m[0].trim(),
|
|
316
326
|
at: m.index
|
|
317
327
|
})),
|
|
328
|
+
...lastParams.map((m) => ({
|
|
329
|
+
kind: "param",
|
|
330
|
+
param: m[1],
|
|
331
|
+
...m[2] ? { unit: m[2].toLowerCase() } : {},
|
|
332
|
+
raw: m[0].trim(),
|
|
333
|
+
at: m.index
|
|
334
|
+
})),
|
|
318
335
|
...lasts.map((m) => ({
|
|
319
336
|
kind: "relative",
|
|
320
337
|
size: parseInt(m[1], 10),
|
|
@@ -329,8 +346,8 @@ function parseWqlSuffixes(raw) {
|
|
|
329
346
|
);
|
|
330
347
|
}
|
|
331
348
|
if (windows.length === 1) {
|
|
332
|
-
const w = windows[0];
|
|
333
|
-
window = w
|
|
349
|
+
const { at: _at, ...w } = windows[0];
|
|
350
|
+
window = w;
|
|
334
351
|
}
|
|
335
352
|
}
|
|
336
353
|
const units = stripRepeated(DISPLAY_UNIT_RE);
|
|
@@ -395,12 +412,14 @@ function normalizeCommaFilterValues(text) {
|
|
|
395
412
|
const merged = [];
|
|
396
413
|
for (let i = 0; i < parts.length; i++) {
|
|
397
414
|
let part = parts[i];
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
415
|
+
if (!part.trim().startsWith("$")) {
|
|
416
|
+
while (i + 1 < parts.length) {
|
|
417
|
+
const next = parts[i + 1].trim();
|
|
418
|
+
if (next && !next.includes(":") && !next.includes('"') && !next.startsWith("$")) {
|
|
419
|
+
part = `${part}|${next}`;
|
|
420
|
+
i++;
|
|
421
|
+
} else break;
|
|
422
|
+
}
|
|
404
423
|
}
|
|
405
424
|
merged.push(part);
|
|
406
425
|
}
|
|
@@ -658,8 +677,9 @@ function normalizeWql(raw) {
|
|
|
658
677
|
}
|
|
659
678
|
function validateSourceFilter(filters) {
|
|
660
679
|
for (const f of filters) {
|
|
661
|
-
if (f.key !== "source") continue;
|
|
680
|
+
if (f.kind === "spreadParam" || f.key !== "source") continue;
|
|
662
681
|
for (const v of f.values) {
|
|
682
|
+
if (v.param !== void 0) continue;
|
|
663
683
|
const val = v.value;
|
|
664
684
|
if (val === "page" || val === "pages") {
|
|
665
685
|
return `source:${val} is retired \u2014 page-ness is handled by type:page or source:guides.`;
|
|
@@ -686,8 +706,9 @@ function validateSourceFilter(filters) {
|
|
|
686
706
|
}
|
|
687
707
|
function validateSubscribedFilter(filters) {
|
|
688
708
|
for (const f of filters) {
|
|
689
|
-
if (f.key !== "subscribed") continue;
|
|
709
|
+
if (f.kind === "spreadParam" || f.key !== "subscribed") continue;
|
|
690
710
|
for (const v of f.values) {
|
|
711
|
+
if (v.param !== void 0) continue;
|
|
691
712
|
if (v.value !== "true" && v.value !== "false") {
|
|
692
713
|
return `Invalid subscribed: value '${v.value}' \u2014 expected true or false`;
|
|
693
714
|
}
|
|
@@ -697,8 +718,9 @@ function validateSubscribedFilter(filters) {
|
|
|
697
718
|
}
|
|
698
719
|
function validateFeedFilter(filters) {
|
|
699
720
|
for (const f of filters) {
|
|
700
|
-
if (f.key !== "feed") continue;
|
|
721
|
+
if (f.kind === "spreadParam" || f.key !== "feed") continue;
|
|
701
722
|
for (const v of f.values) {
|
|
723
|
+
if (v.param !== void 0) continue;
|
|
702
724
|
if (!v.value && !v.wildcard) {
|
|
703
725
|
return `Invalid feed: value \u2014 expected a non-empty feed identifier or wildcard`;
|
|
704
726
|
}
|
|
@@ -749,6 +771,9 @@ function toQueryWindow(w) {
|
|
|
749
771
|
if (w.kind === "relative") {
|
|
750
772
|
return { window: { kind: "relative", size: w.size, unit: w.unit } };
|
|
751
773
|
}
|
|
774
|
+
if (w.kind === "param") {
|
|
775
|
+
return { window: { kind: "param", param: w.param, ...w.unit ? { unit: w.unit } : {} } };
|
|
776
|
+
}
|
|
752
777
|
for (const d of [w.start, w.end]) {
|
|
753
778
|
if (d !== void 0 && !isCivilDate(d)) {
|
|
754
779
|
return { error: `Invalid window date "${d}" \u2014 expected a real calendar date YYYY-MM-DD` };
|
|
@@ -767,11 +792,33 @@ function extractFilters(query, text) {
|
|
|
767
792
|
const filters = query.getChild(Filters);
|
|
768
793
|
if (!filters) return out;
|
|
769
794
|
for (const filter of filters.getChildren(Filter)) {
|
|
795
|
+
const spreadParamNode = filter.getChild(ParamRef);
|
|
796
|
+
if (spreadParamNode && !filter.getChild(TagKey)) {
|
|
797
|
+
const wordNode = spreadParamNode.getChild(Word);
|
|
798
|
+
const param = wordNode ? text.slice(wordNode.from, wordNode.to) : text.slice(spreadParamNode.from + 1, spreadParamNode.to);
|
|
799
|
+
if (param) {
|
|
800
|
+
out.push({
|
|
801
|
+
kind: "spreadParam",
|
|
802
|
+
param,
|
|
803
|
+
key: `$${param}`,
|
|
804
|
+
negate: false,
|
|
805
|
+
values: []
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
770
810
|
const keyNode = filter.getChild(TagKey);
|
|
771
811
|
const valueNode = filter.getChild(TagValue);
|
|
772
812
|
if (!keyNode || !valueNode) continue;
|
|
773
813
|
const values = [];
|
|
774
814
|
for (const valueChild of valueNode.getChildren(Value)) {
|
|
815
|
+
const paramNode = valueChild.getChild(ParamRef);
|
|
816
|
+
if (paramNode) {
|
|
817
|
+
const wordNode = paramNode.getChild(Word);
|
|
818
|
+
const param = wordNode ? text.slice(wordNode.from, wordNode.to) : text.slice(paramNode.from + 1, paramNode.to);
|
|
819
|
+
if (param) values.push({ value: `$${param}`, wildcard: false, param });
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
775
822
|
const raw = text.slice(valueChild.from, valueChild.to);
|
|
776
823
|
const quoted = valueChild.getChild(Quoted) !== null;
|
|
777
824
|
let value = quoted ? raw.slice(1, -1) : raw;
|
|
@@ -869,6 +916,7 @@ function parseAggregateQuery(raw, opts) {
|
|
|
869
916
|
return base;
|
|
870
917
|
} else {
|
|
871
918
|
base.metric = mf.values[0].value;
|
|
919
|
+
if (mf.values[0].param) base.metricParam = mf.values[0].param;
|
|
872
920
|
}
|
|
873
921
|
} else {
|
|
874
922
|
if (!stage || !head || !aggNode || !metricNode) {
|
|
@@ -882,8 +930,13 @@ function parseAggregateQuery(raw, opts) {
|
|
|
882
930
|
}
|
|
883
931
|
base.agg = aggText;
|
|
884
932
|
base.metric = text.slice(metricNode.from, metricNode.to);
|
|
933
|
+
const metricParamNode = metricNode.getChild(ParamRef);
|
|
934
|
+
if (metricParamNode) {
|
|
935
|
+
const w = metricParamNode.getChild(Word);
|
|
936
|
+
base.metricParam = w ? text.slice(w.from, w.to) : base.metric.slice(1);
|
|
937
|
+
}
|
|
885
938
|
}
|
|
886
|
-
const contentKeys = base.filters.map((f) => f.key).filter((k) => WQL_CONTENT_ONLY_KEYS.includes(k));
|
|
939
|
+
const contentKeys = base.filters.filter((f) => f.kind !== "spreadParam" && !f.key.startsWith("$")).map((f) => f.key).filter((k) => WQL_CONTENT_ONLY_KEYS.includes(k));
|
|
887
940
|
if (contentKeys.length > 0) {
|
|
888
941
|
base.error = `Content filter key(s) ${contentKeys.map((k) => `"${k}"`).join(", ")} cannot narrow an aggregate \u2014 facts carry effort/dimension data, not note text. Aggregate dims: ${WQL_TAG_KEYS.join(", ")}, plus any custom dimension.`;
|
|
889
942
|
return base;
|
|
@@ -984,7 +1037,7 @@ function parseFindQuery(raw, opts) {
|
|
|
984
1037
|
if (scope || typeScope) {
|
|
985
1038
|
result.target = "note";
|
|
986
1039
|
} else {
|
|
987
|
-
result.target = WQL_NOTE_HEAD_ALIASES.includes(headName) ? "note" : headName;
|
|
1040
|
+
result.target = WQL_NOTE_HEAD_ALIASES.includes(headName) ? "note" : headName === "feeds" ? "feed" : headName;
|
|
988
1041
|
if (!WQL_FIND_TARGETS.includes(result.target)) {
|
|
989
1042
|
result.error = `Unknown find target "${result.target}". Try: ${WQL_FIND_TARGETS.join(", ")}`;
|
|
990
1043
|
return result;
|
|
@@ -1163,7 +1216,7 @@ function findTargetAdvisories(query) {
|
|
|
1163
1216
|
const advisories = [];
|
|
1164
1217
|
const target = query.target;
|
|
1165
1218
|
const supported = allowedFilterTypesForTarget(target, "find");
|
|
1166
|
-
const ignored = [...new Set(query.filters.filter((f) => !supportsWqlFilterKey(target, "find", f.key)).map((f) => f.key))];
|
|
1219
|
+
const ignored = [...new Set(query.filters.filter((f) => f.kind !== "spreadParam" && !f.key.startsWith("$") && !supportsWqlFilterKey(target, "find", f.key)).map((f) => f.key))];
|
|
1167
1220
|
if (ignored.length) {
|
|
1168
1221
|
const custom = target === "segment" || target === "event" ? ", custom fact dimensions" : "";
|
|
1169
1222
|
const effortKey = ignored.find((key) => WQL_EFFORT_FILTER_KEYS.includes(key));
|
|
@@ -1236,7 +1289,14 @@ function serializeFindStage(f) {
|
|
|
1236
1289
|
return parts.filter(Boolean).join(" ");
|
|
1237
1290
|
}
|
|
1238
1291
|
function serializeFunctionStage(a) {
|
|
1239
|
-
const filters = a.metric ? [
|
|
1292
|
+
const filters = a.metric ? [
|
|
1293
|
+
{
|
|
1294
|
+
key: "metric",
|
|
1295
|
+
negate: false,
|
|
1296
|
+
values: [{ value: a.metric, wildcard: false, ...a.metricParam ? { param: a.metricParam } : {} }]
|
|
1297
|
+
},
|
|
1298
|
+
...a.filters
|
|
1299
|
+
] : a.filters;
|
|
1240
1300
|
let text = `:${a.agg}{${serializeFilters(filters)}}`;
|
|
1241
1301
|
if (a.groupBy.length) text += ` by {${a.groupBy.join(", ")}}`;
|
|
1242
1302
|
if (a.rollup) text += `.rollup(${a.rollup.size}${a.rollup.unit})`;
|
|
@@ -1261,16 +1321,22 @@ function serializeValue(value) {
|
|
|
1261
1321
|
return `"${value}"`;
|
|
1262
1322
|
}
|
|
1263
1323
|
function serializeFilters(filters) {
|
|
1264
|
-
return filters.map((f) =>
|
|
1324
|
+
return filters.map((f) => {
|
|
1325
|
+
if (f.kind === "spreadParam" || f.key.startsWith("$") && f.values.length === 0) {
|
|
1326
|
+
const p = f.param ?? f.key.replace(/^\$/, "");
|
|
1327
|
+
return `$${p}`;
|
|
1328
|
+
}
|
|
1329
|
+
return `${f.negate ? "!" : ""}${f.key}:${f.values.map((v) => v.param ? `$${v.param}` : serializeValue(v.value) + (v.wildcard ? "*" : "")).join("|")}`;
|
|
1330
|
+
}).join(",");
|
|
1265
1331
|
}
|
|
1266
1332
|
function serializeAggHead(agg, metric, filters) {
|
|
1267
1333
|
return `${agg}:${metric}{${serializeFilters(filters)}}`;
|
|
1268
1334
|
}
|
|
1269
1335
|
function serializeAggregateHead(a) {
|
|
1270
|
-
return serializeAggHead(a.agg, a.metric, a.filters);
|
|
1336
|
+
return serializeAggHead(a.agg, a.metricParam ? `$${a.metricParam}` : a.metric, a.filters);
|
|
1271
1337
|
}
|
|
1272
1338
|
function serializeFindHalf(target, filters, last) {
|
|
1273
|
-
const head =
|
|
1339
|
+
const head = `:${target}${filters.length ? `{${serializeFilters(filters)}}` : ""}`;
|
|
1274
1340
|
return last ? `${head} last ${last.size}${last.unit}` : head;
|
|
1275
1341
|
}
|
|
1276
1342
|
function serializeMetricHalf(j) {
|
|
@@ -1279,6 +1345,7 @@ function serializeMetricHalf(j) {
|
|
|
1279
1345
|
function serializeWindow(w) {
|
|
1280
1346
|
if (!w) return "";
|
|
1281
1347
|
if (w.kind === "relative") return `last ${w.size}${w.unit}`;
|
|
1348
|
+
if (w.kind === "param") return `last $${w.param}${w.unit ? ` ${w.unit}` : ""}`;
|
|
1282
1349
|
return w.end ? `from ${w.start} to ${w.end}` : `from ${w.start}`;
|
|
1283
1350
|
}
|
|
1284
1351
|
function serialize(parsed) {
|
|
@@ -1329,220 +1396,1615 @@ function serialize(parsed) {
|
|
|
1329
1396
|
return unhandled.raw ?? "";
|
|
1330
1397
|
}
|
|
1331
1398
|
|
|
1332
|
-
// src/
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
];
|
|
1345
|
-
function isEffortDiscipline(value) {
|
|
1346
|
-
return typeof value === "string" && EFFORT_DISCIPLINES.includes(value.toLowerCase());
|
|
1399
|
+
// src/paramRef.ts
|
|
1400
|
+
function collectFromFilters(filters, out) {
|
|
1401
|
+
for (const f of filters) {
|
|
1402
|
+
if (f.kind === "spreadParam" || f.key.startsWith("$") && f.values.length === 0) {
|
|
1403
|
+
const name = f.param ?? f.key.replace(/^\$/, "");
|
|
1404
|
+
if (name) out.add(name);
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
for (const v of f.values) {
|
|
1408
|
+
if (v.param) out.add(v.param);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1347
1411
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
kettlebell: 1.2,
|
|
1351
|
-
gymnastics: 1.2,
|
|
1352
|
-
recovery: 0.9,
|
|
1353
|
-
bodyweight: 1,
|
|
1354
|
-
cycling: 1,
|
|
1355
|
-
rowing: 1,
|
|
1356
|
-
running: 1,
|
|
1357
|
-
swimming: 1,
|
|
1358
|
-
walking: 1
|
|
1359
|
-
};
|
|
1360
|
-
function disciplineFactorFor(discipline) {
|
|
1361
|
-
if (!discipline) return 1;
|
|
1362
|
-
const key = discipline.toLowerCase();
|
|
1363
|
-
return isEffortDiscipline(key) ? DISCIPLINE_FACTORS[key] : 1;
|
|
1412
|
+
function collectFromWindow(w, out) {
|
|
1413
|
+
if (w?.kind === "param") out.add(w.param);
|
|
1364
1414
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
// NOT styled directly, or highlightTree merges the word into the
|
|
1373
|
-
// parent span.
|
|
1374
|
-
"Aggregator/Word": highlight.tags.keyword,
|
|
1375
|
-
"Metric/Word": highlight.tags.variableName,
|
|
1376
|
-
// Direct Word child of Head = the colon head name (`:sum`, `:journal`).
|
|
1377
|
-
"Head/Word": highlight.tags.keyword,
|
|
1378
|
-
DatasetReference: highlight.tags.keyword,
|
|
1379
|
-
"DatasetReference/Word": highlight.tags.keyword,
|
|
1380
|
-
At: highlight.tags.punctuation,
|
|
1381
|
-
TagKey: highlight.tags.propertyName,
|
|
1382
|
-
"TagKey/Word": highlight.tags.propertyName,
|
|
1383
|
-
TagValue: highlight.tags.string,
|
|
1384
|
-
"TagValue/Value/Word": highlight.tags.string,
|
|
1385
|
-
"TagValue/Word": highlight.tags.string,
|
|
1386
|
-
Negate: highlight.tags.operator,
|
|
1387
|
-
Star: highlight.tags.operator,
|
|
1388
|
-
Dimension: highlight.tags.attributeName,
|
|
1389
|
-
"Dimension/Word": highlight.tags.attributeName,
|
|
1390
|
-
By: highlight.tags.keyword,
|
|
1391
|
-
RollupDot: highlight.tags.keyword,
|
|
1392
|
-
Int: highlight.tags.number,
|
|
1393
|
-
"Rollup/Word": highlight.tags.unit,
|
|
1394
|
-
braceOpen: highlight.tags.bracket,
|
|
1395
|
-
braceClose: highlight.tags.bracket,
|
|
1396
|
-
parenOpen: highlight.tags.bracket,
|
|
1397
|
-
parenClose: highlight.tags.bracket,
|
|
1398
|
-
colon: highlight.tags.punctuation,
|
|
1399
|
-
comma: highlight.tags.punctuation,
|
|
1400
|
-
pipe: highlight.tags.punctuation,
|
|
1401
|
-
dot: highlight.tags.punctuation
|
|
1402
|
-
})
|
|
1403
|
-
]
|
|
1404
|
-
}),
|
|
1405
|
-
languageData: {
|
|
1406
|
-
closeBrackets: { brackets: ["{", "("] }
|
|
1415
|
+
function collectFromSingle(q, out) {
|
|
1416
|
+
if (q.family === "aggregate") {
|
|
1417
|
+
if (q.metricParam) out.add(q.metricParam);
|
|
1418
|
+
collectFromFilters(q.filters, out);
|
|
1419
|
+
collectFromWindow(q.window, out);
|
|
1420
|
+
if (q.join) collectFromFilters(q.join.filters, out);
|
|
1421
|
+
return;
|
|
1407
1422
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1423
|
+
collectFromFilters(q.filters, out);
|
|
1424
|
+
collectFromWindow(q.window, out);
|
|
1425
|
+
if (q.join) collectFromFilters(q.join.filters, out);
|
|
1411
1426
|
}
|
|
1412
|
-
function
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1427
|
+
function collectQueryParams(parsed) {
|
|
1428
|
+
const out = /* @__PURE__ */ new Set();
|
|
1429
|
+
if (parsed.family === "pipeline") {
|
|
1430
|
+
if (parsed.source.kind === "query") collectFromSingle(parsed.source.query, out);
|
|
1431
|
+
for (const t2 of parsed.transforms) collectFromSingle(t2, out);
|
|
1432
|
+
if (parsed.sink) collectFromFilters(parsed.sink.filters, out);
|
|
1433
|
+
return [...out];
|
|
1434
|
+
}
|
|
1435
|
+
collectFromSingle(parsed, out);
|
|
1436
|
+
return [...out];
|
|
1437
|
+
}
|
|
1438
|
+
function parseFilterFragment(fragment) {
|
|
1439
|
+
const trimmed = fragment.trim();
|
|
1440
|
+
if (!trimmed) return { filters: [] };
|
|
1441
|
+
if (trimmed.startsWith(":") || trimmed.startsWith("find:")) {
|
|
1442
|
+
const asQuery = parseQuery(trimmed);
|
|
1443
|
+
if (asQuery.error) return { filters: [], error: asQuery.error };
|
|
1444
|
+
if (asQuery.family === "find" || asQuery.family === "aggregate") {
|
|
1445
|
+
return { filters: asQuery.filters };
|
|
1446
|
+
}
|
|
1447
|
+
if (asQuery.family === "pipeline" && asQuery.source.kind === "query") {
|
|
1448
|
+
return { filters: asQuery.source.query.filters };
|
|
1449
|
+
}
|
|
1450
|
+
return { filters: [] };
|
|
1451
|
+
}
|
|
1452
|
+
const inner = trimmed.startsWith("{") && trimmed.endsWith("}") ? trimmed.slice(1, -1).trim() : trimmed;
|
|
1453
|
+
if (!inner) return { filters: [] };
|
|
1454
|
+
const probe = parseQuery(`:segment{${inner}}`);
|
|
1455
|
+
if (!probe.error && probe.family === "find") {
|
|
1456
|
+
return { filters: probe.filters };
|
|
1457
|
+
}
|
|
1458
|
+
const noteProbe = parseQuery(`:note{${inner}}`);
|
|
1459
|
+
if (!noteProbe.error && noteProbe.family === "find") {
|
|
1460
|
+
return { filters: noteProbe.filters };
|
|
1461
|
+
}
|
|
1462
|
+
return { filters: [], error: noteProbe.error ?? probe.error ?? `Cannot parse filter parameter "${fragment}"` };
|
|
1463
|
+
}
|
|
1464
|
+
function bindWindow(w, params) {
|
|
1465
|
+
if (!w || w.kind !== "param") return { window: w };
|
|
1466
|
+
const raw = (params[w.param] ?? "").trim();
|
|
1467
|
+
if (!raw || raw.toLowerCase() === "all") return { window: void 0 };
|
|
1468
|
+
const withUnit = w.unit && /^\d+$/.test(raw) ? `${raw}${w.unit}` : raw;
|
|
1469
|
+
const rel = /^(?:last\s+)?(\d+)\s*([dw])$/i.exec(withUnit);
|
|
1470
|
+
if (rel) {
|
|
1471
|
+
return {
|
|
1472
|
+
window: {
|
|
1473
|
+
kind: "relative",
|
|
1474
|
+
size: Number(rel[1]),
|
|
1475
|
+
unit: rel[2].toLowerCase()
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
const range = /^(?:from\s+)?(\d{4}-\d{2}-\d{2})(?:\s*(?:to|\.\.)\s*(\d{4}-\d{2}-\d{2}))?$/i.exec(raw);
|
|
1480
|
+
if (range) {
|
|
1481
|
+
return {
|
|
1482
|
+
window: {
|
|
1483
|
+
kind: "range",
|
|
1484
|
+
start: range[1],
|
|
1485
|
+
...range[2] ? { end: range[2] } : {}
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
return { error: `Invalid window value "${raw}" for $${w.param} \u2014 expected <n>d|<n>w or YYYY-MM-DD[..YYYY-MM-DD]` };
|
|
1490
|
+
}
|
|
1491
|
+
function bindFilters(filters, params, opts) {
|
|
1492
|
+
const bound = [];
|
|
1493
|
+
const contentJoinFilters = [];
|
|
1494
|
+
for (const f of filters) {
|
|
1495
|
+
if (f.kind === "spreadParam" || f.key.startsWith("$") && f.values.length === 0) {
|
|
1496
|
+
const name = f.param ?? f.key.replace(/^\$/, "");
|
|
1497
|
+
const rawVal = (params[name] ?? "").trim();
|
|
1498
|
+
if (!rawVal) continue;
|
|
1499
|
+
const frag = parseFilterFragment(rawVal);
|
|
1500
|
+
if (frag.error) return { filters: [], contentJoinFilters: [], error: frag.error };
|
|
1501
|
+
for (const expanded of frag.filters) {
|
|
1502
|
+
if (opts.isAggregate && WQL_CONTENT_ONLY_KEYS.includes(expanded.key)) {
|
|
1503
|
+
contentJoinFilters.push(expanded);
|
|
1504
|
+
} else {
|
|
1505
|
+
bound.push(expanded);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1418
1508
|
continue;
|
|
1419
1509
|
}
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
1510
|
+
if (!f.values.some((v) => v.param !== void 0)) {
|
|
1511
|
+
bound.push(f);
|
|
1422
1512
|
continue;
|
|
1423
1513
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1514
|
+
const nextValues = [];
|
|
1515
|
+
for (const v of f.values) {
|
|
1516
|
+
if (!v.param) {
|
|
1517
|
+
nextValues.push(v);
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
const rawVal = (params[v.param] ?? "").trim();
|
|
1521
|
+
if (!rawVal) continue;
|
|
1522
|
+
const formatted = /[\s,{}"]/.test(rawVal) && !rawVal.startsWith('"') ? `"${rawVal.replace(/"/g, "")}"` : rawVal;
|
|
1523
|
+
const probe = parseQuery(`:segment{${f.key}:${formatted}}`);
|
|
1524
|
+
if (!probe.error && probe.family === "find" && probe.filters[0]) {
|
|
1525
|
+
nextValues.push(...probe.filters[0].values);
|
|
1526
|
+
} else {
|
|
1527
|
+
const noteProbe = parseQuery(`:note{${f.key}:${formatted}}`);
|
|
1528
|
+
if (!noteProbe.error && noteProbe.family === "find" && noteProbe.filters[0]) {
|
|
1529
|
+
nextValues.push(...noteProbe.filters[0].values);
|
|
1530
|
+
} else {
|
|
1531
|
+
return {
|
|
1532
|
+
filters: [],
|
|
1533
|
+
contentJoinFilters: [],
|
|
1534
|
+
error: noteProbe.error ?? probe.error ?? `Invalid value "${rawVal}" for $${v.param}`
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (nextValues.length > 0) {
|
|
1540
|
+
bound.push({ key: f.key, negate: f.negate, values: nextValues });
|
|
1435
1541
|
}
|
|
1436
1542
|
}
|
|
1437
|
-
return {
|
|
1543
|
+
return { filters: bound, contentJoinFilters };
|
|
1438
1544
|
}
|
|
1439
|
-
function
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
if (ch === '"') {
|
|
1444
|
-
quoted = !quoted;
|
|
1445
|
-
out += " ";
|
|
1446
|
-
} else out += quoted ? " " : ch;
|
|
1545
|
+
function bindAggregate(q, params, opts = {}) {
|
|
1546
|
+
const metric = q.metricParam ? (params[q.metricParam] ?? "").trim() : q.metric;
|
|
1547
|
+
if (q.metricParam && !metric && q.agg !== "count") {
|
|
1548
|
+
return { ...q, error: `Parameter $${q.metricParam} resolved to an empty metric` };
|
|
1447
1549
|
}
|
|
1448
|
-
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1550
|
+
const win = bindWindow(q.window, params);
|
|
1551
|
+
if (win.error) return { ...q, error: win.error };
|
|
1552
|
+
const fRes = bindFilters(q.filters, params, { isAggregate: true });
|
|
1553
|
+
if (fRes.error) return { ...q, error: fRes.error };
|
|
1554
|
+
let join = q.join;
|
|
1555
|
+
if (join) {
|
|
1556
|
+
const jRes = bindFilters(join.filters, params, { isAggregate: false });
|
|
1557
|
+
if (jRes.error) return { ...q, error: jRes.error };
|
|
1558
|
+
join = { ...join, filters: jRes.filters };
|
|
1456
1559
|
}
|
|
1457
|
-
|
|
1458
|
-
}
|
|
1459
|
-
function usedEntries(doc, groupStart, fragStart) {
|
|
1460
|
-
const text = maskQuotes(doc.slice(groupStart + 1, fragStart));
|
|
1461
|
-
const used = /* @__PURE__ */ new Set();
|
|
1462
|
-
for (const entry of text.split(",")) {
|
|
1463
|
-
const m = /!?\s*([A-Za-z][\w-]*)/.exec(entry);
|
|
1464
|
-
if (m) used.add(m[1]);
|
|
1560
|
+
if (fRes.contentJoinFilters.length > 0 && !opts.inPipelineTransform) {
|
|
1561
|
+
join = join ? { ...join, filters: [...join.filters, ...fRes.contentJoinFilters] } : { target: "note", filters: fRes.contentJoinFilters };
|
|
1465
1562
|
}
|
|
1466
|
-
return used;
|
|
1467
|
-
}
|
|
1468
|
-
function valueOf(c) {
|
|
1469
|
-
return typeof c.apply === "string" ? c.apply : c.label;
|
|
1470
|
-
}
|
|
1471
|
-
function quoteApply(c) {
|
|
1472
|
-
const value = valueOf(c);
|
|
1473
|
-
if (!/\s/.test(value)) return c;
|
|
1474
1563
|
return {
|
|
1475
|
-
...
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1564
|
+
...q,
|
|
1565
|
+
metric,
|
|
1566
|
+
metricParam: void 0,
|
|
1567
|
+
filters: fRes.filters,
|
|
1568
|
+
window: win.window,
|
|
1569
|
+
...join ? { join } : { join: void 0 }
|
|
1480
1570
|
};
|
|
1481
1571
|
}
|
|
1482
|
-
function
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const k = valueOf(c).toLowerCase();
|
|
1493
|
-
if (!k || seen.has(k)) continue;
|
|
1494
|
-
seen.add(k);
|
|
1495
|
-
out.push(quoteApply(c));
|
|
1496
|
-
}
|
|
1497
|
-
return out;
|
|
1498
|
-
}
|
|
1499
|
-
function narrow(options2, typed) {
|
|
1500
|
-
const q = typed.toLowerCase();
|
|
1501
|
-
if (!q) return [...options2];
|
|
1502
|
-
const prefix = [];
|
|
1503
|
-
const rest = [];
|
|
1504
|
-
for (const o of options2) {
|
|
1505
|
-
const label = o.label.toLowerCase();
|
|
1506
|
-
if (label.startsWith(q)) prefix.push(o);
|
|
1507
|
-
else if (label.includes(q)) rest.push(o);
|
|
1572
|
+
function bindFind(q, params) {
|
|
1573
|
+
const win = bindWindow(q.window, params);
|
|
1574
|
+
if (win.error) return { ...q, error: win.error };
|
|
1575
|
+
const fRes = bindFilters(q.filters, params, { isAggregate: false });
|
|
1576
|
+
if (fRes.error) return { ...q, error: fRes.error };
|
|
1577
|
+
let join = q.join;
|
|
1578
|
+
if (join) {
|
|
1579
|
+
const jRes = bindFilters(join.filters, params, { isAggregate: true });
|
|
1580
|
+
if (jRes.error) return { ...q, error: jRes.error };
|
|
1581
|
+
join = { ...join, filters: jRes.filters };
|
|
1508
1582
|
}
|
|
1509
|
-
return [...prefix, ...rest];
|
|
1510
|
-
}
|
|
1511
|
-
function finish(_context, from, options2, typed) {
|
|
1512
|
-
const ranked = narrow(options2, typed);
|
|
1513
|
-
if (!ranked.length) return null;
|
|
1514
1583
|
return {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1584
|
+
...q,
|
|
1585
|
+
filters: fRes.filters,
|
|
1586
|
+
window: win.window,
|
|
1587
|
+
...join ? { join } : { join: void 0 }
|
|
1518
1588
|
};
|
|
1519
1589
|
}
|
|
1520
|
-
function
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1590
|
+
function bindQueryParams(parsed, params) {
|
|
1591
|
+
if (parsed.error) return { parsed, missing: [] };
|
|
1592
|
+
const referenced = collectQueryParams(parsed);
|
|
1593
|
+
if (referenced.length === 0) return { parsed, missing: [] };
|
|
1594
|
+
const missing = referenced.filter((name) => params[name] === void 0);
|
|
1595
|
+
if (missing.length > 0) return { parsed, missing };
|
|
1596
|
+
let bound;
|
|
1597
|
+
if (parsed.family === "aggregate") {
|
|
1598
|
+
bound = bindAggregate(parsed, params);
|
|
1599
|
+
} else if (parsed.family === "find") {
|
|
1600
|
+
bound = bindFind(parsed, params);
|
|
1601
|
+
} else {
|
|
1602
|
+
let source = parsed.source;
|
|
1603
|
+
let contentJoinForSource = [];
|
|
1604
|
+
if (source.kind === "query") {
|
|
1605
|
+
const sq = source.query.family === "aggregate" ? bindAggregate(source.query, params) : bindFind(source.query, params);
|
|
1606
|
+
if (sq.error) return { parsed: { ...parsed, error: sq.error }, missing: [] };
|
|
1607
|
+
source = { kind: "query", query: sq };
|
|
1608
|
+
}
|
|
1609
|
+
const transforms = [];
|
|
1610
|
+
for (const t2 of parsed.transforms) {
|
|
1611
|
+
const rawFilterBind = bindFilters(t2.filters, params, { isAggregate: true });
|
|
1612
|
+
if (rawFilterBind.contentJoinFilters.length > 0) {
|
|
1613
|
+
contentJoinForSource = [...contentJoinForSource, ...rawFilterBind.contentJoinFilters];
|
|
1614
|
+
}
|
|
1615
|
+
const bt = bindAggregate(t2, params, { inPipelineTransform: true });
|
|
1616
|
+
if (bt.error) return { parsed: { ...parsed, error: bt.error }, missing: [] };
|
|
1617
|
+
transforms.push(bt);
|
|
1618
|
+
}
|
|
1619
|
+
if (contentJoinForSource.length > 0 && source.kind === "query" && source.query.family === "find") {
|
|
1620
|
+
if (source.query.target === "note" || source.query.target === "block") {
|
|
1621
|
+
source = {
|
|
1622
|
+
kind: "query",
|
|
1623
|
+
query: { ...source.query, filters: [...source.query.filters, ...contentJoinForSource] }
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1534
1626
|
}
|
|
1535
|
-
|
|
1627
|
+
let sink = parsed.sink;
|
|
1628
|
+
if (sink) {
|
|
1629
|
+
const sRes = bindFilters(sink.filters, params, { isAggregate: false });
|
|
1630
|
+
if (sRes.error) return { parsed: { ...parsed, error: sRes.error }, missing: [] };
|
|
1631
|
+
sink = { ...sink, filters: sRes.filters };
|
|
1632
|
+
}
|
|
1633
|
+
bound = {
|
|
1634
|
+
...parsed,
|
|
1635
|
+
source,
|
|
1636
|
+
transforms,
|
|
1637
|
+
...sink ? { sink } : {}
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
if (bound.error) return { parsed: bound, missing: [] };
|
|
1641
|
+
const reparsed = parseQuery(serialize(bound));
|
|
1642
|
+
return { parsed: reparsed, missing: [] };
|
|
1536
1643
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
]
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1644
|
+
|
|
1645
|
+
// src/document.ts
|
|
1646
|
+
var FORMULA_CHARS = /^[\w$\s.+\-*/(),]+$/;
|
|
1647
|
+
function referencesOf(expression, known) {
|
|
1648
|
+
const refs = [];
|
|
1649
|
+
for (const match of expression.matchAll(/[A-Za-z_][\w-]*/g)) {
|
|
1650
|
+
const name = match[0];
|
|
1651
|
+
if (known.has(name) && !refs.includes(name)) refs.push(name);
|
|
1652
|
+
}
|
|
1653
|
+
return refs;
|
|
1654
|
+
}
|
|
1655
|
+
function parseDocument(text) {
|
|
1656
|
+
const diagnostics = [];
|
|
1657
|
+
const lines = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("//"));
|
|
1658
|
+
const defaults = {};
|
|
1659
|
+
let hasDefaults = false;
|
|
1660
|
+
const assignments = [];
|
|
1661
|
+
let show;
|
|
1662
|
+
let showChart;
|
|
1663
|
+
let degenerate = false;
|
|
1664
|
+
const known = /* @__PURE__ */ new Set();
|
|
1665
|
+
for (const line of lines) {
|
|
1666
|
+
if (line.startsWith("defaults ")) {
|
|
1667
|
+
hasDefaults = true;
|
|
1668
|
+
const rest = line.slice("defaults ".length).trim();
|
|
1669
|
+
const byMatch = /by\s*\{([^}]*)\}/.exec(rest);
|
|
1670
|
+
if (byMatch) {
|
|
1671
|
+
defaults.groupBy = byMatch[1].split(",").map((d) => d.trim()).filter(Boolean);
|
|
1672
|
+
}
|
|
1673
|
+
const lastParamMatch = /last\s+\$([a-zA-Z_][\w-]*)(?:\s*(d|w))?/.exec(rest);
|
|
1674
|
+
const lastMatch = !lastParamMatch ? /last\s+(\d+)\s*(d|w)/.exec(rest) : null;
|
|
1675
|
+
if (lastParamMatch) {
|
|
1676
|
+
defaults.window = {
|
|
1677
|
+
kind: "param",
|
|
1678
|
+
param: lastParamMatch[1],
|
|
1679
|
+
...lastParamMatch[2] ? { unit: lastParamMatch[2] } : {}
|
|
1680
|
+
};
|
|
1681
|
+
} else if (lastMatch) {
|
|
1682
|
+
defaults.window = { kind: "relative", size: Number(lastMatch[1]), unit: lastMatch[2] };
|
|
1683
|
+
}
|
|
1684
|
+
const fromMatch = /from\s+(\d{4}-\d{2}-\d{2})(?:\s+to\s+(\d{4}-\d{2}-\d{2}))?/.exec(rest);
|
|
1685
|
+
if (fromMatch) {
|
|
1686
|
+
defaults.window = { kind: "range", start: fromMatch[1], end: fromMatch[2] };
|
|
1687
|
+
}
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
if (line.startsWith("show ")) {
|
|
1691
|
+
const rest = line.slice("show ".length).trim();
|
|
1692
|
+
const pipeIdx = rest.indexOf("|");
|
|
1693
|
+
const namesPart = pipeIdx === -1 ? rest : rest.slice(0, pipeIdx).trim();
|
|
1694
|
+
show = namesPart.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1695
|
+
if (pipeIdx !== -1) {
|
|
1696
|
+
const sinkText = rest.slice(pipeIdx + 1).trim();
|
|
1697
|
+
const sinkParsed = parseQuery(`:segment{} | ${sinkText}`);
|
|
1698
|
+
if (!sinkParsed.error && sinkParsed.family === "pipeline" && sinkParsed.sink && sinkParsed.transforms.length === 0) {
|
|
1699
|
+
showChart = sinkParsed.sink;
|
|
1700
|
+
} else {
|
|
1701
|
+
diagnostics.push(`Invalid chart sink on show: "${sinkText}"`);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const assign = /^([A-Za-z_][\w-]*)\s*=\s*(.+)$/.exec(line);
|
|
1707
|
+
if (assign) {
|
|
1708
|
+
const name = assign[1];
|
|
1709
|
+
const body = assign[2].trim();
|
|
1710
|
+
known.add(name);
|
|
1711
|
+
const unitMatch = /->\s*([\w/%.]+)\s*$/.exec(body);
|
|
1712
|
+
const normalizeMatch = /\(normalize\s+([^)]+)\)/.exec(body);
|
|
1713
|
+
const expression = body.replace(/->\s*[\w/%.]+\s*$/, "").replace(/\(normalize\s+[^)]+\)/, "").trim();
|
|
1714
|
+
const looksLikeQuery = /^(sum|avg|min|max|count|last|delta|find|rows)\b|^[:@|]/.test(expression);
|
|
1715
|
+
if (looksLikeQuery) {
|
|
1716
|
+
const parsed = parseQuery(expression);
|
|
1717
|
+
if (!parsed.error) {
|
|
1718
|
+
assignments.push({ name, kind: "query", queryText: expression, parsed, raw: line });
|
|
1719
|
+
} else {
|
|
1720
|
+
diagnostics.push(`Assignment "${name}" is not a valid query`);
|
|
1721
|
+
}
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
const refPipe = /^([A-Za-z_][\w-]*)\s*\|\s*(.+)$/.exec(expression);
|
|
1725
|
+
if (refPipe) {
|
|
1726
|
+
const sourceRef = refPipe[1];
|
|
1727
|
+
const pipeTail = refPipe[2].trim();
|
|
1728
|
+
const synthetic = parseQuery(`:segment{} | ${pipeTail}`);
|
|
1729
|
+
if (!synthetic.error && synthetic.family === "pipeline") {
|
|
1730
|
+
assignments.push({
|
|
1731
|
+
name,
|
|
1732
|
+
kind: "query",
|
|
1733
|
+
sourceRef,
|
|
1734
|
+
pipeTail,
|
|
1735
|
+
transformsText: pipeTail,
|
|
1736
|
+
queryText: expression,
|
|
1737
|
+
parsed: synthetic,
|
|
1738
|
+
raw: line
|
|
1739
|
+
});
|
|
1740
|
+
} else {
|
|
1741
|
+
diagnostics.push(`Assignment "${name}" is not a valid pipeline over "${sourceRef}"`);
|
|
1742
|
+
}
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
if (!FORMULA_CHARS.test(expression)) {
|
|
1746
|
+
diagnostics.push(`Assignment "${name}" has characters a formula cannot parse`);
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const formula = {
|
|
1750
|
+
expression,
|
|
1751
|
+
references: [],
|
|
1752
|
+
...normalizeMatch ? { normalize: normalizeMatch[1].trim() } : {},
|
|
1753
|
+
...unitMatch ? { unit: unitMatch[1] } : {}
|
|
1754
|
+
};
|
|
1755
|
+
assignments.push({ name, kind: "formula", formula, raw: line });
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
degenerate = true;
|
|
1759
|
+
}
|
|
1760
|
+
if (!hasDefaults && show === void 0 && !degenerate && assignments.length === 1 && assignments[0].kind === "query") ;
|
|
1761
|
+
if (!hasDefaults && show === void 0 && assignments.length === 0) {
|
|
1762
|
+
const parsed = parseQuery(lines.join(" "));
|
|
1763
|
+
if (!parsed.error) {
|
|
1764
|
+
known.add("query");
|
|
1765
|
+
assignments.push({ name: "query", kind: "query", queryText: lines.join(" "), parsed, raw: lines.join(" ") });
|
|
1766
|
+
show = ["query"];
|
|
1767
|
+
degenerate = true;
|
|
1768
|
+
} else {
|
|
1769
|
+
diagnostics.push("Body is neither a valid query nor a valid document");
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
for (const assignment of assignments) {
|
|
1773
|
+
if (assignment.sourceRef && !known.has(assignment.sourceRef)) {
|
|
1774
|
+
diagnostics.push(`Assignment "${assignment.name}" references unknown source "${assignment.sourceRef}"`);
|
|
1775
|
+
}
|
|
1776
|
+
if (assignment.kind !== "formula" || !assignment.formula) continue;
|
|
1777
|
+
assignment.formula.references = referencesOf(assignment.formula.expression, known);
|
|
1778
|
+
for (const match of assignment.formula.expression.matchAll(/[A-Za-z_][\w-]*/g)) {
|
|
1779
|
+
const name = match[0];
|
|
1780
|
+
if (!known.has(name)) {
|
|
1781
|
+
diagnostics.push(`Formula "${assignment.name}" references unknown name "${name}"`);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
if (assignment.formula.references.length === 0) {
|
|
1785
|
+
diagnostics.push(`Formula "${assignment.name}" references no named query or formula`);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
const cycle = findCycle(assignments);
|
|
1789
|
+
if (cycle) {
|
|
1790
|
+
diagnostics.push(`Reference cycle detected: ${cycle.join(" -> ")}`);
|
|
1791
|
+
}
|
|
1792
|
+
if (show === void 0) {
|
|
1793
|
+
show = assignments.filter((a) => a.kind === "query").map((a) => a.name);
|
|
1794
|
+
if (assignments.length > 0 && show.length === 0) show = [assignments[0].name];
|
|
1795
|
+
} else {
|
|
1796
|
+
for (const name of show) {
|
|
1797
|
+
if (!known.has(name)) diagnostics.push(`show references unknown name "${name}"`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
doc: {
|
|
1802
|
+
...Object.keys(defaults).length > 0 ? { defaults } : {},
|
|
1803
|
+
assignments,
|
|
1804
|
+
show,
|
|
1805
|
+
...showChart ? { showChart } : {},
|
|
1806
|
+
degenerate
|
|
1807
|
+
},
|
|
1808
|
+
diagnostics
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
function findCycle(assignments) {
|
|
1812
|
+
const deps = /* @__PURE__ */ new Map();
|
|
1813
|
+
for (const a of assignments) {
|
|
1814
|
+
if (a.kind === "formula" && a.formula) deps.set(a.name, a.formula.references);
|
|
1815
|
+
else if (a.sourceRef) deps.set(a.name, [a.sourceRef]);
|
|
1816
|
+
}
|
|
1817
|
+
const state = /* @__PURE__ */ new Map();
|
|
1818
|
+
const stack = [];
|
|
1819
|
+
const visit = (name) => {
|
|
1820
|
+
const s = state.get(name);
|
|
1821
|
+
if (s === 2) return void 0;
|
|
1822
|
+
if (s === 1) {
|
|
1823
|
+
const start = stack.indexOf(name);
|
|
1824
|
+
return [...stack.slice(start === -1 ? 0 : start), name];
|
|
1825
|
+
}
|
|
1826
|
+
state.set(name, 1);
|
|
1827
|
+
stack.push(name);
|
|
1828
|
+
for (const ref of deps.get(name) ?? []) {
|
|
1829
|
+
const cycle = visit(ref);
|
|
1830
|
+
if (cycle) return cycle;
|
|
1831
|
+
}
|
|
1832
|
+
stack.pop();
|
|
1833
|
+
state.set(name, 2);
|
|
1834
|
+
return void 0;
|
|
1835
|
+
};
|
|
1836
|
+
for (const name of deps.keys()) {
|
|
1837
|
+
const cycle = visit(name);
|
|
1838
|
+
if (cycle) return cycle;
|
|
1839
|
+
}
|
|
1840
|
+
return void 0;
|
|
1841
|
+
}
|
|
1842
|
+
function serializeDocument(doc) {
|
|
1843
|
+
if (doc.degenerate) {
|
|
1844
|
+
const only = doc.assignments[0];
|
|
1845
|
+
return only?.queryText ?? "";
|
|
1846
|
+
}
|
|
1847
|
+
const lines = [];
|
|
1848
|
+
if (doc.defaults) {
|
|
1849
|
+
const parts = [];
|
|
1850
|
+
if (doc.defaults.groupBy?.length) parts.push(`by {${doc.defaults.groupBy.join(", ")}}`);
|
|
1851
|
+
if (doc.defaults.window) {
|
|
1852
|
+
const w = doc.defaults.window;
|
|
1853
|
+
parts.push(
|
|
1854
|
+
w.kind === "relative" ? `last ${w.size}${w.unit}` : w.kind === "param" ? `last $${w.param}${w.unit ? ` ${w.unit}` : ""}` : `from ${w.start}${w.end ? ` to ${w.end}` : ""}`
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
if (parts.length > 0) lines.push(`defaults ${parts.join(" ")}`);
|
|
1858
|
+
}
|
|
1859
|
+
for (const a of doc.assignments) {
|
|
1860
|
+
lines.push(a.raw);
|
|
1861
|
+
}
|
|
1862
|
+
if (doc.show.length > 0) {
|
|
1863
|
+
let showLine = `show ${doc.show.join(", ")}`;
|
|
1864
|
+
if (doc.showChart) {
|
|
1865
|
+
const sinkSerialized = serialize({
|
|
1866
|
+
family: "pipeline",
|
|
1867
|
+
raw: "",
|
|
1868
|
+
source: { kind: "dataset", name: "@session" },
|
|
1869
|
+
transforms: [],
|
|
1870
|
+
sink: doc.showChart
|
|
1871
|
+
}).replace(/^@session\s*\|\s*/, "");
|
|
1872
|
+
showLine += ` | ${sinkSerialized}`;
|
|
1873
|
+
}
|
|
1874
|
+
lines.push(showLine);
|
|
1875
|
+
}
|
|
1876
|
+
return lines.join("\n");
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/dashboard/fenceSuffix.ts
|
|
1880
|
+
var DASHBOARD_WIDGET_TYPES = [
|
|
1881
|
+
"table",
|
|
1882
|
+
"list",
|
|
1883
|
+
"value",
|
|
1884
|
+
"timeseries",
|
|
1885
|
+
"bar",
|
|
1886
|
+
"donut",
|
|
1887
|
+
"toplist",
|
|
1888
|
+
"stacked-bar",
|
|
1889
|
+
"goal-rings",
|
|
1890
|
+
"zone-distribution"
|
|
1891
|
+
];
|
|
1892
|
+
function isDashboardWidgetType(type) {
|
|
1893
|
+
return DASHBOARD_WIDGET_TYPES.includes(type);
|
|
1894
|
+
}
|
|
1895
|
+
var DASHBOARD_GRID_MAX_COLS = 4;
|
|
1896
|
+
function parseQueryWidgetSuffix(suffix) {
|
|
1897
|
+
if (suffix === "") {
|
|
1898
|
+
return { type: "", attributes: {}, attributeOrder: [], error: 'missing widget type after "query:"' };
|
|
1899
|
+
}
|
|
1900
|
+
const attributes = {};
|
|
1901
|
+
const attributeOrder = [];
|
|
1902
|
+
const trimmedSuffix = suffix.trim();
|
|
1903
|
+
const headEnd = trimmedSuffix.search(/\s/);
|
|
1904
|
+
const head = headEnd === -1 ? trimmedSuffix : trimmedSuffix.slice(0, headEnd);
|
|
1905
|
+
const attrTail = headEnd === -1 ? "" : trimmedSuffix.slice(headEnd);
|
|
1906
|
+
const attrPattern = /([a-zA-Z][a-zA-Z0-9_-]*)=("([^"]*)"|\S+)/g;
|
|
1907
|
+
for (const match of attrTail.matchAll(attrPattern)) {
|
|
1908
|
+
const key = match[1].toLowerCase();
|
|
1909
|
+
const value = match[3] !== void 0 ? match[3] : match[2];
|
|
1910
|
+
if (key in attributes) {
|
|
1911
|
+
return { type: head, attributes, attributeOrder, error: `duplicate attribute "${key}"` };
|
|
1912
|
+
}
|
|
1913
|
+
attributes[key] = value;
|
|
1914
|
+
attributeOrder.push(key);
|
|
1915
|
+
}
|
|
1916
|
+
const leftover = attrTail.replace(attrPattern, "").trim();
|
|
1917
|
+
if (leftover !== "") {
|
|
1918
|
+
return { type: head, attributes, attributeOrder, error: `malformed attribute "${leftover.split(/\s+/)[0]}"` };
|
|
1919
|
+
}
|
|
1920
|
+
let rest = head.toLowerCase();
|
|
1921
|
+
let spanCols;
|
|
1922
|
+
let spanFull;
|
|
1923
|
+
if (rest.endsWith("-full")) {
|
|
1924
|
+
spanFull = true;
|
|
1925
|
+
rest = rest.slice(0, -"-full".length);
|
|
1926
|
+
} else {
|
|
1927
|
+
const spanMatch = rest.match(/-(\d+)$/);
|
|
1928
|
+
if (spanMatch) {
|
|
1929
|
+
spanCols = Number(spanMatch[1]);
|
|
1930
|
+
rest = rest.slice(0, -spanMatch[0].length);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (rest === "" || !/^[a-z][a-z0-9-]*$/.test(rest)) {
|
|
1934
|
+
return { type: rest, spanCols, spanFull, attributes, attributeOrder, error: `malformed widget type "${suffix}"` };
|
|
1935
|
+
}
|
|
1936
|
+
if (/-(\d+|full)$/.test(rest)) {
|
|
1937
|
+
return { type: rest, spanCols, spanFull, attributes, attributeOrder, error: `malformed widget suffix "${suffix}"` };
|
|
1938
|
+
}
|
|
1939
|
+
if (spanCols != null && (spanCols < 1 || spanCols > DASHBOARD_GRID_MAX_COLS)) {
|
|
1940
|
+
return { type: rest, spanCols, spanFull, attributes, attributeOrder, error: `span ${spanCols} outside 1..${DASHBOARD_GRID_MAX_COLS}` };
|
|
1941
|
+
}
|
|
1942
|
+
return { type: rest, spanCols, spanFull, attributes, attributeOrder };
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// src/queryBlockCodec.ts
|
|
1946
|
+
var KNOWN_YAML_KEYS = /* @__PURE__ */ new Set([
|
|
1947
|
+
"title",
|
|
1948
|
+
"description",
|
|
1949
|
+
"question",
|
|
1950
|
+
"view",
|
|
1951
|
+
"type",
|
|
1952
|
+
"span",
|
|
1953
|
+
"unit",
|
|
1954
|
+
"window",
|
|
1955
|
+
"groupBy",
|
|
1956
|
+
"query",
|
|
1957
|
+
"series",
|
|
1958
|
+
"columns",
|
|
1959
|
+
"sort",
|
|
1960
|
+
"limit",
|
|
1961
|
+
"goal",
|
|
1962
|
+
"targets",
|
|
1963
|
+
"comparePr"
|
|
1964
|
+
]);
|
|
1965
|
+
function unquoteYaml(raw) {
|
|
1966
|
+
const t2 = raw.trim();
|
|
1967
|
+
if (t2.length >= 2 && t2.startsWith('"') && t2.endsWith('"')) {
|
|
1968
|
+
return t2.slice(1, -1).replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
1969
|
+
}
|
|
1970
|
+
if (t2.length >= 2 && t2.startsWith("'") && t2.endsWith("'")) {
|
|
1971
|
+
return t2.slice(1, -1).replace(/''/g, "'");
|
|
1972
|
+
}
|
|
1973
|
+
return t2;
|
|
1974
|
+
}
|
|
1975
|
+
function stripInlineYamlComment(line) {
|
|
1976
|
+
let inSingle = false;
|
|
1977
|
+
let inDouble = false;
|
|
1978
|
+
for (let i = 0; i < line.length; i++) {
|
|
1979
|
+
const ch = line[i];
|
|
1980
|
+
if (inDouble) {
|
|
1981
|
+
if (ch === "\\") i++;
|
|
1982
|
+
else if (ch === '"') inDouble = false;
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
if (inSingle) {
|
|
1986
|
+
if (ch === "'") inSingle = false;
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
if (ch === '"') inDouble = true;
|
|
1990
|
+
else if (ch === "'") inSingle = true;
|
|
1991
|
+
else if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) {
|
|
1992
|
+
return line.slice(0, i).trimEnd();
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return line;
|
|
1996
|
+
}
|
|
1997
|
+
function quoteYamlString(value, opts = {}) {
|
|
1998
|
+
if (value === "") return '""';
|
|
1999
|
+
if (opts.allowWqlBare && value === value.trim() && !/^[{["'&*!|>%@`#-]/.test(value) && !value.includes(": ") && !value.includes(" #") && !value.includes("\n")) {
|
|
2000
|
+
return value;
|
|
2001
|
+
}
|
|
2002
|
+
const looksNumeric = /^-?\d+(?:\.\d+)?$/.test(value);
|
|
2003
|
+
const looksKeyword = /^(true|false|null|yes|no|on|off)$/i.test(value);
|
|
2004
|
+
if (/^[$a-zA-Z0-9_./-]+$/.test(value) && !value.startsWith("-") && !looksNumeric && !looksKeyword) {
|
|
2005
|
+
return value;
|
|
2006
|
+
}
|
|
2007
|
+
if (!/[:"'\n#{}[\],&*?|<>=!%@`]/.test(value) && value === value.trim() && !value.startsWith("-") && !looksNumeric && !looksKeyword) {
|
|
2008
|
+
return value;
|
|
2009
|
+
}
|
|
2010
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
2011
|
+
}
|
|
2012
|
+
function splitFlowList(inner) {
|
|
2013
|
+
const out = [];
|
|
2014
|
+
let current = "";
|
|
2015
|
+
let inSingle = false;
|
|
2016
|
+
let inDouble = false;
|
|
2017
|
+
for (let i = 0; i < inner.length; i++) {
|
|
2018
|
+
const ch = inner[i];
|
|
2019
|
+
if (inDouble) {
|
|
2020
|
+
if (ch === "\\" && i + 1 < inner.length) {
|
|
2021
|
+
current += ch + inner[i + 1];
|
|
2022
|
+
i++;
|
|
2023
|
+
continue;
|
|
2024
|
+
}
|
|
2025
|
+
if (ch === '"') inDouble = false;
|
|
2026
|
+
current += ch;
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
if (inSingle) {
|
|
2030
|
+
if (ch === "'") inSingle = false;
|
|
2031
|
+
current += ch;
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (ch === '"') {
|
|
2035
|
+
inDouble = true;
|
|
2036
|
+
current += ch;
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
if (ch === "'") {
|
|
2040
|
+
inSingle = true;
|
|
2041
|
+
current += ch;
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (ch === ",") {
|
|
2045
|
+
if (current.trim()) out.push(unquoteYaml(current));
|
|
2046
|
+
current = "";
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
current += ch;
|
|
2050
|
+
}
|
|
2051
|
+
if (current.trim()) out.push(unquoteYaml(current));
|
|
2052
|
+
return out;
|
|
2053
|
+
}
|
|
2054
|
+
function isYamlQueryBlockBody(rawBody) {
|
|
2055
|
+
const lines = rawBody.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith("//"));
|
|
2056
|
+
if (lines.length === 0) return false;
|
|
2057
|
+
const first = lines[0];
|
|
2058
|
+
if (first.startsWith(":") || first.startsWith("@")) return false;
|
|
2059
|
+
const m = /^([a-zA-Z][a-zA-Z0-9_]*)\s*:(?:\s+(.*))?$/.exec(first);
|
|
2060
|
+
if (!m) return false;
|
|
2061
|
+
return KNOWN_YAML_KEYS.has(m[1]);
|
|
2062
|
+
}
|
|
2063
|
+
function parseScalarOrParam(raw) {
|
|
2064
|
+
const clean = unquoteYaml(raw).trim();
|
|
2065
|
+
if (!clean) return void 0;
|
|
2066
|
+
if (clean.startsWith("$") && clean.length > 1) {
|
|
2067
|
+
return { param: clean.slice(1) };
|
|
2068
|
+
}
|
|
2069
|
+
const num = Number(clean);
|
|
2070
|
+
if (!Number.isNaN(num)) return num;
|
|
2071
|
+
return void 0;
|
|
2072
|
+
}
|
|
2073
|
+
function formatScalarOrParam(val) {
|
|
2074
|
+
return typeof val === "number" ? String(val) : `$${val.param}`;
|
|
2075
|
+
}
|
|
2076
|
+
function parseYamlWindow(raw) {
|
|
2077
|
+
const clean = unquoteYaml(raw).trim();
|
|
2078
|
+
if (!clean || clean.toLowerCase() === "all") return {};
|
|
2079
|
+
if (clean.startsWith("$") && clean.length > 1) {
|
|
2080
|
+
const m = /^\$([a-zA-Z_][\w-]*)(?:\s*([dw]))?$/i.exec(clean);
|
|
2081
|
+
if (m) {
|
|
2082
|
+
return {
|
|
2083
|
+
window: {
|
|
2084
|
+
kind: "param",
|
|
2085
|
+
param: m[1],
|
|
2086
|
+
...m[2] ? { unit: m[2].toLowerCase() } : {}
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
const relParam = /^last\s+\$([a-zA-Z_][\w-]*)(?:\s*([dw]))?$/i.exec(clean);
|
|
2092
|
+
if (relParam) {
|
|
2093
|
+
return {
|
|
2094
|
+
window: {
|
|
2095
|
+
kind: "param",
|
|
2096
|
+
param: relParam[1],
|
|
2097
|
+
...relParam[2] ? { unit: relParam[2].toLowerCase() } : {}
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
const rel = /^(?:last\s+)?(\d+)\s*([dw])$/i.exec(clean);
|
|
2102
|
+
if (rel) {
|
|
2103
|
+
return {
|
|
2104
|
+
window: {
|
|
2105
|
+
kind: "relative",
|
|
2106
|
+
size: Number(rel[1]),
|
|
2107
|
+
unit: rel[2].toLowerCase()
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
const range = /^(?:from\s+)?(\d{4}-\d{2}-\d{2})(?:\s*(?:to|\.\.)\s*(\d{4}-\d{2}-\d{2}))?$/i.exec(clean);
|
|
2112
|
+
if (range) {
|
|
2113
|
+
return {
|
|
2114
|
+
window: {
|
|
2115
|
+
kind: "range",
|
|
2116
|
+
start: range[1],
|
|
2117
|
+
...range[2] ? { end: range[2] } : {}
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
return { error: `Invalid window "${clean}" \u2014 expected 8w, 14d, $range, or YYYY-MM-DD..YYYY-MM-DD` };
|
|
2122
|
+
}
|
|
2123
|
+
function formatYamlWindow(w) {
|
|
2124
|
+
if (w.kind === "relative") return `${w.size}${w.unit}`;
|
|
2125
|
+
if (w.kind === "param") return w.unit ? `$${w.param} ${w.unit}` : `$${w.param}`;
|
|
2126
|
+
return w.end ? `${w.start}..${w.end}` : `from ${w.start}`;
|
|
2127
|
+
}
|
|
2128
|
+
function parseSortItem(raw) {
|
|
2129
|
+
const clean = unquoteYaml(raw).trim();
|
|
2130
|
+
if (!clean) return void 0;
|
|
2131
|
+
const m = /^(\S+)(?:\s+(asc|desc))?$/i.exec(clean);
|
|
2132
|
+
if (!m) return void 0;
|
|
2133
|
+
return { col: m[1], dir: m[2]?.toLowerCase() ?? "asc" };
|
|
2134
|
+
}
|
|
2135
|
+
function parseIndentedYamlMap(rawBody) {
|
|
2136
|
+
const scalars = {};
|
|
2137
|
+
const stringLists = {};
|
|
2138
|
+
const objectLists = {};
|
|
2139
|
+
const rawLines = rawBody.split(/\r?\n/);
|
|
2140
|
+
let i = 0;
|
|
2141
|
+
while (i < rawLines.length) {
|
|
2142
|
+
const cleaned = stripInlineYamlComment(rawLines[i]);
|
|
2143
|
+
const trimmed = cleaned.trim();
|
|
2144
|
+
if (!trimmed || trimmed.startsWith("//")) {
|
|
2145
|
+
i++;
|
|
2146
|
+
continue;
|
|
2147
|
+
}
|
|
2148
|
+
const indent = cleaned.length - cleaned.trimStart().length;
|
|
2149
|
+
if (indent !== 0) {
|
|
2150
|
+
return { data: { scalars, stringLists, objectLists }, error: `Unexpected indentation on line ${i + 1}: "${trimmed}"` };
|
|
2151
|
+
}
|
|
2152
|
+
const m = /^([a-zA-Z][a-zA-Z0-9_]*)\s*:(?:\s*(.*))?$/.exec(trimmed);
|
|
2153
|
+
if (!m) {
|
|
2154
|
+
return { data: { scalars, stringLists, objectLists }, error: `Malformed YAML property on line ${i + 1}: "${trimmed}"` };
|
|
2155
|
+
}
|
|
2156
|
+
const key = m[1];
|
|
2157
|
+
const rest = (m[2] ?? "").trim();
|
|
2158
|
+
if (rest !== "") {
|
|
2159
|
+
if (rest.startsWith("[") && rest.endsWith("]")) {
|
|
2160
|
+
stringLists[key] = splitFlowList(rest.slice(1, -1));
|
|
2161
|
+
} else {
|
|
2162
|
+
scalars[key] = unquoteYaml(rest);
|
|
2163
|
+
}
|
|
2164
|
+
i++;
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
i++;
|
|
2168
|
+
const strItems = [];
|
|
2169
|
+
const objItems = [];
|
|
2170
|
+
let isObjList = false;
|
|
2171
|
+
while (i < rawLines.length) {
|
|
2172
|
+
const childClean = stripInlineYamlComment(rawLines[i]);
|
|
2173
|
+
const childTrim = childClean.trim();
|
|
2174
|
+
if (!childTrim || childTrim.startsWith("//")) {
|
|
2175
|
+
i++;
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
const childIndent = childClean.length - childClean.trimStart().length;
|
|
2179
|
+
if (childIndent === 0) break;
|
|
2180
|
+
if (childTrim.startsWith("- ")) {
|
|
2181
|
+
const afterDash = childTrim.slice(2).trim();
|
|
2182
|
+
const kvMatch = /^([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*(.*)$/.exec(afterDash);
|
|
2183
|
+
if (kvMatch) {
|
|
2184
|
+
isObjList = true;
|
|
2185
|
+
const itemObj = {
|
|
2186
|
+
[kvMatch[1]]: unquoteYaml(kvMatch[2] ?? "")
|
|
2187
|
+
};
|
|
2188
|
+
const itemIndent = childIndent;
|
|
2189
|
+
i++;
|
|
2190
|
+
while (i < rawLines.length) {
|
|
2191
|
+
const propClean = stripInlineYamlComment(rawLines[i]);
|
|
2192
|
+
const propTrim = propClean.trim();
|
|
2193
|
+
if (!propTrim || propTrim.startsWith("//")) {
|
|
2194
|
+
i++;
|
|
2195
|
+
continue;
|
|
2196
|
+
}
|
|
2197
|
+
const propIndent = propClean.length - propClean.trimStart().length;
|
|
2198
|
+
if (propIndent <= itemIndent) break;
|
|
2199
|
+
const propMatch = /^([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*(.*)$/.exec(propTrim);
|
|
2200
|
+
if (propMatch) {
|
|
2201
|
+
itemObj[propMatch[1]] = unquoteYaml(propMatch[2] ?? "");
|
|
2202
|
+
}
|
|
2203
|
+
i++;
|
|
2204
|
+
}
|
|
2205
|
+
objItems.push(itemObj);
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
strItems.push(unquoteYaml(afterDash));
|
|
2209
|
+
i++;
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
i++;
|
|
2213
|
+
}
|
|
2214
|
+
if (isObjList) objectLists[key] = objItems;
|
|
2215
|
+
else stringLists[key] = strItems;
|
|
2216
|
+
}
|
|
2217
|
+
return { data: { scalars, stringLists, objectLists } };
|
|
2218
|
+
}
|
|
2219
|
+
function parseTargetsString(raw) {
|
|
2220
|
+
return raw.split(/[\s,]+/).map((tok) => parseScalarOrParam(tok)).filter((v) => v !== void 0);
|
|
2221
|
+
}
|
|
2222
|
+
function normalizeSingleWqlForObject(rawWql) {
|
|
2223
|
+
const trimmed = rawWql.trim();
|
|
2224
|
+
const parsed = parseQuery(trimmed);
|
|
2225
|
+
if (parsed.error) return { query: trimmed };
|
|
2226
|
+
if (isFindQuery(parsed) && parsed.pipes) {
|
|
2227
|
+
const columns = parsed.pipes.select?.map((s) => s.unit ? `${s.col} in ${s.unit}` : s.col);
|
|
2228
|
+
const sort = parsed.pipes.order?.map((o) => ({ col: o.col, dir: o.dir }));
|
|
2229
|
+
const limit = parsed.pipes.limit;
|
|
2230
|
+
const stripped = { ...parsed, pipes: void 0 };
|
|
2231
|
+
return {
|
|
2232
|
+
query: serialize(stripped),
|
|
2233
|
+
...columns?.length ? { columns } : {},
|
|
2234
|
+
...sort?.length ? { sort } : {},
|
|
2235
|
+
...limit !== void 0 ? { limit } : {}
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
if (!isPipelineQuery(parsed)) {
|
|
2239
|
+
return { query: trimmed };
|
|
2240
|
+
}
|
|
2241
|
+
let sinkView;
|
|
2242
|
+
let sinkGoal;
|
|
2243
|
+
let sinkTargets;
|
|
2244
|
+
if (parsed.sink) {
|
|
2245
|
+
if (isDashboardWidgetType(parsed.sink.head)) {
|
|
2246
|
+
sinkView = parsed.sink.head;
|
|
2247
|
+
}
|
|
2248
|
+
for (const f of parsed.sink.filters) {
|
|
2249
|
+
if (f.key === "goal" && f.values[0]) {
|
|
2250
|
+
const v = f.values[0];
|
|
2251
|
+
sinkGoal = v.param ? { param: v.param } : parseScalarOrParam(v.value);
|
|
2252
|
+
} else if (f.key === "targets" && f.values[0]) {
|
|
2253
|
+
sinkTargets = parseTargetsString(f.values[0].value);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
if (parsed.source.kind === "query" && parsed.source.query.family === "find" && parsed.source.query.target === "segment" && !parsed.source.query.join && !parsed.source.query.groupBy?.length && parsed.transforms.length === 1) {
|
|
2258
|
+
const src = parsed.source.query;
|
|
2259
|
+
const tr = parsed.transforms[0];
|
|
2260
|
+
if (tr.metric || tr.agg === "count") {
|
|
2261
|
+
const collapsed = {
|
|
2262
|
+
family: "aggregate",
|
|
2263
|
+
raw: "",
|
|
2264
|
+
agg: tr.agg,
|
|
2265
|
+
metric: tr.metric || "sessionLoad",
|
|
2266
|
+
...tr.metricParam ? { metricParam: tr.metricParam } : {},
|
|
2267
|
+
filters: [...src.filters, ...tr.filters],
|
|
2268
|
+
groupBy: tr.groupBy,
|
|
2269
|
+
...tr.rollup ? { rollup: tr.rollup } : {},
|
|
2270
|
+
...tr.displayUnit ?? src.displayUnit ? { displayUnit: tr.displayUnit ?? src.displayUnit } : {},
|
|
2271
|
+
...tr.window ?? src.window ? { window: tr.window ?? src.window } : {}
|
|
2272
|
+
};
|
|
2273
|
+
return {
|
|
2274
|
+
query: serialize(collapsed),
|
|
2275
|
+
...sinkView ? { sinkView } : {},
|
|
2276
|
+
...sinkGoal !== void 0 ? { sinkGoal } : {},
|
|
2277
|
+
...sinkTargets?.length ? { sinkTargets } : {}
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
if (parsed.source.kind === "query" && parsed.transforms.length === 0 && parsed.sink) {
|
|
2282
|
+
return {
|
|
2283
|
+
query: serialize(parsed.source.query),
|
|
2284
|
+
...sinkView ? { sinkView } : {},
|
|
2285
|
+
...sinkGoal !== void 0 ? { sinkGoal } : {},
|
|
2286
|
+
...sinkTargets?.length ? { sinkTargets } : {}
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
if (parsed.sink) {
|
|
2290
|
+
const withoutSink = serialize({ ...parsed, sink: void 0 });
|
|
2291
|
+
return {
|
|
2292
|
+
query: withoutSink,
|
|
2293
|
+
...sinkView ? { sinkView } : {},
|
|
2294
|
+
...sinkGoal !== void 0 ? { sinkGoal } : {},
|
|
2295
|
+
...sinkTargets?.length ? { sinkTargets } : {}
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
return { query: trimmed };
|
|
2299
|
+
}
|
|
2300
|
+
function applyLegacyFenceOptions(base, options2) {
|
|
2301
|
+
const next = { ...base };
|
|
2302
|
+
if (options2.fenceTag) {
|
|
2303
|
+
const tag = options2.fenceTag.trim().replace(/^`+/, "");
|
|
2304
|
+
const colonMatch = tag.match(/^(?:query|widget):(.*)$/i);
|
|
2305
|
+
const suffixText = colonMatch ? colonMatch[1] : tag.toLowerCase() === "query" ? "" : tag;
|
|
2306
|
+
if (suffixText !== "") {
|
|
2307
|
+
const spec = parseQueryWidgetSuffix(suffixText);
|
|
2308
|
+
if (spec.error) next.error = spec.error;
|
|
2309
|
+
if (spec.type) {
|
|
2310
|
+
if (isDashboardWidgetType(spec.type)) {
|
|
2311
|
+
next.view = spec.type;
|
|
2312
|
+
} else {
|
|
2313
|
+
next.rawViewType = spec.type;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
if (spec.spanFull) next.span = "full";
|
|
2317
|
+
else if (spec.spanCols) next.span = spec.spanCols;
|
|
2318
|
+
const extraAttrs = {};
|
|
2319
|
+
for (const [k, v] of Object.entries(spec.attributes)) {
|
|
2320
|
+
if (k === "goal" || k === "param1") {
|
|
2321
|
+
const parsedGoal = parseScalarOrParam(v);
|
|
2322
|
+
if (parsedGoal !== void 0) next.goal = next.goal ?? parsedGoal;
|
|
2323
|
+
else extraAttrs[k] = v;
|
|
2324
|
+
} else if (k === "targets") {
|
|
2325
|
+
const parsedTargets = parseTargetsString(v);
|
|
2326
|
+
if (parsedTargets.length > 0) next.targets = next.targets ?? parsedTargets;
|
|
2327
|
+
else extraAttrs[k] = v;
|
|
2328
|
+
} else if (k === "comparepr") {
|
|
2329
|
+
next.comparePr = v === "true";
|
|
2330
|
+
} else {
|
|
2331
|
+
extraAttrs[k] = v;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
if (Object.keys(extraAttrs).length > 0) {
|
|
2335
|
+
next.attributes = { ...next.attributes ?? {}, ...extraAttrs };
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (!next.title && options2.adjacentTitle) next.title = options2.adjacentTitle;
|
|
2340
|
+
if (!next.description && options2.adjacentDescription) next.description = options2.adjacentDescription;
|
|
2341
|
+
return next;
|
|
2342
|
+
}
|
|
2343
|
+
function parseQueryBlock(rawBody, options2 = {}) {
|
|
2344
|
+
const trimmed = rawBody.trim();
|
|
2345
|
+
if (isYamlQueryBlockBody(trimmed)) {
|
|
2346
|
+
const { data, error } = parseIndentedYamlMap(trimmed);
|
|
2347
|
+
const rawView = (data.scalars.view ?? data.scalars.type ?? "").toLowerCase();
|
|
2348
|
+
const view = isDashboardWidgetType(rawView) ? rawView : "table";
|
|
2349
|
+
const obj2 = {
|
|
2350
|
+
view,
|
|
2351
|
+
...rawView && !isDashboardWidgetType(rawView) ? { rawViewType: rawView } : {},
|
|
2352
|
+
...error ? { error } : {}
|
|
2353
|
+
};
|
|
2354
|
+
if (data.scalars.title) obj2.title = data.scalars.title;
|
|
2355
|
+
const desc = data.scalars.description ?? data.scalars.question;
|
|
2356
|
+
if (desc) obj2.description = desc;
|
|
2357
|
+
if (data.scalars.span) {
|
|
2358
|
+
const s = data.scalars.span.toLowerCase();
|
|
2359
|
+
if (s === "full") obj2.span = "full";
|
|
2360
|
+
else {
|
|
2361
|
+
const n = Number(s);
|
|
2362
|
+
if (n >= 1 && n <= 4) obj2.span = n;
|
|
2363
|
+
else obj2.error = obj2.error ?? `span ${data.scalars.span} outside 1..4`;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
if (data.scalars.unit) obj2.unit = data.scalars.unit;
|
|
2367
|
+
if (data.scalars.window) {
|
|
2368
|
+
const wRes = parseYamlWindow(data.scalars.window);
|
|
2369
|
+
if (wRes.error) obj2.error = obj2.error ?? wRes.error;
|
|
2370
|
+
else if (wRes.window) obj2.window = wRes.window;
|
|
2371
|
+
}
|
|
2372
|
+
const groupByList = data.stringLists.groupBy ?? (data.scalars.groupBy ? splitFlowList(data.scalars.groupBy) : void 0);
|
|
2373
|
+
if (groupByList?.length) obj2.groupBy = groupByList;
|
|
2374
|
+
if (data.scalars.goal !== void 0) {
|
|
2375
|
+
const g = parseScalarOrParam(data.scalars.goal);
|
|
2376
|
+
if (g !== void 0) obj2.goal = g;
|
|
2377
|
+
}
|
|
2378
|
+
const targetStrings = data.stringLists.targets ?? (data.scalars.targets ? parseTargetsString(data.scalars.targets).map(formatScalarOrParam) : void 0);
|
|
2379
|
+
if (targetStrings?.length) {
|
|
2380
|
+
const parsedTargets = targetStrings.map(parseScalarOrParam).filter((v) => v !== void 0);
|
|
2381
|
+
if (parsedTargets.length) obj2.targets = parsedTargets;
|
|
2382
|
+
}
|
|
2383
|
+
if (data.scalars.comparePr !== void 0) {
|
|
2384
|
+
obj2.comparePr = data.scalars.comparePr.toLowerCase() === "true";
|
|
2385
|
+
}
|
|
2386
|
+
const cols = data.stringLists.columns ?? (data.scalars.columns ? splitFlowList(data.scalars.columns) : void 0);
|
|
2387
|
+
if (cols?.length) obj2.columns = cols;
|
|
2388
|
+
if (data.objectLists.sort) {
|
|
2389
|
+
obj2.sort = data.objectLists.sort.filter((item) => Boolean(item.col)).map((item) => ({
|
|
2390
|
+
col: item.col,
|
|
2391
|
+
dir: item.dir?.toLowerCase() === "desc" ? "desc" : "asc"
|
|
2392
|
+
}));
|
|
2393
|
+
} else if (data.stringLists.sort) {
|
|
2394
|
+
obj2.sort = data.stringLists.sort.map(parseSortItem).filter((s) => Boolean(s));
|
|
2395
|
+
} else if (data.scalars.sort) {
|
|
2396
|
+
const single = parseSortItem(data.scalars.sort);
|
|
2397
|
+
if (single) obj2.sort = [single];
|
|
2398
|
+
}
|
|
2399
|
+
if (data.scalars.limit !== void 0) {
|
|
2400
|
+
const lim = Number(data.scalars.limit);
|
|
2401
|
+
if (!Number.isNaN(lim)) obj2.limit = lim;
|
|
2402
|
+
}
|
|
2403
|
+
if (data.scalars.query !== void 0) {
|
|
2404
|
+
const norm2 = normalizeSingleWqlForObject(data.scalars.query);
|
|
2405
|
+
obj2.query = norm2.query;
|
|
2406
|
+
if (!data.scalars.view && !data.scalars.type && norm2.sinkView) obj2.view = norm2.sinkView;
|
|
2407
|
+
if (obj2.goal === void 0 && norm2.sinkGoal !== void 0) obj2.goal = norm2.sinkGoal;
|
|
2408
|
+
if (!obj2.targets && norm2.sinkTargets?.length) obj2.targets = norm2.sinkTargets;
|
|
2409
|
+
}
|
|
2410
|
+
if (data.objectLists.series?.length) {
|
|
2411
|
+
obj2.series = data.objectLists.series.map((item, idx) => ({
|
|
2412
|
+
id: item.id || `s${idx + 1}`,
|
|
2413
|
+
...item.label ? { label: item.label } : {},
|
|
2414
|
+
...item.query ? { query: normalizeSingleWqlForObject(item.query).query } : {},
|
|
2415
|
+
...item.formula ? { formula: item.formula } : {},
|
|
2416
|
+
...item.normalize ? { normalize: item.normalize } : {},
|
|
2417
|
+
...item.unit ? { unit: item.unit } : {},
|
|
2418
|
+
...item.visible === "false" ? { visible: false } : {}
|
|
2419
|
+
}));
|
|
2420
|
+
}
|
|
2421
|
+
return applyLegacyFenceOptions(obj2, options2);
|
|
2422
|
+
}
|
|
2423
|
+
const nonCommentLines = trimmed.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith("//"));
|
|
2424
|
+
if (nonCommentLines.length > 1 || nonCommentLines[0] && /^(?:defaults\s+|show\s+|[A-Za-z_][\w-]*\s*=)/.test(nonCommentLines[0])) {
|
|
2425
|
+
const docParsed = parseDocument(nonCommentLines.join("\n"));
|
|
2426
|
+
if (!docParsed.doc.degenerate && docParsed.doc.assignments.length > 0) {
|
|
2427
|
+
const shownSet = new Set(docParsed.doc.show);
|
|
2428
|
+
const sinkView = docParsed.doc.showChart && isDashboardWidgetType(docParsed.doc.showChart.head) ? docParsed.doc.showChart.head : void 0;
|
|
2429
|
+
const obj2 = {
|
|
2430
|
+
view: sinkView ?? "timeseries",
|
|
2431
|
+
...docParsed.doc.defaults?.window ? { window: docParsed.doc.defaults.window } : {},
|
|
2432
|
+
...docParsed.doc.defaults?.groupBy?.length ? { groupBy: docParsed.doc.defaults.groupBy } : {},
|
|
2433
|
+
series: docParsed.doc.assignments.map((a) => ({
|
|
2434
|
+
id: a.name,
|
|
2435
|
+
...a.kind === "query" ? { query: a.queryText } : {},
|
|
2436
|
+
...a.kind === "formula" && a.formula ? {
|
|
2437
|
+
formula: a.formula.expression,
|
|
2438
|
+
...a.formula.normalize ? { normalize: a.formula.normalize } : {},
|
|
2439
|
+
...a.formula.unit ? { unit: a.formula.unit } : {}
|
|
2440
|
+
} : {},
|
|
2441
|
+
...shownSet.size > 0 && !shownSet.has(a.name) ? { visible: false } : {}
|
|
2442
|
+
}))
|
|
2443
|
+
};
|
|
2444
|
+
return applyLegacyFenceOptions(obj2, options2);
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
const singleLine = nonCommentLines.join(" ");
|
|
2448
|
+
const norm = normalizeSingleWqlForObject(singleLine);
|
|
2449
|
+
const obj = {
|
|
2450
|
+
view: norm.sinkView ?? "table",
|
|
2451
|
+
query: norm.query,
|
|
2452
|
+
...norm.columns ? { columns: norm.columns } : {},
|
|
2453
|
+
...norm.sort ? { sort: norm.sort } : {},
|
|
2454
|
+
...norm.limit !== void 0 ? { limit: norm.limit } : {},
|
|
2455
|
+
...norm.sinkGoal !== void 0 ? { goal: norm.sinkGoal } : {},
|
|
2456
|
+
...norm.sinkTargets?.length ? { targets: norm.sinkTargets } : {}
|
|
2457
|
+
};
|
|
2458
|
+
return applyLegacyFenceOptions(obj, options2);
|
|
2459
|
+
}
|
|
2460
|
+
function isDefaultShorthandQueryBlock(obj) {
|
|
2461
|
+
const effectiveView = obj.rawViewType ?? obj.view;
|
|
2462
|
+
if (effectiveView !== "table") return false;
|
|
2463
|
+
if (obj.title || obj.description || obj.span !== void 0 && obj.span !== 1 || obj.unit || obj.window || obj.groupBy?.length || obj.columns?.length || obj.sort?.length || obj.limit !== void 0 || obj.goal !== void 0 || obj.targets?.length || obj.comparePr !== void 0 || obj.attributes && Object.keys(obj.attributes).length > 0) {
|
|
2464
|
+
return false;
|
|
2465
|
+
}
|
|
2466
|
+
if (obj.series && obj.series.length > 0) {
|
|
2467
|
+
if (obj.series.length > 1) return false;
|
|
2468
|
+
const s = obj.series[0];
|
|
2469
|
+
if (s.label || s.formula || s.normalize || s.unit || s.visible === false || !s.query) return false;
|
|
2470
|
+
return true;
|
|
2471
|
+
}
|
|
2472
|
+
return Boolean(obj.query && !obj.query.includes("\n"));
|
|
2473
|
+
}
|
|
2474
|
+
function serializeQueryBlock(obj, opts = {}) {
|
|
2475
|
+
if (!opts.forceYaml && isDefaultShorthandQueryBlock(obj)) {
|
|
2476
|
+
return (obj.query ?? obj.series?.[0]?.query ?? "").trim();
|
|
2477
|
+
}
|
|
2478
|
+
const lines = [];
|
|
2479
|
+
if (obj.title) lines.push(`title: ${quoteYamlString(obj.title)}`);
|
|
2480
|
+
if (obj.description) lines.push(`description: ${quoteYamlString(obj.description)}`);
|
|
2481
|
+
lines.push(`view: ${quoteYamlString(obj.rawViewType ?? obj.view)}`);
|
|
2482
|
+
if (obj.span !== void 0 && obj.span !== 1) {
|
|
2483
|
+
lines.push(`span: ${obj.span}`);
|
|
2484
|
+
}
|
|
2485
|
+
if (obj.unit) lines.push(`unit: ${quoteYamlString(obj.unit)}`);
|
|
2486
|
+
if (obj.window) lines.push(`window: ${quoteYamlString(formatYamlWindow(obj.window))}`);
|
|
2487
|
+
if (obj.groupBy?.length) {
|
|
2488
|
+
lines.push(`groupBy: [${obj.groupBy.map((g) => quoteYamlString(g)).join(", ")}]`);
|
|
2489
|
+
}
|
|
2490
|
+
if (obj.goal !== void 0) {
|
|
2491
|
+
lines.push(`goal: ${formatScalarOrParam(obj.goal)}`);
|
|
2492
|
+
}
|
|
2493
|
+
if (obj.targets?.length) {
|
|
2494
|
+
lines.push(`targets: [${obj.targets.map(formatScalarOrParam).join(", ")}]`);
|
|
2495
|
+
}
|
|
2496
|
+
if (obj.comparePr !== void 0) {
|
|
2497
|
+
lines.push(`comparePr: ${obj.comparePr ? "true" : "false"}`);
|
|
2498
|
+
}
|
|
2499
|
+
if (obj.columns?.length) {
|
|
2500
|
+
lines.push(`columns: [${obj.columns.map((c) => quoteYamlString(c)).join(", ")}]`);
|
|
2501
|
+
}
|
|
2502
|
+
if (obj.sort?.length) {
|
|
2503
|
+
lines.push(
|
|
2504
|
+
`sort: [${obj.sort.map((s) => quoteYamlString(s.dir === "desc" ? `${s.col} desc` : `${s.col} asc`)).join(", ")}]`
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
if (obj.limit !== void 0) {
|
|
2508
|
+
lines.push(`limit: ${obj.limit}`);
|
|
2509
|
+
}
|
|
2510
|
+
const hasRichSeries = obj.series && (obj.series.length > 1 || obj.series.some((s) => Boolean(s.formula || s.label || s.normalize || s.unit || s.visible === false)));
|
|
2511
|
+
if (hasRichSeries && obj.series) {
|
|
2512
|
+
lines.push("series:");
|
|
2513
|
+
for (const s of obj.series) {
|
|
2514
|
+
lines.push(` - id: ${quoteYamlString(s.id)}`);
|
|
2515
|
+
if (s.label) lines.push(` label: ${quoteYamlString(s.label)}`);
|
|
2516
|
+
if (s.query) lines.push(` query: ${quoteYamlString(s.query, { allowWqlBare: true })}`);
|
|
2517
|
+
if (s.formula) lines.push(` formula: ${quoteYamlString(s.formula, { allowWqlBare: true })}`);
|
|
2518
|
+
if (s.normalize) lines.push(` normalize: ${quoteYamlString(s.normalize)}`);
|
|
2519
|
+
if (s.unit) lines.push(` unit: ${quoteYamlString(s.unit)}`);
|
|
2520
|
+
if (s.visible === false) lines.push(" visible: false");
|
|
2521
|
+
}
|
|
2522
|
+
} else {
|
|
2523
|
+
const singleQuery = obj.query ?? obj.series?.[0]?.query ?? "";
|
|
2524
|
+
lines.push(`query: ${quoteYamlString(singleQuery, { allowWqlBare: true })}`);
|
|
2525
|
+
}
|
|
2526
|
+
return lines.join("\n");
|
|
2527
|
+
}
|
|
2528
|
+
function serializeQueryBlockFence(obj, opts = {}) {
|
|
2529
|
+
return `\`\`\`query
|
|
2530
|
+
${serializeQueryBlock(obj, opts)}
|
|
2531
|
+
\`\`\``;
|
|
2532
|
+
}
|
|
2533
|
+
function applyTopLevelClausesToSingleQuery(rawQuery, obj) {
|
|
2534
|
+
const trimmed = rawQuery.trim();
|
|
2535
|
+
if (!trimmed) return "";
|
|
2536
|
+
const hasTopOverrides = Boolean(
|
|
2537
|
+
obj.window || obj.groupBy?.length || obj.unit || obj.columns?.length || obj.sort?.length || obj.limit !== void 0
|
|
2538
|
+
);
|
|
2539
|
+
if (!hasTopOverrides) return trimmed;
|
|
2540
|
+
const parsed = parseQuery(trimmed);
|
|
2541
|
+
if (parsed.error) return trimmed;
|
|
2542
|
+
if (parsed.family === "aggregate") {
|
|
2543
|
+
const next = {
|
|
2544
|
+
...parsed,
|
|
2545
|
+
...obj.groupBy?.length && parsed.groupBy.length === 0 ? { groupBy: obj.groupBy } : {},
|
|
2546
|
+
...obj.window && !parsed.window ? { window: obj.window } : {},
|
|
2547
|
+
...obj.unit && !parsed.displayUnit ? { displayUnit: obj.unit } : {}
|
|
2548
|
+
};
|
|
2549
|
+
return serialize(next);
|
|
2550
|
+
}
|
|
2551
|
+
if (parsed.family === "find") {
|
|
2552
|
+
const pipes = { ...parsed.pipes ?? {} };
|
|
2553
|
+
if (obj.columns?.length && !pipes.select) {
|
|
2554
|
+
pipes.select = obj.columns.map((c) => {
|
|
2555
|
+
const m = /^([\w.-]+)(?:\s+in\s+([\w/%]+))?$/i.exec(c.trim());
|
|
2556
|
+
return m ? { col: m[1], ...m[2] ? { unit: m[2] } : {} } : { col: c.trim() };
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
if (obj.sort?.length && !pipes.order) {
|
|
2560
|
+
pipes.order = obj.sort.map((s) => ({ col: s.col, dir: s.dir }));
|
|
2561
|
+
}
|
|
2562
|
+
if (obj.limit !== void 0 && pipes.limit === void 0) {
|
|
2563
|
+
pipes.limit = obj.limit;
|
|
2564
|
+
}
|
|
2565
|
+
const next = {
|
|
2566
|
+
...parsed,
|
|
2567
|
+
...obj.groupBy?.length && !parsed.groupBy?.length ? { groupBy: obj.groupBy } : {},
|
|
2568
|
+
...obj.window && !parsed.window ? { window: obj.window } : {},
|
|
2569
|
+
...obj.unit && !parsed.displayUnit ? { displayUnit: obj.unit } : {},
|
|
2570
|
+
...Object.keys(pipes).length > 0 ? { pipes } : {}
|
|
2571
|
+
};
|
|
2572
|
+
return serialize(next);
|
|
2573
|
+
}
|
|
2574
|
+
if (parsed.family === "pipeline") {
|
|
2575
|
+
const transforms = [...parsed.transforms];
|
|
2576
|
+
if (transforms.length > 0 && obj.groupBy?.length) {
|
|
2577
|
+
const lastIdx = transforms.length - 1;
|
|
2578
|
+
if (transforms[lastIdx].groupBy.length === 0) {
|
|
2579
|
+
transforms[lastIdx] = { ...transforms[lastIdx], groupBy: obj.groupBy };
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
let source = parsed.source;
|
|
2583
|
+
if (obj.window && source.kind === "query" && !source.query.window) {
|
|
2584
|
+
source = { kind: "query", query: { ...source.query, window: obj.window } };
|
|
2585
|
+
}
|
|
2586
|
+
return serialize({ ...parsed, source, transforms });
|
|
2587
|
+
}
|
|
2588
|
+
return trimmed;
|
|
2589
|
+
}
|
|
2590
|
+
function queryBlockToDocumentText(obj) {
|
|
2591
|
+
const hasMultiOrFormula = obj.series && (obj.series.length > 1 || obj.series.some((s) => Boolean(s.formula || s.visible === false)));
|
|
2592
|
+
if (!hasMultiOrFormula) {
|
|
2593
|
+
const single = obj.query ?? obj.series?.[0]?.query ?? "";
|
|
2594
|
+
return applyTopLevelClausesToSingleQuery(single, obj);
|
|
2595
|
+
}
|
|
2596
|
+
const lines = [];
|
|
2597
|
+
if (obj.groupBy?.length || obj.window) {
|
|
2598
|
+
const parts = [];
|
|
2599
|
+
if (obj.groupBy?.length) parts.push(`by {${obj.groupBy.join(", ")}}`);
|
|
2600
|
+
if (obj.window) {
|
|
2601
|
+
const w = obj.window;
|
|
2602
|
+
parts.push(
|
|
2603
|
+
w.kind === "relative" ? `last ${w.size}${w.unit}` : w.kind === "param" ? `last $${w.param}${w.unit ? ` ${w.unit}` : ""}` : `from ${w.start}${w.end ? ` to ${w.end}` : ""}`
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
lines.push(`defaults ${parts.join(" ")}`);
|
|
2607
|
+
}
|
|
2608
|
+
const showIds = [];
|
|
2609
|
+
for (const s of obj.series ?? []) {
|
|
2610
|
+
if (s.visible !== false) showIds.push(s.id);
|
|
2611
|
+
if (s.formula) {
|
|
2612
|
+
const normPart = s.normalize ? ` (normalize ${s.normalize})` : "";
|
|
2613
|
+
const unitPart = s.unit ? ` -> ${s.unit}` : "";
|
|
2614
|
+
lines.push(`${s.id} = ${s.formula}${normPart}${unitPart}`);
|
|
2615
|
+
} else if (s.query) {
|
|
2616
|
+
lines.push(`${s.id} = ${s.query}`);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
if (showIds.length > 0) {
|
|
2620
|
+
lines.push(`show ${showIds.join(", ")}`);
|
|
2621
|
+
}
|
|
2622
|
+
return lines.join("\n");
|
|
2623
|
+
}
|
|
2624
|
+
function queryBlockAttributes(obj) {
|
|
2625
|
+
const attributes = { ...obj.attributes ?? {} };
|
|
2626
|
+
const attributeOrder = Object.keys(attributes);
|
|
2627
|
+
const setAttr = (k, v) => {
|
|
2628
|
+
if (!(k in attributes)) attributeOrder.push(k);
|
|
2629
|
+
attributes[k] = v;
|
|
2630
|
+
};
|
|
2631
|
+
if (obj.goal !== void 0) {
|
|
2632
|
+
setAttr("goal", formatScalarOrParam(obj.goal));
|
|
2633
|
+
}
|
|
2634
|
+
if (obj.targets?.length) {
|
|
2635
|
+
setAttr("targets", obj.targets.map(formatScalarOrParam).join(" "));
|
|
2636
|
+
}
|
|
2637
|
+
if (obj.comparePr !== void 0) {
|
|
2638
|
+
setAttr("comparePr", obj.comparePr ? "true" : "false");
|
|
2639
|
+
}
|
|
2640
|
+
if (obj.series) {
|
|
2641
|
+
for (const s of obj.series) {
|
|
2642
|
+
if (s.label) setAttr(`label.${s.id}`, s.label);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
if (!obj.series && obj.title) ;
|
|
2646
|
+
return { attributes, attributeOrder };
|
|
2647
|
+
}
|
|
2648
|
+
function collectQueryBlockParams(obj) {
|
|
2649
|
+
const out = /* @__PURE__ */ new Set();
|
|
2650
|
+
const addFromText = (text) => {
|
|
2651
|
+
if (!text) return;
|
|
2652
|
+
const parsed = parseQuery(text);
|
|
2653
|
+
if (!parsed.error) {
|
|
2654
|
+
for (const m of text.matchAll(/\$([A-Za-z_][\w-]*)/g)) out.add(m[1]);
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
for (const m of text.matchAll(/\$([A-Za-z_][\w-]*)/g)) out.add(m[1]);
|
|
2658
|
+
};
|
|
2659
|
+
if (obj.window?.kind === "param") out.add(obj.window.param);
|
|
2660
|
+
if (obj.goal && typeof obj.goal === "object") out.add(obj.goal.param);
|
|
2661
|
+
for (const t2 of obj.targets ?? []) {
|
|
2662
|
+
if (typeof t2 === "object") out.add(t2.param);
|
|
2663
|
+
}
|
|
2664
|
+
addFromText(obj.query);
|
|
2665
|
+
for (const s of obj.series ?? []) {
|
|
2666
|
+
addFromText(s.query);
|
|
2667
|
+
addFromText(s.formula);
|
|
2668
|
+
}
|
|
2669
|
+
for (const v of Object.values(obj.attributes ?? {})) {
|
|
2670
|
+
addFromText(v);
|
|
2671
|
+
}
|
|
2672
|
+
return [...out];
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// src/disciplines.ts
|
|
2676
|
+
var EFFORT_DISCIPLINES = [
|
|
2677
|
+
"bodyweight",
|
|
2678
|
+
"cycling",
|
|
2679
|
+
"gymnastics",
|
|
2680
|
+
"kettlebell",
|
|
2681
|
+
"recovery",
|
|
2682
|
+
"rowing",
|
|
2683
|
+
"running",
|
|
2684
|
+
"strength",
|
|
2685
|
+
"swimming",
|
|
2686
|
+
"walking"
|
|
2687
|
+
];
|
|
2688
|
+
function isEffortDiscipline(value) {
|
|
2689
|
+
return typeof value === "string" && EFFORT_DISCIPLINES.includes(value.toLowerCase());
|
|
2690
|
+
}
|
|
2691
|
+
var DISCIPLINE_FACTORS = {
|
|
2692
|
+
strength: 1.2,
|
|
2693
|
+
kettlebell: 1.2,
|
|
2694
|
+
gymnastics: 1.2,
|
|
2695
|
+
recovery: 0.9,
|
|
2696
|
+
bodyweight: 1,
|
|
2697
|
+
cycling: 1,
|
|
2698
|
+
rowing: 1,
|
|
2699
|
+
running: 1,
|
|
2700
|
+
swimming: 1,
|
|
2701
|
+
walking: 1
|
|
2702
|
+
};
|
|
2703
|
+
function disciplineFactorFor(discipline) {
|
|
2704
|
+
if (!discipline) return 1;
|
|
2705
|
+
const key = discipline.toLowerCase();
|
|
2706
|
+
return isEffortDiscipline(key) ? DISCIPLINE_FACTORS[key] : 1;
|
|
2707
|
+
}
|
|
2708
|
+
var wqlLanguage = language.LRLanguage.define({
|
|
2709
|
+
parser: parser.configure({
|
|
2710
|
+
props: [
|
|
2711
|
+
highlight.styleTags({
|
|
2712
|
+
// Rule styles color stray punctuation; the Word tokens inside carry
|
|
2713
|
+
// the visible styling (unstyled children reset the parent class) —
|
|
2714
|
+
// so parents with punctuation children (Head spans `:journal`) are
|
|
2715
|
+
// NOT styled directly, or highlightTree merges the word into the
|
|
2716
|
+
// parent span.
|
|
2717
|
+
"Aggregator/Word": highlight.tags.keyword,
|
|
2718
|
+
"Metric/Word": highlight.tags.variableName,
|
|
2719
|
+
// Direct Word child of Head = the colon head name (`:sum`, `:journal`).
|
|
2720
|
+
"Head/Word": highlight.tags.keyword,
|
|
2721
|
+
DatasetReference: highlight.tags.keyword,
|
|
2722
|
+
"DatasetReference/Word": highlight.tags.keyword,
|
|
2723
|
+
At: highlight.tags.punctuation,
|
|
2724
|
+
ParamRef: highlight.tags.special(highlight.tags.variableName),
|
|
2725
|
+
"ParamRef/Word": highlight.tags.special(highlight.tags.variableName),
|
|
2726
|
+
Dollar: highlight.tags.punctuation,
|
|
2727
|
+
TagKey: highlight.tags.propertyName,
|
|
2728
|
+
"TagKey/Word": highlight.tags.propertyName,
|
|
2729
|
+
TagValue: highlight.tags.string,
|
|
2730
|
+
"TagValue/Value/Word": highlight.tags.string,
|
|
2731
|
+
"TagValue/Word": highlight.tags.string,
|
|
2732
|
+
Negate: highlight.tags.operator,
|
|
2733
|
+
Star: highlight.tags.operator,
|
|
2734
|
+
Dimension: highlight.tags.attributeName,
|
|
2735
|
+
"Dimension/Word": highlight.tags.attributeName,
|
|
2736
|
+
By: highlight.tags.keyword,
|
|
2737
|
+
RollupDot: highlight.tags.keyword,
|
|
2738
|
+
Int: highlight.tags.number,
|
|
2739
|
+
"Rollup/Word": highlight.tags.unit,
|
|
2740
|
+
braceOpen: highlight.tags.bracket,
|
|
2741
|
+
braceClose: highlight.tags.bracket,
|
|
2742
|
+
parenOpen: highlight.tags.bracket,
|
|
2743
|
+
parenClose: highlight.tags.bracket,
|
|
2744
|
+
colon: highlight.tags.punctuation,
|
|
2745
|
+
comma: highlight.tags.punctuation,
|
|
2746
|
+
pipe: highlight.tags.punctuation,
|
|
2747
|
+
dot: highlight.tags.punctuation
|
|
2748
|
+
})
|
|
2749
|
+
]
|
|
2750
|
+
}),
|
|
2751
|
+
languageData: {
|
|
2752
|
+
closeBrackets: { brackets: ["{", "("] }
|
|
2753
|
+
}
|
|
2754
|
+
});
|
|
2755
|
+
function options(labels) {
|
|
2756
|
+
return labels.map((label) => typeof label === "string" ? { label } : label);
|
|
2757
|
+
}
|
|
2758
|
+
function scanContext(doc, pos) {
|
|
2759
|
+
let depth = 0, quoted = false, pipeFrom = -1, lastBrace = -1, fragStart = 0;
|
|
2760
|
+
for (let i = 0; i < pos; i++) {
|
|
2761
|
+
const ch = doc[i];
|
|
2762
|
+
if (quoted) {
|
|
2763
|
+
if (ch === '"') quoted = false;
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
if (ch === '"') {
|
|
2767
|
+
quoted = true;
|
|
2768
|
+
continue;
|
|
2769
|
+
}
|
|
2770
|
+
if (ch === "{") {
|
|
2771
|
+
depth++;
|
|
2772
|
+
lastBrace = i;
|
|
2773
|
+
fragStart = i + 1;
|
|
2774
|
+
} else if (ch === "}") {
|
|
2775
|
+
depth = Math.max(0, depth - 1);
|
|
2776
|
+
fragStart = i + 1;
|
|
2777
|
+
} else if (ch === "," && depth > 0) fragStart = i + 1;
|
|
2778
|
+
else if (ch === "|" && depth === 0) {
|
|
2779
|
+
pipeFrom = i + 1;
|
|
2780
|
+
fragStart = i + 1;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
return { depth, quoted, pipeFrom, lastBrace, fragStart };
|
|
2784
|
+
}
|
|
2785
|
+
function maskQuotes(text) {
|
|
2786
|
+
let out = "";
|
|
2787
|
+
let quoted = false;
|
|
2788
|
+
for (const ch of text) {
|
|
2789
|
+
if (ch === '"') {
|
|
2790
|
+
quoted = !quoted;
|
|
2791
|
+
out += " ";
|
|
2792
|
+
} else out += quoted ? " " : ch;
|
|
2793
|
+
}
|
|
2794
|
+
return out;
|
|
2795
|
+
}
|
|
2796
|
+
function unquotedColonIndex(text) {
|
|
2797
|
+
let quoted = false;
|
|
2798
|
+
for (let i = 0; i < text.length; i++) {
|
|
2799
|
+
const ch = text[i];
|
|
2800
|
+
if (ch === '"') quoted = !quoted;
|
|
2801
|
+
else if (ch === ":" && !quoted) return i;
|
|
2802
|
+
}
|
|
2803
|
+
return -1;
|
|
2804
|
+
}
|
|
2805
|
+
function usedEntries(doc, groupStart, fragStart) {
|
|
2806
|
+
const text = maskQuotes(doc.slice(groupStart + 1, fragStart));
|
|
2807
|
+
const used = /* @__PURE__ */ new Set();
|
|
2808
|
+
for (const entry of text.split(",")) {
|
|
2809
|
+
const m = /!?\s*([A-Za-z][\w-]*)/.exec(entry);
|
|
2810
|
+
if (m) used.add(m[1]);
|
|
2811
|
+
}
|
|
2812
|
+
return used;
|
|
2813
|
+
}
|
|
2814
|
+
function valueOf(c) {
|
|
2815
|
+
return typeof c.apply === "string" ? c.apply : c.label;
|
|
2816
|
+
}
|
|
2817
|
+
function quoteApply(c) {
|
|
2818
|
+
const value = valueOf(c);
|
|
2819
|
+
if (!/\s/.test(value)) return c;
|
|
2820
|
+
return {
|
|
2821
|
+
...c,
|
|
2822
|
+
apply: (view, _c, from, to) => {
|
|
2823
|
+
const text = view.state.sliceDoc(Math.max(0, from - 1), from) === '"' ? value : `"${value}"`;
|
|
2824
|
+
view.dispatch({ changes: { from, to, insert: text }, selection: { anchor: from + text.length }, userEvent: "input.complete" });
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
function mergeValues(host, statics) {
|
|
2829
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2830
|
+
const out = [];
|
|
2831
|
+
host.forEach((c, i) => {
|
|
2832
|
+
const k = valueOf(c).toLowerCase();
|
|
2833
|
+
if (!k || seen.has(k)) return;
|
|
2834
|
+
seen.add(k);
|
|
2835
|
+
out.push(quoteApply({ ...c, boost: Math.max(-99, Math.min(99, 80 - i)) }));
|
|
2836
|
+
});
|
|
2837
|
+
for (const c of statics) {
|
|
2838
|
+
const k = valueOf(c).toLowerCase();
|
|
2839
|
+
if (!k || seen.has(k)) continue;
|
|
2840
|
+
seen.add(k);
|
|
2841
|
+
out.push(quoteApply(c));
|
|
2842
|
+
}
|
|
2843
|
+
return out;
|
|
2844
|
+
}
|
|
2845
|
+
function narrow(options2, typed) {
|
|
2846
|
+
const q = typed.toLowerCase();
|
|
2847
|
+
if (!q) return [...options2];
|
|
2848
|
+
const prefix = [];
|
|
2849
|
+
const rest = [];
|
|
2850
|
+
for (const o of options2) {
|
|
2851
|
+
const label = o.label.toLowerCase();
|
|
2852
|
+
if (label.startsWith(q)) prefix.push(o);
|
|
2853
|
+
else if (label.includes(q)) rest.push(o);
|
|
2854
|
+
}
|
|
2855
|
+
return [...prefix, ...rest];
|
|
2856
|
+
}
|
|
2857
|
+
function finish(_context, from, options2, typed) {
|
|
2858
|
+
const ranked = narrow(options2, typed);
|
|
2859
|
+
if (!ranked.length) return null;
|
|
2860
|
+
return {
|
|
2861
|
+
from,
|
|
2862
|
+
options: ranked.map((o, i) => o.boost === void 0 ? { ...o, boost: Math.max(-99, 50 - i) } : o),
|
|
2863
|
+
filter: false
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
function chained(list, suffix) {
|
|
2867
|
+
return list.map((c) => ({
|
|
2868
|
+
...c,
|
|
2869
|
+
apply: (view, _c, from, to) => {
|
|
2870
|
+
const text = typeof c.apply === "string" ? c.apply : c.label;
|
|
2871
|
+
const selfTerminated = /\W$/.test(text);
|
|
2872
|
+
const present = view.state.sliceDoc(to, to + suffix.length) === suffix;
|
|
2873
|
+
const insert = selfTerminated || present ? text : text + suffix;
|
|
2874
|
+
view.dispatch({
|
|
2875
|
+
changes: { from, to: to + (selfTerminated && present ? suffix.length : 0), insert },
|
|
2876
|
+
selection: { anchor: from + insert.length },
|
|
2877
|
+
userEvent: "input.complete"
|
|
2878
|
+
});
|
|
2879
|
+
autocomplete.startCompletion(view);
|
|
2880
|
+
}
|
|
2881
|
+
}));
|
|
2882
|
+
}
|
|
2883
|
+
var HEAD_OPTIONS = [
|
|
2884
|
+
...WQL_FUNCTION_HEADS.map((label) => ({ label, detail: "function head", type: "keyword" })),
|
|
2885
|
+
...WQL_SOURCE_HEADS.map((label) => ({ label, detail: "source head", type: "keyword" })),
|
|
2886
|
+
...WQL_CHART_HEADS.map((label) => ({ label, detail: "chart head", type: "class" })),
|
|
2887
|
+
...WQL_STANDARD_DATASETS.map((label) => ({ label, detail: "page dataset", type: "class" }))
|
|
2888
|
+
];
|
|
2889
|
+
var PIPELINE_STAGE_HEAD_OPTIONS = [
|
|
2890
|
+
...WQL_FUNCTION_HEADS.map((label) => ({ label, detail: "function head", type: "keyword" })),
|
|
2891
|
+
...WQL_CHART_HEADS.map((label) => ({ label, detail: "chart head", type: "class" }))
|
|
2892
|
+
];
|
|
2893
|
+
var CHART_HEAD_OPTIONS = WQL_CHART_HEADS.map((label) => ({
|
|
2894
|
+
label,
|
|
2895
|
+
detail: "chart head",
|
|
2896
|
+
type: "class"
|
|
2897
|
+
}));
|
|
2898
|
+
var TOKEN_COMPLETION_OPTIONS = [
|
|
2899
|
+
{ label: "$thisnote", detail: "current note ID", type: "variable" },
|
|
2900
|
+
{ label: "$note", detail: "current note ID", type: "variable" },
|
|
2901
|
+
{ label: "$scope", detail: "entity scope filter", type: "variable" }
|
|
2902
|
+
];
|
|
2903
|
+
function splitLinePipeRanges(line, lineOffset = 0) {
|
|
2904
|
+
const stages = [];
|
|
2905
|
+
let depth = 0;
|
|
2906
|
+
let quoted = false;
|
|
2907
|
+
let start = 0;
|
|
2908
|
+
for (let i = 0; i < line.length; i++) {
|
|
2909
|
+
const ch = line[i];
|
|
2910
|
+
if (quoted) {
|
|
2911
|
+
if (ch === '"') quoted = false;
|
|
2912
|
+
continue;
|
|
2913
|
+
}
|
|
2914
|
+
if (ch === '"') {
|
|
2915
|
+
quoted = true;
|
|
2916
|
+
continue;
|
|
2917
|
+
}
|
|
2918
|
+
if (ch === "{") depth++;
|
|
2919
|
+
else if (ch === "}") depth = Math.max(0, depth - 1);
|
|
2920
|
+
else if (ch === "|" && depth === 0) {
|
|
2921
|
+
stages.push({ text: line.slice(start, i), from: lineOffset + start, to: lineOffset + i });
|
|
2922
|
+
start = i + 1;
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
stages.push({ text: line.slice(start), from: lineOffset + start, to: lineOffset + line.length });
|
|
2926
|
+
return stages;
|
|
2927
|
+
}
|
|
2928
|
+
function resolveWqlCursorStage(doc, pos) {
|
|
2929
|
+
const clamped = Math.max(0, Math.min(pos, doc.length));
|
|
2930
|
+
const lineStart = doc.lastIndexOf("\n", Math.max(0, clamped - 1)) + 1;
|
|
2931
|
+
const nextNl = doc.indexOf("\n", clamped);
|
|
2932
|
+
const lineEnd = nextNl === -1 ? doc.length : nextNl;
|
|
2933
|
+
const rawLine = doc.slice(lineStart, lineEnd);
|
|
2934
|
+
if (/^\s*defaults\b/.test(rawLine)) {
|
|
2935
|
+
return { role: "defaults", stageIndex: 0, from: lineStart, to: lineEnd };
|
|
2936
|
+
}
|
|
2937
|
+
const showM = /^(\s*show\s+)/.exec(rawLine);
|
|
2938
|
+
if (showM) {
|
|
2939
|
+
const bodyOffset = lineStart + showM[1].length;
|
|
2940
|
+
const stages2 = splitLinePipeRanges(rawLine.slice(showM[1].length), bodyOffset);
|
|
2941
|
+
const activeIdx2 = Math.max(0, stages2.findIndex((s) => clamped >= s.from && clamped <= s.to));
|
|
2942
|
+
const active2 = stages2[activeIdx2] ?? stages2[stages2.length - 1];
|
|
2943
|
+
if (activeIdx2 > 0) {
|
|
2944
|
+
const headM2 = /^\s*:?([A-Za-z][\w-]*)/.exec(active2.text);
|
|
2945
|
+
return { role: "sink", stageIndex: activeIdx2, head: headM2?.[1], from: active2.from, to: active2.to };
|
|
2946
|
+
}
|
|
2947
|
+
return { role: "binding", stageIndex: 0, from: active2.from, to: active2.to };
|
|
2948
|
+
}
|
|
2949
|
+
const assignM = /^(\s*[A-Za-z_][\w-]*\s*=\s*)/.exec(rawLine);
|
|
2950
|
+
const exprStart = assignM ? lineStart + assignM[1].length : lineStart;
|
|
2951
|
+
if (assignM && clamped < exprStart) {
|
|
2952
|
+
return { role: "binding", stageIndex: 0, from: lineStart, to: exprStart };
|
|
2953
|
+
}
|
|
2954
|
+
const exprText = doc.slice(exprStart, lineEnd);
|
|
2955
|
+
const stages = splitLinePipeRanges(exprText, exprStart);
|
|
2956
|
+
let activeIdx = stages.findIndex((s) => clamped >= s.from && clamped <= s.to);
|
|
2957
|
+
if (activeIdx === -1) activeIdx = stages.length - 1;
|
|
2958
|
+
const active = stages[activeIdx];
|
|
2959
|
+
const headM = /^\s*([:@]?)([A-Za-z][\w-]*)/.exec(active.text);
|
|
2960
|
+
const headName = headM?.[2]?.toLowerCase();
|
|
2961
|
+
if (headName && WQL_CHART_HEADS.includes(headName)) {
|
|
2962
|
+
return { role: "sink", stageIndex: activeIdx, head: headName, from: active.from, to: active.to };
|
|
2963
|
+
}
|
|
2964
|
+
if (activeIdx === 0) {
|
|
2965
|
+
if (headName && WQL_FUNCTION_HEADS.includes(headName) && stages.length === 1) {
|
|
2966
|
+
return { role: "reduce", stageIndex: 0, reduceIndex: 0, head: headName, from: active.from, to: active.to };
|
|
2967
|
+
}
|
|
2968
|
+
return { role: "source", stageIndex: 0, head: headM ? `${headM[1] ?? ""}${headName}` : void 0, from: active.from, to: active.to };
|
|
2969
|
+
}
|
|
2970
|
+
if (/^\s*(?:select|order\s+by|limit|offset)\b/i.test(active.text)) {
|
|
2971
|
+
return { role: "source", stageIndex: activeIdx, head: "pipes", from: active.from, to: active.to };
|
|
2972
|
+
}
|
|
2973
|
+
return {
|
|
2974
|
+
role: "reduce",
|
|
2975
|
+
stageIndex: activeIdx,
|
|
2976
|
+
reduceIndex: activeIdx - 1,
|
|
2977
|
+
head: headName,
|
|
2978
|
+
from: active.from,
|
|
2979
|
+
to: active.to
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
function collectPriorBindings(doc, lineStart) {
|
|
2983
|
+
if (lineStart <= 0) return [];
|
|
2984
|
+
const priorText = doc.slice(0, lineStart);
|
|
2985
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2986
|
+
const out = [];
|
|
2987
|
+
for (const raw of priorText.split("\n")) {
|
|
2988
|
+
const line = raw.trim();
|
|
2989
|
+
if (!line || line.startsWith("//")) continue;
|
|
2990
|
+
const m = /^([A-Za-z_][\w-]*)\s*=\s*(.+)$/.exec(line);
|
|
2991
|
+
if (!m) continue;
|
|
2992
|
+
const name = m[1];
|
|
2993
|
+
if (seen.has(name)) continue;
|
|
2994
|
+
seen.add(name);
|
|
2995
|
+
const rhs = m[2].trim();
|
|
2996
|
+
const isList = /^:(?:note|notes|journal|notebook|notebooks|playground|block|effort|session|segment|event)\b/.test(rhs) && !rhs.includes("| :");
|
|
2997
|
+
out.push({
|
|
2998
|
+
label: name,
|
|
2999
|
+
detail: isList ? "bound list" : "bound value",
|
|
3000
|
+
type: "variable"
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
return out;
|
|
3004
|
+
}
|
|
3005
|
+
function headApply(o, colonTyped) {
|
|
3006
|
+
if (o.label.startsWith("@")) return { ...o, apply: o.label };
|
|
3007
|
+
return { ...o, apply: `${colonTyped ? "" : ":"}${o.label}{` };
|
|
1546
3008
|
}
|
|
1547
3009
|
var LEGACY_HEAD_OPTIONS = WQL_AGGREGATORS.map((label) => ({ label, type: "keyword" }));
|
|
1548
3010
|
var NOTE_COLUMNS = ["title", "date", "createdAt", "type", "sourceId", "notebookId"];
|
|
@@ -1568,12 +3030,14 @@ var UNIT_OPTIONS = WQL_DISPLAY_UNITS.map((label) => ({ label, detail: "display u
|
|
|
1568
3030
|
function resolveHead(_agg, _typedTarget) {
|
|
1569
3031
|
return { family: "aggregate" };
|
|
1570
3032
|
}
|
|
3033
|
+
var CHART_PARAM_KEYS = ["label", "unit", "goal", "limit", "stack", "type", "smooth"];
|
|
1571
3034
|
function resolveColonHead(name) {
|
|
1572
3035
|
const n = name.toLowerCase();
|
|
1573
3036
|
if (WQL_FUNCTION_HEADS.includes(n)) return { family: "aggregate" };
|
|
1574
|
-
if (WQL_CHART_HEADS.includes(n)) return { family: "find" };
|
|
3037
|
+
if (WQL_CHART_HEADS.includes(n)) return { family: "find", target: "__chart__" };
|
|
1575
3038
|
const headTarget = (head) => {
|
|
1576
3039
|
if (WQL_NOTE_HEAD_ALIASES.includes(head)) return "note";
|
|
3040
|
+
if (head === "feeds") return "feed";
|
|
1577
3041
|
const scoped = WQL_SOURCE_HEAD_SCOPES[head] || WQL_TYPE_HEADS[head];
|
|
1578
3042
|
return scoped ? "note" : head;
|
|
1579
3043
|
};
|
|
@@ -1591,6 +3055,7 @@ var FIND_KEY_UNION = [
|
|
|
1591
3055
|
...new Set(WQL_FIND_TARGETS.flatMap((t2) => [...wqlFilterKeys(t2, "find")]))
|
|
1592
3056
|
];
|
|
1593
3057
|
function filterKeysFor(head) {
|
|
3058
|
+
if (head.target === "__chart__") return CHART_PARAM_KEYS;
|
|
1594
3059
|
return head.family === "find" ? head.target ? wqlFilterKeys(head.target, "find") : FIND_KEY_UNION : wqlFilterKeys("", "aggregate");
|
|
1595
3060
|
}
|
|
1596
3061
|
function pipeColumns(target) {
|
|
@@ -1797,39 +3262,148 @@ function wqlCompletionSource(options_ = {}) {
|
|
|
1797
3262
|
{ label: "order by", apply: "order by ", detail: head.target === "session" ? "advisory: order ignored for sessions" : "sort rows", type: "keyword" },
|
|
1798
3263
|
{ label: "select", apply: "select ", detail: SELECT_IGNORED.has(head.target ?? "") ? `advisory: select ignored for find:${head.target}` : "choose columns", type: "keyword" }
|
|
1799
3264
|
];
|
|
1800
|
-
|
|
3265
|
+
const colonTyped = /^[ \t]*:/.test(text);
|
|
3266
|
+
return finish(context, from, [
|
|
3267
|
+
...PIPELINE_STAGE_HEAD_OPTIONS.map((o) => headApply(o, colonTyped)),
|
|
3268
|
+
...chained(kw, " "),
|
|
3269
|
+
limitSnippet
|
|
3270
|
+
], typed);
|
|
1801
3271
|
};
|
|
1802
3272
|
function inner(context, replaceSelection = false) {
|
|
1803
|
-
const
|
|
1804
|
-
const
|
|
1805
|
-
const
|
|
1806
|
-
const
|
|
3273
|
+
const fullDoc = context.state.doc.toString();
|
|
3274
|
+
const fullPos = context.pos;
|
|
3275
|
+
const lineStart = fullDoc.lastIndexOf("\n", Math.max(0, fullPos - 1)) + 1;
|
|
3276
|
+
const nextNl = fullDoc.indexOf("\n", fullPos);
|
|
3277
|
+
const lineEnd = nextNl === -1 ? fullDoc.length : nextNl;
|
|
3278
|
+
const lineText = fullDoc.slice(lineStart, lineEnd);
|
|
3279
|
+
const priorBindings = collectPriorBindings(fullDoc, lineStart);
|
|
3280
|
+
const dollarMatch = context.matchBefore(/\$[A-Za-z_0-9-]*/);
|
|
3281
|
+
if (dollarMatch) {
|
|
3282
|
+
const typedToken = replaceSelection ? "$" : dollarMatch.text;
|
|
3283
|
+
return finish(context, dollarMatch.from, TOKEN_COMPLETION_OPTIONS, typedToken);
|
|
3284
|
+
}
|
|
3285
|
+
const defaultsM = /^(\s*defaults\s+)/.exec(lineText);
|
|
3286
|
+
if (defaultsM && fullPos >= lineStart + defaultsM[1].length) {
|
|
3287
|
+
const subOffset = lineStart + defaultsM[1].length;
|
|
3288
|
+
const subDoc = ":segment{} " + fullDoc.slice(subOffset, lineEnd);
|
|
3289
|
+
const shift = ":segment{} ".length - subOffset;
|
|
3290
|
+
const subPos = fullPos + shift;
|
|
3291
|
+
const word2 = context.matchBefore(/[\w.*-]*/) ?? { from: fullPos, text: "" };
|
|
3292
|
+
const typed2 = replaceSelection ? "" : word2.text;
|
|
3293
|
+
const scan = scanContext(subDoc, subPos);
|
|
3294
|
+
const subCtx2 = new autocomplete.CompletionContext(state.EditorState.create({ doc: subDoc }), subPos, context.explicit);
|
|
3295
|
+
const res = suffixSlot(subCtx2, subDoc, scan, word2.from + shift, typed2, { family: "aggregate" }, ":segment{}".length);
|
|
3296
|
+
return res && { ...res, from: res.from - shift, ...res.to !== void 0 ? { to: res.to - shift } : {} };
|
|
3297
|
+
}
|
|
3298
|
+
const showM = /^(\s*show\s+)/.exec(lineText);
|
|
3299
|
+
if (showM && fullPos >= lineStart + showM[1].length) {
|
|
3300
|
+
const showBodyOffset = lineStart + showM[1].length;
|
|
3301
|
+
const showStages = splitLinePipeRanges(lineText.slice(showM[1].length), showBodyOffset);
|
|
3302
|
+
const showActiveIdx = Math.max(0, showStages.findIndex((s) => fullPos >= s.from && fullPos <= s.to));
|
|
3303
|
+
if (showActiveIdx === 0) {
|
|
3304
|
+
const word3 = context.matchBefore(/[\w.*-]*/) ?? { from: fullPos, text: "" };
|
|
3305
|
+
const typed3 = replaceSelection ? "" : word3.text;
|
|
3306
|
+
return finish(context, word3.from, priorBindings, typed3);
|
|
3307
|
+
}
|
|
3308
|
+
const sinkStage = showStages[showActiveIdx];
|
|
3309
|
+
const stageDoc = fullDoc.slice(sinkStage.from, lineEnd);
|
|
3310
|
+
const stagePos = fullPos - sinkStage.from;
|
|
3311
|
+
const word2 = context.matchBefore(/[\w.*-]*/) ?? { from: fullPos, text: "" };
|
|
3312
|
+
const typed2 = replaceSelection ? "" : word2.text;
|
|
3313
|
+
const scan = scanContext(stageDoc, stagePos);
|
|
3314
|
+
const colonHeadM = /^[ \t]*:[ \t]*([A-Za-z][\w-]*)/.exec(stageDoc);
|
|
3315
|
+
if (scan.depth > 0 && colonHeadM) {
|
|
3316
|
+
const head = resolveColonHead(colonHeadM[1]);
|
|
3317
|
+
const subCtx2 = new autocomplete.CompletionContext(state.EditorState.create({ doc: stageDoc }), stagePos, context.explicit);
|
|
3318
|
+
const res = braceSlot(subCtx2, stageDoc, scan, word2.from - sinkStage.from, typed2, head);
|
|
3319
|
+
const rebase = (r) => r && { ...r, from: r.from + sinkStage.from, ...r.to !== void 0 ? { to: r.to + sinkStage.from } : {} };
|
|
3320
|
+
return res instanceof Promise ? res.then(rebase) : rebase(res);
|
|
3321
|
+
}
|
|
3322
|
+
const colonTyped2 = word2.from > sinkStage.from && fullDoc[word2.from - 1] === ":";
|
|
3323
|
+
const headFrom2 = colonTyped2 ? word2.from : word2.from;
|
|
3324
|
+
return finish(context, headFrom2, CHART_HEAD_OPTIONS.map((o) => headApply(o, colonTyped2)), typed2);
|
|
3325
|
+
}
|
|
3326
|
+
const assignM = /^(\s*[A-Za-z_][\w-]*\s*=\s*)/.exec(lineText);
|
|
3327
|
+
const exprOffset = assignM && fullPos >= lineStart + assignM[1].length ? lineStart + assignM[1].length : lineStart;
|
|
3328
|
+
const exprDoc = fullDoc.slice(exprOffset, lineEnd);
|
|
3329
|
+
const exprPos = fullPos - exprOffset;
|
|
3330
|
+
const word = context.matchBefore(/[\w.*-]*/) ?? { from: fullPos, to: fullPos, text: "" };
|
|
3331
|
+
const opens = word.from === word.to && fullPos > exprOffset && (/[{,:|(!"@]/.test(fullDoc[fullPos - 1]) || /\|[ \t]*$/.test(exprDoc.slice(0, exprPos)));
|
|
1807
3332
|
if (word.from === word.to && !context.explicit && !opens) return null;
|
|
1808
|
-
const
|
|
1809
|
-
if (
|
|
3333
|
+
const fullScan = scanContext(exprDoc, exprPos);
|
|
3334
|
+
if (fullScan.quoted && fullScan.depth === 0) return null;
|
|
1810
3335
|
const typed = replaceSelection ? "" : word.text;
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
const
|
|
1817
|
-
const
|
|
3336
|
+
const stages = splitLinePipeRanges(exprDoc, 0);
|
|
3337
|
+
let activeStageIdx = stages.findIndex((s) => exprPos >= s.from && exprPos <= s.to);
|
|
3338
|
+
if (activeStageIdx === -1) activeStageIdx = stages.length - 1;
|
|
3339
|
+
const activeStage = stages[activeStageIdx];
|
|
3340
|
+
const firstStageText = stages[0].text;
|
|
3341
|
+
const firstColonM = /^[ \t]*:[ \t]*([A-Za-z][\w-]*)/.exec(firstStageText);
|
|
3342
|
+
const firstHeadM = firstColonM ?? /^[ \t]*([A-Za-z]+)[ \t]*:[ \t]*([A-Za-z0-9_.-]*)/.exec(firstStageText);
|
|
3343
|
+
const firstHead = firstColonM ? resolveColonHead(firstColonM[1]) : firstHeadM ? resolveHead(firstHeadM[1], firstHeadM[2]) : { family: "aggregate" };
|
|
3344
|
+
if (activeStageIdx > 0) {
|
|
3345
|
+
const stageText = activeStage.text;
|
|
3346
|
+
const stagePos = exprPos - activeStage.from;
|
|
3347
|
+
const stageAbsOffset = exprOffset + activeStage.from;
|
|
3348
|
+
const stageColonM = /^[ \t]*:[ \t]*([A-Za-z][\w-]*)/.exec(stageText);
|
|
3349
|
+
if (stageColonM || /^[ \t]*:[ \t]*$/.test(stageText.slice(0, stagePos))) {
|
|
3350
|
+
const stageScan = scanContext(stageText, stagePos);
|
|
3351
|
+
const stageHead = stageColonM ? resolveColonHead(stageColonM[1]) : { family: "aggregate" };
|
|
3352
|
+
const subCtx3 = new autocomplete.CompletionContext(state.EditorState.create({ doc: stageText }), stagePos, context.explicit);
|
|
3353
|
+
const rebase = (r) => r && { ...r, from: r.from + stageAbsOffset, ...r.to !== void 0 ? { to: r.to + stageAbsOffset } : {} };
|
|
3354
|
+
if (stageScan.depth > 0) {
|
|
3355
|
+
const res3 = braceSlot(subCtx3, stageText, stageScan, word.from - stageAbsOffset, typed, stageHead);
|
|
3356
|
+
return res3 instanceof Promise ? res3.then(rebase) : rebase(res3);
|
|
3357
|
+
}
|
|
3358
|
+
if (!stageColonM || stagePos <= stageColonM[0].length) {
|
|
3359
|
+
return finish(context, word.from, PIPELINE_STAGE_HEAD_OPTIONS.map((o) => headApply(o, true)), typed);
|
|
3360
|
+
}
|
|
3361
|
+
const res2 = suffixSlot(subCtx3, stageText, stageScan, word.from - stageAbsOffset, typed, stageHead, stageColonM[0].length);
|
|
3362
|
+
return rebase(res2);
|
|
3363
|
+
}
|
|
3364
|
+
const subCtx2 = new autocomplete.CompletionContext(state.EditorState.create({ doc: exprDoc }), exprPos, context.explicit);
|
|
3365
|
+
const res = pipeSlot(subCtx2, exprDoc, fullScan, word.from - exprOffset, typed, firstHead);
|
|
3366
|
+
return res && { ...res, from: res.from + exprOffset, ...res.to !== void 0 ? { to: res.to + exprOffset } : {} };
|
|
3367
|
+
}
|
|
3368
|
+
const subCtx = new autocomplete.CompletionContext(state.EditorState.create({ doc: exprDoc }), exprPos, context.explicit);
|
|
3369
|
+
const rebaseExpr = (r) => r && { ...r, from: r.from + exprOffset, ...r.to !== void 0 ? { to: r.to + exprOffset } : {} };
|
|
3370
|
+
if (fullScan.depth > 0) {
|
|
3371
|
+
const res = braceSlot(subCtx, exprDoc, fullScan, word.from - exprOffset, typed, firstHead);
|
|
3372
|
+
return res instanceof Promise ? res.then(rebaseExpr) : rebaseExpr(res);
|
|
3373
|
+
}
|
|
3374
|
+
if (priorBindings.length > 0 && /[+\-*/(]\s*[\w-]*$/.test(exprDoc.slice(0, exprPos))) {
|
|
3375
|
+
return finish(context, word.from, priorBindings, typed);
|
|
3376
|
+
}
|
|
3377
|
+
const datasetTyped = word.from > exprOffset && fullDoc[word.from - 1] === "@";
|
|
3378
|
+
const colonTyped = word.from > exprOffset && fullDoc[word.from - 1] === ":";
|
|
3379
|
+
const docKeywords = exprOffset === lineStart && lineStart > 0 ? [
|
|
3380
|
+
{ label: "defaults", apply: "defaults ", detail: "block defaults", type: "keyword" },
|
|
3381
|
+
{ label: "show", apply: "show ", detail: "display outputs", type: "keyword" }
|
|
3382
|
+
] : [];
|
|
3383
|
+
const headPool = datasetTyped ? HEAD_OPTIONS.filter((o) => o.label.startsWith("@")) : colonTyped ? HEAD_OPTIONS : [...priorBindings, ...docKeywords, ...HEAD_OPTIONS];
|
|
1818
3384
|
const headTyped = datasetTyped ? "@" + typed : typed;
|
|
1819
3385
|
const headFrom = datasetTyped ? word.from - 1 : word.from;
|
|
1820
|
-
if (!
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
3386
|
+
if (!firstHeadM) {
|
|
3387
|
+
return finish(
|
|
3388
|
+
context,
|
|
3389
|
+
headFrom,
|
|
3390
|
+
headPool.map((o) => o.type === "variable" || o.label === "defaults" || o.label === "show" ? o : headApply(o, colonTyped)),
|
|
3391
|
+
headTyped
|
|
3392
|
+
);
|
|
3393
|
+
}
|
|
3394
|
+
if (firstColonM) {
|
|
3395
|
+
if (exprPos <= firstColonM[0].length) {
|
|
3396
|
+
return finish(context, headFrom, HEAD_OPTIONS.map((o) => headApply(o, true)), headTyped);
|
|
1824
3397
|
}
|
|
1825
|
-
return suffixSlot(
|
|
3398
|
+
return rebaseExpr(suffixSlot(subCtx, exprDoc, fullScan, word.from - exprOffset, typed, firstHead, firstColonM[0].length));
|
|
1826
3399
|
}
|
|
1827
|
-
const colonIdx =
|
|
1828
|
-
if (
|
|
1829
|
-
if (
|
|
1830
|
-
|
|
3400
|
+
const colonIdx = firstHeadM[0].indexOf(":");
|
|
3401
|
+
if (exprPos <= colonIdx) return finish(context, word.from, [...priorBindings, ...chained(LEGACY_HEAD_OPTIONS, ":")], typed);
|
|
3402
|
+
if (exprPos <= firstHeadM[0].length) {
|
|
3403
|
+
const res = metricHeadSlot(subCtx, word.from - exprOffset, typed);
|
|
3404
|
+
return res instanceof Promise ? res.then(rebaseExpr) : rebaseExpr(res);
|
|
1831
3405
|
}
|
|
1832
|
-
return suffixSlot(
|
|
3406
|
+
return rebaseExpr(suffixSlot(subCtx, exprDoc, fullScan, word.from - exprOffset, typed, firstHead, firstHeadM[0].length));
|
|
1833
3407
|
}
|
|
1834
3408
|
function source(context) {
|
|
1835
3409
|
const selection = context.state.selection.main;
|
|
@@ -1996,7 +3570,7 @@ function zonedNoon(iso, timeZone) {
|
|
|
1996
3570
|
return zonedStartOfDay(iso, timeZone) + 12 * 36e5;
|
|
1997
3571
|
}
|
|
1998
3572
|
function resolveWindowRange(w, ctx) {
|
|
1999
|
-
if (!w) return void 0;
|
|
3573
|
+
if (!w || w.kind === "param") return void 0;
|
|
2000
3574
|
if (w.kind === "relative") {
|
|
2001
3575
|
const today = civilDateOf(ctx.instant, ctx.timeZone);
|
|
2002
3576
|
const start2 = w.unit === "d" ? civilDateAdd(today, -(w.size - 1)) : civilDateAdd(civilMonday(today), -7 * (w.size - 1));
|
|
@@ -2024,176 +3598,6 @@ function inRange(instant, range) {
|
|
|
2024
3598
|
return instant <= range.end;
|
|
2025
3599
|
}
|
|
2026
3600
|
|
|
2027
|
-
// src/document.ts
|
|
2028
|
-
var FORMULA_CHARS = /^[\w\s.+\-*/(),]+$/;
|
|
2029
|
-
function referencesOf(expression, known) {
|
|
2030
|
-
const refs = [];
|
|
2031
|
-
for (const match of expression.matchAll(/[A-Za-z_][\w-]*/g)) {
|
|
2032
|
-
const name = match[0];
|
|
2033
|
-
if (known.has(name) && !refs.includes(name)) refs.push(name);
|
|
2034
|
-
}
|
|
2035
|
-
return refs;
|
|
2036
|
-
}
|
|
2037
|
-
function parseDocument(text) {
|
|
2038
|
-
const diagnostics = [];
|
|
2039
|
-
const lines = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("//"));
|
|
2040
|
-
const defaults = {};
|
|
2041
|
-
let hasDefaults = false;
|
|
2042
|
-
const assignments = [];
|
|
2043
|
-
let show;
|
|
2044
|
-
let degenerate = false;
|
|
2045
|
-
const known = /* @__PURE__ */ new Set();
|
|
2046
|
-
for (const line of lines) {
|
|
2047
|
-
if (line.startsWith("defaults ")) {
|
|
2048
|
-
hasDefaults = true;
|
|
2049
|
-
const rest = line.slice("defaults ".length).trim();
|
|
2050
|
-
const byMatch = /by\s*\{([^}]*)\}/.exec(rest);
|
|
2051
|
-
if (byMatch) {
|
|
2052
|
-
defaults.groupBy = byMatch[1].split(",").map((d) => d.trim()).filter(Boolean);
|
|
2053
|
-
}
|
|
2054
|
-
const lastMatch = /last\s+(\d+)\s*(d|w)/.exec(rest);
|
|
2055
|
-
if (lastMatch) {
|
|
2056
|
-
defaults.window = { kind: "relative", size: Number(lastMatch[1]), unit: lastMatch[2] };
|
|
2057
|
-
}
|
|
2058
|
-
const fromMatch = /from\s+(\d{4}-\d{2}-\d{2})(?:\s+to\s+(\d{4}-\d{2}-\d{2}))?/.exec(rest);
|
|
2059
|
-
if (fromMatch) {
|
|
2060
|
-
defaults.window = { kind: "range", start: fromMatch[1], end: fromMatch[2] };
|
|
2061
|
-
}
|
|
2062
|
-
continue;
|
|
2063
|
-
}
|
|
2064
|
-
if (line.startsWith("show ")) {
|
|
2065
|
-
show = line.slice("show ".length).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2066
|
-
continue;
|
|
2067
|
-
}
|
|
2068
|
-
const assign = /^([A-Za-z_][\w-]*)\s*=\s*(.+)$/.exec(line);
|
|
2069
|
-
if (assign) {
|
|
2070
|
-
const name = assign[1];
|
|
2071
|
-
const body = assign[2].trim();
|
|
2072
|
-
known.add(name);
|
|
2073
|
-
const unitMatch = /->\s*([\w/%.]+)\s*$/.exec(body);
|
|
2074
|
-
const normalizeMatch = /\(normalize\s+([^)]+)\)/.exec(body);
|
|
2075
|
-
const expression = body.replace(/->\s*[\w/%.]+\s*$/, "").replace(/\(normalize\s+[^)]+\)/, "").trim();
|
|
2076
|
-
const looksLikeQuery = /^(sum|avg|min|max|count|last|delta|find|rows)\b|^[:@|]/.test(expression);
|
|
2077
|
-
if (looksLikeQuery) {
|
|
2078
|
-
const parsed = parseQuery(expression);
|
|
2079
|
-
if (!parsed.error) {
|
|
2080
|
-
assignments.push({ name, kind: "query", queryText: expression, parsed, raw: line });
|
|
2081
|
-
} else {
|
|
2082
|
-
diagnostics.push(`Assignment "${name}" is not a valid query`);
|
|
2083
|
-
}
|
|
2084
|
-
continue;
|
|
2085
|
-
}
|
|
2086
|
-
if (!FORMULA_CHARS.test(expression)) {
|
|
2087
|
-
diagnostics.push(`Assignment "${name}" has characters a formula cannot parse`);
|
|
2088
|
-
continue;
|
|
2089
|
-
}
|
|
2090
|
-
const formula = {
|
|
2091
|
-
expression,
|
|
2092
|
-
references: [],
|
|
2093
|
-
...normalizeMatch ? { normalize: normalizeMatch[1].trim() } : {},
|
|
2094
|
-
...unitMatch ? { unit: unitMatch[1] } : {}
|
|
2095
|
-
};
|
|
2096
|
-
assignments.push({ name, kind: "formula", formula, raw: line });
|
|
2097
|
-
continue;
|
|
2098
|
-
}
|
|
2099
|
-
degenerate = true;
|
|
2100
|
-
}
|
|
2101
|
-
if (!hasDefaults && show === void 0 && !degenerate && assignments.length === 1 && assignments[0].kind === "query") ;
|
|
2102
|
-
if (!hasDefaults && show === void 0 && assignments.length === 0) {
|
|
2103
|
-
const parsed = parseQuery(lines.join(" "));
|
|
2104
|
-
if (!parsed.error) {
|
|
2105
|
-
known.add("query");
|
|
2106
|
-
assignments.push({ name: "query", kind: "query", queryText: lines.join(" "), parsed, raw: lines.join(" ") });
|
|
2107
|
-
show = ["query"];
|
|
2108
|
-
degenerate = true;
|
|
2109
|
-
} else {
|
|
2110
|
-
diagnostics.push("Body is neither a valid query nor a valid document");
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
for (const assignment of assignments) {
|
|
2114
|
-
if (assignment.kind !== "formula" || !assignment.formula) continue;
|
|
2115
|
-
assignment.formula.references = referencesOf(assignment.formula.expression, known);
|
|
2116
|
-
for (const match of assignment.formula.expression.matchAll(/[A-Za-z_][\w-]*/g)) {
|
|
2117
|
-
const name = match[0];
|
|
2118
|
-
if (!known.has(name)) {
|
|
2119
|
-
diagnostics.push(`Formula "${assignment.name}" references unknown name "${name}"`);
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
if (assignment.formula.references.length === 0) {
|
|
2123
|
-
diagnostics.push(`Formula "${assignment.name}" references no named query or formula`);
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
const cycle = findCycle(assignments);
|
|
2127
|
-
if (cycle) {
|
|
2128
|
-
diagnostics.push(`Reference cycle detected: ${cycle.join(" -> ")}`);
|
|
2129
|
-
}
|
|
2130
|
-
if (show === void 0) {
|
|
2131
|
-
show = assignments.filter((a) => a.kind === "query").map((a) => a.name);
|
|
2132
|
-
if (assignments.length > 0 && show.length === 0) show = [assignments[0].name];
|
|
2133
|
-
} else {
|
|
2134
|
-
for (const name of show) {
|
|
2135
|
-
if (!known.has(name)) diagnostics.push(`show references unknown name "${name}"`);
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
return {
|
|
2139
|
-
doc: {
|
|
2140
|
-
...Object.keys(defaults).length > 0 ? { defaults } : {},
|
|
2141
|
-
assignments,
|
|
2142
|
-
show,
|
|
2143
|
-
degenerate
|
|
2144
|
-
},
|
|
2145
|
-
diagnostics
|
|
2146
|
-
};
|
|
2147
|
-
}
|
|
2148
|
-
function findCycle(assignments) {
|
|
2149
|
-
const formulas = new Map(assignments.filter((a) => a.kind === "formula").map((a) => [a.name, a.formula.references]));
|
|
2150
|
-
const state = /* @__PURE__ */ new Map();
|
|
2151
|
-
const stack = [];
|
|
2152
|
-
const visit = (name) => {
|
|
2153
|
-
const s = state.get(name);
|
|
2154
|
-
if (s === 2) return void 0;
|
|
2155
|
-
if (s === 1) {
|
|
2156
|
-
const start = stack.indexOf(name);
|
|
2157
|
-
return [...stack.slice(start === -1 ? 0 : start), name];
|
|
2158
|
-
}
|
|
2159
|
-
state.set(name, 1);
|
|
2160
|
-
stack.push(name);
|
|
2161
|
-
for (const ref of formulas.get(name) ?? []) {
|
|
2162
|
-
const cycle = visit(ref);
|
|
2163
|
-
if (cycle) return cycle;
|
|
2164
|
-
}
|
|
2165
|
-
stack.pop();
|
|
2166
|
-
state.set(name, 2);
|
|
2167
|
-
return void 0;
|
|
2168
|
-
};
|
|
2169
|
-
for (const name of formulas.keys()) {
|
|
2170
|
-
const cycle = visit(name);
|
|
2171
|
-
if (cycle) return cycle;
|
|
2172
|
-
}
|
|
2173
|
-
return void 0;
|
|
2174
|
-
}
|
|
2175
|
-
function serializeDocument(doc) {
|
|
2176
|
-
if (doc.degenerate) {
|
|
2177
|
-
const only = doc.assignments[0];
|
|
2178
|
-
return only?.queryText ?? "";
|
|
2179
|
-
}
|
|
2180
|
-
const lines = [];
|
|
2181
|
-
if (doc.defaults) {
|
|
2182
|
-
const parts = [];
|
|
2183
|
-
if (doc.defaults.groupBy?.length) parts.push(`by {${doc.defaults.groupBy.join(", ")}}`);
|
|
2184
|
-
if (doc.defaults.window) {
|
|
2185
|
-
const w = doc.defaults.window;
|
|
2186
|
-
parts.push(w.kind === "relative" ? `last ${w.size}${w.unit}` : `from ${w.start}${w.end ? ` to ${w.end}` : ""}`);
|
|
2187
|
-
}
|
|
2188
|
-
if (parts.length > 0) lines.push(`defaults ${parts.join(" ")}`);
|
|
2189
|
-
}
|
|
2190
|
-
for (const a of doc.assignments) {
|
|
2191
|
-
lines.push(a.raw);
|
|
2192
|
-
}
|
|
2193
|
-
if (doc.show.length > 0) lines.push(`show ${doc.show.join(", ")}`);
|
|
2194
|
-
return lines.join("\n");
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
3601
|
// src/documentRunner.ts
|
|
2198
3602
|
function createBuiltinFormulaEvaluator() {
|
|
2199
3603
|
const evalExpr = (expr, inputs, position) => {
|
|
@@ -2281,6 +3685,8 @@ function appendSuffixes(queryText, window, groupBy) {
|
|
|
2281
3685
|
}
|
|
2282
3686
|
if (window.kind === "relative" && !/\blast\b/.test(text)) {
|
|
2283
3687
|
text += ` last ${window.size}${window.unit}`;
|
|
3688
|
+
} else if (window.kind === "param" && !/\blast\b/.test(text)) {
|
|
3689
|
+
text += ` last $${window.param}${window.unit ? ` ${window.unit}` : ""}`;
|
|
2284
3690
|
} else if (window.kind === "range" && !/\bfrom\b/.test(text)) {
|
|
2285
3691
|
text += ` from ${window.start}${window.end ? ` to ${window.end}` : ""}`;
|
|
2286
3692
|
}
|
|
@@ -2302,6 +3708,7 @@ var QueryDocumentRunner = class {
|
|
|
2302
3708
|
const doc = parsed.doc;
|
|
2303
3709
|
const outputs = [];
|
|
2304
3710
|
const values = /* @__PURE__ */ new Map();
|
|
3711
|
+
const findPayloads = /* @__PURE__ */ new Map();
|
|
2305
3712
|
const evaluate = async (assignment) => {
|
|
2306
3713
|
if (assignment.kind === "query") {
|
|
2307
3714
|
const merged = this.mergeDefaults(assignment, doc);
|
|
@@ -2311,6 +3718,7 @@ var QueryDocumentRunner = class {
|
|
|
2311
3718
|
const findRun = await this.options?.runFind?.(text2) ?? {
|
|
2312
3719
|
error: "find queries are not supported by this host"
|
|
2313
3720
|
};
|
|
3721
|
+
findPayloads.set(assignment.name, { payload: findRun, queryText: text2 });
|
|
2314
3722
|
outputs.push({
|
|
2315
3723
|
name: assignment.name,
|
|
2316
3724
|
kind: "find",
|
|
@@ -2327,18 +3735,43 @@ var QueryDocumentRunner = class {
|
|
|
2327
3735
|
return;
|
|
2328
3736
|
}
|
|
2329
3737
|
if (parsedFamily === "pipeline") {
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
3738
|
+
let pipelineRun;
|
|
3739
|
+
if (assignment.sourceRef && (assignment.pipeTail ?? assignment.transformsText)) {
|
|
3740
|
+
const rawTail = assignment.pipeTail ?? assignment.transformsText;
|
|
3741
|
+
const tailWithDefaults = merged.window ? appendSuffixes(rawTail, merged.window, merged.groupBy) : merged.groupBy?.length && !/by\s*\{/.test(rawTail) ? `${rawTail} by {${merged.groupBy.join(", ")}}` : rawTail;
|
|
3742
|
+
const sourceEntry = findPayloads.get(assignment.sourceRef);
|
|
3743
|
+
if (sourceEntry && this.options?.runPipelineFromFind) {
|
|
3744
|
+
pipelineRun = await this.options.runPipelineFromFind(sourceEntry.payload, tailWithDefaults);
|
|
3745
|
+
} else if (sourceEntry && this.options?.runAggregateEvents) {
|
|
3746
|
+
pipelineRun = await this.options.runAggregateEvents(tailWithDefaults, sourceEntry.payload.events ?? [], sourceEntry.payload);
|
|
3747
|
+
} else if (sourceEntry) {
|
|
3748
|
+
pipelineRun = await this.options?.runPipeline?.(`${sourceEntry.queryText} | ${tailWithDefaults}`) ?? {
|
|
3749
|
+
error: "pipeline queries are not supported by this host"
|
|
3750
|
+
};
|
|
3751
|
+
} else {
|
|
3752
|
+
pipelineRun = { error: `Source "${assignment.sourceRef}" is not a valid list source` };
|
|
3753
|
+
}
|
|
3754
|
+
} else {
|
|
3755
|
+
const text2 = merged.window ? appendSuffixes(merged.queryText, merged.window, merged.groupBy) : merged.queryText;
|
|
3756
|
+
pipelineRun = await this.options?.runPipeline?.(text2) ?? {
|
|
3757
|
+
error: "pipeline queries are not supported by this host"
|
|
3758
|
+
};
|
|
3759
|
+
}
|
|
2334
3760
|
const byPosition3 = /* @__PURE__ */ new Map();
|
|
2335
3761
|
for (const s of pipelineRun.series ?? []) {
|
|
2336
3762
|
for (const point of s.points) {
|
|
2337
3763
|
if (!point.missing) byPosition3.set(point.ts, { value: point.value, missingDerived: false });
|
|
2338
3764
|
}
|
|
2339
3765
|
}
|
|
3766
|
+
const lastTransform = assignment.parsed?.family === "pipeline" ? assignment.parsed.transforms[assignment.parsed.transforms.length - 1] : void 0;
|
|
3767
|
+
const hasBuckets2 = Boolean(merged.groupBy && merged.groupBy.length > 0 || lastTransform?.groupBy?.length || lastTransform?.rollup);
|
|
2340
3768
|
if (byPosition3.size > 0) {
|
|
2341
|
-
values.set(assignment.name, {
|
|
3769
|
+
values.set(assignment.name, {
|
|
3770
|
+
byPosition: byPosition3,
|
|
3771
|
+
unit: pipelineRun.unit,
|
|
3772
|
+
groupKeys: (pipelineRun.series ?? []).map((s) => s.key),
|
|
3773
|
+
isScalar: !hasBuckets2 && byPosition3.size === 1
|
|
3774
|
+
});
|
|
2342
3775
|
}
|
|
2343
3776
|
outputs.push({
|
|
2344
3777
|
name: assignment.name,
|
|
@@ -2370,7 +3803,13 @@ var QueryDocumentRunner = class {
|
|
|
2370
3803
|
}
|
|
2371
3804
|
}
|
|
2372
3805
|
}
|
|
2373
|
-
|
|
3806
|
+
const hasBuckets = Boolean(merged.groupBy && merged.groupBy.length > 0 || assignment.parsed?.family === "aggregate" && assignment.parsed.rollup);
|
|
3807
|
+
values.set(assignment.name, {
|
|
3808
|
+
byPosition: byPosition2,
|
|
3809
|
+
unit: run.unit,
|
|
3810
|
+
groupKeys: series2.map((s) => s.key),
|
|
3811
|
+
isScalar: !hasBuckets && byPosition2.size === 1
|
|
3812
|
+
});
|
|
2374
3813
|
outputs.push({
|
|
2375
3814
|
name: assignment.name,
|
|
2376
3815
|
kind: "aggregate",
|
|
@@ -2385,14 +3824,30 @@ var QueryDocumentRunner = class {
|
|
|
2385
3824
|
const formula = assignment.formula;
|
|
2386
3825
|
const inputs = {};
|
|
2387
3826
|
const positions = /* @__PURE__ */ new Set();
|
|
3827
|
+
const seriesRefs = formula.references.filter((ref) => {
|
|
3828
|
+
const v = values.get(ref);
|
|
3829
|
+
return v !== void 0 && !v.isScalar;
|
|
3830
|
+
});
|
|
2388
3831
|
for (const ref of formula.references) {
|
|
2389
3832
|
const input = values.get(ref);
|
|
2390
3833
|
if (!input) {
|
|
2391
3834
|
diagnostics.push(`Formula "${assignment.name}" references unavailable input "${ref}"`);
|
|
2392
3835
|
return;
|
|
2393
3836
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
3837
|
+
const scalarFallback = input.isScalar ? [...input.byPosition.values()][0] : void 0;
|
|
3838
|
+
if (!input.isScalar) {
|
|
3839
|
+
for (const position of input.byPosition.keys()) positions.add(position);
|
|
3840
|
+
}
|
|
3841
|
+
inputs[ref] = (position) => input.byPosition.get(position) ?? scalarFallback;
|
|
3842
|
+
}
|
|
3843
|
+
if (positions.size === 0 && formula.references.length > 0) {
|
|
3844
|
+
for (const ref of formula.references) {
|
|
3845
|
+
const anchorTs = [...values.get(ref)?.byPosition.keys() ?? []][0];
|
|
3846
|
+
if (anchorTs !== void 0) {
|
|
3847
|
+
positions.add(anchorTs);
|
|
3848
|
+
break;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
2396
3851
|
}
|
|
2397
3852
|
const byPosition = /* @__PURE__ */ new Map();
|
|
2398
3853
|
let error;
|
|
@@ -2418,7 +3873,7 @@ var QueryDocumentRunner = class {
|
|
|
2418
3873
|
pairs: pairCount,
|
|
2419
3874
|
error: "corr needs at least two paired positions"
|
|
2420
3875
|
});
|
|
2421
|
-
values.set(assignment.name, { byPosition: /* @__PURE__ */ new Map(), groupKeys: [] });
|
|
3876
|
+
values.set(assignment.name, { byPosition: /* @__PURE__ */ new Map(), groupKeys: [], isScalar: false });
|
|
2422
3877
|
return;
|
|
2423
3878
|
}
|
|
2424
3879
|
const mean = (xs) => xs.reduce((s, v) => s + v, 0) / xs.length;
|
|
@@ -2437,13 +3892,13 @@ var QueryDocumentRunner = class {
|
|
|
2437
3892
|
pairs: pairCount,
|
|
2438
3893
|
error: "corr is undefined for zero-variance inputs"
|
|
2439
3894
|
});
|
|
2440
|
-
values.set(assignment.name, { byPosition: /* @__PURE__ */ new Map(), groupKeys: [] });
|
|
3895
|
+
values.set(assignment.name, { byPosition: /* @__PURE__ */ new Map(), groupKeys: [], isScalar: false });
|
|
2441
3896
|
return;
|
|
2442
3897
|
}
|
|
2443
3898
|
const value = cov / Math.sqrt(va * vb);
|
|
2444
3899
|
byPosition.set(0, { value, missingDerived: false });
|
|
2445
3900
|
outputs.push({ name: assignment.name, kind: "formula", pairs: pairCount, series: [{ key: "corr", label: "corr", points: [{ ts: 0, value }] }] });
|
|
2446
|
-
values.set(assignment.name, { byPosition, groupKeys: ["corr"] });
|
|
3901
|
+
values.set(assignment.name, { byPosition, groupKeys: ["corr"], isScalar: true });
|
|
2447
3902
|
return;
|
|
2448
3903
|
}
|
|
2449
3904
|
for (const position of [...positions].sort((a, b) => a - b)) {
|
|
@@ -2463,7 +3918,7 @@ var QueryDocumentRunner = class {
|
|
|
2463
3918
|
...v.missingDerived ? { missingDerived: true } : {}
|
|
2464
3919
|
}))
|
|
2465
3920
|
}];
|
|
2466
|
-
values.set(assignment.name, { byPosition, unit: formula.unit, groupKeys: [assignment.name] });
|
|
3921
|
+
values.set(assignment.name, { byPosition, unit: formula.unit, groupKeys: [assignment.name], isScalar: seriesRefs.length === 0 && byPosition.size === 1 });
|
|
2467
3922
|
outputs.push({
|
|
2468
3923
|
name: assignment.name,
|
|
2469
3924
|
kind: "formula",
|
|
@@ -2479,7 +3934,7 @@ var QueryDocumentRunner = class {
|
|
|
2479
3934
|
progressed = false;
|
|
2480
3935
|
for (let i = pending.length - 1; i >= 0; i--) {
|
|
2481
3936
|
const assignment = pending[i];
|
|
2482
|
-
const refs = assignment.formula?.references ?? [];
|
|
3937
|
+
const refs = assignment.formula?.references ?? (assignment.sourceRef ? [assignment.sourceRef] : []);
|
|
2483
3938
|
if (refs.every((r) => evaluated.has(r))) {
|
|
2484
3939
|
await evaluate(assignment);
|
|
2485
3940
|
evaluated.add(assignment.name);
|
|
@@ -2488,27 +3943,197 @@ var QueryDocumentRunner = class {
|
|
|
2488
3943
|
}
|
|
2489
3944
|
}
|
|
2490
3945
|
}
|
|
2491
|
-
const
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
3946
|
+
const byName = new Map(outputs.map((o) => [o.name, o]));
|
|
3947
|
+
const orderedOutputs = doc.show.map((name) => byName.get(name)).filter((o) => o !== void 0).map((o) => doc.showChart && !o.chart ? { ...o, chart: doc.showChart } : o);
|
|
3948
|
+
return {
|
|
3949
|
+
outputs: orderedOutputs,
|
|
3950
|
+
diagnostics,
|
|
3951
|
+
...doc.showChart ? { showChart: doc.showChart } : {}
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
/** Defaults precedence (ticket 17): explicit per-query range/grouping >
|
|
3955
|
+
* block defaults > host. Explicit per-query by{} fully replaces block
|
|
3956
|
+
* grouping — never merges. */
|
|
3957
|
+
mergeDefaults(assignment, doc) {
|
|
3958
|
+
const parsed = assignment.parsed;
|
|
3959
|
+
const pipelineTransformGroupBy = parsed?.family === "pipeline" ? parsed.transforms.find((t2) => t2.groupBy.length > 0)?.groupBy : void 0;
|
|
3960
|
+
const aggregateGroupBy = parsed?.family === "aggregate" ? parsed.groupBy : pipelineTransformGroupBy;
|
|
3961
|
+
const groupBy = aggregateGroupBy?.length ? aggregateGroupBy : doc.defaults?.groupBy;
|
|
3962
|
+
const window = (parsed && parsed.family !== "pipeline" ? parsed.window : void 0) ?? doc.defaults?.window;
|
|
3963
|
+
return {
|
|
3964
|
+
queryText: assignment.queryText ?? "",
|
|
3965
|
+
...groupBy ? { groupBy } : {},
|
|
3966
|
+
...window ? { window } : {}
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
};
|
|
3970
|
+
|
|
3971
|
+
// src/dashboard/model.ts
|
|
3972
|
+
function resolveWidgetType(type) {
|
|
3973
|
+
return type == null || type === "" ? "table" : type;
|
|
3974
|
+
}
|
|
3975
|
+
function unknownWidgetTypeMessage(type) {
|
|
3976
|
+
return `unknown widget type "${type}"`;
|
|
3977
|
+
}
|
|
3978
|
+
function unknownTokensMessage(missing) {
|
|
3979
|
+
return `unknown token${missing.length > 1 ? "s" : ""}: ${missing.map((m) => `$${m}`).join(", ")}`;
|
|
3980
|
+
}
|
|
3981
|
+
var PLANNED_WIDGET_TYPES = [];
|
|
3982
|
+
var KNOWN_CALC_METRICS = new Set(WQL_CALC_TARGETS);
|
|
3983
|
+
function isProposedMetric(metricKey) {
|
|
3984
|
+
if (!metricKey) return false;
|
|
3985
|
+
if (!metricKey.startsWith("calc.")) return false;
|
|
3986
|
+
return !KNOWN_CALC_METRICS.has(metricKey);
|
|
3987
|
+
}
|
|
3988
|
+
var TOKEN_PREFIX = "dashboard.";
|
|
3989
|
+
function isDashboardMeta(meta) {
|
|
3990
|
+
return meta["dashboard"] === "true" || meta["dashboard"] === true;
|
|
3991
|
+
}
|
|
3992
|
+
function extractDashboardTokens(meta) {
|
|
3993
|
+
const tokens = [];
|
|
3994
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
3995
|
+
if (!key.startsWith(TOKEN_PREFIX) || key === TOKEN_PREFIX) continue;
|
|
3996
|
+
const name = key.slice(TOKEN_PREFIX.length);
|
|
3997
|
+
if (name === "") continue;
|
|
3998
|
+
if (Array.isArray(value)) {
|
|
3999
|
+
if (value.length > 0) tokens.push({ name, values: value.map(String), isList: true });
|
|
4000
|
+
} else {
|
|
4001
|
+
tokens.push({ name, values: [String(value)], isList: false });
|
|
4002
|
+
}
|
|
2496
4003
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
4004
|
+
return tokens;
|
|
4005
|
+
}
|
|
4006
|
+
function defaultTokenValues(tokens) {
|
|
4007
|
+
const values = {};
|
|
4008
|
+
for (const token of tokens) values[token.name] = token.values[0];
|
|
4009
|
+
return values;
|
|
4010
|
+
}
|
|
4011
|
+
function setDashboardTokenValue(meta, name, value) {
|
|
4012
|
+
const key = `${TOKEN_PREFIX}${name}`;
|
|
4013
|
+
const current = meta[key];
|
|
4014
|
+
const next = { ...meta };
|
|
4015
|
+
if (Array.isArray(current)) {
|
|
4016
|
+
if (!current.includes(value)) return meta;
|
|
4017
|
+
next[key] = [value, ...current.filter((v) => v !== value)];
|
|
4018
|
+
} else if (current != null) {
|
|
4019
|
+
next[key] = typeof current === "number" && !isNaN(Number(value)) ? Number(value) : value;
|
|
4020
|
+
} else {
|
|
4021
|
+
next[key] = value;
|
|
2510
4022
|
}
|
|
2511
|
-
|
|
4023
|
+
return next;
|
|
4024
|
+
}
|
|
4025
|
+
var TOKEN_REF_RE = /\$([A-Za-z_][\w-]*)/g;
|
|
4026
|
+
function substituteTokens(query, values) {
|
|
4027
|
+
const missing = /* @__PURE__ */ new Set();
|
|
4028
|
+
let hadEmpty = false;
|
|
4029
|
+
let substituted = query.replace(TOKEN_REF_RE, (raw, name) => {
|
|
4030
|
+
const value = values[name];
|
|
4031
|
+
if (value == null) {
|
|
4032
|
+
missing.add(name);
|
|
4033
|
+
return raw;
|
|
4034
|
+
}
|
|
4035
|
+
if (value.trim() === "") hadEmpty = true;
|
|
4036
|
+
return value;
|
|
4037
|
+
});
|
|
4038
|
+
if (hadEmpty) {
|
|
4039
|
+
substituted = substituted.replace(/\{\s*,+/g, "{").replace(/,+\s*\}/g, "}").replace(/,\s*,+/g, ", ").replace(/\blast\s+(?:[dw]\b)?(?=\s*(?:\||$|\r?\n))/gi, "").replace(/[ \t]{2,}/g, " ").trim();
|
|
4040
|
+
}
|
|
4041
|
+
return { query: substituted, missing: [...missing] };
|
|
4042
|
+
}
|
|
4043
|
+
function referencedTokens(query) {
|
|
4044
|
+
const names = /* @__PURE__ */ new Set();
|
|
4045
|
+
for (const match of query.matchAll(TOKEN_REF_RE)) names.add(match[1]);
|
|
4046
|
+
return [...names];
|
|
4047
|
+
}
|
|
4048
|
+
function headingText(content) {
|
|
4049
|
+
const line = content.split("\n", 1)[0].replace(/^#{1,6}\s*/, "").trim();
|
|
4050
|
+
return line === "" ? void 0 : line;
|
|
4051
|
+
}
|
|
4052
|
+
function paragraphText(content) {
|
|
4053
|
+
const text = content.replace(/\s*\n\s*/g, " ").trim();
|
|
4054
|
+
return text === "" ? void 0 : text;
|
|
4055
|
+
}
|
|
4056
|
+
function buildDashboardDocument(sections, meta) {
|
|
4057
|
+
const widgets = [];
|
|
4058
|
+
sections.forEach((section, i) => {
|
|
4059
|
+
if (section.type !== "query") return;
|
|
4060
|
+
let adjacentTitle;
|
|
4061
|
+
let adjacentQuestion;
|
|
4062
|
+
const above = sections[i - 1];
|
|
4063
|
+
if (above?.type === "markdown" && above.subtype === "paragraph") {
|
|
4064
|
+
adjacentQuestion = paragraphText(above.content);
|
|
4065
|
+
const heading = sections[i - 2];
|
|
4066
|
+
if (heading?.type === "markdown" && heading.subtype === "heading") {
|
|
4067
|
+
adjacentTitle = headingText(heading.content);
|
|
4068
|
+
}
|
|
4069
|
+
} else if (above?.type === "markdown" && above.subtype === "heading") {
|
|
4070
|
+
adjacentTitle = headingText(above.content);
|
|
4071
|
+
}
|
|
4072
|
+
if (isYamlQueryBlockBody(section.content)) {
|
|
4073
|
+
const obj2 = parseQueryBlock(section.content, {
|
|
4074
|
+
fenceTag: section.fenceTag,
|
|
4075
|
+
adjacentTitle,
|
|
4076
|
+
adjacentDescription: adjacentQuestion
|
|
4077
|
+
});
|
|
4078
|
+
const compiledBody = queryBlockToDocumentText(obj2);
|
|
4079
|
+
const derivedAttrs = queryBlockAttributes(obj2);
|
|
4080
|
+
const mergedAttributes = {
|
|
4081
|
+
...derivedAttrs.attributes,
|
|
4082
|
+
...section.attributes ?? {}
|
|
4083
|
+
};
|
|
4084
|
+
const mergedOrder = [
|
|
4085
|
+
...derivedAttrs.attributeOrder,
|
|
4086
|
+
...(section.attributeOrder ?? []).filter((k) => !(k in derivedAttrs.attributes))
|
|
4087
|
+
];
|
|
4088
|
+
widgets.push({
|
|
4089
|
+
key: `w${widgets.length}`,
|
|
4090
|
+
type: section.widgetType ?? obj2.rawViewType ?? obj2.view,
|
|
4091
|
+
spanCols: section.spanCols ?? (typeof obj2.span === "number" ? obj2.span : void 0),
|
|
4092
|
+
spanFull: section.spanFull ?? (obj2.span === "full" ? true : void 0),
|
|
4093
|
+
widgetError: section.widgetError ?? obj2.error,
|
|
4094
|
+
body: compiledBody,
|
|
4095
|
+
rawBody: section.content.trim(),
|
|
4096
|
+
queryObject: obj2,
|
|
4097
|
+
attributes: mergedAttributes,
|
|
4098
|
+
attributeOrder: mergedOrder,
|
|
4099
|
+
title: obj2.title ?? adjacentTitle,
|
|
4100
|
+
question: obj2.description ?? adjacentQuestion
|
|
4101
|
+
});
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
const body = section.content.split("\n").filter((l) => {
|
|
4105
|
+
const s = l.trim();
|
|
4106
|
+
return s !== "" && !s.startsWith("#");
|
|
4107
|
+
}).map((l) => l.trim()).join("\n");
|
|
4108
|
+
const synthesizedTag = section.fenceTag ?? (section.widgetType ? `query:${section.widgetType}${section.spanFull ? "-full" : section.spanCols ? `-${section.spanCols}` : ""}` : "query");
|
|
4109
|
+
const obj = parseQueryBlock(section.content, {
|
|
4110
|
+
fenceTag: synthesizedTag,
|
|
4111
|
+
adjacentTitle,
|
|
4112
|
+
adjacentDescription: adjacentQuestion
|
|
4113
|
+
});
|
|
4114
|
+
widgets.push({
|
|
4115
|
+
key: `w${widgets.length}`,
|
|
4116
|
+
type: section.widgetType ?? "",
|
|
4117
|
+
spanCols: section.spanCols,
|
|
4118
|
+
spanFull: section.spanFull,
|
|
4119
|
+
widgetError: section.widgetError,
|
|
4120
|
+
body,
|
|
4121
|
+
rawBody: body,
|
|
4122
|
+
queryObject: obj,
|
|
4123
|
+
attributes: section.attributes ?? {},
|
|
4124
|
+
attributeOrder: section.attributeOrder ?? [],
|
|
4125
|
+
title: adjacentTitle,
|
|
4126
|
+
question: adjacentQuestion
|
|
4127
|
+
});
|
|
4128
|
+
});
|
|
4129
|
+
const titleMeta = meta["title"];
|
|
4130
|
+
return {
|
|
4131
|
+
isDashboard: isDashboardMeta(meta),
|
|
4132
|
+
title: typeof titleMeta === "string" && titleMeta !== "" ? titleMeta : void 0,
|
|
4133
|
+
tokens: extractDashboardTokens(meta),
|
|
4134
|
+
widgets
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
2512
4137
|
|
|
2513
4138
|
// src/queryDocumentRunner.ts
|
|
2514
4139
|
var PageSourceRegistry = class extends Map {
|
|
@@ -2540,8 +4165,55 @@ var QueryDocumentRunner2 = class {
|
|
|
2540
4165
|
// that supply none still get a single capture for the run.
|
|
2541
4166
|
context: runOptions.context ?? this.initialOptions.context ?? captureContext()
|
|
2542
4167
|
};
|
|
2543
|
-
const
|
|
2544
|
-
const
|
|
4168
|
+
const tokens = captured.tokens ?? {};
|
|
4169
|
+
const initialParsed = parseDocument(text);
|
|
4170
|
+
let effectiveText;
|
|
4171
|
+
if (initialParsed.diagnostics.length === 0 && initialParsed.doc.assignments.length > 0) {
|
|
4172
|
+
const missingAll = /* @__PURE__ */ new Set();
|
|
4173
|
+
const boundAssignments = initialParsed.doc.assignments.map((a) => {
|
|
4174
|
+
if (a.kind === "query" && a.parsed && !a.sourceRef) {
|
|
4175
|
+
const bound = bindQueryParams(a.parsed, tokens);
|
|
4176
|
+
for (const m of bound.missing) missingAll.add(m);
|
|
4177
|
+
if (bound.missing.length === 0 && !bound.parsed.error) {
|
|
4178
|
+
const serialized = serialize(bound.parsed);
|
|
4179
|
+
return {
|
|
4180
|
+
...a,
|
|
4181
|
+
parsed: bound.parsed,
|
|
4182
|
+
queryText: serialized,
|
|
4183
|
+
raw: initialParsed.doc.degenerate ? serialized : `${a.name} = ${serialized}`
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
const subRaw = substituteTokens(a.raw, tokens);
|
|
4188
|
+
for (const m of subRaw.missing) missingAll.add(m);
|
|
4189
|
+
return { ...a, raw: subRaw.query };
|
|
4190
|
+
});
|
|
4191
|
+
let boundDefaults = initialParsed.doc.defaults;
|
|
4192
|
+
if (boundDefaults?.window?.kind === "param") {
|
|
4193
|
+
const probe = bindQueryParams(
|
|
4194
|
+
{ family: "find", raw: ":segment{}", target: "segment", filters: [], window: boundDefaults.window },
|
|
4195
|
+
tokens
|
|
4196
|
+
);
|
|
4197
|
+
for (const m of probe.missing) missingAll.add(m);
|
|
4198
|
+
if (probe.missing.length === 0 && probe.parsed.family === "find") {
|
|
4199
|
+
boundDefaults = { ...boundDefaults, window: probe.parsed.window };
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
if (missingAll.size > 0) {
|
|
4203
|
+
return {
|
|
4204
|
+
outputs: [],
|
|
4205
|
+
diagnostics: [unknownTokensMessage([...missingAll])]
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
effectiveText = serializeDocument({
|
|
4209
|
+
...initialParsed.doc,
|
|
4210
|
+
...boundDefaults ? { defaults: boundDefaults } : {},
|
|
4211
|
+
assignments: boundAssignments
|
|
4212
|
+
});
|
|
4213
|
+
} else {
|
|
4214
|
+
effectiveText = substituteTokens2(text, tokens);
|
|
4215
|
+
}
|
|
4216
|
+
const parsed = parseDocument(effectiveText);
|
|
2545
4217
|
if (parsed.doc.assignments.some((a) => consumesRollupFacts(a.parsed))) {
|
|
2546
4218
|
await this.host.rollupEnsure?.();
|
|
2547
4219
|
}
|
|
@@ -2561,16 +4233,16 @@ var QueryDocumentRunner2 = class {
|
|
|
2561
4233
|
runPipeline: async (queryText) => {
|
|
2562
4234
|
const res = await this.host.runPipeline?.(queryText, captured.context);
|
|
2563
4235
|
return res ?? { error: "pipeline queries are not supported by this host" };
|
|
2564
|
-
}
|
|
4236
|
+
},
|
|
4237
|
+
...this.host.runPipelineFromFind ? {
|
|
4238
|
+
runPipelineFromFind: async (sourceFind, pipeTail) => this.host.runPipelineFromFind(sourceFind, pipeTail, captured.context)
|
|
4239
|
+
} : {}
|
|
2565
4240
|
});
|
|
2566
|
-
return formulaRunner.run(
|
|
4241
|
+
return formulaRunner.run(effectiveText);
|
|
2567
4242
|
}
|
|
2568
4243
|
};
|
|
2569
|
-
function
|
|
2570
|
-
return text
|
|
2571
|
-
const value = tokens[name];
|
|
2572
|
-
return value !== void 0 ? value : match;
|
|
2573
|
-
});
|
|
4244
|
+
function substituteTokens2(text, tokens) {
|
|
4245
|
+
return substituteTokens(text, tokens).query;
|
|
2574
4246
|
}
|
|
2575
4247
|
function consumesRollupFacts(parsed) {
|
|
2576
4248
|
if (!parsed) return false;
|
|
@@ -2678,8 +4350,9 @@ function firstEffortSlug(metrics) {
|
|
|
2678
4350
|
}
|
|
2679
4351
|
return void 0;
|
|
2680
4352
|
}
|
|
4353
|
+
var PLUMBING_OUTPUT_TYPES = /* @__PURE__ */ new Set(["load", "compiler", "system", "event"]);
|
|
2681
4354
|
function toEventRows(logs, identity) {
|
|
2682
|
-
return logs.map((output, seq) => ({
|
|
4355
|
+
return logs.filter((output) => !PLUMBING_OUTPUT_TYPES.has(output.outputType ?? "segment")).map((output, seq) => ({
|
|
2683
4356
|
id: `${identity.resultId}:${seq}`,
|
|
2684
4357
|
resultId: identity.resultId,
|
|
2685
4358
|
noteId: identity.noteId,
|
|
@@ -2752,13 +4425,18 @@ function toSummaryEventRows(logs, identity) {
|
|
|
2752
4425
|
blockVersion: identity.blockVersion
|
|
2753
4426
|
}));
|
|
2754
4427
|
}
|
|
2755
|
-
function
|
|
4428
|
+
function matchesFactMetricKey(metricKey, queryMetric) {
|
|
4429
|
+
return !queryMetric || metricKey === queryMetric || queryMetric === "rep" && metricKey === "reps" || queryMetric === "reps" && metricKey === "rep";
|
|
4430
|
+
}
|
|
4431
|
+
function projectEventToFacts(record, filter) {
|
|
4432
|
+
const resolvedFilter = typeof filter === "object" && filter !== null ? filter : void 0;
|
|
2756
4433
|
const metrics = wodWikiCore.createMetricOwnershipLedger(record.metrics).visible();
|
|
2757
4434
|
if (record.grain === "summary") {
|
|
2758
4435
|
const m = metrics[0];
|
|
2759
4436
|
if (!m || typeof m.value !== "number") return [];
|
|
2760
4437
|
const fieldRef = readFieldRef(m.metadata);
|
|
2761
4438
|
const metricKey = metadataString(m.metadata, "canonicalKey") ?? fieldRef?.path ?? (m.type ?? "");
|
|
4439
|
+
if (!matchesFactMetricKey(metricKey, resolvedFilter?.metricKey)) return [];
|
|
2762
4440
|
const temporal = record.metricTemporal?.[0];
|
|
2763
4441
|
const dims2 = readGroupTags(m.metadata);
|
|
2764
4442
|
return [{
|
|
@@ -2811,13 +4489,15 @@ function projectEventToFacts(record) {
|
|
|
2811
4489
|
if (dimKey) dims[dimKey] = String(m.value);
|
|
2812
4490
|
}
|
|
2813
4491
|
const rowDims = Object.keys(dims).length > 0 ? dims : void 0;
|
|
4492
|
+
let metricOrdinal = 0;
|
|
2814
4493
|
metrics.forEach((m) => {
|
|
2815
4494
|
if (m.type === wodWikiCore.MetricType.Label || m.type === "label" || typeof m.value !== "number") return;
|
|
2816
4495
|
const fieldRef = readFieldRef(m.metadata);
|
|
2817
4496
|
const legacyFallback = m.type === wodWikiCore.MetricType.Rep || m.type === "rep" ? "reps" : m.type ?? "metric";
|
|
2818
4497
|
if (!fieldRef && legacyFallback === "custom") return;
|
|
2819
4498
|
const metricKey = metadataString(m.metadata, "canonicalKey") ?? fieldRef?.path ?? (labelName ? resolveCanonicalMetricKey(labelName) : legacyFallback);
|
|
2820
|
-
const ordinal =
|
|
4499
|
+
const ordinal = metricOrdinal++;
|
|
4500
|
+
if (!matchesFactMetricKey(metricKey, resolvedFilter?.metricKey)) return [];
|
|
2821
4501
|
const temporal = record.metricTemporal?.[ordinal];
|
|
2822
4502
|
const factTimestamp = temporal?.temporalKind === "instant" && temporal.instant !== void 0 ? temporal.instant : record.timestamp;
|
|
2823
4503
|
facts.push({
|
|
@@ -2969,18 +4649,7 @@ function feedOfItem(item) {
|
|
|
2969
4649
|
const id = item.feedSourceId;
|
|
2970
4650
|
return typeof id === "string" && id.trim() ? id : void 0;
|
|
2971
4651
|
}
|
|
2972
|
-
function
|
|
2973
|
-
if (item.notebookId && item.notebookId.trim()) {
|
|
2974
|
-
return wodWikiCore.toNotebookId(item.notebookId);
|
|
2975
|
-
}
|
|
2976
|
-
if (item.tags) {
|
|
2977
|
-
for (const tag of item.tags) {
|
|
2978
|
-
if (tag.startsWith("notebook:") || tag.startsWith("notebook/")) {
|
|
2979
|
-
const slug2 = wodWikiCore.notebookSlugFromId(tag);
|
|
2980
|
-
if (slug2) return wodWikiCore.toNotebookId(slug2);
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
4652
|
+
function resolveIntrinsicNotebook(item) {
|
|
2984
4653
|
const corpus = /^markdown\/(notebooks|canvas|efforts|dashboards|equipment)\/(.+)$/.exec(item.sourcePath ?? "");
|
|
2985
4654
|
if (corpus) {
|
|
2986
4655
|
const [, family, rest] = corpus;
|
|
@@ -2999,10 +4668,67 @@ function notebookOfItem(item) {
|
|
|
2999
4668
|
if (isJournalRow(item)) return "notebook/journal";
|
|
3000
4669
|
return void 0;
|
|
3001
4670
|
}
|
|
4671
|
+
function notebooksOfItem(item) {
|
|
4672
|
+
const out = [];
|
|
4673
|
+
const push = (raw) => {
|
|
4674
|
+
if (!raw || !raw.trim()) return;
|
|
4675
|
+
const id = wodWikiCore.toNotebookId(raw);
|
|
4676
|
+
if (!out.includes(id)) out.push(id);
|
|
4677
|
+
};
|
|
4678
|
+
if (item.notebookIds?.length) {
|
|
4679
|
+
for (const raw of item.notebookIds) push(raw);
|
|
4680
|
+
}
|
|
4681
|
+
if (item.notebookId?.trim()) {
|
|
4682
|
+
push(item.notebookId);
|
|
4683
|
+
}
|
|
4684
|
+
if (item.tags) {
|
|
4685
|
+
for (const tag of item.tags) {
|
|
4686
|
+
if (tag.startsWith("notebook:") || tag.startsWith("notebook/")) {
|
|
4687
|
+
const slug = wodWikiCore.notebookSlugFromId(tag);
|
|
4688
|
+
if (slug) push(slug);
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
if (out.length === 0) {
|
|
4693
|
+
const intrinsic = resolveIntrinsicNotebook(item);
|
|
4694
|
+
if (intrinsic) {
|
|
4695
|
+
push(intrinsic);
|
|
4696
|
+
const isCorePrimitive = (intrinsic === "notebook/efforts" || intrinsic === "notebook/equipment") && item.seedOrigin === "seed" && (!item.feedSourceId || item.feedSourceId === "core");
|
|
4697
|
+
if (isCorePrimitive) {
|
|
4698
|
+
push("notebook/defaults");
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
return out;
|
|
4703
|
+
}
|
|
4704
|
+
function notebookOfItem(item) {
|
|
4705
|
+
if (item.notebookId && item.notebookId.trim()) {
|
|
4706
|
+
return wodWikiCore.toNotebookId(item.notebookId);
|
|
4707
|
+
}
|
|
4708
|
+
if (item.notebookIds?.length) {
|
|
4709
|
+
const nonDefaultUser = item.notebookIds.map((id) => wodWikiCore.toNotebookId(id)).find((id) => id !== "notebook/journal" && id !== "notebook/playground");
|
|
4710
|
+
if (nonDefaultUser) return nonDefaultUser;
|
|
4711
|
+
return wodWikiCore.toNotebookId(item.notebookIds[0]);
|
|
4712
|
+
}
|
|
4713
|
+
if (item.tags) {
|
|
4714
|
+
for (const tag of item.tags) {
|
|
4715
|
+
if (tag.startsWith("notebook:") || tag.startsWith("notebook/")) {
|
|
4716
|
+
const slug = wodWikiCore.notebookSlugFromId(tag);
|
|
4717
|
+
if (slug) return wodWikiCore.toNotebookId(slug);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
return resolveIntrinsicNotebook(item);
|
|
4722
|
+
}
|
|
3002
4723
|
function notebookOriginOfItem(item) {
|
|
3003
4724
|
const notebook = notebookOfItem(item);
|
|
3004
4725
|
if (!notebook) return void 0;
|
|
4726
|
+
if (notebook === "notebook/defaults") return "default";
|
|
3005
4727
|
if (item.notebookId?.trim()) return "user";
|
|
4728
|
+
if (item.notebookIds?.some((id) => {
|
|
4729
|
+
const norm = wodWikiCore.toNotebookId(id);
|
|
4730
|
+
return norm !== "notebook/journal" && norm !== "notebook/playground" && norm !== "notebook/defaults";
|
|
4731
|
+
})) return "user";
|
|
3006
4732
|
if (item.tags?.some((t2) => t2.startsWith("notebook:") || t2.startsWith("notebook/"))) return "user";
|
|
3007
4733
|
if (notebook === "notebook/journal" || notebook === "notebook/playground") return "default";
|
|
3008
4734
|
return "feed";
|
|
@@ -3087,9 +4813,9 @@ function applyNotebookFilter(items, filters) {
|
|
|
3087
4813
|
if (filter.key !== "notebook") continue;
|
|
3088
4814
|
const wanted = new Set(filter.values.map((v) => wodWikiCore.toNotebookId(v.value)));
|
|
3089
4815
|
items = items.filter((item) => {
|
|
3090
|
-
const
|
|
3091
|
-
if (
|
|
3092
|
-
const hit = wanted.has(nb);
|
|
4816
|
+
const memberships = notebooksOfItem(item);
|
|
4817
|
+
if (memberships.length === 0) return filter.negate;
|
|
4818
|
+
const hit = memberships.some((nb) => wanted.has(nb));
|
|
3093
4819
|
return filter.negate ? !hit : hit;
|
|
3094
4820
|
});
|
|
3095
4821
|
}
|
|
@@ -3225,14 +4951,38 @@ function civilDatesCoveredByRange(start, end, timeZone) {
|
|
|
3225
4951
|
}
|
|
3226
4952
|
return dates;
|
|
3227
4953
|
}
|
|
3228
|
-
|
|
4954
|
+
var EMPTY_FACT_DIMS = {
|
|
4955
|
+
noteTags: /* @__PURE__ */ new Map(),
|
|
4956
|
+
typedNoteTags: /* @__PURE__ */ new Map(),
|
|
4957
|
+
effortsBySlug: /* @__PURE__ */ new Map()
|
|
4958
|
+
};
|
|
4959
|
+
function resolveDimensionContext(dimsOrNoteTags) {
|
|
4960
|
+
if ("noteTags" in dimsOrNoteTags) return dimsOrNoteTags;
|
|
4961
|
+
return { ...EMPTY_FACT_DIMS, noteTags: dimsOrNoteTags };
|
|
4962
|
+
}
|
|
4963
|
+
var TYPED_TAG_KEY_SET = new Set(WQL_TYPED_TAG_KEYS);
|
|
4964
|
+
function factTagValue(row, key, dimsOrNoteTags = EMPTY_FACT_DIMS) {
|
|
4965
|
+
const ctx = resolveDimensionContext(dimsOrNoteTags);
|
|
4966
|
+
const effort = row.effortSlug ? ctx.effortsBySlug.get(row.effortSlug) : void 0;
|
|
3229
4967
|
switch (key) {
|
|
3230
4968
|
case "effort":
|
|
3231
4969
|
return row.effortSlug;
|
|
3232
|
-
case "discipline":
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
4970
|
+
case "discipline": {
|
|
4971
|
+
const explicit = row.discipline ?? row.dimensions?.["discipline"];
|
|
4972
|
+
if (explicit) return explicit;
|
|
4973
|
+
const fromEffort = effort?.baseAttributes?.discipline;
|
|
4974
|
+
if (fromEffort) return fromEffort;
|
|
4975
|
+
const fromNote = ctx.typedNoteTags.get(row.noteId)?.get("discipline");
|
|
4976
|
+
return fromNote && fromNote.length > 0 ? fromNote : void 0;
|
|
4977
|
+
}
|
|
4978
|
+
case "intensity": {
|
|
4979
|
+
const explicit = row.intensityTier ?? row.dimensions?.["intensity"];
|
|
4980
|
+
if (explicit) return explicit;
|
|
4981
|
+
const fromEffort = effort?.baseAttributes?.intensityTier;
|
|
4982
|
+
if (fromEffort) return fromEffort;
|
|
4983
|
+
const fromNote = ctx.typedNoteTags.get(row.noteId)?.get("intensity");
|
|
4984
|
+
return fromNote && fromNote.length > 0 ? fromNote : void 0;
|
|
4985
|
+
}
|
|
3236
4986
|
case "grade":
|
|
3237
4987
|
return row.grade;
|
|
3238
4988
|
case "note":
|
|
@@ -3250,15 +5000,27 @@ function factTagValue(row, key, noteTags) {
|
|
|
3250
5000
|
case "result":
|
|
3251
5001
|
return row.resultId;
|
|
3252
5002
|
case "tags":
|
|
3253
|
-
return noteTags.get(row.noteId) ?? [];
|
|
5003
|
+
return ctx.noteTags.get(row.noteId) ?? [];
|
|
5004
|
+
case "equipment": {
|
|
5005
|
+
const explicit = row.dimensions?.["equipment"];
|
|
5006
|
+
if (explicit) return explicit;
|
|
5007
|
+
if (effort?.equipment && effort.equipment.length > 0) return effort.equipment;
|
|
5008
|
+
const fromNote = ctx.typedNoteTags.get(row.noteId)?.get("equipment");
|
|
5009
|
+
return fromNote && fromNote.length > 0 ? fromNote : void 0;
|
|
5010
|
+
}
|
|
3254
5011
|
default: {
|
|
3255
|
-
const
|
|
3256
|
-
|
|
3257
|
-
|
|
5012
|
+
const rowDims = row.dimensions;
|
|
5013
|
+
const explicit = rowDims ? rowDims[key] ?? rowDims[wodWikiCore.normalizeFieldComponent(key)] : void 0;
|
|
5014
|
+
if (explicit !== void 0) return explicit;
|
|
5015
|
+
if (TYPED_TAG_KEY_SET.has(key)) {
|
|
5016
|
+
const fromNote = ctx.typedNoteTags.get(row.noteId)?.get(key);
|
|
5017
|
+
if (fromNote && fromNote.length > 0) return fromNote;
|
|
5018
|
+
}
|
|
5019
|
+
return void 0;
|
|
3258
5020
|
}
|
|
3259
5021
|
}
|
|
3260
5022
|
}
|
|
3261
|
-
function matchesFilters(row, filters,
|
|
5023
|
+
function matchesFilters(row, filters, dimsOrNoteTags) {
|
|
3262
5024
|
const groups = /* @__PURE__ */ new Map();
|
|
3263
5025
|
for (const f of filters) {
|
|
3264
5026
|
const groupKey = `${f.negate ? "!" : ""}${f.key}`;
|
|
@@ -3269,7 +5031,7 @@ function matchesFilters(row, filters, noteTags) {
|
|
|
3269
5031
|
return [...groups.values()].every((group) => {
|
|
3270
5032
|
const key = group[0].key;
|
|
3271
5033
|
const negate = group[0].negate;
|
|
3272
|
-
const raw = factTagValue(row, key,
|
|
5034
|
+
const raw = factTagValue(row, key, dimsOrNoteTags);
|
|
3273
5035
|
const rowValues = Array.isArray(raw) ? raw : raw !== void 0 ? [raw] : [];
|
|
3274
5036
|
const hit = rowValues.some(
|
|
3275
5037
|
(value) => group.some(
|
|
@@ -3281,11 +5043,11 @@ function matchesFilters(row, filters, noteTags) {
|
|
|
3281
5043
|
return negate ? !hit : hit;
|
|
3282
5044
|
});
|
|
3283
5045
|
}
|
|
3284
|
-
function dimValue(row, dim,
|
|
5046
|
+
function dimValue(row, dim, dimsOrNoteTags, ctx) {
|
|
3285
5047
|
if (dim === "day") return row.metricDate ?? civilDateOf(row.timestamp, ctx.timeZone);
|
|
3286
5048
|
if (dim === "week") return civilMonday(row.metricDate ?? civilDateOf(row.timestamp, ctx.timeZone));
|
|
3287
5049
|
if (dim === "session") return row.resultId;
|
|
3288
|
-
const raw = factTagValue(row, dim,
|
|
5050
|
+
const raw = factTagValue(row, dim, dimsOrNoteTags);
|
|
3289
5051
|
if (raw === void 0) return UNASSIGNED;
|
|
3290
5052
|
if (typeof raw === "string") return raw;
|
|
3291
5053
|
return raw.length ? raw.join(",") : UNASSIGNED;
|
|
@@ -3443,11 +5205,15 @@ var QueryService = class {
|
|
|
3443
5205
|
relationStore;
|
|
3444
5206
|
datasetStore;
|
|
3445
5207
|
isNotebookSubscribed;
|
|
3446
|
-
|
|
5208
|
+
feedCatalogStore;
|
|
5209
|
+
// Optional storage seam properties for block_efforts, block_equipment, and typed tag resolution.
|
|
3447
5210
|
// Injected by the app adapter (`services/queryService.ts`); tests can supply fakes.
|
|
3448
5211
|
blockEffortsStore = {
|
|
3449
5212
|
getAllFromIndex: async () => []
|
|
3450
5213
|
};
|
|
5214
|
+
blockEquipmentStore = {
|
|
5215
|
+
getAllFromIndex: async () => []
|
|
5216
|
+
};
|
|
3451
5217
|
tagsStore = {
|
|
3452
5218
|
getAllFromIndex: async () => []
|
|
3453
5219
|
};
|
|
@@ -3455,7 +5221,7 @@ var QueryService = class {
|
|
|
3455
5221
|
getAllFromIndex: async () => []
|
|
3456
5222
|
};
|
|
3457
5223
|
constructor(storesOrEventStore, noteStore, blockStore, effortStore) {
|
|
3458
|
-
if (storesOrEventStore && typeof storesOrEventStore === "object" && ("eventStore" in storesOrEventStore || "noteStore" in storesOrEventStore || "blockStore" in storesOrEventStore || "effortStore" in storesOrEventStore || "pageStore" in storesOrEventStore || "relationStore" in storesOrEventStore || "datasetStore" in storesOrEventStore)) {
|
|
5224
|
+
if (storesOrEventStore && typeof storesOrEventStore === "object" && ("eventStore" in storesOrEventStore || "noteStore" in storesOrEventStore || "blockStore" in storesOrEventStore || "effortStore" in storesOrEventStore || "blockEffortsStore" in storesOrEventStore || "blockEquipmentStore" in storesOrEventStore || "tagsStore" in storesOrEventStore || "noteTagsStore" in storesOrEventStore || "pageStore" in storesOrEventStore || "relationStore" in storesOrEventStore || "datasetStore" in storesOrEventStore || "feedCatalogStore" in storesOrEventStore || "isNotebookSubscribed" in storesOrEventStore)) {
|
|
3459
5225
|
const stores = storesOrEventStore;
|
|
3460
5226
|
this.store = stores.eventStore ?? defaultEventStore;
|
|
3461
5227
|
this.noteStore = stores.noteStore ?? defaultNoteStore;
|
|
@@ -3465,9 +5231,11 @@ var QueryService = class {
|
|
|
3465
5231
|
this.relationStore = stores.relationStore;
|
|
3466
5232
|
this.datasetStore = stores.datasetStore;
|
|
3467
5233
|
if (stores.blockEffortsStore) this.blockEffortsStore = stores.blockEffortsStore;
|
|
5234
|
+
if (stores.blockEquipmentStore) this.blockEquipmentStore = stores.blockEquipmentStore;
|
|
3468
5235
|
if (stores.tagsStore) this.tagsStore = stores.tagsStore;
|
|
3469
5236
|
if (stores.noteTagsStore) this.noteTagsStore = stores.noteTagsStore;
|
|
3470
5237
|
if (stores.isNotebookSubscribed) this.isNotebookSubscribed = stores.isNotebookSubscribed;
|
|
5238
|
+
if (stores.feedCatalogStore) this.feedCatalogStore = stores.feedCatalogStore;
|
|
3471
5239
|
} else {
|
|
3472
5240
|
this.store = storesOrEventStore ?? defaultEventStore;
|
|
3473
5241
|
this.noteStore = noteStore ?? defaultNoteStore;
|
|
@@ -3478,7 +5246,7 @@ var QueryService = class {
|
|
|
3478
5246
|
/** Windowed flat-fact fetch — the projected view over the event store. */
|
|
3479
5247
|
async getFactsByTimeRange(start, end) {
|
|
3480
5248
|
const rows = await this.store.getEventsByTimeRange(start, end);
|
|
3481
|
-
return rows.flatMap(projectEventToFacts);
|
|
5249
|
+
return rows.flatMap((row) => projectEventToFacts(row));
|
|
3482
5250
|
}
|
|
3483
5251
|
async runQuery(raw, options2 = {}) {
|
|
3484
5252
|
const parsed = parseQuery(raw);
|
|
@@ -3553,13 +5321,15 @@ var QueryService = class {
|
|
|
3553
5321
|
groups.sort((a, b) => sessionCompletedAt(b[1]) - sessionCompletedAt(a[1]));
|
|
3554
5322
|
const planeFilters = parsed.filters.filter((f) => f.key === "plane");
|
|
3555
5323
|
const factFilters = parsed.filters.filter((f) => !["result", "block", "note", "plane"].includes(f.key));
|
|
3556
|
-
const touchesTags = factFilters.some((f) => isTagLabelKey(f.key));
|
|
3557
|
-
let sessionNoteTags = /* @__PURE__ */ new Map();
|
|
3558
|
-
if (touchesTags && groups.length > 0) {
|
|
3559
|
-
const dummyPoints = groups.map(([, rows]) => ({ noteId: rows[0]?.noteId ?? "" }));
|
|
3560
|
-
sessionNoteTags = await this.loadNoteTags(dummyPoints, true);
|
|
3561
|
-
}
|
|
3562
5324
|
const normalizedFactFilters = factFilters.map((f) => f.key === "tag" ? { ...f, key: "tags" } : f);
|
|
5325
|
+
const sessionDims = groups.length > 0 && normalizedFactFilters.length > 0 ? await this.loadFactDimensions(
|
|
5326
|
+
groups.map(([, rows]) => ({
|
|
5327
|
+
noteId: rows[0]?.noteId ?? "",
|
|
5328
|
+
effortSlug: rows.find((r) => r.effortSlug)?.effortSlug
|
|
5329
|
+
})),
|
|
5330
|
+
normalizedFactFilters,
|
|
5331
|
+
[]
|
|
5332
|
+
) : EMPTY_FACT_DIMS;
|
|
3563
5333
|
let runs = groups.map(([resultId, rows]) => {
|
|
3564
5334
|
let events = rows;
|
|
3565
5335
|
if (planeFilters.length > 0) {
|
|
@@ -3573,7 +5343,7 @@ var QueryService = class {
|
|
|
3573
5343
|
if (normalizedFactFilters.length > 0) {
|
|
3574
5344
|
const matchingSession = events.some((row) => {
|
|
3575
5345
|
const facts = projectEventToFacts(row);
|
|
3576
|
-
return facts.some((fact) => matchesFilters(fact, normalizedFactFilters,
|
|
5346
|
+
return facts.some((fact) => matchesFilters(fact, normalizedFactFilters, sessionDims));
|
|
3577
5347
|
});
|
|
3578
5348
|
if (!matchingSession) events = [];
|
|
3579
5349
|
}
|
|
@@ -3667,14 +5437,17 @@ var QueryService = class {
|
|
|
3667
5437
|
};
|
|
3668
5438
|
const rawTagFilters = parsed.filters.filter((f) => !["result", "block", "note", "plane"].includes(f.key));
|
|
3669
5439
|
const tagFilters = rawTagFilters.map((f) => f.key === "tag" ? { ...f, key: "tags" } : f);
|
|
3670
|
-
const
|
|
3671
|
-
|
|
5440
|
+
const tableDims = eligibleRecords.length > 0 && tagFilters.length > 0 ? await this.loadFactDimensions(
|
|
5441
|
+
eligibleRecords.map((r) => ({ noteId: r.noteId, effortSlug: r.effortSlug })),
|
|
5442
|
+
tagFilters,
|
|
5443
|
+
[]
|
|
5444
|
+
) : EMPTY_FACT_DIMS;
|
|
3672
5445
|
const timeScoped = !!(parsed.window || options2.range);
|
|
3673
5446
|
const eligible = eligibleRecords.filter((row) => !timeScoped || effectiveTimeWindow(anchorTs(row), parsed.window, options2.range, ctx)).filter((row) => {
|
|
3674
5447
|
if (tagFilters.length === 0) return true;
|
|
3675
5448
|
const facts = projectEventToFacts(row);
|
|
3676
5449
|
if (facts.length === 0) return false;
|
|
3677
|
-
return facts.some((fact) => matchesFilters(fact, tagFilters,
|
|
5450
|
+
return facts.some((fact) => matchesFilters(fact, tagFilters, tableDims));
|
|
3678
5451
|
});
|
|
3679
5452
|
const totalCount = eligible.length;
|
|
3680
5453
|
const unitByMetric = /* @__PURE__ */ new Map();
|
|
@@ -3707,7 +5480,13 @@ var QueryService = class {
|
|
|
3707
5480
|
return { name: col, type: "number", ...unitFor(col) ? { unit: unitFor(col) } : {} };
|
|
3708
5481
|
});
|
|
3709
5482
|
const typeOf = new Map(columns.map((c) => [c.name, c.type]));
|
|
3710
|
-
|
|
5483
|
+
const offset = pipes?.offset ?? 0;
|
|
5484
|
+
const limit = pipes?.limit;
|
|
5485
|
+
const hasOrder = Boolean(pipes?.order && pipes.order.length > 0);
|
|
5486
|
+
const hasGroupBy = Boolean(parsed.groupBy && parsed.groupBy.length > 0);
|
|
5487
|
+
const canSliceBeforeProject = !hasOrder && !hasGroupBy && (limit !== void 0 || offset > 0);
|
|
5488
|
+
const rowsToProject = canSliceBeforeProject ? limit !== void 0 ? eligible.slice(offset, offset + limit) : eligible.slice(offset) : eligible;
|
|
5489
|
+
let records = rowsToProject.map((row) => {
|
|
3711
5490
|
const record = {};
|
|
3712
5491
|
for (const col of selectCols) {
|
|
3713
5492
|
const type = typeOf.get(col);
|
|
@@ -3761,9 +5540,7 @@ var QueryService = class {
|
|
|
3761
5540
|
}
|
|
3762
5541
|
groups = [...groupMap.entries()].map(([key, rows]) => ({ key, label: key, rows }));
|
|
3763
5542
|
}
|
|
3764
|
-
const
|
|
3765
|
-
const limit = pipes?.limit;
|
|
3766
|
-
const page = limit !== void 0 ? records.slice(offset, offset + limit) : records.slice(offset);
|
|
5543
|
+
const page = canSliceBeforeProject ? records : limit !== void 0 ? records.slice(offset, offset + limit) : records.slice(offset);
|
|
3767
5544
|
return {
|
|
3768
5545
|
parsed,
|
|
3769
5546
|
notes: [],
|
|
@@ -3790,6 +5567,17 @@ var QueryService = class {
|
|
|
3790
5567
|
if (parsed.error) {
|
|
3791
5568
|
return { parsed, notes: [], blocks: [], stages: { selected: 0, matched: 0 } };
|
|
3792
5569
|
}
|
|
5570
|
+
const bound = bindQueryParams(parsed, options2.params ?? {});
|
|
5571
|
+
if (bound.missing.length > 0) {
|
|
5572
|
+
const errored = { ...parsed, error: unknownTokensMessage(bound.missing) };
|
|
5573
|
+
return { parsed: errored, notes: [], blocks: [], stages: { selected: 0, matched: 0 } };
|
|
5574
|
+
}
|
|
5575
|
+
if (bound.parsed.family === "find") {
|
|
5576
|
+
parsed = bound.parsed;
|
|
5577
|
+
if (parsed.error) {
|
|
5578
|
+
return { parsed, notes: [], blocks: [], stages: { selected: 0, matched: 0 } };
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
3793
5581
|
const computed = findTargetAdvisories(parsed);
|
|
3794
5582
|
if (computed.length) {
|
|
3795
5583
|
const merged = [.../* @__PURE__ */ new Set([...parsed.advisories ?? [], ...computed])];
|
|
@@ -3819,6 +5607,9 @@ var QueryService = class {
|
|
|
3819
5607
|
if (parsed.target === "equipment") {
|
|
3820
5608
|
return this.runFindEquipment(parsed, options2);
|
|
3821
5609
|
}
|
|
5610
|
+
if (parsed.target === "feed") {
|
|
5611
|
+
return this.runFindFeed(parsed, options2);
|
|
5612
|
+
}
|
|
3822
5613
|
const read = compileContentRead(parsed.filters, "note", parsed.sourceScope);
|
|
3823
5614
|
const paging = domainPaging(parsed, options2, read.residual);
|
|
3824
5615
|
const fetchedNotes = (read.filters.length > 0 || read.selection.length > 0 || paging.paged) && this.noteStore.queryDomain ? await this.noteStore.queryDomain({
|
|
@@ -3888,6 +5679,31 @@ var QueryService = class {
|
|
|
3888
5679
|
}
|
|
3889
5680
|
}
|
|
3890
5681
|
notes = applyNotebookFilter(notes, parsed.filters);
|
|
5682
|
+
if (notes.length === 0 && this.feedCatalogStore?.getRemoteNotebookNotes) {
|
|
5683
|
+
const posNbFilter = parsed.filters.find((f) => f.key === "notebook" && !f.negate && f.values.length === 1 && !f.values[0].wildcard);
|
|
5684
|
+
const subFilter = parsed.filters.find((f) => f.key === "subscribed" && !f.negate);
|
|
5685
|
+
const explicitlySubscribedOnly = subFilter !== void 0 && subFilter.values.some((v) => v.value === "true") && !subFilter.values.some((v) => v.value === "false");
|
|
5686
|
+
if (posNbFilter && !explicitlySubscribedOnly) {
|
|
5687
|
+
const wantedSlug = wodWikiCore.notebookSlugFromId(posNbFilter.values[0].value);
|
|
5688
|
+
const posFeedFilter = parsed.filters.find((f) => f.key === "feed" && !f.negate && f.values.length === 1 && !f.values[0].wildcard);
|
|
5689
|
+
let remoteNotes = await this.feedCatalogStore.getRemoteNotebookNotes(
|
|
5690
|
+
wantedSlug,
|
|
5691
|
+
posFeedFilter?.values[0]?.value
|
|
5692
|
+
);
|
|
5693
|
+
for (const filter of parsed.filters) {
|
|
5694
|
+
if (filter.key === "text") {
|
|
5695
|
+
const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
|
|
5696
|
+
remoteNotes = remoteNotes.filter((n) => {
|
|
5697
|
+
const hit = n.title.toLowerCase().includes(search);
|
|
5698
|
+
return filter.negate ? !hit : hit;
|
|
5699
|
+
});
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
if (remoteNotes.length > 0) {
|
|
5703
|
+
notes = remoteNotes;
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
3891
5707
|
const isSubscribedFn = options2.isNotebookSubscribed ?? this.isNotebookSubscribed ?? (() => true);
|
|
3892
5708
|
for (const filter of parsed.filters) {
|
|
3893
5709
|
if (filter.key === "feed") {
|
|
@@ -3949,7 +5765,7 @@ var QueryService = class {
|
|
|
3949
5765
|
const offset = pipes.offset ?? 0;
|
|
3950
5766
|
notes = pipes.limit !== void 0 ? notes.slice(offset, offset + pipes.limit) : notes.slice(offset);
|
|
3951
5767
|
}
|
|
3952
|
-
const relations = notes.length && (this.pageStore || this.relationStore) ? await this.containersAndPlacements(notes) : void 0;
|
|
5768
|
+
const relations = !options2.skipContainers && notes.length && (this.pageStore || this.relationStore) ? await this.containersAndPlacements(notes) : void 0;
|
|
3953
5769
|
return {
|
|
3954
5770
|
parsed,
|
|
3955
5771
|
notes,
|
|
@@ -4221,7 +6037,15 @@ var QueryService = class {
|
|
|
4221
6037
|
stages: { selected: 0, buckets: 0, aggregated: 0, groups: 0 },
|
|
4222
6038
|
matched: []
|
|
4223
6039
|
};
|
|
4224
|
-
|
|
6040
|
+
const bound = bindQueryParams(parsed, options2.params ?? {});
|
|
6041
|
+
if (bound.missing.length > 0) {
|
|
6042
|
+
const err = unknownTokensMessage(bound.missing);
|
|
6043
|
+
return { ...empty, parsed: { ...parsed, error: err }, error: err };
|
|
6044
|
+
}
|
|
6045
|
+
if (bound.parsed.family === "aggregate") {
|
|
6046
|
+
parsed = bound.parsed;
|
|
6047
|
+
}
|
|
6048
|
+
if (parsed.error) return { ...empty, parsed, error: parsed.error };
|
|
4225
6049
|
if (parsed.join) return this.runJoined(parsed, options2);
|
|
4226
6050
|
const ctx = runContext(options2);
|
|
4227
6051
|
const range = resolveAggregateRange(parsed, options2, ctx);
|
|
@@ -4254,7 +6078,15 @@ var QueryService = class {
|
|
|
4254
6078
|
stages: { selected: 0, buckets: 0, aggregated: 0, groups: 0 },
|
|
4255
6079
|
matched: []
|
|
4256
6080
|
};
|
|
4257
|
-
|
|
6081
|
+
const bound = bindQueryParams(query, options2.params ?? {});
|
|
6082
|
+
if (bound.missing.length > 0) {
|
|
6083
|
+
const err = unknownTokensMessage(bound.missing);
|
|
6084
|
+
return { ...empty, parsed: { ...query, error: err }, error: err };
|
|
6085
|
+
}
|
|
6086
|
+
if (bound.parsed.family === "aggregate") {
|
|
6087
|
+
query = bound.parsed;
|
|
6088
|
+
}
|
|
6089
|
+
if (query.error) return { ...empty, parsed: query, error: query.error };
|
|
4258
6090
|
if (query.join) {
|
|
4259
6091
|
return {
|
|
4260
6092
|
...empty,
|
|
@@ -4279,33 +6111,41 @@ var QueryService = class {
|
|
|
4279
6111
|
const error = parsed.error ?? `Not a pipeline: "${parsed.raw}". Pipelines look like :source{filters} | :sum{metric:x} | :table.`;
|
|
4280
6112
|
return { parsed: { family: "pipeline", raw: parsed.raw, source: { kind: "dataset", name: "" }, transforms: [], error }, error };
|
|
4281
6113
|
}
|
|
4282
|
-
|
|
4283
|
-
|
|
6114
|
+
let pipeline = parsed;
|
|
6115
|
+
const fail = (error) => ({ parsed: pipeline, error });
|
|
6116
|
+
if (pipeline.error) return fail(pipeline.error);
|
|
6117
|
+
const bound = bindQueryParams(pipeline, options2.params ?? {});
|
|
6118
|
+
if (bound.missing.length > 0) return fail(unknownTokensMessage(bound.missing));
|
|
6119
|
+
if (bound.parsed.family === "pipeline") {
|
|
6120
|
+
pipeline = bound.parsed;
|
|
6121
|
+
if (pipeline.error) return fail(pipeline.error);
|
|
6122
|
+
}
|
|
4284
6123
|
let content;
|
|
4285
6124
|
let carried;
|
|
4286
6125
|
let sourceEvents;
|
|
4287
6126
|
let datasetNotes = [];
|
|
4288
|
-
if (
|
|
4289
|
-
const name =
|
|
6127
|
+
if (pipeline.source.kind === "dataset") {
|
|
6128
|
+
const name = pipeline.source.name;
|
|
4290
6129
|
const dataset = options2.pageSources?.get(name) ?? this.datasetStore?.getDataset(name);
|
|
4291
6130
|
if (!dataset) return fail(`Unknown dataset "${name}" \u2014 the host must register it before the query runs.`);
|
|
4292
6131
|
sourceEvents = dataset.events;
|
|
4293
6132
|
datasetNotes = dataset.notes;
|
|
4294
|
-
} else if (isFindQuery(
|
|
4295
|
-
const q =
|
|
6133
|
+
} else if (isFindQuery(pipeline.source.query)) {
|
|
6134
|
+
const q = pipeline.source.query;
|
|
4296
6135
|
content = await this.runFind(q, {
|
|
4297
6136
|
context: options2.context,
|
|
6137
|
+
skipContainers: pipeline.transforms.length > 0,
|
|
4298
6138
|
...options2.rangeStart !== void 0 || options2.rangeEnd !== void 0 ? { range: { start: options2.rangeStart ?? 0, end: options2.rangeEnd ?? Number.MAX_SAFE_INTEGER, endExclusive: false } } : {}
|
|
4299
6139
|
});
|
|
4300
6140
|
if (q.error) return fail(q.error);
|
|
4301
6141
|
} else {
|
|
4302
|
-
carried = await this.run(
|
|
4303
|
-
if (
|
|
6142
|
+
carried = await this.run(pipeline.source.query, options2);
|
|
6143
|
+
if (pipeline.source.query.error) return fail(pipeline.source.query.error);
|
|
4304
6144
|
}
|
|
4305
|
-
if (
|
|
6145
|
+
if (pipeline.transforms.length === 0) {
|
|
4306
6146
|
if (content) {
|
|
4307
6147
|
return {
|
|
4308
|
-
parsed,
|
|
6148
|
+
parsed: pipeline,
|
|
4309
6149
|
...content.notes.length ? { notes: content.notes } : {},
|
|
4310
6150
|
...content.blocks.length ? { blocks: content.blocks } : {},
|
|
4311
6151
|
...content.efforts?.length ? { efforts: content.efforts } : {},
|
|
@@ -4313,37 +6153,81 @@ var QueryService = class {
|
|
|
4313
6153
|
...content.table ? { table: content.table } : {},
|
|
4314
6154
|
...content.containers ? { containers: content.containers } : {},
|
|
4315
6155
|
...content.placements ? { placements: content.placements } : {},
|
|
4316
|
-
...
|
|
6156
|
+
...pipeline.sink ? { chart: pipeline.sink } : {}
|
|
4317
6157
|
};
|
|
4318
6158
|
}
|
|
4319
|
-
if (
|
|
4320
|
-
return { parsed, notes: datasetNotes, ...
|
|
6159
|
+
if (pipeline.source.kind === "dataset") {
|
|
6160
|
+
return { parsed: pipeline, notes: datasetNotes, ...pipeline.sink ? { chart: pipeline.sink } : {} };
|
|
4321
6161
|
}
|
|
4322
6162
|
return {
|
|
4323
|
-
parsed,
|
|
6163
|
+
parsed: pipeline,
|
|
4324
6164
|
...carried ? { series: carried.series, ...carried.unit ? { unit: carried.unit } : {}, ...carried.error ? { error: carried.error } : {} } : {},
|
|
4325
|
-
...
|
|
6165
|
+
...pipeline.sink ? { chart: pipeline.sink } : {}
|
|
4326
6166
|
};
|
|
4327
6167
|
}
|
|
4328
6168
|
if (!carried) {
|
|
4329
6169
|
if (!sourceEvents && content) sourceEvents = await this.sourceEventsFromFind(content);
|
|
4330
|
-
carried = await this.runAggregateEvents(
|
|
4331
|
-
for (const transform of
|
|
6170
|
+
carried = await this.runAggregateEvents(pipeline.transforms[0], sourceEvents ?? [], options2);
|
|
6171
|
+
for (const transform of pipeline.transforms.slice(1)) {
|
|
4332
6172
|
if (carried.error) break;
|
|
4333
6173
|
carried = reduceCarriedResult(carried, transform, options2, runContext(options2));
|
|
4334
6174
|
}
|
|
4335
6175
|
} else {
|
|
4336
|
-
for (const transform of
|
|
6176
|
+
for (const transform of pipeline.transforms) {
|
|
4337
6177
|
if (carried.error) break;
|
|
4338
6178
|
carried = reduceCarriedResult(carried, transform, options2, runContext(options2));
|
|
4339
6179
|
}
|
|
4340
6180
|
}
|
|
4341
6181
|
return {
|
|
4342
|
-
parsed,
|
|
6182
|
+
parsed: pipeline,
|
|
6183
|
+
series: carried.series,
|
|
6184
|
+
...carried.unit ? { unit: carried.unit } : {},
|
|
6185
|
+
...carried.error ? { error: carried.error } : {},
|
|
6186
|
+
...pipeline.sink ? { chart: pipeline.sink } : {}
|
|
6187
|
+
};
|
|
6188
|
+
}
|
|
6189
|
+
/**
|
|
6190
|
+
* Evaluate pipeline tail stages (`:sum{…} | :bar{…}`) over an already-fetched
|
|
6191
|
+
* `FindQueryResult` (or `FamilyRunPayload`). Resolves and caches `content.events`
|
|
6192
|
+
* on the first call so multiple downstream named branches (`kb = bench | :sum{…}`,
|
|
6193
|
+
* `bb = bench | :sum{…}`) share the exact `EventRecord[]` slice in memory.
|
|
6194
|
+
*/
|
|
6195
|
+
async runPipelineFromFind(content, pipeTail, options2 = {}) {
|
|
6196
|
+
const synthetic = parseQuery(`:segment{} | ${pipeTail}`);
|
|
6197
|
+
if (!isPipelineQuery(synthetic) || synthetic.error) {
|
|
6198
|
+
const error = synthetic.error ?? `Invalid pipeline tail: "${pipeTail}"`;
|
|
6199
|
+
return { parsed: { family: "pipeline", raw: pipeTail, source: { kind: "dataset", name: "" }, transforms: [], error }, error };
|
|
6200
|
+
}
|
|
6201
|
+
if (!content.events) {
|
|
6202
|
+
content.events = await this.sourceEventsFromFind({
|
|
6203
|
+
parsed: { family: "find", raw: "", target: "note", filters: [] },
|
|
6204
|
+
notes: content.notes ?? [],
|
|
6205
|
+
blocks: content.blocks ?? [],
|
|
6206
|
+
stages: { selected: 0, matched: 0 }
|
|
6207
|
+
});
|
|
6208
|
+
}
|
|
6209
|
+
const sourceEvents = content.events;
|
|
6210
|
+
if (synthetic.transforms.length === 0) {
|
|
6211
|
+
return {
|
|
6212
|
+
parsed: synthetic,
|
|
6213
|
+
...content.notes?.length ? { notes: content.notes } : {},
|
|
6214
|
+
...content.blocks?.length ? { blocks: content.blocks } : {},
|
|
6215
|
+
...content.runs?.length ? { runs: content.runs } : {},
|
|
6216
|
+
...content.table ? { table: content.table } : {},
|
|
6217
|
+
...synthetic.sink ? { chart: synthetic.sink } : {}
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
6220
|
+
let carried = await this.runAggregateEvents(synthetic.transforms[0], sourceEvents, options2);
|
|
6221
|
+
for (const transform of synthetic.transforms.slice(1)) {
|
|
6222
|
+
if (carried.error) break;
|
|
6223
|
+
carried = reduceCarriedResult(carried, transform, options2, runContext(options2));
|
|
6224
|
+
}
|
|
6225
|
+
return {
|
|
6226
|
+
parsed: synthetic,
|
|
4343
6227
|
series: carried.series,
|
|
4344
6228
|
...carried.unit ? { unit: carried.unit } : {},
|
|
4345
6229
|
...carried.error ? { error: carried.error } : {},
|
|
4346
|
-
...
|
|
6230
|
+
...synthetic.sink ? { chart: synthetic.sink } : {}
|
|
4347
6231
|
};
|
|
4348
6232
|
}
|
|
4349
6233
|
/** Events behind a content source — resolved ONLY when a transform needs
|
|
@@ -4394,17 +6278,16 @@ var QueryService = class {
|
|
|
4394
6278
|
error: `Aggregate "${parsed.agg}" requires a metric \u2014 use ${parsed.agg}{metric:<key>}`
|
|
4395
6279
|
};
|
|
4396
6280
|
}
|
|
4397
|
-
const matchesMetric = (metricKey, queryMetric) => !queryMetric || metricKey === queryMetric || queryMetric === "rep" && metricKey === "reps" || queryMetric === "reps" && metricKey === "rep";
|
|
4398
6281
|
const factTime = (row) => {
|
|
4399
6282
|
if (row.metricDate) return zonedNoon(row.metricDate, ctx.timeZone);
|
|
4400
6283
|
return row.timestamp;
|
|
4401
6284
|
};
|
|
4402
|
-
const
|
|
4403
|
-
const
|
|
4404
|
-
const
|
|
4405
|
-
const matched = candidates.filter((row) => matchesFilters(row, parsed.filters,
|
|
6285
|
+
const metricFilter = parsed.metric ? { metricKey: parsed.metric } : void 0;
|
|
6286
|
+
const candidates = eventRows.flatMap((row) => projectEventToFacts(row, metricFilter)).filter((row) => !range || inRange(factTime(row), range));
|
|
6287
|
+
const factDims = await this.loadFactDimensions(candidates, parsed.filters, parsed.groupBy);
|
|
6288
|
+
const matched = candidates.filter((row) => matchesFilters(row, parsed.filters, factDims));
|
|
4406
6289
|
const { selected, report } = selectContributions(matched, parsed.agg);
|
|
4407
|
-
return this.buildResult(selected, parsed, options2,
|
|
6290
|
+
return this.buildResult(selected, parsed, options2, factDims, range, report);
|
|
4408
6291
|
}
|
|
4409
6292
|
/**
|
|
4410
6293
|
* Stages 2–4 — BUCKET → GROUP → AGGREGATE over an already-selected +
|
|
@@ -4566,11 +6449,10 @@ var QueryService = class {
|
|
|
4566
6449
|
if (joinRange) {
|
|
4567
6450
|
facts = facts.filter((f) => inRange(f.timestamp, joinRange));
|
|
4568
6451
|
}
|
|
4569
|
-
const
|
|
4570
|
-
const
|
|
4571
|
-
const matched = facts.filter((f) => matchesFilters(f, parsed.filters, noteTags));
|
|
6452
|
+
const factDims = await this.loadFactDimensions(facts, parsed.filters, parsed.groupBy);
|
|
6453
|
+
const matched = facts.filter((f) => matchesFilters(f, parsed.filters, factDims));
|
|
4572
6454
|
const { selected, report } = selectContributions(matched, parsed.agg);
|
|
4573
|
-
return this.buildResult(selected, parsed, options2,
|
|
6455
|
+
return this.buildResult(selected, parsed, options2, factDims, joinRange, report);
|
|
4574
6456
|
}
|
|
4575
6457
|
/** Direction 1 — keep only content owning a wod block whose raw-log metric
|
|
4576
6458
|
* aggregate satisfies the predicate. `notes` for :note, `blocks` for
|
|
@@ -4593,9 +6475,9 @@ var QueryService = class {
|
|
|
4593
6475
|
* and return the ids whose aggregate satisfies the join predicate. */
|
|
4594
6476
|
async contentIdsSatisfying(contentIds, join) {
|
|
4595
6477
|
const facts = dedupeById(await this.deriveMetricFacts(contentIds, join.metric));
|
|
4596
|
-
const
|
|
6478
|
+
const factDims = await this.loadFactDimensions(facts, join.filters, []);
|
|
4597
6479
|
const filtered = selectContributions(
|
|
4598
|
-
facts.filter((f) => matchesFilters(f, join.filters,
|
|
6480
|
+
facts.filter((f) => matchesFilters(f, join.filters, factDims)),
|
|
4599
6481
|
join.agg
|
|
4600
6482
|
).selected;
|
|
4601
6483
|
const byContent = /* @__PURE__ */ new Map();
|
|
@@ -4621,7 +6503,8 @@ var QueryService = class {
|
|
|
4621
6503
|
async deriveMetricFacts(contentIds, metricKey) {
|
|
4622
6504
|
const ids = [...new Set(contentIds)];
|
|
4623
6505
|
const rows = await Promise.all(ids.map((blockContentId) => this.store.getEventsByContent(blockContentId)));
|
|
4624
|
-
|
|
6506
|
+
const metricFilter = metricKey ? { metricKey } : void 0;
|
|
6507
|
+
return rows.flat().flatMap((row) => projectEventToFacts(row, metricFilter));
|
|
4625
6508
|
}
|
|
4626
6509
|
/** All content blocks — journal and seeded corpus in one store. */
|
|
4627
6510
|
async allContentBlocks() {
|
|
@@ -4654,22 +6537,78 @@ var QueryService = class {
|
|
|
4654
6537
|
}
|
|
4655
6538
|
return ids;
|
|
4656
6539
|
}
|
|
4657
|
-
/**
|
|
4658
|
-
*
|
|
4659
|
-
*
|
|
4660
|
-
|
|
6540
|
+
/**
|
|
6541
|
+
* Load 3-tier fact dimensions (`noteTags`, `typedNoteTags`, `effortsBySlug`)
|
|
6542
|
+
* in at most one batched `relationStore.getBatches` call and one
|
|
6543
|
+
* `effortStore.getAllEfforts` call per query run.
|
|
6544
|
+
*/
|
|
6545
|
+
async loadFactDimensions(rows, filters, groupBy) {
|
|
6546
|
+
if (rows.length === 0) return EMPTY_FACT_DIMS;
|
|
6547
|
+
const keys = /* @__PURE__ */ new Set([...filters.map((f) => f.key), ...groupBy]);
|
|
6548
|
+
const needsUntypedTags = keys.has("tags") || keys.has("tag");
|
|
6549
|
+
const neededTypedKeys = [...keys].filter((k) => TYPED_TAG_KEY_SET.has(k) || k === "discipline" || k === "intensity");
|
|
6550
|
+
const needsTypedTags = neededTypedKeys.length > 0;
|
|
6551
|
+
const needsEfforts = keys.has("equipment") || keys.has("discipline") || keys.has("intensity");
|
|
4661
6552
|
const noteTags = /* @__PURE__ */ new Map();
|
|
4662
|
-
|
|
4663
|
-
const
|
|
4664
|
-
if (
|
|
4665
|
-
const
|
|
4666
|
-
for (const
|
|
4667
|
-
|
|
6553
|
+
const typedNoteTags = /* @__PURE__ */ new Map();
|
|
6554
|
+
const effortsBySlug = /* @__PURE__ */ new Map();
|
|
6555
|
+
if (needsEfforts) {
|
|
6556
|
+
const efforts = await this.effortStore.getAllEfforts();
|
|
6557
|
+
for (const effort of efforts) {
|
|
6558
|
+
effortsBySlug.set(effort.slug, effort);
|
|
6559
|
+
}
|
|
6560
|
+
}
|
|
6561
|
+
if (needsUntypedTags || needsTypedTags) {
|
|
6562
|
+
const noteIds = [...new Set(rows.map((r) => r.noteId).filter(Boolean))];
|
|
6563
|
+
if (this.relationStore) {
|
|
6564
|
+
const bundles = await this.relationStore.getBatches(noteIds);
|
|
6565
|
+
for (const id of noteIds) {
|
|
6566
|
+
const tags = bundles.get(id)?.tags ?? [];
|
|
6567
|
+
if (needsUntypedTags) {
|
|
6568
|
+
noteTags.set(id, tags.map((t2) => t2.label));
|
|
6569
|
+
}
|
|
6570
|
+
if (needsTypedTags) {
|
|
6571
|
+
const byType = /* @__PURE__ */ new Map();
|
|
6572
|
+
for (const tag of tags) {
|
|
6573
|
+
if (!tag.type) continue;
|
|
6574
|
+
const list = byType.get(tag.type);
|
|
6575
|
+
if (list) {
|
|
6576
|
+
if (!list.includes(tag.label)) list.push(tag.label);
|
|
6577
|
+
} else {
|
|
6578
|
+
byType.set(tag.type, [tag.label]);
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
typedNoteTags.set(id, byType);
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
} else {
|
|
6585
|
+
if (needsUntypedTags) {
|
|
6586
|
+
await Promise.all(noteIds.map(async (id) => noteTags.set(id, await this.noteStore.getNoteTagLabels(id))));
|
|
6587
|
+
}
|
|
6588
|
+
if (needsTypedTags && this.tagsStore.getAll) {
|
|
6589
|
+
const allTags = await this.tagsStore.getAll();
|
|
6590
|
+
const tagById = new Map(allTags.map((t2) => [t2.id, t2]));
|
|
6591
|
+
await Promise.all(
|
|
6592
|
+
noteIds.map(async (id) => {
|
|
6593
|
+
const links = await this.noteTagsStore.getAllFromIndex("by-note", id);
|
|
6594
|
+
const byType = /* @__PURE__ */ new Map();
|
|
6595
|
+
for (const link of links) {
|
|
6596
|
+
const tag = tagById.get(link.tagId);
|
|
6597
|
+
if (!tag?.type) continue;
|
|
6598
|
+
const list = byType.get(tag.type);
|
|
6599
|
+
if (list) {
|
|
6600
|
+
if (!list.includes(tag.label)) list.push(tag.label);
|
|
6601
|
+
} else {
|
|
6602
|
+
byType.set(tag.type, [tag.label]);
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
typedNoteTags.set(id, byType);
|
|
6606
|
+
})
|
|
6607
|
+
);
|
|
6608
|
+
}
|
|
4668
6609
|
}
|
|
4669
|
-
return noteTags;
|
|
4670
6610
|
}
|
|
4671
|
-
|
|
4672
|
-
return noteTags;
|
|
6611
|
+
return { noteTags, typedNoteTags, effortsBySlug };
|
|
4673
6612
|
}
|
|
4674
6613
|
/** Resolve matching note IDs for an exercise slug via the block_efforts containment index. */
|
|
4675
6614
|
async getNoteIdsForEffort(effortSlug) {
|
|
@@ -4678,25 +6617,195 @@ var QueryService = class {
|
|
|
4678
6617
|
}
|
|
4679
6618
|
/** Resolve matching note IDs for a canonical typed tag (e.g. equipment:kettlebell). */
|
|
4680
6619
|
async getNoteIdsForTypedTag(type, label) {
|
|
6620
|
+
const out = /* @__PURE__ */ new Set();
|
|
4681
6621
|
const tags = await this.tagsStore.getAllFromIndex("by-label", label);
|
|
4682
6622
|
const typedTag = tags.find((t2) => t2.type === type);
|
|
4683
|
-
if (
|
|
4684
|
-
|
|
4685
|
-
|
|
6623
|
+
if (typedTag) {
|
|
6624
|
+
const links = await this.noteTagsStore.getAllFromIndex("by-tag", typedTag.id);
|
|
6625
|
+
for (const link of links) out.add(link.noteId);
|
|
6626
|
+
}
|
|
6627
|
+
if (type === "equipment") {
|
|
6628
|
+
const slug = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
6629
|
+
const candidates = slug && slug !== label ? [label, slug] : [label];
|
|
6630
|
+
for (const key of candidates) {
|
|
6631
|
+
const rows = await this.blockEquipmentStore.getAllFromIndex("by-equipment", key);
|
|
6632
|
+
for (const row of rows) {
|
|
6633
|
+
if (row?.noteId) out.add(row.noteId);
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
return out;
|
|
6638
|
+
}
|
|
6639
|
+
/**
|
|
6640
|
+
* Execute a :notebook query (ticket 08) — the notebook plane is a NOTE
|
|
6641
|
+
* plane (ticket 05 semantics preserved): bare `:notebook{}` returns the
|
|
6642
|
+
* notes of every notebook — journal, playground, defaults, user-created,
|
|
6643
|
+
* feed-delivered. `notebook:{slug}` narrows to members of those
|
|
6644
|
+
* notebooks and `!notebook:` excludes them; the result shape never
|
|
6645
|
+
* changes with polarity, and container criteria (tags:, effort:, typed
|
|
6646
|
+
* tags, text:) narrow the members exactly like the note plane — this
|
|
6647
|
+
* delegates to it. The notebook CONTAINER listing (the /notebooks
|
|
6648
|
+
* management surface) is the app's NotebookService model, not WQL rows.
|
|
6649
|
+
*/
|
|
6650
|
+
async runFindNotebook(parsed, options2 = {}) {
|
|
6651
|
+
const wantsContainers = options2.notebookPlane === "containers" || parsed.filters.some(
|
|
6652
|
+
(f) => f.key === "type" && !f.negate && f.values.some((v) => v.value === "notebook")
|
|
6653
|
+
);
|
|
6654
|
+
if (wantsContainers && this.feedCatalogStore) {
|
|
6655
|
+
return this.runFindNotebookContainers(parsed);
|
|
6656
|
+
}
|
|
6657
|
+
return this.runFind({ ...parsed, target: "note" }, options2);
|
|
6658
|
+
}
|
|
6659
|
+
/**
|
|
6660
|
+
* Execute `:feed{}` — the distributed Feed sources plane.
|
|
6661
|
+
* Returns one synthetic Note-shaped row per registered Feed source
|
|
6662
|
+
* (including "My Published Feed" when local public content or server snapshot exists).
|
|
6663
|
+
*/
|
|
6664
|
+
async runFindFeed(parsed, _options = {}) {
|
|
6665
|
+
const allFeeds = this.feedCatalogStore ? await this.feedCatalogStore.getAllFeeds() : [];
|
|
6666
|
+
const selected = allFeeds.length;
|
|
6667
|
+
let matched = allFeeds;
|
|
6668
|
+
for (const filter of parsed.filters) {
|
|
6669
|
+
if (filter.key === "feed") {
|
|
6670
|
+
const wanted = new Set(filter.values.map((v) => v.value));
|
|
6671
|
+
const hasWildcard = filter.values.some((v) => v.wildcard);
|
|
6672
|
+
matched = matched.filter((f) => {
|
|
6673
|
+
const hit = hasWildcard ? f.id !== "local" : wanted.has(f.id);
|
|
6674
|
+
return filter.negate ? !hit : hit;
|
|
6675
|
+
});
|
|
6676
|
+
} else if (filter.key === "subscribed") {
|
|
6677
|
+
const vals = new Set(filter.values.map((v) => v.value));
|
|
6678
|
+
const wantsTrue = vals.has("true");
|
|
6679
|
+
const wantsFalse = vals.has("false");
|
|
6680
|
+
matched = matched.filter((f) => {
|
|
6681
|
+
const hit = wantsTrue && wantsFalse ? true : wantsTrue ? f.subscribed : wantsFalse ? !f.subscribed : true;
|
|
6682
|
+
return filter.negate ? !hit : hit;
|
|
6683
|
+
});
|
|
6684
|
+
} else if (filter.key === "text") {
|
|
6685
|
+
const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
|
|
6686
|
+
matched = matched.filter((f) => {
|
|
6687
|
+
const hit = f.label.toLowerCase().includes(search) || (f.description ?? "").toLowerCase().includes(search) || f.url.toLowerCase().includes(search);
|
|
6688
|
+
return filter.negate ? !hit : hit;
|
|
6689
|
+
});
|
|
6690
|
+
} else if (isTagLabelKey(filter.key)) {
|
|
6691
|
+
const wanted = new Set(filter.values.map((v) => v.value.toLowerCase()));
|
|
6692
|
+
matched = matched.filter((f) => {
|
|
6693
|
+
const rowTags = [
|
|
6694
|
+
`tier:${f.tier}`,
|
|
6695
|
+
f.tier,
|
|
6696
|
+
f.subscribed ? "subscribed:true" : "subscribed:false",
|
|
6697
|
+
...f.unpublishedChanges ? ["status:unpublished", "unpublished"] : [],
|
|
6698
|
+
...f.updateAvailable ? ["status:update-available", "update-available"] : []
|
|
6699
|
+
].map((t2) => t2.toLowerCase());
|
|
6700
|
+
const hit = rowTags.some((t2) => wanted.has(t2));
|
|
6701
|
+
return filter.negate ? !hit : hit;
|
|
6702
|
+
});
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
let rows = matched.map((f) => {
|
|
6706
|
+
const summaryParts = [
|
|
6707
|
+
`${f.notebookCount} notebook${f.notebookCount === 1 ? "" : "s"}`,
|
|
6708
|
+
f.itemCount > 0 ? `${f.itemCount} notes` : "",
|
|
6709
|
+
f.unpublishedChanges ? "Unpublished changes" : "",
|
|
6710
|
+
f.updateAvailable ? `Update v${f.localVersion ?? 0} \u2192 v${f.remoteVersion}` : ""
|
|
6711
|
+
].filter(Boolean);
|
|
6712
|
+
const body = [f.description ?? "", summaryParts.join(" \xB7 "), f.url].filter(Boolean).join("\n");
|
|
6713
|
+
return {
|
|
6714
|
+
id: `feed/${f.id}`,
|
|
6715
|
+
title: f.label,
|
|
6716
|
+
type: "feed",
|
|
6717
|
+
rawContent: body,
|
|
6718
|
+
sourceRevision: 1,
|
|
6719
|
+
blocks: {},
|
|
6720
|
+
createdAt: f.builtAt ? Date.parse(f.builtAt) || 0 : 0,
|
|
6721
|
+
sourceId: `feed:${f.id}`,
|
|
6722
|
+
sourcePath: f.url,
|
|
6723
|
+
...f.id !== "local" ? { feedOrigin: "feed", feedSourceId: f.id, seedOrigin: "seed" } : { seedOrigin: "user" },
|
|
6724
|
+
tags: [
|
|
6725
|
+
`tier:${f.tier}`,
|
|
6726
|
+
f.subscribed ? "subscribed:true" : "subscribed:false",
|
|
6727
|
+
...f.unpublishedChanges ? ["status:unpublished"] : [],
|
|
6728
|
+
...f.updateAvailable ? ["status:update-available"] : []
|
|
6729
|
+
]
|
|
6730
|
+
};
|
|
6731
|
+
});
|
|
6732
|
+
rows = applyOrderAndPage(rows, parsed);
|
|
6733
|
+
return { parsed, notes: rows, blocks: [], stages: { selected, matched: rows.length } };
|
|
4686
6734
|
}
|
|
4687
6735
|
/**
|
|
4688
|
-
* Execute
|
|
4689
|
-
*
|
|
4690
|
-
* notes of every notebook — journal, playground, defaults, user-created,
|
|
4691
|
-
* feed-delivered. `notebook:{slug}` narrows to members of those
|
|
4692
|
-
* notebooks and `!notebook:` excludes them; the result shape never
|
|
4693
|
-
* changes with polarity, and container criteria (tags:, effort:, typed
|
|
4694
|
-
* tags, text:) narrow the members exactly like the note plane — this
|
|
4695
|
-
* delegates to it. The notebook CONTAINER listing (the /notebooks
|
|
4696
|
-
* management surface) is the app's NotebookService model, not WQL rows.
|
|
6736
|
+
* Execute `:notebook{type:notebook, ...}` — the Notebook container listing
|
|
6737
|
+
* across user-owned local notebooks and manifest-discovered remote Feed notebooks.
|
|
4697
6738
|
*/
|
|
4698
|
-
async
|
|
4699
|
-
|
|
6739
|
+
async runFindNotebookContainers(parsed) {
|
|
6740
|
+
const allNotebooks = this.feedCatalogStore ? await this.feedCatalogStore.getAllNotebooks() : [];
|
|
6741
|
+
const selected = allNotebooks.length;
|
|
6742
|
+
let matched = allNotebooks;
|
|
6743
|
+
for (const filter of parsed.filters) {
|
|
6744
|
+
if (filter.key === "feed") {
|
|
6745
|
+
const wanted = new Set(filter.values.map((v) => v.value));
|
|
6746
|
+
const hasWildcard = filter.values.some((v) => v.wildcard);
|
|
6747
|
+
matched = matched.filter((nb) => {
|
|
6748
|
+
const hit = hasWildcard ? nb.feedId !== "local" : wanted.has(nb.feedId);
|
|
6749
|
+
return filter.negate ? !hit : hit;
|
|
6750
|
+
});
|
|
6751
|
+
} else if (filter.key === "notebook") {
|
|
6752
|
+
const wanted = new Set(filter.values.map((v) => wodWikiCore.notebookSlugFromId(v.value)));
|
|
6753
|
+
matched = matched.filter((nb) => {
|
|
6754
|
+
const hit = wanted.has(nb.slug) || wanted.has(nb.id) || wanted.has(wodWikiCore.notebookSlugFromId(nb.chunkId));
|
|
6755
|
+
return filter.negate ? !hit : hit;
|
|
6756
|
+
});
|
|
6757
|
+
} else if (filter.key === "text") {
|
|
6758
|
+
const search = filter.values.map((v) => v.value).join(" ").toLowerCase();
|
|
6759
|
+
matched = matched.filter((nb) => {
|
|
6760
|
+
const hit = nb.label.toLowerCase().includes(search) || (nb.description ?? "").toLowerCase().includes(search) || nb.feedLabel.toLowerCase().includes(search);
|
|
6761
|
+
return filter.negate ? !hit : hit;
|
|
6762
|
+
});
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
const subFilter = parsed.filters.find((f) => f.key === "subscribed");
|
|
6766
|
+
if (subFilter) {
|
|
6767
|
+
const vals = new Set(subFilter.values.map((v) => v.value));
|
|
6768
|
+
const wantsTrue = vals.has("true");
|
|
6769
|
+
const wantsFalse = vals.has("false");
|
|
6770
|
+
matched = matched.filter((nb) => {
|
|
6771
|
+
const isSub = nb.origin === "user" || nb.origin === "default" || nb.subscribed;
|
|
6772
|
+
const hit = wantsTrue && wantsFalse ? true : wantsTrue ? isSub : wantsFalse ? !isSub : false;
|
|
6773
|
+
return subFilter.negate ? !hit : hit;
|
|
6774
|
+
});
|
|
6775
|
+
} else {
|
|
6776
|
+
matched = matched.filter((nb) => nb.origin === "user" || nb.origin === "default" || nb.subscribed);
|
|
6777
|
+
}
|
|
6778
|
+
let rows = matched.map((nb) => {
|
|
6779
|
+
const isLocal = nb.origin === "user" || nb.origin === "default";
|
|
6780
|
+
return {
|
|
6781
|
+
id: isLocal ? `notebook/${nb.slug}` : `feed-notebook/${nb.feedId}/${nb.slug}`,
|
|
6782
|
+
title: nb.label,
|
|
6783
|
+
type: "notebook",
|
|
6784
|
+
rawContent: nb.description ?? "",
|
|
6785
|
+
sourceRevision: 1,
|
|
6786
|
+
blocks: {},
|
|
6787
|
+
createdAt: nb.updatedAt ? Date.parse(nb.updatedAt) || 0 : 0,
|
|
6788
|
+
notebookId: `notebook/${nb.slug}`,
|
|
6789
|
+
sourceId: `notebook:${nb.slug}`,
|
|
6790
|
+
...nb.feedUrl ? { sourcePath: nb.feedUrl } : {},
|
|
6791
|
+
...isLocal ? { seedOrigin: "user", ...nb.public !== void 0 ? { public: nb.public } : {} } : {
|
|
6792
|
+
feedOrigin: "feed",
|
|
6793
|
+
feedSourceId: nb.feedId,
|
|
6794
|
+
seedOrigin: "seed",
|
|
6795
|
+
seedChunkId: nb.chunkId,
|
|
6796
|
+
...nb.version !== void 0 ? { seedVersion: nb.version } : {}
|
|
6797
|
+
},
|
|
6798
|
+
tags: [
|
|
6799
|
+
...nb.category ? [`category:${nb.category}`] : [],
|
|
6800
|
+
`origin:${nb.origin}`,
|
|
6801
|
+
`count:${nb.count}`,
|
|
6802
|
+
nb.subscribed ? "subscribed:true" : "subscribed:false",
|
|
6803
|
+
...nb.public ? ["visibility:public"] : []
|
|
6804
|
+
]
|
|
6805
|
+
};
|
|
6806
|
+
});
|
|
6807
|
+
rows = applyOrderAndPage(rows, parsed);
|
|
6808
|
+
return { parsed, notes: rows, blocks: [], stages: { selected, matched: rows.length } };
|
|
4700
6809
|
}
|
|
4701
6810
|
/**
|
|
4702
6811
|
* Execute a :page query (ticket 08) — the placement plane. Results are
|
|
@@ -4973,194 +7082,6 @@ function hasLegacyIdentity(row) {
|
|
|
4973
7082
|
return false;
|
|
4974
7083
|
}
|
|
4975
7084
|
|
|
4976
|
-
// src/dashboard/model.ts
|
|
4977
|
-
var DASHBOARD_WIDGET_TYPES = [
|
|
4978
|
-
"table",
|
|
4979
|
-
"list",
|
|
4980
|
-
"value",
|
|
4981
|
-
"timeseries",
|
|
4982
|
-
"bar",
|
|
4983
|
-
"donut",
|
|
4984
|
-
"toplist",
|
|
4985
|
-
"stacked-bar",
|
|
4986
|
-
"goal-rings",
|
|
4987
|
-
"zone-distribution"
|
|
4988
|
-
];
|
|
4989
|
-
function isDashboardWidgetType(type) {
|
|
4990
|
-
return DASHBOARD_WIDGET_TYPES.includes(type);
|
|
4991
|
-
}
|
|
4992
|
-
function resolveWidgetType(type) {
|
|
4993
|
-
return type == null || type === "" ? "table" : type;
|
|
4994
|
-
}
|
|
4995
|
-
function unknownWidgetTypeMessage(type) {
|
|
4996
|
-
return `unknown widget type "${type}"`;
|
|
4997
|
-
}
|
|
4998
|
-
function unknownTokensMessage(missing) {
|
|
4999
|
-
return `unknown token${missing.length > 1 ? "s" : ""}: ${missing.map((m) => `$${m}`).join(", ")}`;
|
|
5000
|
-
}
|
|
5001
|
-
var PLANNED_WIDGET_TYPES = [];
|
|
5002
|
-
var KNOWN_CALC_METRICS = new Set(WQL_CALC_TARGETS);
|
|
5003
|
-
function isProposedMetric(metricKey) {
|
|
5004
|
-
if (!metricKey) return false;
|
|
5005
|
-
if (!metricKey.startsWith("calc.")) return false;
|
|
5006
|
-
return !KNOWN_CALC_METRICS.has(metricKey);
|
|
5007
|
-
}
|
|
5008
|
-
var DASHBOARD_GRID_MAX_COLS = 4;
|
|
5009
|
-
function parseQueryWidgetSuffix(suffix) {
|
|
5010
|
-
if (suffix === "") {
|
|
5011
|
-
return { type: "", attributes: {}, attributeOrder: [], error: 'missing widget type after "query:"' };
|
|
5012
|
-
}
|
|
5013
|
-
const attributes = {};
|
|
5014
|
-
const attributeOrder = [];
|
|
5015
|
-
const trimmedSuffix = suffix.trim();
|
|
5016
|
-
const headEnd = trimmedSuffix.search(/\s/);
|
|
5017
|
-
const head = headEnd === -1 ? trimmedSuffix : trimmedSuffix.slice(0, headEnd);
|
|
5018
|
-
const attrTail = headEnd === -1 ? "" : trimmedSuffix.slice(headEnd);
|
|
5019
|
-
const attrPattern = /([a-zA-Z][a-zA-Z0-9_-]*)=("([^"]*)"|\S+)/g;
|
|
5020
|
-
let consumed = 0;
|
|
5021
|
-
for (const match of attrTail.matchAll(attrPattern)) {
|
|
5022
|
-
consumed += match[0].length;
|
|
5023
|
-
const key = match[1].toLowerCase();
|
|
5024
|
-
const value = match[3] !== void 0 ? match[3] : match[2];
|
|
5025
|
-
if (key in attributes) {
|
|
5026
|
-
return { type: head, attributes, attributeOrder, error: `duplicate attribute "${key}"` };
|
|
5027
|
-
}
|
|
5028
|
-
attributes[key] = value;
|
|
5029
|
-
attributeOrder.push(key);
|
|
5030
|
-
}
|
|
5031
|
-
const leftover = attrTail.replace(attrPattern, "").trim();
|
|
5032
|
-
if (leftover !== "") {
|
|
5033
|
-
return { type: head, attributes, attributeOrder, error: `malformed attribute "${leftover.split(/\s+/)[0]}"` };
|
|
5034
|
-
}
|
|
5035
|
-
let rest = head.toLowerCase();
|
|
5036
|
-
let spanCols;
|
|
5037
|
-
let spanFull;
|
|
5038
|
-
if (rest.endsWith("-full")) {
|
|
5039
|
-
spanFull = true;
|
|
5040
|
-
rest = rest.slice(0, -"-full".length);
|
|
5041
|
-
} else {
|
|
5042
|
-
const spanMatch = rest.match(/-(\d+)$/);
|
|
5043
|
-
if (spanMatch) {
|
|
5044
|
-
spanCols = Number(spanMatch[1]);
|
|
5045
|
-
rest = rest.slice(0, -spanMatch[0].length);
|
|
5046
|
-
}
|
|
5047
|
-
}
|
|
5048
|
-
if (rest === "" || !/^[a-z][a-z0-9-]*$/.test(rest)) {
|
|
5049
|
-
return { type: rest, spanCols, spanFull, attributes, attributeOrder, error: `malformed widget type "${suffix}"` };
|
|
5050
|
-
}
|
|
5051
|
-
if (/-(\d+|full)$/.test(rest)) {
|
|
5052
|
-
return { type: rest, spanCols, spanFull, attributes, attributeOrder, error: `malformed widget suffix "${suffix}"` };
|
|
5053
|
-
}
|
|
5054
|
-
if (spanCols != null && (spanCols < 1 || spanCols > DASHBOARD_GRID_MAX_COLS)) {
|
|
5055
|
-
return { type: rest, spanCols, spanFull, attributes, attributeOrder, error: `span ${spanCols} outside 1..${DASHBOARD_GRID_MAX_COLS}` };
|
|
5056
|
-
}
|
|
5057
|
-
return { type: rest, spanCols, spanFull, attributes, attributeOrder };
|
|
5058
|
-
}
|
|
5059
|
-
var TOKEN_PREFIX = "dashboard.";
|
|
5060
|
-
function isDashboardMeta(meta) {
|
|
5061
|
-
return meta["dashboard"] === "true" || meta["dashboard"] === true;
|
|
5062
|
-
}
|
|
5063
|
-
function extractDashboardTokens(meta) {
|
|
5064
|
-
const tokens = [];
|
|
5065
|
-
for (const [key, value] of Object.entries(meta)) {
|
|
5066
|
-
if (!key.startsWith(TOKEN_PREFIX) || key === TOKEN_PREFIX) continue;
|
|
5067
|
-
const name = key.slice(TOKEN_PREFIX.length);
|
|
5068
|
-
if (name === "") continue;
|
|
5069
|
-
if (Array.isArray(value)) {
|
|
5070
|
-
if (value.length > 0) tokens.push({ name, values: value.map(String), isList: true });
|
|
5071
|
-
} else {
|
|
5072
|
-
tokens.push({ name, values: [String(value)], isList: false });
|
|
5073
|
-
}
|
|
5074
|
-
}
|
|
5075
|
-
return tokens;
|
|
5076
|
-
}
|
|
5077
|
-
function defaultTokenValues(tokens) {
|
|
5078
|
-
const values = {};
|
|
5079
|
-
for (const token of tokens) values[token.name] = token.values[0];
|
|
5080
|
-
return values;
|
|
5081
|
-
}
|
|
5082
|
-
function setDashboardTokenValue(meta, name, value) {
|
|
5083
|
-
const key = `${TOKEN_PREFIX}${name}`;
|
|
5084
|
-
const current = meta[key];
|
|
5085
|
-
const next = { ...meta };
|
|
5086
|
-
if (Array.isArray(current)) {
|
|
5087
|
-
if (!current.includes(value)) return meta;
|
|
5088
|
-
next[key] = [value, ...current.filter((v) => v !== value)];
|
|
5089
|
-
} else if (current != null) {
|
|
5090
|
-
next[key] = typeof current === "number" && !isNaN(Number(value)) ? Number(value) : value;
|
|
5091
|
-
} else {
|
|
5092
|
-
next[key] = value;
|
|
5093
|
-
}
|
|
5094
|
-
return next;
|
|
5095
|
-
}
|
|
5096
|
-
var TOKEN_REF_RE = /\$([A-Za-z][\w-]*)/g;
|
|
5097
|
-
function substituteTokens2(query, values) {
|
|
5098
|
-
const missing = /* @__PURE__ */ new Set();
|
|
5099
|
-
const substituted = query.replace(TOKEN_REF_RE, (raw, name) => {
|
|
5100
|
-
const value = values[name];
|
|
5101
|
-
if (value == null) {
|
|
5102
|
-
missing.add(name);
|
|
5103
|
-
return raw;
|
|
5104
|
-
}
|
|
5105
|
-
return value;
|
|
5106
|
-
});
|
|
5107
|
-
return { query: substituted, missing: [...missing] };
|
|
5108
|
-
}
|
|
5109
|
-
function referencedTokens(query) {
|
|
5110
|
-
const names = /* @__PURE__ */ new Set();
|
|
5111
|
-
for (const match of query.matchAll(TOKEN_REF_RE)) names.add(match[1]);
|
|
5112
|
-
return [...names];
|
|
5113
|
-
}
|
|
5114
|
-
function headingText(content) {
|
|
5115
|
-
const line = content.split("\n", 1)[0].replace(/^#{1,6}\s*/, "").trim();
|
|
5116
|
-
return line === "" ? void 0 : line;
|
|
5117
|
-
}
|
|
5118
|
-
function paragraphText(content) {
|
|
5119
|
-
const text = content.replace(/\s*\n\s*/g, " ").trim();
|
|
5120
|
-
return text === "" ? void 0 : text;
|
|
5121
|
-
}
|
|
5122
|
-
function buildDashboardDocument(sections, meta) {
|
|
5123
|
-
const widgets = [];
|
|
5124
|
-
sections.forEach((section, i) => {
|
|
5125
|
-
if (section.type !== "query") return;
|
|
5126
|
-
const body = section.content.split("\n").filter((l) => {
|
|
5127
|
-
const s = l.trim();
|
|
5128
|
-
return s !== "" && !s.startsWith("#");
|
|
5129
|
-
}).map((l) => l.trim()).join("\n");
|
|
5130
|
-
let title;
|
|
5131
|
-
let question;
|
|
5132
|
-
const above = sections[i - 1];
|
|
5133
|
-
if (above?.type === "markdown" && above.subtype === "paragraph") {
|
|
5134
|
-
question = paragraphText(above.content);
|
|
5135
|
-
const heading = sections[i - 2];
|
|
5136
|
-
if (heading?.type === "markdown" && heading.subtype === "heading") {
|
|
5137
|
-
title = headingText(heading.content);
|
|
5138
|
-
}
|
|
5139
|
-
} else if (above?.type === "markdown" && above.subtype === "heading") {
|
|
5140
|
-
title = headingText(above.content);
|
|
5141
|
-
}
|
|
5142
|
-
widgets.push({
|
|
5143
|
-
key: `w${widgets.length}`,
|
|
5144
|
-
type: section.widgetType ?? "",
|
|
5145
|
-
spanCols: section.spanCols,
|
|
5146
|
-
spanFull: section.spanFull,
|
|
5147
|
-
widgetError: section.widgetError,
|
|
5148
|
-
body,
|
|
5149
|
-
attributes: section.attributes ?? {},
|
|
5150
|
-
attributeOrder: section.attributeOrder ?? [],
|
|
5151
|
-
title,
|
|
5152
|
-
question
|
|
5153
|
-
});
|
|
5154
|
-
});
|
|
5155
|
-
const titleMeta = meta["title"];
|
|
5156
|
-
return {
|
|
5157
|
-
isDashboard: isDashboardMeta(meta),
|
|
5158
|
-
title: typeof titleMeta === "string" && titleMeta !== "" ? titleMeta : void 0,
|
|
5159
|
-
tokens: extractDashboardTokens(meta),
|
|
5160
|
-
widgets
|
|
5161
|
-
};
|
|
5162
|
-
}
|
|
5163
|
-
|
|
5164
7085
|
// src/dashboard/parser.ts
|
|
5165
7086
|
function parseDashboardNote(rawContent) {
|
|
5166
7087
|
const { meta } = wodWikiCore.parseFrontmatter(rawContent);
|
|
@@ -5211,6 +7132,7 @@ function parseDashboardNote(rawContent) {
|
|
|
5211
7132
|
sections.push({
|
|
5212
7133
|
type: "query",
|
|
5213
7134
|
content: bodyLines.join("\n"),
|
|
7135
|
+
fenceTag: tag,
|
|
5214
7136
|
widgetType,
|
|
5215
7137
|
spanCols,
|
|
5216
7138
|
spanFull,
|
|
@@ -5291,11 +7213,11 @@ dashboard.metric:
|
|
|
5291
7213
|
- totalVolume
|
|
5292
7214
|
---
|
|
5293
7215
|
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
sum:$metric{} by {week}
|
|
7216
|
+
\`\`\`query
|
|
7217
|
+
title: Weekly Trend
|
|
7218
|
+
description: How does $metric trend week over week?
|
|
7219
|
+
view: timeseries
|
|
7220
|
+
query: sum:$metric{} by {week}
|
|
5299
7221
|
\`\`\`
|
|
5300
7222
|
`;
|
|
5301
7223
|
}
|
|
@@ -5321,34 +7243,59 @@ function widgetFenceTag(spec) {
|
|
|
5321
7243
|
function widgetBodyLine(spec) {
|
|
5322
7244
|
return spec.wql.trim();
|
|
5323
7245
|
}
|
|
7246
|
+
function widgetSpecToQueryObject(spec) {
|
|
7247
|
+
if (spec.queryObject) {
|
|
7248
|
+
const base = { ...spec.queryObject };
|
|
7249
|
+
if (spec.title !== void 0) base.title = spec.title.trim() || void 0;
|
|
7250
|
+
if (spec.question !== void 0) base.description = spec.question.trim() || void 0;
|
|
7251
|
+
if (spec.spanFull) base.span = "full";
|
|
7252
|
+
else if (spec.spanCols !== void 0) base.span = spec.spanCols;
|
|
7253
|
+
return base;
|
|
7254
|
+
}
|
|
7255
|
+
const tag = widgetFenceTag(spec).slice(3);
|
|
7256
|
+
return parseQueryBlock(spec.wql, {
|
|
7257
|
+
fenceTag: tag,
|
|
7258
|
+
adjacentTitle: spec.title?.trim() || void 0,
|
|
7259
|
+
adjacentDescription: spec.question?.trim() || void 0
|
|
7260
|
+
});
|
|
7261
|
+
}
|
|
5324
7262
|
function renderWidgetGroup(spec) {
|
|
5325
|
-
const
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
7263
|
+
const obj = widgetSpecToQueryObject(spec);
|
|
7264
|
+
return serializeQueryBlockFence(obj).split("\n");
|
|
7265
|
+
}
|
|
7266
|
+
function normalizedBlockBodies(block) {
|
|
7267
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
7268
|
+
const trimmed = block.content.trim();
|
|
7269
|
+
candidates.add(trimmed);
|
|
7270
|
+
const legacyNorm = block.content.split("\n").filter((l) => {
|
|
7271
|
+
const s = l.trim();
|
|
7272
|
+
return s !== "" && !s.startsWith("#");
|
|
7273
|
+
}).map((l) => l.trim()).join("\n");
|
|
7274
|
+
candidates.add(legacyNorm);
|
|
7275
|
+
if (isYamlQueryBlockBody(block.content)) {
|
|
7276
|
+
const obj = parseQueryBlock(block.content, { fenceTag: block.fenceTag });
|
|
7277
|
+
candidates.add(queryBlockToDocumentText(obj).trim());
|
|
7278
|
+
}
|
|
7279
|
+
return candidates;
|
|
5335
7280
|
}
|
|
5336
7281
|
function findWidgetGroup(sections, key, expectedBody) {
|
|
5337
7282
|
const querySections = sections.filter((s) => s.type === "query");
|
|
5338
7283
|
const widgetIndex = querySections.findIndex((_, i) => `w${i}` === key);
|
|
5339
7284
|
if (widgetIndex === -1) return { ok: false, reason: "not-found" };
|
|
5340
7285
|
const block = querySections[widgetIndex];
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
}).map((l) => l.trim()).join("\n");
|
|
5345
|
-
if (body !== expectedBody.trim()) return { ok: false, reason: "stale-body" };
|
|
7286
|
+
if (!normalizedBlockBodies(block).has(expectedBody.trim())) {
|
|
7287
|
+
return { ok: false, reason: "stale-body" };
|
|
7288
|
+
}
|
|
5346
7289
|
return {
|
|
5347
7290
|
ok: true,
|
|
5348
7291
|
group: { widgetIndex, startLine: groupStartLine(sections, block), endLine: block.endLine }
|
|
5349
7292
|
};
|
|
5350
7293
|
}
|
|
5351
7294
|
function groupStartLine(sections, block) {
|
|
7295
|
+
if (isYamlQueryBlockBody(block.content)) {
|
|
7296
|
+
const obj = parseQueryBlock(block.content, { fenceTag: block.fenceTag });
|
|
7297
|
+
if (obj.title) return block.startLine;
|
|
7298
|
+
}
|
|
5352
7299
|
const idx = sections.indexOf(block);
|
|
5353
7300
|
const above = sections[idx - 1];
|
|
5354
7301
|
if (above?.type === "markdown" && above.subtype === "paragraph") {
|
|
@@ -5480,6 +7427,16 @@ function resizeWidget(raw, key, expectedBody, span) {
|
|
|
5480
7427
|
(s) => s.type === "query" && s.startLine >= group.startLine && s.endLine <= group.endLine
|
|
5481
7428
|
);
|
|
5482
7429
|
const lines = raw.split("\n");
|
|
7430
|
+
if (isYamlQueryBlockBody(block.content)) {
|
|
7431
|
+
const obj = parseQueryBlock(block.content, { fenceTag: block.fenceTag });
|
|
7432
|
+
if (span.spanFull) obj.span = "full";
|
|
7433
|
+
else if (span.spanCols != null && span.spanCols > 1) obj.span = span.spanCols;
|
|
7434
|
+
else delete obj.span;
|
|
7435
|
+
const indent2 = lines[block.startLine].match(/^\s*/)[0];
|
|
7436
|
+
const nextFenceLines = serializeQueryBlockFence(obj, { forceYaml: true }).split("\n").map((l) => indent2 + l);
|
|
7437
|
+
lines.splice(block.startLine, block.endLine - block.startLine + 1, ...nextFenceLines);
|
|
7438
|
+
return widgetResult(raw, lines.join("\n"));
|
|
7439
|
+
}
|
|
5483
7440
|
const indent = lines[block.startLine].match(/^\s*/)[0];
|
|
5484
7441
|
lines[block.startLine] = indent + widgetFenceTag({
|
|
5485
7442
|
type: block.widgetType ?? "",
|
|
@@ -5590,6 +7547,7 @@ function resolveEntityScope(input) {
|
|
|
5590
7547
|
scope: scopeFilter,
|
|
5591
7548
|
effort: slug,
|
|
5592
7549
|
note: id,
|
|
7550
|
+
thisnote: id,
|
|
5593
7551
|
metric: "totalVolume"
|
|
5594
7552
|
}
|
|
5595
7553
|
};
|
|
@@ -5616,6 +7574,7 @@ function resolveEntityScope(input) {
|
|
|
5616
7574
|
scope: scopeFilter,
|
|
5617
7575
|
equipment: slug,
|
|
5618
7576
|
note: id,
|
|
7577
|
+
thisnote: id,
|
|
5619
7578
|
metric: "totalVolume"
|
|
5620
7579
|
}
|
|
5621
7580
|
};
|
|
@@ -5651,6 +7610,7 @@ function resolveEntityScope(input) {
|
|
|
5651
7610
|
tag: slug,
|
|
5652
7611
|
category: cat,
|
|
5653
7612
|
note: id,
|
|
7613
|
+
thisnote: id,
|
|
5654
7614
|
metric: "tis"
|
|
5655
7615
|
}
|
|
5656
7616
|
};
|
|
@@ -5679,6 +7639,7 @@ function resolveEntityScope(input) {
|
|
|
5679
7639
|
scope: scopeFilter,
|
|
5680
7640
|
category: cat,
|
|
5681
7641
|
note: id,
|
|
7642
|
+
thisnote: id,
|
|
5682
7643
|
metric: "tis"
|
|
5683
7644
|
}
|
|
5684
7645
|
};
|
|
@@ -5800,12 +7761,34 @@ function resolveEntityScope(input) {
|
|
|
5800
7761
|
tokens: {
|
|
5801
7762
|
scope: scopeFilter,
|
|
5802
7763
|
note: id || slug,
|
|
7764
|
+
thisnote: id || slug,
|
|
5803
7765
|
metric: "tis"
|
|
5804
7766
|
}
|
|
5805
7767
|
};
|
|
5806
7768
|
}
|
|
5807
7769
|
}
|
|
5808
7770
|
}
|
|
7771
|
+
function resolveQueryExecutionScope(input = {}) {
|
|
7772
|
+
const entityTokens = input.noteId?.trim() ? resolveEntityScope({ noteId: input.noteId, rawContent: input.rawContent }).tokens : {};
|
|
7773
|
+
const fmMeta = input.rawContent ? wodWikiCore.parseFrontmatter(input.rawContent).meta : void 0;
|
|
7774
|
+
const fmTokens = fmMeta ? defaultTokenValues(extractDashboardTokens(fmMeta)) : {};
|
|
7775
|
+
const tokens = {
|
|
7776
|
+
...entityTokens,
|
|
7777
|
+
...fmTokens,
|
|
7778
|
+
...input.tokenValues ?? {}
|
|
7779
|
+
};
|
|
7780
|
+
const pageSources = input.pageSources instanceof PageSourceRegistry ? input.pageSources : new PageSourceRegistry(input.pageSources ? [...input.pageSources.entries()] : []);
|
|
7781
|
+
if (input.noteEvents !== void 0 || input.note !== void 0) {
|
|
7782
|
+
const existing = pageSources.get("@note");
|
|
7783
|
+
pageSources.set("@note", {
|
|
7784
|
+
events: input.noteEvents ?? existing?.events ?? [],
|
|
7785
|
+
notes: input.note ? [input.note] : existing?.notes ?? []
|
|
7786
|
+
});
|
|
7787
|
+
} else if (input.noteId?.trim() && !pageSources.has("@note")) {
|
|
7788
|
+
pageSources.set("@note", { events: [], notes: [] });
|
|
7789
|
+
}
|
|
7790
|
+
return { tokens, pageSources };
|
|
7791
|
+
}
|
|
5809
7792
|
function buildWidgetsForWqlScope(wqlOrScope, extraWidgets = []) {
|
|
5810
7793
|
if (typeof wqlOrScope === "string") {
|
|
5811
7794
|
const query = wqlOrScope.trim();
|
|
@@ -5983,14 +7966,143 @@ function buildDashboardFromScope(options2) {
|
|
|
5983
7966
|
return headerLines.join("\n") + bodyGroups.join("\n\n") + "\n";
|
|
5984
7967
|
}
|
|
5985
7968
|
|
|
7969
|
+
// src/queryCache.ts
|
|
7970
|
+
var MUTATION_MESSAGE = "ANALYTICS_MUTATION";
|
|
7971
|
+
var AnalyticsInvalidationBus = class {
|
|
7972
|
+
makeChannel;
|
|
7973
|
+
channel;
|
|
7974
|
+
generation = 0;
|
|
7975
|
+
listeners = /* @__PURE__ */ new Set();
|
|
7976
|
+
constructor(makeChannel) {
|
|
7977
|
+
this.makeChannel = makeChannel;
|
|
7978
|
+
}
|
|
7979
|
+
get generationId() {
|
|
7980
|
+
return this.generation;
|
|
7981
|
+
}
|
|
7982
|
+
/** Commit hook: bump local generation and broadcast the receipt. */
|
|
7983
|
+
async publishMutation() {
|
|
7984
|
+
this.generation += 1;
|
|
7985
|
+
this.notify();
|
|
7986
|
+
this.channel ??= this.makeChannel();
|
|
7987
|
+
try {
|
|
7988
|
+
this.channel?.postMessage({ type: MUTATION_MESSAGE, generation: this.generation });
|
|
7989
|
+
} catch {
|
|
7990
|
+
}
|
|
7991
|
+
}
|
|
7992
|
+
/** Listen for receipts from OTHER tabs (and this one). */
|
|
7993
|
+
subscribe() {
|
|
7994
|
+
this.channel ??= this.makeChannel();
|
|
7995
|
+
this.channel?.addEventListener("message", (event) => {
|
|
7996
|
+
const data = event.data;
|
|
7997
|
+
if (data?.type !== MUTATION_MESSAGE) return;
|
|
7998
|
+
this.generation = Math.max(this.generation, data.generation ?? 0) + 1;
|
|
7999
|
+
this.notify();
|
|
8000
|
+
});
|
|
8001
|
+
}
|
|
8002
|
+
onChange(listener) {
|
|
8003
|
+
this.listeners.add(listener);
|
|
8004
|
+
return () => this.listeners.delete(listener);
|
|
8005
|
+
}
|
|
8006
|
+
notify() {
|
|
8007
|
+
for (const listener of this.listeners) listener();
|
|
8008
|
+
}
|
|
8009
|
+
};
|
|
8010
|
+
var QueryCache = class {
|
|
8011
|
+
entries = /* @__PURE__ */ new Map();
|
|
8012
|
+
maxEntries;
|
|
8013
|
+
constructor(bus, options2 = {}) {
|
|
8014
|
+
this.maxEntries = Math.max(1, options2.maxEntries ?? DEFAULT_MAX_ENTRIES);
|
|
8015
|
+
bus.onChange(() => this.entries.clear());
|
|
8016
|
+
}
|
|
8017
|
+
get(key, generation) {
|
|
8018
|
+
const entry = this.entries.get(key);
|
|
8019
|
+
if (!entry) return void 0;
|
|
8020
|
+
if (entry.generation !== generation) {
|
|
8021
|
+
this.entries.delete(key);
|
|
8022
|
+
return void 0;
|
|
8023
|
+
}
|
|
8024
|
+
this.entries.delete(key);
|
|
8025
|
+
this.entries.set(key, entry);
|
|
8026
|
+
return entry.value;
|
|
8027
|
+
}
|
|
8028
|
+
set(key, value, generation) {
|
|
8029
|
+
this.entries.delete(key);
|
|
8030
|
+
this.entries.set(key, { generation, value });
|
|
8031
|
+
while (this.entries.size > this.maxEntries) {
|
|
8032
|
+
const oldest = this.entries.keys().next();
|
|
8033
|
+
if (oldest.done) break;
|
|
8034
|
+
this.entries.delete(oldest.value);
|
|
8035
|
+
}
|
|
8036
|
+
}
|
|
8037
|
+
clear() {
|
|
8038
|
+
this.entries.clear();
|
|
8039
|
+
}
|
|
8040
|
+
/** Current entry count — the LRU bound's observable state. */
|
|
8041
|
+
get size() {
|
|
8042
|
+
return this.entries.size;
|
|
8043
|
+
}
|
|
8044
|
+
};
|
|
8045
|
+
var DEFAULT_MAX_ENTRIES = 64;
|
|
8046
|
+
function aggKey(a) {
|
|
8047
|
+
return [a.agg, a.metric, a.filters, a.groupBy, a.rollup ?? null, a.window ?? null, a.displayUnit ?? null, a.join ?? null];
|
|
8048
|
+
}
|
|
8049
|
+
function findKey(f) {
|
|
8050
|
+
return [f.target, f.filters, f.sourceScope ?? null, f.window ?? null, f.groupBy ?? null, f.displayUnit ?? null, f.pipes ?? null, f.join ?? null];
|
|
8051
|
+
}
|
|
8052
|
+
function computeCacheKey(parsed, context) {
|
|
8053
|
+
const canonical = JSON.stringify([
|
|
8054
|
+
parsed.family,
|
|
8055
|
+
parsed.family === "aggregate" ? aggKey(parsed) : null,
|
|
8056
|
+
parsed.family === "find" ? findKey(parsed) : null,
|
|
8057
|
+
parsed.family === "pipeline" ? [
|
|
8058
|
+
// Dataset/source identity participates like a query: a
|
|
8059
|
+
// different registry binding or source content is a
|
|
8060
|
+
// different query even with identical stages.
|
|
8061
|
+
parsed.source.kind === "dataset" ? ["dataset", parsed.source.name] : ["query", parsed.source.query.family === "aggregate" ? aggKey(parsed.source.query) : findKey(parsed.source.query)],
|
|
8062
|
+
parsed.transforms.map(aggKey),
|
|
8063
|
+
parsed.sink ? [parsed.sink.head, parsed.sink.filters] : null
|
|
8064
|
+
] : null
|
|
8065
|
+
]);
|
|
8066
|
+
return JSON.stringify([
|
|
8067
|
+
canonical,
|
|
8068
|
+
context.rangeStart ?? null,
|
|
8069
|
+
context.rangeEnd ?? null,
|
|
8070
|
+
context.timeZone ?? null,
|
|
8071
|
+
context.instant ?? null,
|
|
8072
|
+
context.generation,
|
|
8073
|
+
context.unitDefaults ? JSON.stringify(Object.entries(context.unitDefaults).sort()) : null
|
|
8074
|
+
]);
|
|
8075
|
+
}
|
|
8076
|
+
function coalescingEventStore(inner) {
|
|
8077
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
8078
|
+
return {
|
|
8079
|
+
...inner,
|
|
8080
|
+
getEventsByTimeRange(start, end) {
|
|
8081
|
+
const key = `${start}:${end}`;
|
|
8082
|
+
const existing = inFlight.get(key);
|
|
8083
|
+
if (existing) return existing.promise;
|
|
8084
|
+
const entry = {
|
|
8085
|
+
promise: inner.getEventsByTimeRange(start, end).finally(() => {
|
|
8086
|
+
if (inFlight.get(key) === entry) inFlight.delete(key);
|
|
8087
|
+
})
|
|
8088
|
+
};
|
|
8089
|
+
inFlight.set(key, entry);
|
|
8090
|
+
return entry.promise;
|
|
8091
|
+
},
|
|
8092
|
+
invalidateInFlight() {
|
|
8093
|
+
inFlight.clear();
|
|
8094
|
+
}
|
|
8095
|
+
};
|
|
8096
|
+
}
|
|
8097
|
+
|
|
5986
8098
|
// src/version.ts
|
|
5987
|
-
var VERSION = "0.
|
|
5988
|
-
var GIT_SHA = "
|
|
5989
|
-
var BUILD_TIME = "2026-11-
|
|
8099
|
+
var VERSION = "0.53.2203";
|
|
8100
|
+
var GIT_SHA = "522974d";
|
|
8101
|
+
var BUILD_TIME = "2026-11-11T22:34:03.328Z";
|
|
5990
8102
|
var SEMVER = {
|
|
5991
|
-
version: "0.
|
|
5992
|
-
gitSha: "
|
|
5993
|
-
buildTime: "2026-11-
|
|
8103
|
+
version: "0.53.2203",
|
|
8104
|
+
gitSha: "522974d",
|
|
8105
|
+
buildTime: "2026-11-11T22:34:03.328Z"
|
|
5994
8106
|
};
|
|
5995
8107
|
|
|
5996
8108
|
Object.defineProperty(exports, "DEFAULT_NOTEBOOK_SLUGS", {
|
|
@@ -6077,6 +8189,7 @@ Object.defineProperty(exports, "toNotebookId", {
|
|
|
6077
8189
|
enumerable: true,
|
|
6078
8190
|
get: function () { return wodWikiCore.toNotebookId; }
|
|
6079
8191
|
});
|
|
8192
|
+
exports.AnalyticsInvalidationBus = AnalyticsInvalidationBus;
|
|
6080
8193
|
exports.BUILD_TIME = BUILD_TIME;
|
|
6081
8194
|
exports.CONTENT_GROUPING_DIMENSIONS = CONTENT_GROUPING_DIMENSIONS;
|
|
6082
8195
|
exports.DASHBOARD_GRID_MAX_COLS = DASHBOARD_GRID_MAX_COLS;
|
|
@@ -6087,6 +8200,7 @@ exports.EFFORT_DISCIPLINES = EFFORT_DISCIPLINES;
|
|
|
6087
8200
|
exports.GIT_SHA = GIT_SHA;
|
|
6088
8201
|
exports.PLANNED_WIDGET_TYPES = PLANNED_WIDGET_TYPES;
|
|
6089
8202
|
exports.PageSourceRegistry = PageSourceRegistry;
|
|
8203
|
+
exports.QueryCache = QueryCache;
|
|
6090
8204
|
exports.QueryDocumentRunner = QueryDocumentRunner;
|
|
6091
8205
|
exports.QueryService = QueryService;
|
|
6092
8206
|
exports.SEMVER = SEMVER;
|
|
@@ -6125,6 +8239,7 @@ exports.WQL_VIRTUAL_DIMS = WQL_VIRTUAL_DIMS;
|
|
|
6125
8239
|
exports.allowedFilterTypesForTarget = allowedFilterTypesForTarget;
|
|
6126
8240
|
exports.appendSuffixes = appendSuffixes;
|
|
6127
8241
|
exports.appendWidget = appendWidget;
|
|
8242
|
+
exports.bindQueryParams = bindQueryParams;
|
|
6128
8243
|
exports.buildDashboardDocument = buildDashboardDocument;
|
|
6129
8244
|
exports.buildDashboardFromScope = buildDashboardFromScope;
|
|
6130
8245
|
exports.buildDashboardScaffold = buildDashboardScaffold;
|
|
@@ -6137,6 +8252,10 @@ exports.civilDateDiff = civilDateDiff;
|
|
|
6137
8252
|
exports.civilDateOf = civilDateOf;
|
|
6138
8253
|
exports.civilDatesCoveredByRange = civilDatesCoveredByRange;
|
|
6139
8254
|
exports.civilMonday = civilMonday;
|
|
8255
|
+
exports.coalescingEventStore = coalescingEventStore;
|
|
8256
|
+
exports.collectQueryBlockParams = collectQueryBlockParams;
|
|
8257
|
+
exports.collectQueryParams = collectQueryParams;
|
|
8258
|
+
exports.computeCacheKey = computeCacheKey;
|
|
6140
8259
|
exports.consumesRollupFacts = consumesRollupFacts;
|
|
6141
8260
|
exports.convertDisplay = convertDisplay;
|
|
6142
8261
|
exports.createBuiltinFormulaEvaluator = createBuiltinFormulaEvaluator;
|
|
@@ -6156,11 +8275,14 @@ exports.inRange = inRange;
|
|
|
6156
8275
|
exports.isAggregateQuery = isAggregateQuery;
|
|
6157
8276
|
exports.isDashboardMeta = isDashboardMeta;
|
|
6158
8277
|
exports.isDashboardWidgetType = isDashboardWidgetType;
|
|
8278
|
+
exports.isDefaultShorthandQueryBlock = isDefaultShorthandQueryBlock;
|
|
6159
8279
|
exports.isEffortDiscipline = isEffortDiscipline;
|
|
6160
8280
|
exports.isFindQuery = isFindQuery;
|
|
6161
8281
|
exports.isItemSubscribed = isItemSubscribed;
|
|
6162
8282
|
exports.isPipelineQuery = isPipelineQuery;
|
|
6163
8283
|
exports.isProposedMetric = isProposedMetric;
|
|
8284
|
+
exports.isYamlQueryBlockBody = isYamlQueryBlockBody;
|
|
8285
|
+
exports.matchesFactMetricKey = matchesFactMetricKey;
|
|
6164
8286
|
exports.migrateLegacyChunkId = migrateLegacyChunkId;
|
|
6165
8287
|
exports.migrateLegacyDashboardBodies = migrateLegacyDashboardBodies;
|
|
6166
8288
|
exports.migrateLegacyRouteId = migrateLegacyRouteId;
|
|
@@ -6173,12 +8295,17 @@ exports.normalizeWql = normalizeWql;
|
|
|
6173
8295
|
exports.notebookOfItem = notebookOfItem;
|
|
6174
8296
|
exports.notebookOriginOfItem = notebookOriginOfItem;
|
|
6175
8297
|
exports.notebookSlugOfSourceId = notebookSlugOfSourceId;
|
|
8298
|
+
exports.notebooksOfItem = notebooksOfItem;
|
|
6176
8299
|
exports.parseDashboardNote = parseDashboardNote;
|
|
6177
8300
|
exports.parseDocument = parseDocument;
|
|
8301
|
+
exports.parseFilterFragment = parseFilterFragment;
|
|
6178
8302
|
exports.parseQuery = parseQuery;
|
|
8303
|
+
exports.parseQueryBlock = parseQueryBlock;
|
|
6179
8304
|
exports.parseQueryWidgetSuffix = parseQueryWidgetSuffix;
|
|
6180
8305
|
exports.parseWqlSuffixes = parseWqlSuffixes;
|
|
6181
8306
|
exports.projectEventToFacts = projectEventToFacts;
|
|
8307
|
+
exports.queryBlockAttributes = queryBlockAttributes;
|
|
8308
|
+
exports.queryBlockToDocumentText = queryBlockToDocumentText;
|
|
6182
8309
|
exports.referencedTokens = referencedTokens;
|
|
6183
8310
|
exports.removeWidget = removeWidget;
|
|
6184
8311
|
exports.renderWidgetGroup = renderWidgetGroup;
|
|
@@ -6186,17 +8313,21 @@ exports.resizeWidget = resizeWidget;
|
|
|
6186
8313
|
exports.resolveCanonicalMetricKey = resolveCanonicalMetricKey;
|
|
6187
8314
|
exports.resolveEntityScope = resolveEntityScope;
|
|
6188
8315
|
exports.resolveOutputUnit = resolveOutputUnit;
|
|
8316
|
+
exports.resolveQueryExecutionScope = resolveQueryExecutionScope;
|
|
6189
8317
|
exports.resolveWidgetType = resolveWidgetType;
|
|
6190
8318
|
exports.resolveWindowRange = resolveWindowRange;
|
|
8319
|
+
exports.resolveWqlCursorStage = resolveWqlCursorStage;
|
|
6191
8320
|
exports.rowsParseError = rowsParseError;
|
|
6192
8321
|
exports.serialize = serialize;
|
|
6193
8322
|
exports.serializeDocument = serializeDocument;
|
|
8323
|
+
exports.serializeQueryBlock = serializeQueryBlock;
|
|
8324
|
+
exports.serializeQueryBlockFence = serializeQueryBlockFence;
|
|
6194
8325
|
exports.setDashboardTokenValue = setDashboardTokenValue;
|
|
6195
8326
|
exports.sourceMatches = sourceMatches;
|
|
6196
8327
|
exports.sourceShape = sourceShape;
|
|
6197
8328
|
exports.splitAtWhere = splitAtWhere;
|
|
6198
|
-
exports.substituteTokens =
|
|
6199
|
-
exports.substituteTokensRaw =
|
|
8329
|
+
exports.substituteTokens = substituteTokens;
|
|
8330
|
+
exports.substituteTokensRaw = substituteTokens2;
|
|
6200
8331
|
exports.supportsWqlFilterKey = supportsWqlFilterKey;
|
|
6201
8332
|
exports.systemTimeZone = systemTimeZone;
|
|
6202
8333
|
exports.toEventRows = toEventRows;
|
|
@@ -6206,6 +8337,7 @@ exports.unknownWidgetTypeMessage = unknownWidgetTypeMessage;
|
|
|
6206
8337
|
exports.updateWidget = updateWidget;
|
|
6207
8338
|
exports.widgetBodyLine = widgetBodyLine;
|
|
6208
8339
|
exports.widgetFenceTag = widgetFenceTag;
|
|
8340
|
+
exports.widgetSpecToQueryObject = widgetSpecToQueryObject;
|
|
6209
8341
|
exports.wql = wql;
|
|
6210
8342
|
exports.wqlCompletion = wqlCompletion;
|
|
6211
8343
|
exports.wqlCompletionSource = wqlCompletionSource;
|