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