@shipeasy/openapi 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-RTN572TI.js → chunk-75Z4SQOM.js} +75 -1
- package/dist/chunk-75Z4SQOM.js.map +1 -0
- package/dist/{chunk-DW33MG4B.js → chunk-ZYK74ZIW.js} +426 -121
- package/dist/chunk-ZYK74ZIW.js.map +1 -0
- package/dist/{client-BPVn4hMC.d.ts → client-DAGqRpF1.d.ts} +1362 -238
- package/dist/client.d.ts +1 -1
- package/dist/client.js +19 -1
- package/dist/custom.d.ts +5 -5
- package/dist/custom.js +128 -84
- package/dist/custom.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +1648 -907
- package/dist/schemas.js +113 -1
- package/openapi-sdk.json +31 -9
- package/openapi.json +4040 -2083
- package/openapi.yaml +1775 -408
- package/package.json +1 -1
- package/spec/components/schemas/alert-rules.yaml +405 -7
- package/spec/components/schemas/alerts.yaml +9 -7
- package/spec/components/schemas/common.yaml +2 -2
- package/spec/components/schemas/connectors.yaml +69 -6
- package/spec/components/schemas/events.yaml +20 -9
- package/spec/components/schemas/keys.yaml +30 -0
- package/spec/components/schemas/metrics.yaml +131 -251
- package/spec/components/schemas/ops.yaml +377 -29
- package/spec/components/schemas/projects.yaml +18 -0
- package/spec/components/schemas/tags.yaml +91 -0
- package/spec/openapi.yaml +43 -0
- package/spec/paths/alerts.yaml +5 -3
- package/spec/paths/i18n.yaml +2 -1
- package/spec/paths/keys.yaml +79 -0
- package/spec/paths/metrics.yaml +26 -17
- package/spec/paths/ops.yaml +388 -11
- package/spec/paths/tags.yaml +270 -0
- package/dist/chunk-DW33MG4B.js.map +0 -1
- package/dist/chunk-RTN572TI.js.map +0 -1
package/dist/client.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { T as Client, V as Config, X as ConfigureOptions, ai as CreateClientConfig, k4 as Options, kH as RequestResult, p4 as ackOpsItem, p5 as addToGateWhitelist, p6 as answerOpsQuestion, p7 as approveEvent, p8 as askOpsQuestions, p9 as cancelOpsQuestion, pa as client, pb as configure, pc as createAlertRule, pd as createAttribute, pe as createClient, pf as createClientConfig, pg as createConfig, ph as createConnector, pi as createEvent, pj as createExperiment, pk as createExperimentReadout, pl as createGate, pm as createGateTemplate, pn as createI18nDraft, po as createI18nProfile, pp as createKey, pq as createKillswitch, pr as createMetric, ps as createOpsComment, pt as createOpsInvestigation, pu as createOpsItem, pv as createPublicBug, pw as createPublicFeatureRequest, px as createTriggerConnector, py as createUniverse, pz as deleteAlertRule, pA as deleteAttribute, pB as deleteConfig, pC as deleteConnector, pD as deleteEvent, pE as deleteExperiment, pF as deleteGate, pG as deleteGateTemplate, pH as deleteI18nDraft, pI as deleteI18nKey, pJ as deleteI18nProfile, pK as deleteKillswitch, pL as deleteMetric, pM as deleteOpsItem, pN as deleteUniverse, pO as disableGate, pP as discardConfigDraft, pQ as enableGate, pR as fileErrorTicket, pS as fireConnector, pT as getAttribute, pU as getConfig, pV as getConnector, pW as getCurrentProject, pX as getEntityTags, pY as getError, pZ as getErrorSeries, p_ as getEvent, p$ as getExperiment, q0 as getExperimentReadout, q1 as getExperimentResults, q2 as getExperimentTimeseries, q3 as getGate, q4 as getGateTemplate, q5 as getGateWhitelist, q6 as getKillswitch, q7 as getMetric, q8 as getMetricSeries, q9 as getOpsItem, qa as getProject, qb as linkPrToOpsItem, qc as listAlertRules, qd as listAlerts, qe as listAttributes, qf as listConfigActivity, qg as listConfigVersions, qh as listConfigs, qi as listConnectors, qj as listErrors, qk as listEvents, ql as listExperiments, qm as listGateActivity, qn as listGateTemplates, qo as listGates, qp as listI18nDraftKeys, qq as listI18nDrafts, qr as listI18nKeys, qs as listI18nProfiles, qt as listKeys, qu as listKillswitches, qv as listMetricExperiments, qw as listMetrics, qx as listOpsAgents, qy as listOpsComments, qz as listOpsInvestigations, qA as listOpsItems, qB as listOpsQuestions, qC as listSlackChannels, qD as listTags, qE as listUniverses, qF as notifyOps, qG as publishConfigDraft, qH as publishI18nProfile, qI as pushI18nKeys, qJ as reanalyzeExperiment, qK as removeFromGateWhitelist, qL as resolveError, qM as revokeKey, qN as saveConfigDraft, qO as searchResources, qP as setEntityTags, qQ as setExperimentMetrics, qR as setExperimentStatus, qS as setGateWhitelist, qT as setI18nLabel, qU as setKillswitchSwitch, qV as setKillswitchValue, qW as testConnector, qX as toggleKillswitch, qY as unarchiveMetric, qZ as unsetKillswitchSwitch, q_ as updateAlert, q$ as updateAlertRule, r0 as updateAttribute, r1 as updateConfig, r2 as updateConfigSchema, r3 as updateConnector, r4 as updateEntityTags, r5 as updateErrorStatus, r6 as updateEvent, r7 as updateExperiment, r8 as updateGate, r9 as updateGateTemplate, ra as updateI18nDraft, rb as updateI18nKey, rc as updateKey, rd as updateKillswitch, re as updateMetric, rf as updateOpsInvestigation, rg as updateOpsItem, rh as updateProject, ri as updateTriggerConnector, rj as updateUniverse, rk as upsertI18nDraftKey, rl as upsertI18nKeys, rm as upsertProject } from './client-DAGqRpF1.js';
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ackOpsItem,
|
|
3
3
|
addToGateWhitelist,
|
|
4
|
+
answerOpsQuestion,
|
|
4
5
|
approveEvent,
|
|
6
|
+
askOpsQuestions,
|
|
7
|
+
cancelOpsQuestion,
|
|
5
8
|
client,
|
|
6
9
|
configure,
|
|
7
10
|
createAlertRule,
|
|
@@ -51,6 +54,7 @@ import {
|
|
|
51
54
|
getConfig,
|
|
52
55
|
getConnector,
|
|
53
56
|
getCurrentProject,
|
|
57
|
+
getEntityTags,
|
|
54
58
|
getError,
|
|
55
59
|
getErrorSeries,
|
|
56
60
|
getEvent,
|
|
@@ -92,7 +96,9 @@ import {
|
|
|
92
96
|
listOpsComments,
|
|
93
97
|
listOpsInvestigations,
|
|
94
98
|
listOpsItems,
|
|
99
|
+
listOpsQuestions,
|
|
95
100
|
listSlackChannels,
|
|
101
|
+
listTags,
|
|
96
102
|
listUniverses,
|
|
97
103
|
notifyOps,
|
|
98
104
|
publishConfigDraft,
|
|
@@ -104,6 +110,7 @@ import {
|
|
|
104
110
|
revokeKey,
|
|
105
111
|
saveConfigDraft,
|
|
106
112
|
searchResources,
|
|
113
|
+
setEntityTags,
|
|
107
114
|
setExperimentMetrics,
|
|
108
115
|
setExperimentStatus,
|
|
109
116
|
setGateWhitelist,
|
|
@@ -120,6 +127,7 @@ import {
|
|
|
120
127
|
updateConfig,
|
|
121
128
|
updateConfigSchema,
|
|
122
129
|
updateConnector,
|
|
130
|
+
updateEntityTags,
|
|
123
131
|
updateErrorStatus,
|
|
124
132
|
updateEvent,
|
|
125
133
|
updateExperiment,
|
|
@@ -127,6 +135,7 @@ import {
|
|
|
127
135
|
updateGateTemplate,
|
|
128
136
|
updateI18nDraft,
|
|
129
137
|
updateI18nKey,
|
|
138
|
+
updateKey,
|
|
130
139
|
updateKillswitch,
|
|
131
140
|
updateMetric,
|
|
132
141
|
updateOpsInvestigation,
|
|
@@ -137,11 +146,14 @@ import {
|
|
|
137
146
|
upsertI18nDraftKey,
|
|
138
147
|
upsertI18nKeys,
|
|
139
148
|
upsertProject
|
|
140
|
-
} from "./chunk-
|
|
149
|
+
} from "./chunk-75Z4SQOM.js";
|
|
141
150
|
export {
|
|
142
151
|
ackOpsItem,
|
|
143
152
|
addToGateWhitelist,
|
|
153
|
+
answerOpsQuestion,
|
|
144
154
|
approveEvent,
|
|
155
|
+
askOpsQuestions,
|
|
156
|
+
cancelOpsQuestion,
|
|
145
157
|
client,
|
|
146
158
|
configure,
|
|
147
159
|
createAlertRule,
|
|
@@ -191,6 +203,7 @@ export {
|
|
|
191
203
|
getConfig,
|
|
192
204
|
getConnector,
|
|
193
205
|
getCurrentProject,
|
|
206
|
+
getEntityTags,
|
|
194
207
|
getError,
|
|
195
208
|
getErrorSeries,
|
|
196
209
|
getEvent,
|
|
@@ -232,7 +245,9 @@ export {
|
|
|
232
245
|
listOpsComments,
|
|
233
246
|
listOpsInvestigations,
|
|
234
247
|
listOpsItems,
|
|
248
|
+
listOpsQuestions,
|
|
235
249
|
listSlackChannels,
|
|
250
|
+
listTags,
|
|
236
251
|
listUniverses,
|
|
237
252
|
notifyOps,
|
|
238
253
|
publishConfigDraft,
|
|
@@ -244,6 +259,7 @@ export {
|
|
|
244
259
|
revokeKey,
|
|
245
260
|
saveConfigDraft,
|
|
246
261
|
searchResources,
|
|
262
|
+
setEntityTags,
|
|
247
263
|
setExperimentMetrics,
|
|
248
264
|
setExperimentStatus,
|
|
249
265
|
setGateWhitelist,
|
|
@@ -260,6 +276,7 @@ export {
|
|
|
260
276
|
updateConfig,
|
|
261
277
|
updateConfigSchema,
|
|
262
278
|
updateConnector,
|
|
279
|
+
updateEntityTags,
|
|
263
280
|
updateErrorStatus,
|
|
264
281
|
updateEvent,
|
|
265
282
|
updateExperiment,
|
|
@@ -267,6 +284,7 @@ export {
|
|
|
267
284
|
updateGateTemplate,
|
|
268
285
|
updateI18nDraft,
|
|
269
286
|
updateI18nKey,
|
|
287
|
+
updateKey,
|
|
270
288
|
updateKillswitch,
|
|
271
289
|
updateMetric,
|
|
272
290
|
updateOpsInvestigation,
|
package/dist/custom.d.ts
CHANGED
|
@@ -68,12 +68,12 @@ declare const opId: (op: CustomOp) => string;
|
|
|
68
68
|
* examples.
|
|
69
69
|
*
|
|
70
70
|
* Source of truth for semantics is the RUNTIME parser + IR that the server
|
|
71
|
-
* actually runs: the main repo's `packages/query-dsl` (`parse.ts` /
|
|
72
|
-
* The
|
|
73
|
-
*
|
|
74
|
-
*
|
|
71
|
+
* actually runs: the main repo's `packages/query-dsl` (`parse-expr.ts` /
|
|
72
|
+
* `expr.ts`). The CLI forwards the raw `--query` string to the server, which
|
|
73
|
+
* parses it — there is no second parser here to keep in step, only this text,
|
|
74
|
+
* which has to stay in sync with `packages/query-dsl`.
|
|
75
75
|
*/
|
|
76
|
-
declare const METRIC_GRAMMAR = "SHIPEASY METRIC QUERY DSL\n=========================\n\nA metric is an aggregation over an event stream. Almost always that is ONE\naggregation over ONE event; the single exception is the binary `ratio(...)`\nform, which divides one count-style arm by another (each arm its own event and\nfilters) in one pass. This DSL is the string you pass to\n`metrics create --query \"...\"` (equivalently the typed `query_ir`). There is\nno general arithmetic, no free-form formulas, and no subqueries \u2014 `ratio` is\nthe only division / two-event form.\n\n\nGRAMMAR (BNF)\n-------------\n Query := Agg | Ratio\n Agg := AggFunc \"(\" Selector (\",\" Identifier)? \")\" GroupBy?\n Ratio := \"ratio\" \"(\" RatioArm \",\" RatioArm \")\"\n RatioArm := (\"count_users\" | \"count\") \"(\" Selector \")\"\n AggFunc := \"count_users\" | \"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"\n | \"unique\" | \"p50\" | \"p75\" | \"p90\" | \"p95\" | \"p99\" | \"p999\"\n | \"retention_\" <N> \"d\" (N = 1..90)\n Selector := Identifier (\"{\" Filter (\",\" Filter)* \",\"? \"}\")?\n Filter := Identifier MatchOp StringLiteral\n MatchOp := \"=\" | \"!=\" | \"=~\" | \"!~\"\n GroupBy := (\"by\" | \"without\") \"(\" Identifier (\",\" Identifier)* \",\"? \")\"\n StringLiteral := double-quoted; \\\" is a literal quote, \\\\ is a backslash\n Identifier := [A-Za-z_][A-Za-z0-9_]* (case-sensitive)\n\n The trailing Identifier after the selector \u2014 \"(\" Selector \",\" Identifier \")\" \u2014\n is the VALUE LABEL: the numeric event label the aggregation reduces. Whether it\n is required, optional, or forbidden depends on the aggregation (see below).\n\n\nAGGREGATIONS\n------------\n Function Value label Meaning In experiments\n -------------- ------------ ------------------------------ ---------------\n count_users forbidden distinct users who fired it exact\n count forbidden number of events (rows) exact\n sum(e, v) required \u03A3 of numeric label v exact\n avg(e, v) required mean of label v exact\n retention_Nd(e) forbidden % of users who returned exact\n within N days (N = 1..90)\n min(e, v) required minimum of label v approx \u2192 avg *\n max(e, v) required maximum of label v approx \u2192 avg *\n unique(e, v) required distinct values of label v approx \u2192 avg *\n p50 \u2026 p999(e,v) required quantile of v (50/75/90/ approx \u2192 avg *\n 95/99/99.9th percentile)\n ratio(a, b) forbidden a \u00F7 b (see RATIO below) proportion **\n\n * Display-only aggregations. The experiment t-test needs a per-user mean and\n variance, so min/max/unique/quantile are computed exactly on dashboards but\n collapse to a per-user `avg` when used as an experiment metric. If you need\n an exact experiment metric, pick count_users / count / sum / avg / retention.\n\n ** In an experiment a ratio collapses to a per-user 0/1 outcome (a proportion),\n not a ratio-of-sums \u2014 see RATIO.\n\n \"count\" is the DSL spelling of the internal count_events (one per matching\n row); \"count_users\" is unique-by-user. Getting these two confused is the most\n common mistake \u2014 count double-counts a user who fired the event twice.\n\n\nSELECTOR & FILTERS\n------------------\n The selector is the event name plus an optional `{ ... }` filter set that\n narrows which events count. Each filter compares an event LABEL to a quoted\n string:\n\n = equal != not equal\n =~ regex match !~ regex NOT match\n\n - Values are ALWAYS double-quoted strings, even for numeric labels \u2014 they are\n coerced on the server (`amount=~\"^1[0-9]$\"`, `status=\"200\"`).\n - Multiple filters are AND-ed: `{country=\"US\", tier!=\"free\"}`.\n - Every filter label AND the value label must be a label DECLARED on the source\n event. Undeclared labels are a validation error, not a silent no-match.\n\n\nGROUP-BY\n--------\n Splits the result into one series per label combination.\n by (a, b) one series per distinct (a, b) \u2014 keep only these labels\n without (a, b) group by all declared labels EXCEPT a, b\n\n NOTE: group-by is a DASHBOARD-only feature. In an experiment the group-by is\n ignored \u2014 a per-user reducer is applied across the whole arm instead. Group-by\n is NOT allowed on a `ratio` query.\n\n\nRATIO (success rate, conversion, failure %, \u2026)\n-----------------------------------------------\n `ratio(numerator, denominator)` divides one count-style arm by another. Each\n arm is an independent selector with its OWN event and filters, so the two arms\n can be different events:\n\n ratio(count(checkout_completed), count(checkout_started))\n ratio(count_users(paid), count_users(signed_up))\n ratio(count(payment{ok=\"1\"}), count(payment)) one event, filtered arm\n\n Rules:\n - Arms are limited to `count` and `count_users` \u2014 no sum/avg/quantile arm.\n - `count` arms divide event counts; `count_users` arms divide distinct-user\n counts. Pick one shape and use it on both arms unless you mean to mix them.\n - A zero denominator yields 0, not an error.\n - No `by (...)` / `without (...)`, and no retention arm, on a ratio.\n - It is a COHORT RATE over the window \u2014 numerator events \u00F7 denominator events \u2014\n NOT a per-id join. It never matches an individual attempt to its outcome by a\n correlation id; it counts each side over the window and divides. If you need a\n matched pair, resolve the outcome in your own code and emit ONE event carrying\n the result (e.g. an `ok` label), then use `ratio(count(e{ok=\"1\"}), count(e))`\n or `avg(e, ok)`.\n - In an experiment a per-user ratio is a 0/1 outcome \u2014 did the numerator happen,\n among denominator-eligible users \u2014 i.e. a proportion, not a ratio-of-sums.\n - When creating with `--event-name`, pass the NUMERATOR's event name.\n\n\nNOT SUPPORTED (will fail to parse)\n----------------------------------\n - general arithmetic / free-form formulas: `count(a) + count(b)`, `2 * sum(x)`,\n `avg(a) / avg(b)` \u2014 the ONLY division form is the fixed binary\n `ratio(arm, arm)` with count-style arms (see RATIO)\n - a ratio arm that isn't `count` / `count_users`: `ratio(sum(a, v), count(b))`\n - more than two events in one query\n - `by (...)` / `without (...)` on a ratio\n - arbitrary quantiles (only the fixed p50/p75/p90/p95/p99/p999)\n - unquoted or numeric filter values: `{status=200}` \u2192 must be `\"200\"`\n - trailing tokens after the query\n\n\nEXAMPLES (query \u2014 what it measures)\n--------------------------------------\n count_users(checkout_completed)\n distinct users who completed checkout.\n\n count(add_to_cart{country=\"US\"})\n number of US add-to-cart events (a user can count many times).\n\n sum(purchase{country=\"US\"}, amount)\n total purchase amount from US purchase events.\n\n avg(req_dur{tier!=\"free\"}, ms) without (region)\n mean request duration for non-free tiers, one series per region-excluded\n label combination.\n\n p99(req_dur{route=~\"/api/.*\"}, ms) by (route, status)\n 99th-percentile latency of /api/* requests, split by route and status.\n\n unique(login{method=\"sso\"}, device_id)\n distinct SSO devices seen (display-only; an experiment would use per-user avg).\n\n retention_7d(session_start)\n share of users who started a new session within 7 days.\n\n ratio(count(checkout_completed), count(checkout_started))\n checkout conversion rate \u2014 completions \u00F7 starts over the window.\n\n ratio(count(payment{ok=\"0\"}), count(payment))\n payment failure rate from a single event's outcome label.\n";
|
|
76
|
+
declare const METRIC_GRAMMAR = "SHIPEASY METRIC QUERY DSL\n=========================\n\nA metric is an EXPRESSION over an event stream. An aggregation over one event\nis a leaf; leaves compose with `+ - * /`, parentheses group, and a metric may\nend with `by (...)` or `without (...)` to split it into one series per label\ncombination. This DSL is the string you pass to `metrics create --query \"...\"`\n(equivalently the typed `query_ir`).\n\nSo `count(a) / count(b)` is a rate, `count(a) / count(b) * 100` is a\npercentage, and `(count(a) - count(b)) / count(c)` is as ordinary as it looks.\n`ratio(a, b)` still parses and means exactly `a / b`.\n\n\nGRAMMAR (BNF)\n-------------\n Query := Expression GroupBy?\n Expression := Term ((\"+\" | \"-\") Term)*\n Term := Factor ((\"*\" | \"/\") Factor)*\n Factor := \"-\"? Primary\n Primary := Number | \"(\" Expression \")\" | Aggregate | Function\n Selector := Identifier (\"{\" Predicate ((\",\"|\"and\"|\"or\") Predicate)* \",\"? \"}\")?\n Predicate := \"not\"? ( Identifier (\"=\" | \"=~\") StringLiteral\n | Identifier (\">\" | \">=\" | \"<\" | \"<=\") Number\n | Identifier \"not\"? \"in\" \"(\" StringLiteral (\",\" ...)* \")\"\n | Identifier \":\" \"*\"\n | \"(\" Predicate \")\" )\n GroupBy := (\"by\" | \"without\") \"(\" Identifier (\",\" Identifier)* \",\"? \")\"\n StringLiteral := double-quoted; \\\" is a literal quote, \\\\ is a backslash\n Identifier := [A-Za-z_][A-Za-z0-9_]* (case-sensitive)\n\n There is ONE negation and it goes in front of a predicate: `not status=\"ok\"`.\n `!=` and `!~` are refused by name.\n\n The trailing Identifier after the selector \u2014 \"(\" Selector \",\" Identifier \")\" \u2014\n is the VALUE LABEL: the numeric event label the aggregation reduces. Whether it\n is allowed, and whether it is needed, is per aggregation (see below).\n\n<aggregate> ::=\n { count ( <selector> )\n | sum ( <selector> [ , <value-label> ] )\n | avg ( <selector> [ , <value-label> ] )\n | min ( <selector> [ , <value-label> ] )\n | max ( <selector> [ , <value-label> ] )\n | stddev ( <selector> [ , <value-label> ] )\n | p ( <percentile> , <selector> [ , <value-label> ] )\n }\n\n\nAGGREGATIONS\n------------\n| Aggregation | Measures | Value label |\n| --- | --- | --- |\n| `count` | how many matching events happened | not allowed |\n| `sum` | total of a numeric label | optional \u2014 defaults to the event's own value |\n| `avg` | mean of a numeric label | optional \u2014 defaults to the event's own value |\n| `min` | smallest value of a numeric label | optional \u2014 defaults to the event's own value |\n| `max` | largest value of a numeric label | optional \u2014 defaults to the event's own value |\n| `stddev` | spread of a numeric label around its mean | optional \u2014 defaults to the event's own value |\n| `p` | a percentile of a numeric label \u2014 `p(95, req, ms)` | optional \u2014 defaults to the event's own value |\n\n A value label is optional wherever it is allowed: omitted, the aggregation\n reduces the event's own default numeric value. `count` takes the event alone.\n\n `count` is one per matching ROW, so a user who fired the event twice counts\n twice.\n\n In an experiment, `min`, `max` and `p` collapse to a per-user `avg`: the\n t-test needs a per-user mean and variance, and those three have neither. They\n are exact on a dashboard. For an exact experiment metric pick count / sum /\n avg, or a rate \u2014 a rate collapses to a per-user 0/1 outcome (a proportion),\n not a ratio-of-sums.\n\n\nSELECTOR & FILTERS\n------------------\n The selector is the event name plus an optional `{ ... }` filter set that\n narrows which events count. Each filter compares an event LABEL to a quoted\n string:\n\n = equal =~ glob match\n > >= < <= order, NUMERIC labels only, value written BARE\n in (\"a\", \"b\") a value set label:* the label is set at all\n not <predicate> negation, in FRONT of the predicate\n\n There is no `!=` and no `!~`. One negation, one place it goes:\n `{not tier=\"free\"}`. Predicates join with `and` / `or` (a comma is another\n `and`) and parenthesise.\n\n - `=~` takes a GLOB, NOT a regex. Two wildcards, and a glob matches the\n WHOLE value \u2014 there are no anchors because there is nothing to anchor:\n\n * any sequence of characters ? exactly one character\n\n route=~\"/api*\" starts with /api\n route=~\"*checkout\" ends with checkout\n route=~\"*admin*\" contains admin\n route=~\"/v?/users\" /v1/users, /v2/users \u2026\n route=~\"/health\" EXACTLY /health \u2014 same as `route=\"/health\"`\n\n Every other character is a LITERAL, dots included, so `host=~\"*.acme.com\"`\n works as written. Regex syntax \u2014 `^` `$` `.*` `|` `[]` `()` `+` `{}` `\\\\` \u2014 is\n REJECTED at save time rather than reinterpreted: `^/api` would be a glob for\n a value that literally starts with a caret, and `/api/.*` would match\n `/api/.x` but not `/api/x`. Write `/api*` instead.\n - Values are ALWAYS double-quoted strings, even for numeric labels \u2014 they are\n coerced on the server (`status=\"200\"`) \u2014 EXCEPT the order comparisons,\n whose value is written bare (`ms > 500`). A glob on a numeric or boolean\n label is a validation error.\n - Multiple predicates are AND-ed: `{country=\"US\", not tier=\"free\"}`.\n - Every filter label AND the value label must be a label DECLARED on the source\n event. Undeclared labels are a validation error, not a silent no-match.\n\n\nGROUP-BY\n--------\n Splits the result into one series per label combination.\n by (a, b) one series per distinct (a, b) \u2014 keep only these labels\n without (a, b) group by all declared labels EXCEPT a, b\n\n NOTE: group-by is a DASHBOARD-only feature. In an experiment the group-by is\n ignored \u2014 a per-user reducer is applied across the whole arm instead.\n\n\nRATES (success rate, conversion, failure %, \u2026)\n----------------------------------------------\n Division is ordinary: `a / b`. Each side is its own expression with its own\n event and filters, so the two sides can be different events. `ratio(a, b)` is\n the same thing spelled as a function.\n\n count(checkout_completed) / count(checkout_started)\n count(paid) / count(signed_up)\n count(payment{ok=\"1\"}) / count(payment) one event, filtered side\n count(errors) / count(requests) * 100 as a percentage\n\n Rules:\n - A zero denominator yields 0, not an error.\n - `by (...)` / `without (...)` applies to the whole rate, and works.\n - AS AN EXPERIMENT METRIC both sides must be `count`. That is the one real\n restriction, and it is about the per-user collapse rather than the query:\n a rate becomes \"did the numerator happen, among the users eligible for the\n denominator\", which is a 0/1 outcome per user. `sum(a, v) / count(b)`\n charts fine and is refused when attached to an experiment.\n - It is a COHORT RATE over the window \u2014 numerator events \u00F7 denominator events \u2014\n NOT a per-id join. It never matches an individual attempt to its outcome by a\n correlation id; it counts each side over the window and divides. If you need a\n matched pair, resolve the outcome in your own code and emit ONE event carrying\n the result (e.g. an `ok` label), then use `ratio(count(e{ok=\"1\"}), count(e))`\n or `avg(e, ok)`.\n - In an experiment a per-user ratio is a 0/1 outcome \u2014 did the numerator happen,\n among denominator-eligible users \u2014 i.e. a proportion, not a ratio-of-sums.\n - When creating with `--event-name`, pass the NUMERATOR's event name.\n\n\nREFUSED BY NAME\n---------------\n Each of these is recognised in order to be refused, with a message saying what\n to write instead. They are not gaps.\n\n| Spelling | Why it is refused |\n| --- | --- |\n| `count_users(\u2026)` | removed \u2014 Analytics Engine samples rows and weights the survivors, and a distinct count cannot be reweighted (sampling reweights rows, not sets), so it under-reports by the sample interval. Use count() |\n| `unique(\u2026)` | removed from the language \u2014 count(DISTINCT \u2026) takes no sample weight, has no conditional form and cannot merge across groups. Stored metrics using it still render; no new one can be authored. Use count() |\n| `retention_Nd(\u2026)` | has no time-series form; retention lives in the experiment analyzer |\n| `anomalies(\u2026)` | detection is a property of the alert rule now, not of the metric: create an alert with kind 'anomaly' and set its sigma. The metric stays what it measures, and the same seasonal baseline can be drawn on its chart by turning the band on |\n| `deviation(\u2026)` | detection is a property of the alert rule now, not of the metric: an alert with kind 'anomaly' compares this metric against its own seasonal baseline in sigmas, at a sigma and a direction you set on the rule \u2014 so one metric can back rules of different sensitivity |\n| `outliers(\u2026)` | detection is a property of the alert rule now, not of the metric: an alert with kind 'outliers' on a metric grouped by() compares each group against its peers in the same bucket, at a sigma you set on the rule |\n| `expected(\u2026)` | the seasonal band is a display property of the metric now, not a function: turn the band on and every chart of this metric draws it \u2014 at the same sigma an anomaly alert would fire at |\n| `forecast(\u2026)` | the projection is a display property of the metric now, not a function: set the metric's forecast method and horizon, and every chart of it projects forward without changing what the metric measures |\n\n Also refused: `!=` and `!~` (write `not label=\"v\"`), a regex inside `=~`\n (globs only), an order comparison on a text label, a quoted comparison value\n (`ms > \"500\"`), and trailing tokens after the query.\n\n\nEXAMPLES (query \u2014 what it measures)\n--------------------------------------\n count(checkout_completed)\n number of completed checkouts.\n\n count(add_to_cart{country=\"US\"})\n number of US add-to-cart events (a user can count many times).\n\n sum(purchase{country=\"US\"}, amount)\n total purchase amount from US purchase events.\n\n avg(req_dur{not tier=\"free\"}, ms) without (region)\n mean request duration for non-free tiers, one series per region-excluded\n label combination.\n\n p99(req_dur{route=~\"/api*\"}, ms) by (route, status)\n 99th-percentile latency of /api requests, split by route and status.\n\n ratio(count(checkout_completed), count(checkout_started))\n checkout conversion rate \u2014 completions \u00F7 starts over the window.\n\n ratio(count(payment{ok=\"0\"}), count(payment))\n payment failure rate from a single event's outcome label.\n";
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Every custom (non-spec) operation, in surface order. The CLI and MCP each
|
package/dist/custom.js
CHANGED
|
@@ -1,65 +1,90 @@
|
|
|
1
|
+
// src/custom/grammar.generated.ts
|
|
2
|
+
var AGGREGATES_BLOCK = `<aggregate> ::=
|
|
3
|
+
{ count ( <selector> )
|
|
4
|
+
| sum ( <selector> [ , <value-label> ] )
|
|
5
|
+
| avg ( <selector> [ , <value-label> ] )
|
|
6
|
+
| min ( <selector> [ , <value-label> ] )
|
|
7
|
+
| max ( <selector> [ , <value-label> ] )
|
|
8
|
+
| stddev ( <selector> [ , <value-label> ] )
|
|
9
|
+
| p ( <percentile> , <selector> [ , <value-label> ] )
|
|
10
|
+
}`;
|
|
11
|
+
var AGGREGATIONS_BLOCK = `| Aggregation | Measures | Value label |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| \`count\` | how many matching events happened | not allowed |
|
|
14
|
+
| \`sum\` | total of a numeric label | optional \u2014 defaults to the event's own value |
|
|
15
|
+
| \`avg\` | mean of a numeric label | optional \u2014 defaults to the event's own value |
|
|
16
|
+
| \`min\` | smallest value of a numeric label | optional \u2014 defaults to the event's own value |
|
|
17
|
+
| \`max\` | largest value of a numeric label | optional \u2014 defaults to the event's own value |
|
|
18
|
+
| \`stddev\` | spread of a numeric label around its mean | optional \u2014 defaults to the event's own value |
|
|
19
|
+
| \`p\` | a percentile of a numeric label \u2014 \`p(95, req, ms)\` | optional \u2014 defaults to the event's own value |`;
|
|
20
|
+
var REFUSALS_BLOCK = `| Spelling | Why it is refused |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| \`count_users(\u2026)\` | removed \u2014 Analytics Engine samples rows and weights the survivors, and a distinct count cannot be reweighted (sampling reweights rows, not sets), so it under-reports by the sample interval. Use count() |
|
|
23
|
+
| \`unique(\u2026)\` | removed from the language \u2014 count(DISTINCT \u2026) takes no sample weight, has no conditional form and cannot merge across groups. Stored metrics using it still render; no new one can be authored. Use count() |
|
|
24
|
+
| \`retention_Nd(\u2026)\` | has no time-series form; retention lives in the experiment analyzer |
|
|
25
|
+
| \`anomalies(\u2026)\` | detection is a property of the alert rule now, not of the metric: create an alert with kind 'anomaly' and set its sigma. The metric stays what it measures, and the same seasonal baseline can be drawn on its chart by turning the band on |
|
|
26
|
+
| \`deviation(\u2026)\` | detection is a property of the alert rule now, not of the metric: an alert with kind 'anomaly' compares this metric against its own seasonal baseline in sigmas, at a sigma and a direction you set on the rule \u2014 so one metric can back rules of different sensitivity |
|
|
27
|
+
| \`outliers(\u2026)\` | detection is a property of the alert rule now, not of the metric: an alert with kind 'outliers' on a metric grouped by() compares each group against its peers in the same bucket, at a sigma you set on the rule |
|
|
28
|
+
| \`expected(\u2026)\` | the seasonal band is a display property of the metric now, not a function: turn the band on and every chart of this metric draws it \u2014 at the same sigma an anomaly alert would fire at |
|
|
29
|
+
| \`forecast(\u2026)\` | the projection is a display property of the metric now, not a function: set the metric's forecast method and horizon, and every chart of it projects forward without changing what the metric measures |`;
|
|
30
|
+
|
|
1
31
|
// src/custom/grammar.ts
|
|
2
32
|
var METRIC_GRAMMAR = `SHIPEASY METRIC QUERY DSL
|
|
3
33
|
=========================
|
|
4
34
|
|
|
5
|
-
A metric is an
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
35
|
+
A metric is an EXPRESSION over an event stream. An aggregation over one event
|
|
36
|
+
is a leaf; leaves compose with \`+ - * /\`, parentheses group, and a metric may
|
|
37
|
+
end with \`by (...)\` or \`without (...)\` to split it into one series per label
|
|
38
|
+
combination. This DSL is the string you pass to \`metrics create --query "..."\`
|
|
39
|
+
(equivalently the typed \`query_ir\`).
|
|
40
|
+
|
|
41
|
+
So \`count(a) / count(b)\` is a rate, \`count(a) / count(b) * 100\` is a
|
|
42
|
+
percentage, and \`(count(a) - count(b)) / count(c)\` is as ordinary as it looks.
|
|
43
|
+
\`ratio(a, b)\` still parses and means exactly \`a / b\`.
|
|
12
44
|
|
|
13
45
|
|
|
14
46
|
GRAMMAR (BNF)
|
|
15
47
|
-------------
|
|
16
|
-
Query :=
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
48
|
+
Query := Expression GroupBy?
|
|
49
|
+
Expression := Term (("+" | "-") Term)*
|
|
50
|
+
Term := Factor (("*" | "/") Factor)*
|
|
51
|
+
Factor := "-"? Primary
|
|
52
|
+
Primary := Number | "(" Expression ")" | Aggregate | Function
|
|
53
|
+
Selector := Identifier ("{" Predicate ((","|"and"|"or") Predicate)* ","? "}")?
|
|
54
|
+
Predicate := "not"? ( Identifier ("=" | "=~") StringLiteral
|
|
55
|
+
| Identifier (">" | ">=" | "<" | "<=") Number
|
|
56
|
+
| Identifier "not"? "in" "(" StringLiteral ("," ...)* ")"
|
|
57
|
+
| Identifier ":" "*"
|
|
58
|
+
| "(" Predicate ")" )
|
|
26
59
|
GroupBy := ("by" | "without") "(" Identifier ("," Identifier)* ","? ")"
|
|
27
60
|
StringLiteral := double-quoted; \\" is a literal quote, \\\\ is a backslash
|
|
28
61
|
Identifier := [A-Za-z_][A-Za-z0-9_]* (case-sensitive)
|
|
29
62
|
|
|
63
|
+
There is ONE negation and it goes in front of a predicate: \`not status="ok"\`.
|
|
64
|
+
\`!=\` and \`!~\` are refused by name.
|
|
65
|
+
|
|
30
66
|
The trailing Identifier after the selector \u2014 "(" Selector "," Identifier ")" \u2014
|
|
31
67
|
is the VALUE LABEL: the numeric event label the aggregation reduces. Whether it
|
|
32
|
-
is
|
|
68
|
+
is allowed, and whether it is needed, is per aggregation (see below).
|
|
69
|
+
|
|
70
|
+
${AGGREGATES_BLOCK}
|
|
33
71
|
|
|
34
72
|
|
|
35
73
|
AGGREGATIONS
|
|
36
74
|
------------
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
count
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
ratio(a, b) forbidden a \xF7 b (see RATIO below) proportion **
|
|
51
|
-
|
|
52
|
-
* Display-only aggregations. The experiment t-test needs a per-user mean and
|
|
53
|
-
variance, so min/max/unique/quantile are computed exactly on dashboards but
|
|
54
|
-
collapse to a per-user \`avg\` when used as an experiment metric. If you need
|
|
55
|
-
an exact experiment metric, pick count_users / count / sum / avg / retention.
|
|
56
|
-
|
|
57
|
-
** In an experiment a ratio collapses to a per-user 0/1 outcome (a proportion),
|
|
58
|
-
not a ratio-of-sums \u2014 see RATIO.
|
|
59
|
-
|
|
60
|
-
"count" is the DSL spelling of the internal count_events (one per matching
|
|
61
|
-
row); "count_users" is unique-by-user. Getting these two confused is the most
|
|
62
|
-
common mistake \u2014 count double-counts a user who fired the event twice.
|
|
75
|
+
${AGGREGATIONS_BLOCK}
|
|
76
|
+
|
|
77
|
+
A value label is optional wherever it is allowed: omitted, the aggregation
|
|
78
|
+
reduces the event's own default numeric value. \`count\` takes the event alone.
|
|
79
|
+
|
|
80
|
+
\`count\` is one per matching ROW, so a user who fired the event twice counts
|
|
81
|
+
twice.
|
|
82
|
+
|
|
83
|
+
In an experiment, \`min\`, \`max\` and \`p\` collapse to a per-user \`avg\`: the
|
|
84
|
+
t-test needs a per-user mean and variance, and those three have neither. They
|
|
85
|
+
are exact on a dashboard. For an exact experiment metric pick count / sum /
|
|
86
|
+
avg, or a rate \u2014 a rate collapses to a per-user 0/1 outcome (a proportion),
|
|
87
|
+
not a ratio-of-sums.
|
|
63
88
|
|
|
64
89
|
|
|
65
90
|
SELECTOR & FILTERS
|
|
@@ -68,12 +93,36 @@ SELECTOR & FILTERS
|
|
|
68
93
|
narrows which events count. Each filter compares an event LABEL to a quoted
|
|
69
94
|
string:
|
|
70
95
|
|
|
71
|
-
= equal
|
|
72
|
-
|
|
96
|
+
= equal =~ glob match
|
|
97
|
+
> >= < <= order, NUMERIC labels only, value written BARE
|
|
98
|
+
in ("a", "b") a value set label:* the label is set at all
|
|
99
|
+
not <predicate> negation, in FRONT of the predicate
|
|
100
|
+
|
|
101
|
+
There is no \`!=\` and no \`!~\`. One negation, one place it goes:
|
|
102
|
+
\`{not tier="free"}\`. Predicates join with \`and\` / \`or\` (a comma is another
|
|
103
|
+
\`and\`) and parenthesise.
|
|
73
104
|
|
|
105
|
+
- \`=~\` takes a GLOB, NOT a regex. Two wildcards, and a glob matches the
|
|
106
|
+
WHOLE value \u2014 there are no anchors because there is nothing to anchor:
|
|
107
|
+
|
|
108
|
+
* any sequence of characters ? exactly one character
|
|
109
|
+
|
|
110
|
+
route=~"/api*" starts with /api
|
|
111
|
+
route=~"*checkout" ends with checkout
|
|
112
|
+
route=~"*admin*" contains admin
|
|
113
|
+
route=~"/v?/users" /v1/users, /v2/users \u2026
|
|
114
|
+
route=~"/health" EXACTLY /health \u2014 same as \`route="/health"\`
|
|
115
|
+
|
|
116
|
+
Every other character is a LITERAL, dots included, so \`host=~"*.acme.com"\`
|
|
117
|
+
works as written. Regex syntax \u2014 \`^\` \`$\` \`.*\` \`|\` \`[]\` \`()\` \`+\` \`{}\` \`\\\\\` \u2014 is
|
|
118
|
+
REJECTED at save time rather than reinterpreted: \`^/api\` would be a glob for
|
|
119
|
+
a value that literally starts with a caret, and \`/api/.*\` would match
|
|
120
|
+
\`/api/.x\` but not \`/api/x\`. Write \`/api*\` instead.
|
|
74
121
|
- Values are ALWAYS double-quoted strings, even for numeric labels \u2014 they are
|
|
75
|
-
coerced on the server (\`
|
|
76
|
-
|
|
122
|
+
coerced on the server (\`status="200"\`) \u2014 EXCEPT the order comparisons,
|
|
123
|
+
whose value is written bare (\`ms > 500\`). A glob on a numeric or boolean
|
|
124
|
+
label is a validation error.
|
|
125
|
+
- Multiple predicates are AND-ed: \`{country="US", not tier="free"}\`.
|
|
77
126
|
- Every filter label AND the value label must be a label DECLARED on the source
|
|
78
127
|
event. Undeclared labels are a validation error, not a silent no-match.
|
|
79
128
|
|
|
@@ -85,26 +134,28 @@ GROUP-BY
|
|
|
85
134
|
without (a, b) group by all declared labels EXCEPT a, b
|
|
86
135
|
|
|
87
136
|
NOTE: group-by is a DASHBOARD-only feature. In an experiment the group-by is
|
|
88
|
-
ignored \u2014 a per-user reducer is applied across the whole arm instead.
|
|
89
|
-
is NOT allowed on a \`ratio\` query.
|
|
137
|
+
ignored \u2014 a per-user reducer is applied across the whole arm instead.
|
|
90
138
|
|
|
91
139
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
140
|
+
RATES (success rate, conversion, failure %, \u2026)
|
|
141
|
+
----------------------------------------------
|
|
142
|
+
Division is ordinary: \`a / b\`. Each side is its own expression with its own
|
|
143
|
+
event and filters, so the two sides can be different events. \`ratio(a, b)\` is
|
|
144
|
+
the same thing spelled as a function.
|
|
97
145
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
146
|
+
count(checkout_completed) / count(checkout_started)
|
|
147
|
+
count(paid) / count(signed_up)
|
|
148
|
+
count(payment{ok="1"}) / count(payment) one event, filtered side
|
|
149
|
+
count(errors) / count(requests) * 100 as a percentage
|
|
101
150
|
|
|
102
151
|
Rules:
|
|
103
|
-
- Arms are limited to \`count\` and \`count_users\` \u2014 no sum/avg/quantile arm.
|
|
104
|
-
- \`count\` arms divide event counts; \`count_users\` arms divide distinct-user
|
|
105
|
-
counts. Pick one shape and use it on both arms unless you mean to mix them.
|
|
106
152
|
- A zero denominator yields 0, not an error.
|
|
107
|
-
-
|
|
153
|
+
- \`by (...)\` / \`without (...)\` applies to the whole rate, and works.
|
|
154
|
+
- AS AN EXPERIMENT METRIC both sides must be \`count\`. That is the one real
|
|
155
|
+
restriction, and it is about the per-user collapse rather than the query:
|
|
156
|
+
a rate becomes "did the numerator happen, among the users eligible for the
|
|
157
|
+
denominator", which is a 0/1 outcome per user. \`sum(a, v) / count(b)\`
|
|
158
|
+
charts fine and is refused when attached to an experiment.
|
|
108
159
|
- It is a COHORT RATE over the window \u2014 numerator events \xF7 denominator events \u2014
|
|
109
160
|
NOT a per-id join. It never matches an individual attempt to its outcome by a
|
|
110
161
|
correlation id; it counts each side over the window and divides. If you need a
|
|
@@ -116,23 +167,22 @@ RATIO (success rate, conversion, failure %, \u2026)
|
|
|
116
167
|
- When creating with \`--event-name\`, pass the NUMERATOR's event name.
|
|
117
168
|
|
|
118
169
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- trailing tokens after the query
|
|
170
|
+
REFUSED BY NAME
|
|
171
|
+
---------------
|
|
172
|
+
Each of these is recognised in order to be refused, with a message saying what
|
|
173
|
+
to write instead. They are not gaps.
|
|
174
|
+
|
|
175
|
+
${REFUSALS_BLOCK}
|
|
176
|
+
|
|
177
|
+
Also refused: \`!=\` and \`!~\` (write \`not label="v"\`), a regex inside \`=~\`
|
|
178
|
+
(globs only), an order comparison on a text label, a quoted comparison value
|
|
179
|
+
(\`ms > "500"\`), and trailing tokens after the query.
|
|
130
180
|
|
|
131
181
|
|
|
132
182
|
EXAMPLES (query \u2014 what it measures)
|
|
133
183
|
--------------------------------------
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
count(checkout_completed)
|
|
185
|
+
number of completed checkouts.
|
|
136
186
|
|
|
137
187
|
count(add_to_cart{country="US"})
|
|
138
188
|
number of US add-to-cart events (a user can count many times).
|
|
@@ -140,18 +190,12 @@ EXAMPLES (query \u2014 what it measures)
|
|
|
140
190
|
sum(purchase{country="US"}, amount)
|
|
141
191
|
total purchase amount from US purchase events.
|
|
142
192
|
|
|
143
|
-
avg(req_dur{tier
|
|
193
|
+
avg(req_dur{not tier="free"}, ms) without (region)
|
|
144
194
|
mean request duration for non-free tiers, one series per region-excluded
|
|
145
195
|
label combination.
|
|
146
196
|
|
|
147
|
-
p99(req_dur{route=~"/api
|
|
148
|
-
99th-percentile latency of /api
|
|
149
|
-
|
|
150
|
-
unique(login{method="sso"}, device_id)
|
|
151
|
-
distinct SSO devices seen (display-only; an experiment would use per-user avg).
|
|
152
|
-
|
|
153
|
-
retention_7d(session_start)
|
|
154
|
-
share of users who started a new session within 7 days.
|
|
197
|
+
p99(req_dur{route=~"/api*"}, ms) by (route, status)
|
|
198
|
+
99th-percentile latency of /api requests, split by route and status.
|
|
155
199
|
|
|
156
200
|
ratio(count(checkout_completed), count(checkout_started))
|
|
157
201
|
checkout conversion rate \u2014 completions \xF7 starts over the window.
|