@rudderhq/server 0.7.0 → 0.7.1
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/bootstrap/register-api-routes.js +1 -1
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +3 -1
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
- package/dist/product-analytics-collector-config.d.ts +33 -0
- package/dist/product-analytics-collector-config.d.ts.map +1 -0
- package/dist/product-analytics-collector-config.js +67 -0
- package/dist/product-analytics-collector-config.js.map +1 -0
- package/dist/product-analytics-collector-server.d.ts +26 -0
- package/dist/product-analytics-collector-server.d.ts.map +1 -0
- package/dist/product-analytics-collector-server.js +150 -0
- package/dist/product-analytics-collector-server.js.map +1 -0
- package/dist/routes/instance-settings.d.ts +1 -0
- package/dist/routes/instance-settings.d.ts.map +1 -1
- package/dist/routes/instance-settings.js +58 -0
- package/dist/routes/instance-settings.js.map +1 -1
- package/dist/routes/product-analytics-collector-report.d.ts +7 -0
- package/dist/routes/product-analytics-collector-report.d.ts.map +1 -0
- package/dist/routes/product-analytics-collector-report.js +159 -0
- package/dist/routes/product-analytics-collector-report.js.map +1 -0
- package/dist/routes/product-analytics-collector.d.ts +14 -2
- package/dist/routes/product-analytics-collector.d.ts.map +1 -1
- package/dist/routes/product-analytics-collector.js +141 -4
- package/dist/routes/product-analytics-collector.js.map +1 -1
- package/dist/routes/product-analytics.d.ts.map +1 -1
- package/dist/routes/product-analytics.js +40 -3
- package/dist/routes/product-analytics.js.map +1 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +4 -0
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/product-analytics-collector-maintenance.d.ts +25 -0
- package/dist/services/product-analytics-collector-maintenance.d.ts.map +1 -0
- package/dist/services/product-analytics-collector-maintenance.js +309 -0
- package/dist/services/product-analytics-collector-maintenance.js.map +1 -0
- package/dist/services/product-analytics-collector-maintenance.test.d.ts +2 -0
- package/dist/services/product-analytics-collector-maintenance.test.d.ts.map +1 -0
- package/dist/services/product-analytics-collector-maintenance.test.js +14 -0
- package/dist/services/product-analytics-collector-maintenance.test.js.map +1 -0
- package/dist/services/product-analytics-collector.d.ts +75 -3
- package/dist/services/product-analytics-collector.d.ts.map +1 -1
- package/dist/services/product-analytics-collector.js +363 -12
- package/dist/services/product-analytics-collector.js.map +1 -1
- package/dist/services/product-analytics-collector.test.js +25 -0
- package/dist/services/product-analytics-collector.test.js.map +1 -1
- package/dist/services/product-analytics-fixture.d.ts +7 -0
- package/dist/services/product-analytics-fixture.d.ts.map +1 -0
- package/dist/services/product-analytics-fixture.js +65 -0
- package/dist/services/product-analytics-fixture.js.map +1 -0
- package/dist/services/product-analytics-prd-gates.test.d.ts +2 -0
- package/dist/services/product-analytics-prd-gates.test.d.ts.map +1 -0
- package/dist/services/product-analytics-prd-gates.test.js +135 -0
- package/dist/services/product-analytics-prd-gates.test.js.map +1 -0
- package/dist/services/product-analytics.d.ts +17 -5
- package/dist/services/product-analytics.d.ts.map +1 -1
- package/dist/services/product-analytics.js +94 -12
- package/dist/services/product-analytics.js.map +1 -1
- package/package.json +17 -14
- package/ui-dist/assets/{LibraryLiveSurface-DeUIV5N_.js → LibraryLiveSurface-CZED7oKj.js} +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BaoLM9Pd.js → abnfDiagram-VRR7QNED-BoPyIoAq.js} +1 -1
- package/ui-dist/assets/{arc-Cx0L6o2z.js → arc-DTYs4RCH.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-zVfZP7v4.js → architectureDiagram-ZJ3FMSHR-BGfp2S_3.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-BiWWFP9R.js → blockDiagram-677ZJIJ3-aW0EGIUf.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-CX65GSJ6.js → c4Diagram-LMCZKHZV--1itKgA2.js} +1 -1
- package/ui-dist/assets/channel-DLNwhPnC.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-CH6PlelX.js → chunk-2Q5K7J3B-ZIqIozPQ.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-C7ZEXYO8.js → chunk-32BRIVSS-DiwBEZVQ.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-CyInWuue.js → chunk-5VM5RSS4-CLOrTmL8.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-D58qFIdM.js → chunk-EX3LRPZG-D6xZzF47.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-CJ3Qx30f.js → chunk-JWPE2WC7-B9tYvTZl.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-BO4bKVLH.js → chunk-MOJQB5TN-D_qo-SzM.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-DxBmxcYc.js → chunk-RYQCIY6F-ChPHGc2P.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-Chdnd6Lq.js → chunk-V7JOEXUC-Dk6GbZYp.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-GkW_x_pD.js → chunk-VR4S4FIN-B6AREC1T.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-BJyBGcSF.js → chunk-XXDRQBXY-CUyO-JUr.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-JsIc-rlc.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-JsIc-rlc.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-BWP2r8TX.js → cose-bilkent-JH36ORCC-BCxIInLZ.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-C08dfI3w.js → cynefin-VYW2F7L2-DqsnpnHA.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-h6dXjTaO.js → cynefinDiagram-TSTJHNR4-BD7QQNLW.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-CWDgoFQD.js → dagre-VKFMJZFB-DaE160f7.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-CK1j48d-.js → diagram-FQU43EPY-DZ0c59y8.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-CoSiG57q.js → diagram-G47NLZAW-C_413g5E.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-DRG-xXGQ.js → diagram-NH7WQ7WH-D_o9pXB-.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-D04mMwYs.js → diagram-OA4YK3LP-BI2xMqiW.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-ZkC1ZQMq.js → diagram-WEI45ONY-hhM8R_14.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-BxMIw9PZ.js → ebnfDiagram-CCIWWBDH-DfFgEtmG.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-B4Zwbn84.js → erDiagram-Q63AITRT-CCw4ysHK.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-D5pYz8Px.js → flowDiagram-23GEKE2U-CzC1SMmo.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-RSramodc.js → ganttDiagram-NO4QXBWP-CbPJIrIw.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-BFQfCkMd.js → gitGraphDiagram-IHSO6WYX-jCxJ66_C.js} +1 -1
- package/ui-dist/assets/{graph-DuSS8poS.js → graph-BfNZ9Jzj.js} +1 -1
- package/ui-dist/assets/{index-DRbt_Svi.js → index-B3h9Vdxx.js} +1 -1
- package/ui-dist/assets/{index-9U78M9Np.js → index-B8q4cbOz.js} +1 -1
- package/ui-dist/assets/{index-olsHIcXs.js → index-BE1vSI6a.js} +1 -1
- package/ui-dist/assets/{index-B-NP8fOI.js → index-BKn4zRjf.js} +1 -1
- package/ui-dist/assets/{index-BKXUkeeU.js → index-BULll2e0.js} +1 -1
- package/ui-dist/assets/{index-BpvytaKr.js → index-BjxRqzZK.js} +1 -1
- package/ui-dist/assets/{index-Cyq94-QT.js → index-Bwo-C8W5.js} +1 -1
- package/ui-dist/assets/{index-BADa8mxG.js → index-ByUFNj0O.js} +1 -1
- package/ui-dist/assets/{index-CHbXMgL0.js → index-Chop-Dpa.js} +1 -1
- package/ui-dist/assets/{index-BU0KLNTt.js → index-Ck-ZNQeD.js} +541 -528
- package/ui-dist/assets/index-CxPYXL3t.css +1 -0
- package/ui-dist/assets/{index-wZlfBtwh.js → index-D00XdcOq.js} +1 -1
- package/ui-dist/assets/{index-BGcgy63z.js → index-D_K-9VDs.js} +1 -1
- package/ui-dist/assets/{index-DzdRgH-y.js → index-DiMDROQY.js} +1 -1
- package/ui-dist/assets/{index-5tzBDABj.js → index-DmjAbzHq.js} +1 -1
- package/ui-dist/assets/{index-De3vaw_P.js → index-SkHIC7Ma.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-f5i4pO3J.js → infoDiagram-FWYZ7A6U-BkDimGw3.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-CVEdv5i_.js → ishikawaDiagram-FXEZZL3T-BAyUfn6x.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-Cvp7vWcy.js → journeyDiagram-5HDEW3XC-DlbpTZHG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-C6dURjjK.js → kanban-definition-HUTT4EX6-Cd67S14K.js} +1 -1
- package/ui-dist/assets/{layout-DxdOu9MY.js → layout-Dc4YEiCC.js} +1 -1
- package/ui-dist/assets/{linear-CRi8RB_7.js → linear-BhRsbD_5.js} +1 -1
- package/ui-dist/assets/{map-BlpDrw_l.js → map-CEWybj6z.js} +1 -1
- package/ui-dist/assets/{mermaid.core-2Gz9YusH.js → mermaid.core-CqqAFYNg.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BmSjJLTI.js → mindmap-definition-LN4V7U3C-CCb-XmEi.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-BTUboUw8.js → pegDiagram-2B236MQR-CqUV14VZ.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-CVPP8SYv.js → pieDiagram-ENE6RG2P-Dn_M_GRM.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BG4FmirT.js → quadrantDiagram-ABIIQ3AL-rAEhJDr2.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-DFRNo9hF.js → railroadDiagram-RFXS5EU6-iE2o_jkH.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DpnVF9qP.js → requirementDiagram-TGXJPOKE-BFyKBR1h.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-D1ETFdSE.js → sankeyDiagram-HTMAVEWB-DRa_2YB6.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CNqzB_AL.js → sequenceDiagram-DBY2YBRQ-BG8VIjD7.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-DuoMCjdH.js → sizeCapture-X5ZJPWSS-CS1xlbzG.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-fmHeKL8l.js → stateDiagram-2N3HPSRC-BSl2RUJ1.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DUVNSzI8.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-hjosaIhT.js → swimlanes-5IMT3BWC-BT8Hrm9J.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DfLFj7sh.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-xoMEClc-.js → timeline-definition-FHXFAJF6-CfYIqEuQ.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-sdSXQfX-.js → vennDiagram-L72KCM5P-C8zBH7o9.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-BbQBSKlD.js → wardleyDiagram-EHGQE667-D5HyFfI9.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-BfAtadyK.js → xychartDiagram-FW5EYKEG-CGItJmLx.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-CHc3ibOz.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-9iLZpz3t.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-9iLZpz3t.js +0 -1
- package/ui-dist/assets/index-QrRs-r-4.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DajAVikh.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DJjG9OJ-.js +0 -8
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const PRODUCT_ANALYTICS_FIXTURE_NOW = "2026-08-04T00:00:00.000Z";
|
|
2
|
+
export const PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS = [
|
|
3
|
+
"11111111-1111-4111-8111-111111111111",
|
|
4
|
+
"11111111-1111-4111-8111-111111111112",
|
|
5
|
+
"11111111-1111-4111-8111-111111111113",
|
|
6
|
+
];
|
|
7
|
+
export const PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION = {
|
|
8
|
+
installationId: PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[0],
|
|
9
|
+
mode: "anonymous",
|
|
10
|
+
consentVersion: "v1",
|
|
11
|
+
consentEpoch: 1,
|
|
12
|
+
pseudonymousInstallationId: "fixture-installation-hash",
|
|
13
|
+
};
|
|
14
|
+
const EVENT_NAMES = ["human_work_started", "run_succeeded", "output_ready", "work_loop_completed"];
|
|
15
|
+
export function productAnalyticsFixtureEvent(index, overrides = {}) {
|
|
16
|
+
const installationIndex = index % PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.length;
|
|
17
|
+
const eventName = EVENT_NAMES[index % EVENT_NAMES.length];
|
|
18
|
+
const occurredAt = new Date(Date.parse(PRODUCT_ANALYTICS_FIXTURE_NOW) - (index % 3) * 60 * 60 * 1000).toISOString();
|
|
19
|
+
const properties = eventName === "run_succeeded"
|
|
20
|
+
? { run_kind: "agent", runtime: "local", attempt_kind: "initial" }
|
|
21
|
+
: eventName === "output_ready"
|
|
22
|
+
? { output_kind: "patch" }
|
|
23
|
+
: eventName === "work_loop_completed"
|
|
24
|
+
? { work_surface: "issue", origin: "human", review_required: false }
|
|
25
|
+
: { work_surface: "issue", origin: "human" };
|
|
26
|
+
return {
|
|
27
|
+
eventId: `22222222-2222-4${String(index % 10)}22-8222-${String(index).padStart(12, "0")}`,
|
|
28
|
+
eventName,
|
|
29
|
+
schemaVersion: 1,
|
|
30
|
+
occurredAt,
|
|
31
|
+
environment: "production",
|
|
32
|
+
appVersion: "0.6.6-fixture",
|
|
33
|
+
releaseChannel: "stable",
|
|
34
|
+
deploymentMode: "desktop_local",
|
|
35
|
+
actorKind: "user",
|
|
36
|
+
origin: "human",
|
|
37
|
+
pseudonymousInstallationId: `fixture-installation-hash-${installationIndex}`,
|
|
38
|
+
pseudonymousOrgId: "fixture-org-hash",
|
|
39
|
+
pseudonymousWorkId: `fixture-work-${index}`,
|
|
40
|
+
pseudonymousWorkCycleId: `fixture-cycle-${index}`,
|
|
41
|
+
pseudonymousRootRunId: null,
|
|
42
|
+
pseudonymousRunId: `fixture-run-${index}`,
|
|
43
|
+
completionRevision: eventNameIsCompletion(index) ? 1 : null,
|
|
44
|
+
properties,
|
|
45
|
+
confidence: "exact",
|
|
46
|
+
isBackfill: index % 11 === 0,
|
|
47
|
+
...overrides,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function eventNameIsCompletion(index) {
|
|
51
|
+
return EVENT_NAMES[index % EVENT_NAMES.length] === "work_loop_completed";
|
|
52
|
+
}
|
|
53
|
+
export function productAnalyticsFixtureDashboardEvents(count = 30) {
|
|
54
|
+
return Array.from({ length: count }, (_, index) => {
|
|
55
|
+
const event = productAnalyticsFixtureEvent(index);
|
|
56
|
+
const installationId = PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[index % PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.length];
|
|
57
|
+
return {
|
|
58
|
+
...event,
|
|
59
|
+
installationId,
|
|
60
|
+
effectiveAt: event.occurredAt,
|
|
61
|
+
late: false,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=product-analytics-fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-analytics-fixture.js","sourceRoot":"","sources":["../../src/services/product-analytics-fixture.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,6BAA6B,GAAG,0BAA0B,CAAC;AACxE,MAAM,CAAC,MAAM,uCAAuC,GAAG;IACrD,sCAAsC;IACtC,sCAAsC;IACtC,sCAAsC;CAC9B,CAAC;AAEX,MAAM,CAAC,MAAM,uCAAuC,GAA2C;IAC7F,cAAc,EAAE,uCAAuC,CAAC,CAAC,CAAC;IAC1D,IAAI,EAAE,WAAW;IACjB,cAAc,EAAE,IAAI;IACpB,YAAY,EAAE,CAAC;IACf,0BAA0B,EAAE,2BAA2B;CACxD,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,oBAAoB,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,CAAU,CAAC;AAE5G,MAAM,UAAU,4BAA4B,CAC1C,KAAa,EACb,YAAqD,EAAE;IAEvD,MAAM,iBAAiB,GAAG,KAAK,GAAG,uCAAuC,CAAC,MAAM,CAAC;IACjF,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACpH,MAAM,UAAU,GAAiD,SAAS,KAAK,eAAe;QAC5F,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE;QAClE,CAAC,CAAC,SAAS,KAAK,cAAc;YAC5B,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE;YAC1B,CAAC,CAAC,SAAS,KAAK,qBAAqB;gBACnC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE;gBACpE,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACnD,OAAO;QACL,OAAO,EAAE,kBAAkB,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,WAAW,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE;QACzF,SAAS;QACT,aAAa,EAAE,CAAC;QAChB,UAAU;QACV,WAAW,EAAE,YAAY;QACzB,UAAU,EAAE,eAAe;QAC3B,cAAc,EAAE,QAAQ;QACxB,cAAc,EAAE,eAAe;QAC/B,SAAS,EAAE,MAAM;QACjB,MAAM,EAAE,OAAO;QACf,0BAA0B,EAAE,6BAA6B,iBAAiB,EAAE;QAC5E,iBAAiB,EAAE,kBAAkB;QACrC,kBAAkB,EAAE,gBAAgB,KAAK,EAAE;QAC3C,uBAAuB,EAAE,iBAAiB,KAAK,EAAE;QACjD,qBAAqB,EAAE,IAAI;QAC3B,iBAAiB,EAAE,eAAe,KAAK,EAAE;QACzC,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QAC3D,UAAU;QACV,UAAU,EAAE,OAAO;QACnB,UAAU,EAAE,KAAK,GAAG,EAAE,KAAK,CAAC;QAC5B,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa;IAC1C,OAAO,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,qBAAqB,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,sCAAsC,CAAC,KAAK,GAAG,EAAE;IAC/D,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QAChD,MAAM,KAAK,GAAG,4BAA4B,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,cAAc,GAAG,uCAAuC,CAAC,KAAK,GAAG,uCAAuC,CAAC,MAAM,CAAC,CAAC;QACvH,OAAO;YACL,GAAG,KAAK;YACR,cAAc;YACd,WAAW,EAAE,KAAK,CAAC,UAAU;YAC7B,IAAI,EAAE,KAAK;SACZ,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-analytics-prd-gates.test.d.ts","sourceRoot":"","sources":["../../src/services/product-analytics-prd-gates.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildProductAnalyticsCollectorDashboard, createProductAnalyticsCollector, InMemoryProductAnalyticsCollectorStore, validateProductAnalyticsCollectorEvent, } from "./product-analytics-collector.js";
|
|
3
|
+
import { PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION, PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS, PRODUCT_ANALYTICS_FIXTURE_NOW, productAnalyticsFixtureDashboardEvents, productAnalyticsFixtureEvent, } from "./product-analytics-fixture.js";
|
|
4
|
+
const SENSITIVE_TOKEN = /(prompt|transcript|title|description|body|content|url|token|secret|password|credential|email|hostname|username)/i;
|
|
5
|
+
describe("product analytics PRD gates", () => {
|
|
6
|
+
it("uses a deterministic fixture and keeps every accepted seed privacy-safe", () => {
|
|
7
|
+
const first = productAnalyticsFixtureEvent(0);
|
|
8
|
+
const second = productAnalyticsFixtureEvent(0);
|
|
9
|
+
expect(first).toEqual(second);
|
|
10
|
+
expect(first.occurredAt).toBe(PRODUCT_ANALYTICS_FIXTURE_NOW);
|
|
11
|
+
const seeds = Array.from({ length: 20 }, (_, index) => productAnalyticsFixtureEvent(index));
|
|
12
|
+
for (const seed of seeds) {
|
|
13
|
+
const serialized = JSON.stringify(seed);
|
|
14
|
+
expect(serialized).not.toMatch(SENSITIVE_TOKEN);
|
|
15
|
+
const result = validateProductAnalyticsCollectorEvent(seed);
|
|
16
|
+
expect(result).not.toHaveProperty("status", "rejected");
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
it("proves idempotency, offline-safe retry, revoke races, and shared-user isolation", async () => {
|
|
20
|
+
const store = new InMemoryProductAnalyticsCollectorStore();
|
|
21
|
+
store.setInstallationState(PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION.installationId, {
|
|
22
|
+
consentVersion: "v1",
|
|
23
|
+
consentEpoch: 1,
|
|
24
|
+
revoked: false,
|
|
25
|
+
});
|
|
26
|
+
const collector = createProductAnalyticsCollector(store);
|
|
27
|
+
const payload = productAnalyticsFixtureEvent(1, { pseudonymousInstallationId: "fixture-installation-hash" });
|
|
28
|
+
const first = collector.ingestBatch({
|
|
29
|
+
authorization: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
|
|
30
|
+
events: [payload],
|
|
31
|
+
now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
|
|
32
|
+
});
|
|
33
|
+
const duplicate = collector.ingestBatch({
|
|
34
|
+
authorization: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
|
|
35
|
+
events: [payload],
|
|
36
|
+
now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
|
|
37
|
+
});
|
|
38
|
+
expect(first.accepted).toBe(1);
|
|
39
|
+
expect(duplicate.duplicate).toBe(1);
|
|
40
|
+
const revoked = collector.advanceConsent({
|
|
41
|
+
installationId: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION.installationId,
|
|
42
|
+
consentVersion: "v1",
|
|
43
|
+
consentEpoch: 2,
|
|
44
|
+
revoked: true,
|
|
45
|
+
});
|
|
46
|
+
const stale = collector.ingestBatch({
|
|
47
|
+
authorization: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
|
|
48
|
+
events: [productAnalyticsFixtureEvent(2)],
|
|
49
|
+
now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
|
|
50
|
+
});
|
|
51
|
+
expect(revoked.revoked).toBe(true);
|
|
52
|
+
expect(stale.rejected[0]?.errorCode).toBe("revoked");
|
|
53
|
+
const sharedUserStore = new InMemoryProductAnalyticsCollectorStore();
|
|
54
|
+
const sharedUserCollector = createProductAnalyticsCollector(sharedUserStore);
|
|
55
|
+
const sharedAuthorization = (installationId) => ({
|
|
56
|
+
...PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
|
|
57
|
+
installationId,
|
|
58
|
+
analyticsSubject: "shared-user-subject",
|
|
59
|
+
pseudonymousInstallationId: null,
|
|
60
|
+
mode: "account_linked",
|
|
61
|
+
});
|
|
62
|
+
for (const installationId of PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.slice(0, 2)) {
|
|
63
|
+
sharedUserStore.setSubjectState(installationId, "shared-user-subject", { consentVersion: "v1", consentEpoch: 1, revoked: false });
|
|
64
|
+
}
|
|
65
|
+
const sharedFirst = sharedUserCollector.ingestBatch({
|
|
66
|
+
authorization: sharedAuthorization(PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[0]),
|
|
67
|
+
events: [productAnalyticsFixtureEvent(10, { eventId: "33333333-3333-4333-8333-333333333333" })],
|
|
68
|
+
now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
|
|
69
|
+
});
|
|
70
|
+
const sharedSecond = sharedUserCollector.ingestBatch({
|
|
71
|
+
authorization: sharedAuthorization(PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[1]),
|
|
72
|
+
events: [productAnalyticsFixtureEvent(11, { eventId: "33333333-3333-4333-8333-333333333334" })],
|
|
73
|
+
now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
|
|
74
|
+
});
|
|
75
|
+
expect(sharedFirst.accepted).toBe(1);
|
|
76
|
+
expect(sharedSecond.accepted).toBe(1);
|
|
77
|
+
expect(sharedUserCollector.store.listEvents().map((event) => event.installationId).sort()).toEqual([...PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.slice(0, 2)].sort());
|
|
78
|
+
expect(sharedUserCollector.store.listEvents().every((event) => event.analyticsSubject === "shared-user-subject")).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it("builds thresholded rollups, installation cohorts, and quality counters", () => {
|
|
81
|
+
const events = productAnalyticsFixtureDashboardEvents(30);
|
|
82
|
+
const dashboard = buildProductAnalyticsCollectorDashboard(events, { privacyThreshold: 2 });
|
|
83
|
+
expect(dashboard.rollups.length).toBeGreaterThan(0);
|
|
84
|
+
expect(dashboard.rollups.every((rollup) => rollup.contributingInstallations >= 2 || rollup.eventCount === null)).toBe(true);
|
|
85
|
+
expect(dashboard.cohorts).toEqual([
|
|
86
|
+
{ firstSeenDay: "2026-08-03", installationCount: 2, suppressed: false },
|
|
87
|
+
{ firstSeenDay: "2026-08-04", installationCount: null, suppressed: true },
|
|
88
|
+
]);
|
|
89
|
+
expect(dashboard.quality).toMatchObject({
|
|
90
|
+
acceptedEventCount: 30,
|
|
91
|
+
backfillEventCount: 3,
|
|
92
|
+
lateEventCount: 0,
|
|
93
|
+
missingPseudonymousInstallationCount: 0,
|
|
94
|
+
privacyThreshold: 2,
|
|
95
|
+
});
|
|
96
|
+
const singleton = {
|
|
97
|
+
...events[0],
|
|
98
|
+
installationId: "44444444-4444-4444-8444-444444444444",
|
|
99
|
+
eventName: "human_work_started",
|
|
100
|
+
occurredAt: "2026-08-05T00:00:00.000Z",
|
|
101
|
+
effectiveAt: "2026-08-04T00:00:00.000Z",
|
|
102
|
+
late: true,
|
|
103
|
+
};
|
|
104
|
+
const recomputed = buildProductAnalyticsCollectorDashboard([...events, singleton], { privacyThreshold: 2 });
|
|
105
|
+
const suppressed = recomputed.rollups.find((rollup) => rollup.day === "2026-08-04" && rollup.eventName === "human_work_started");
|
|
106
|
+
expect(suppressed).toMatchObject({ contributingInstallations: 1, eventCount: null, suppressed: true });
|
|
107
|
+
expect(recomputed.quality.lateEventCount).toBe(1);
|
|
108
|
+
expect(recomputed).toEqual(buildProductAnalyticsCollectorDashboard([singleton, ...events], { privacyThreshold: 2 }));
|
|
109
|
+
const dashboardJson = JSON.stringify(recomputed);
|
|
110
|
+
for (const installationId of PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS) {
|
|
111
|
+
expect(dashboardJson).not.toContain(installationId);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
it("keeps the deterministic ingest benchmark bounded", () => {
|
|
115
|
+
const store = new InMemoryProductAnalyticsCollectorStore();
|
|
116
|
+
store.setInstallationState(PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION.installationId, { consentVersion: "v1", consentEpoch: 1, revoked: false });
|
|
117
|
+
const collector = createProductAnalyticsCollector(store);
|
|
118
|
+
const events = Array.from({ length: 100 }, (_, index) => productAnalyticsFixtureEvent(index));
|
|
119
|
+
const benchmarkAuthorization = { ...PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION, pseudonymousInstallationId: null };
|
|
120
|
+
const startedAt = performance.now();
|
|
121
|
+
let accepted = 0;
|
|
122
|
+
for (let offset = 0; offset < events.length; offset += 25) {
|
|
123
|
+
accepted += collector.ingestBatch({
|
|
124
|
+
authorization: benchmarkAuthorization,
|
|
125
|
+
events: events.slice(offset, offset + 25),
|
|
126
|
+
now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
|
|
127
|
+
}).accepted;
|
|
128
|
+
}
|
|
129
|
+
const elapsedMs = performance.now() - startedAt;
|
|
130
|
+
console.info(`[product-analytics-benchmark] events=${events.length} accepted=${accepted} elapsed_ms=${elapsedMs.toFixed(2)}`);
|
|
131
|
+
expect(accepted).toBe(100);
|
|
132
|
+
expect(elapsedMs).toBeLessThan(500);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=product-analytics-prd-gates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-analytics-prd-gates.test.js","sourceRoot":"","sources":["../../src/services/product-analytics-prd-gates.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export type ProductAnalyticsEnvironment = "production" | "development" | "test";
|
|
|
14
14
|
export type ProductAnalyticsReleaseChannel = "development" | "preview" | "canary" | "stable";
|
|
15
15
|
export type ProductAnalyticsDeploymentMode = "desktop_local" | "self_hosted" | "remote_server";
|
|
16
16
|
type AnalyticsProperties = Record<string, ProductAnalyticsPropertyValue>;
|
|
17
|
+
export declare const PRODUCT_ANALYTICS_EVENT_PROPERTY_ALLOWLIST: Record<ProductAnalyticsEventName, ReadonlySet<string>>;
|
|
17
18
|
export type RecordProductAnalyticsEventInput = {
|
|
18
19
|
orgId?: string | null;
|
|
19
20
|
eventName: ProductAnalyticsEventName;
|
|
@@ -59,11 +60,11 @@ export declare function enqueueProductAnalyticsEvent(db: Db, input: {
|
|
|
59
60
|
leaseExpiresAt: Date | null;
|
|
60
61
|
attemptCount: number;
|
|
61
62
|
installationId: string;
|
|
63
|
+
consentVersion: string;
|
|
62
64
|
consentEpoch: number;
|
|
63
65
|
deliveryMode: string;
|
|
64
66
|
consentScope: string;
|
|
65
67
|
consentedLocalUserId: string | null;
|
|
66
|
-
consentVersion: string;
|
|
67
68
|
nextAttemptAt: Date;
|
|
68
69
|
deliveredAt: Date | null;
|
|
69
70
|
deadLetteredAt: Date | null;
|
|
@@ -90,11 +91,11 @@ export declare function ensureProductAnalyticsWorkCycle(db: Db, input: EnsurePro
|
|
|
90
91
|
completedAt: Date | null;
|
|
91
92
|
reviewDecision: string | null;
|
|
92
93
|
actorId: string | null;
|
|
94
|
+
completionRevision: number;
|
|
95
|
+
invalidatedAt: Date | null;
|
|
93
96
|
workSurface: string;
|
|
94
97
|
workId: string;
|
|
95
98
|
workCycleId: string;
|
|
96
|
-
completionRevision: number;
|
|
97
|
-
invalidatedAt: Date | null;
|
|
98
99
|
outputReadyAt: Date | null;
|
|
99
100
|
rootRunIds: string[];
|
|
100
101
|
}>;
|
|
@@ -152,7 +153,7 @@ export declare function setProductAnalyticsInstallationMode(db: Db, installation
|
|
|
152
153
|
createdAt: Date;
|
|
153
154
|
updatedAt: Date;
|
|
154
155
|
}>;
|
|
155
|
-
export declare function reconcileProductAnalyticsInstallationMode(db: Db, installationId: string): Promise<{
|
|
156
|
+
export declare function reconcileProductAnalyticsInstallationMode(db: Db, installationId: string, preferredMode?: "off" | "anonymous" | "account_linked"): Promise<{
|
|
156
157
|
id: string;
|
|
157
158
|
installationId: string;
|
|
158
159
|
installationSecretHash: string;
|
|
@@ -199,9 +200,9 @@ export declare function recordProductAnalyticsConsent(db: Db, input: {
|
|
|
199
200
|
decidedAt: Date;
|
|
200
201
|
localUserId: string | null;
|
|
201
202
|
installationId: string;
|
|
203
|
+
consentEpoch: number;
|
|
202
204
|
decision: string;
|
|
203
205
|
policyVersion: string;
|
|
204
|
-
consentEpoch: number;
|
|
205
206
|
decidedByLocalUserId: string | null;
|
|
206
207
|
uploadFromAt: Date | null;
|
|
207
208
|
}>;
|
|
@@ -243,6 +244,7 @@ export declare function claimProductAnalyticsOutboxBatch(db: Db, input: {
|
|
|
243
244
|
installationId: string;
|
|
244
245
|
deliveryMode: string;
|
|
245
246
|
consentScope: string;
|
|
247
|
+
consentedLocalUserId: string | null;
|
|
246
248
|
consentEpoch: number;
|
|
247
249
|
events: {
|
|
248
250
|
eventId: string;
|
|
@@ -285,12 +287,22 @@ export declare function acknowledgeProductAnalyticsOutboxClaim(db: Db, input: {
|
|
|
285
287
|
installationSecret: string;
|
|
286
288
|
eventIds: string[];
|
|
287
289
|
claimToken: string;
|
|
290
|
+
consentedLocalUserId?: string | null;
|
|
288
291
|
delivered?: boolean;
|
|
289
292
|
errorCode?: string;
|
|
290
293
|
}): Promise<{
|
|
291
294
|
updatedCount: number;
|
|
292
295
|
state: string;
|
|
293
296
|
}>;
|
|
297
|
+
export declare function cleanupProductAnalyticsRetention(db: Db, input: {
|
|
298
|
+
installationId: string;
|
|
299
|
+
retentionDays?: number;
|
|
300
|
+
now?: Date;
|
|
301
|
+
}): Promise<{
|
|
302
|
+
deletedEvents: number;
|
|
303
|
+
droppedDueToRetention: number;
|
|
304
|
+
cutoff: string;
|
|
305
|
+
}>;
|
|
294
306
|
export declare function pseudonymizeProductAnalyticsId(secret: string, value: string): string;
|
|
295
307
|
export declare function buildProductAnalyticsExportPayload(secret: string, event: Record<string, unknown>): {
|
|
296
308
|
eventId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-analytics.d.ts","sourceRoot":"","sources":["../../src/services/product-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"product-analytics.d.ts","sourceRoot":"","sources":["../../src/services/product-analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { productAnalyticsConsentLedger, productAnalyticsEvents, productAnalyticsInstallations, productAnalyticsOutbox, productAnalyticsWorkCycles, } from "@rudderhq/db";
|
|
1
|
+
import { productAnalyticsConsentLedger, productAnalyticsEvents, productAnalyticsInstallations, productAnalyticsOutbox, productAnalyticsWorkCycleRevisions, productAnalyticsWorkCycles, } from "@rudderhq/db";
|
|
2
2
|
import { and, asc, desc, eq, gte, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
3
3
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { forbidden, unauthorized, unprocessable } from "../errors.js";
|
|
@@ -44,7 +44,9 @@ const ACTOR_TYPE_SET = new Set(["human", "agent", "system", "automation"]);
|
|
|
44
44
|
// focused on content-bearing names so safe dimensions such as creation_path do
|
|
45
45
|
// not get rejected by a substring match.
|
|
46
46
|
const SENSITIVE_PROPERTY_KEY = /(prompt|transcript|title|description|body|content|url|token|secret|password|credential|email|hostname|username)/i;
|
|
47
|
-
const
|
|
47
|
+
const PRODUCT_ANALYTICS_OUTBOX_MAX_ROWS = 10_000;
|
|
48
|
+
const PRODUCT_ANALYTICS_OUTBOX_MAX_BYTES = 32 * 1024 * 1024;
|
|
49
|
+
export const PRODUCT_ANALYTICS_EVENT_PROPERTY_ALLOWLIST = {
|
|
48
50
|
organization_created: new Set([
|
|
49
51
|
"creation_path",
|
|
50
52
|
"template_kind",
|
|
@@ -81,7 +83,7 @@ function assertBoundedText(value, field, maxLength) {
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
function validateProperties(eventName, properties) {
|
|
84
|
-
const allowed =
|
|
86
|
+
const allowed = PRODUCT_ANALYTICS_EVENT_PROPERTY_ALLOWLIST[eventName];
|
|
85
87
|
for (const [key, value] of Object.entries(properties)) {
|
|
86
88
|
if (!allowed.has(key) || SENSITIVE_PROPERTY_KEY.test(key)) {
|
|
87
89
|
throw unprocessable(`Product analytics property is not allowed: ${key}`);
|
|
@@ -181,10 +183,23 @@ export async function recordProductAnalyticsEvent(db, input) {
|
|
|
181
183
|
return rows[0] ?? null;
|
|
182
184
|
}
|
|
183
185
|
export async function enqueueProductAnalyticsEvent(db, input) {
|
|
184
|
-
const [
|
|
186
|
+
const [event] = await db.select({ environment: productAnalyticsEvents.environment }).from(productAnalyticsEvents)
|
|
187
|
+
.where(eq(productAnalyticsEvents.id, input.eventId)).limit(1);
|
|
188
|
+
if (!event || event.environment !== "production")
|
|
189
|
+
return null;
|
|
190
|
+
const [installation] = await db.select({ mode: productAnalyticsInstallations.mode, state: productAnalyticsInstallations.state }).from(productAnalyticsInstallations)
|
|
185
191
|
.where(eq(productAnalyticsInstallations.installationId, input.installationId)).limit(1);
|
|
186
192
|
if (!installation || installation.mode === "off")
|
|
187
193
|
return null;
|
|
194
|
+
const [backlog] = await db.select({ count: sql `count(*)`, bytes: sql `coalesce(sum(length(${productAnalyticsEvents.properties}::text)), 0)` })
|
|
195
|
+
.from(productAnalyticsOutbox)
|
|
196
|
+
.innerJoin(productAnalyticsEvents, eq(productAnalyticsOutbox.eventId, productAnalyticsEvents.id))
|
|
197
|
+
.where(and(eq(productAnalyticsOutbox.installationId, input.installationId), inArray(productAnalyticsOutbox.state, ["pending", "claimed", "retry_wait"])));
|
|
198
|
+
if (Number(backlog?.count ?? 0) >= PRODUCT_ANALYTICS_OUTBOX_MAX_ROWS || Number(backlog?.bytes ?? 0) >= PRODUCT_ANALYTICS_OUTBOX_MAX_BYTES) {
|
|
199
|
+
await db.update(productAnalyticsInstallations).set({ state: { ...(installation.state ?? {}), coverageGap: true }, updatedAt: new Date() })
|
|
200
|
+
.where(eq(productAnalyticsInstallations.installationId, input.installationId));
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
188
203
|
const scope = installation.mode === "account_linked" ? "account_linked_user" : "anonymous_installation";
|
|
189
204
|
const conditions = [eq(productAnalyticsConsentLedger.installationId, input.installationId), eq(productAnalyticsConsentLedger.scope, scope), eq(productAnalyticsConsentLedger.decision, "granted")];
|
|
190
205
|
conditions.push(scope === "account_linked_user" && input.localUserId ? eq(productAnalyticsConsentLedger.localUserId, input.localUserId) : isNull(productAnalyticsConsentLedger.localUserId));
|
|
@@ -247,6 +262,12 @@ export async function completeProductAnalyticsWorkCycle(db, input) {
|
|
|
247
262
|
if (lockedCycle.state === "completed" && lockedCycle.invalidatedAt === null) {
|
|
248
263
|
return { event: null, revision: lockedCycle.completionRevision };
|
|
249
264
|
}
|
|
265
|
+
const rootRunRows = await tx.select({ rootRunId: productAnalyticsEvents.rootRunId }).from(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.orgId, input.orgId), eq(productAnalyticsEvents.workCycleId, input.workCycleId), sql `${productAnalyticsEvents.rootRunId} is not null`));
|
|
266
|
+
const rootRunIds = [...new Set([
|
|
267
|
+
...(lockedCycle.rootRunIds ?? []),
|
|
268
|
+
...rootRunRows.map((row) => row.rootRunId).filter((value) => Boolean(value)),
|
|
269
|
+
...(input.rootRunId ? [input.rootRunId] : []),
|
|
270
|
+
])];
|
|
250
271
|
const revision = (lockedCycle.completionRevision ?? 0) + 1;
|
|
251
272
|
const event = await recordProductAnalyticsEvent(tx, {
|
|
252
273
|
orgId: input.orgId,
|
|
@@ -269,8 +290,16 @@ export async function completeProductAnalyticsWorkCycle(db, input) {
|
|
|
269
290
|
});
|
|
270
291
|
await tx
|
|
271
292
|
.update(productAnalyticsWorkCycles)
|
|
272
|
-
.set({ state: "completed", completionRevision: revision, completedAt: new Date(), invalidatedAt: null, updatedAt: new Date() })
|
|
293
|
+
.set({ state: "completed", completionRevision: revision, completedAt: new Date(), invalidatedAt: null, rootRunIds, updatedAt: new Date() })
|
|
273
294
|
.where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
|
|
295
|
+
await tx.insert(productAnalyticsWorkCycleRevisions).values({
|
|
296
|
+
orgId: input.orgId,
|
|
297
|
+
workCycleId: input.workCycleId,
|
|
298
|
+
completionRevision: revision,
|
|
299
|
+
completionEventId: event?.id,
|
|
300
|
+
completedAt: new Date(),
|
|
301
|
+
updatedAt: new Date(),
|
|
302
|
+
}).onConflictDoNothing();
|
|
274
303
|
return { event, revision };
|
|
275
304
|
});
|
|
276
305
|
}
|
|
@@ -297,6 +326,12 @@ export async function invalidateProductAnalyticsWorkCycle(db, input) {
|
|
|
297
326
|
dedupeKey: `work_loop_invalidated:${input.workCycleId}:${input.completionRevision}:${input.reasonCode}`,
|
|
298
327
|
properties: { reason_code: input.reasonCode },
|
|
299
328
|
});
|
|
329
|
+
await tx.update(productAnalyticsWorkCycleRevisions).set({
|
|
330
|
+
invalidatedAt: input.occurredAt ?? new Date(),
|
|
331
|
+
invalidationReasonCode: input.reasonCode,
|
|
332
|
+
invalidationEventId: event?.id,
|
|
333
|
+
updatedAt: new Date(),
|
|
334
|
+
}).where(and(eq(productAnalyticsWorkCycleRevisions.orgId, input.orgId), eq(productAnalyticsWorkCycleRevisions.workCycleId, input.workCycleId), eq(productAnalyticsWorkCycleRevisions.completionRevision, input.completionRevision)));
|
|
300
335
|
await tx.update(productAnalyticsWorkCycles).set({ state: "invalidated", invalidatedAt: input.occurredAt ?? new Date(), updatedAt: new Date() })
|
|
301
336
|
.where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
|
|
302
337
|
return event;
|
|
@@ -335,7 +370,7 @@ export async function setProductAnalyticsInstallationMode(db, installationId, mo
|
|
|
335
370
|
.where(eq(productAnalyticsInstallations.installationId, installationId)).returning();
|
|
336
371
|
return row ?? null;
|
|
337
372
|
}
|
|
338
|
-
export async function reconcileProductAnalyticsInstallationMode(db, installationId) {
|
|
373
|
+
export async function reconcileProductAnalyticsInstallationMode(db, installationId, preferredMode) {
|
|
339
374
|
const consent = await db.select({ scope: productAnalyticsConsentLedger.scope, decision: productAnalyticsConsentLedger.decision })
|
|
340
375
|
.from(productAnalyticsConsentLedger)
|
|
341
376
|
.where(eq(productAnalyticsConsentLedger.installationId, installationId))
|
|
@@ -345,9 +380,9 @@ export async function reconcileProductAnalyticsInstallationMode(db, installation
|
|
|
345
380
|
if (!latestByScope.has(row.scope))
|
|
346
381
|
latestByScope.set(row.scope, row.decision);
|
|
347
382
|
}
|
|
348
|
-
const mode = latestByScope.get("account_linked_user") === "granted"
|
|
383
|
+
const mode = preferredMode ?? (latestByScope.get("account_linked_user") === "granted"
|
|
349
384
|
? "account_linked"
|
|
350
|
-
: latestByScope.get("anonymous_installation") === "granted" ? "anonymous" : "off";
|
|
385
|
+
: latestByScope.get("anonymous_installation") === "granted" ? "anonymous" : "off");
|
|
351
386
|
return setProductAnalyticsInstallationMode(db, installationId, mode);
|
|
352
387
|
}
|
|
353
388
|
export async function getProductAnalyticsInstallationState(db, installationId) {
|
|
@@ -470,6 +505,30 @@ export async function claimProductAnalyticsOutboxBatch(db, input) {
|
|
|
470
505
|
});
|
|
471
506
|
throw error;
|
|
472
507
|
}
|
|
508
|
+
// Keep anonymous exporter output available to the local Privacy & Telemetry page.
|
|
509
|
+
// Account-linked payloads are deliberately never retained in installation-wide
|
|
510
|
+
// state because another signed-in user may later read the settings page.
|
|
511
|
+
const [installation] = await db.select({ state: productAnalyticsInstallations.state })
|
|
512
|
+
.from(productAnalyticsInstallations)
|
|
513
|
+
.where(eq(productAnalyticsInstallations.installationId, input.installationId))
|
|
514
|
+
.limit(1);
|
|
515
|
+
if (installation) {
|
|
516
|
+
const nextState = { ...(installation.state ?? {}) };
|
|
517
|
+
if (input.deliveryMode === "anonymous") {
|
|
518
|
+
nextState.lastPayloadAt = new Date().toISOString();
|
|
519
|
+
nextState.lastPayload = payloads.slice(-20);
|
|
520
|
+
nextState.lastPayloadMode = "anonymous";
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
delete nextState.lastPayloadAt;
|
|
524
|
+
delete nextState.lastPayload;
|
|
525
|
+
delete nextState.lastPayloadMode;
|
|
526
|
+
}
|
|
527
|
+
await db.update(productAnalyticsInstallations).set({
|
|
528
|
+
state: nextState,
|
|
529
|
+
updatedAt: new Date(),
|
|
530
|
+
}).where(eq(productAnalyticsInstallations.installationId, input.installationId));
|
|
531
|
+
}
|
|
473
532
|
const first = rows[0];
|
|
474
533
|
if (!first.claimToken)
|
|
475
534
|
throw unprocessable("Product analytics outbox claim token is missing");
|
|
@@ -478,6 +537,7 @@ export async function claimProductAnalyticsOutboxBatch(db, input) {
|
|
|
478
537
|
installationId: first.installationId,
|
|
479
538
|
deliveryMode: first.deliveryMode,
|
|
480
539
|
consentScope: first.consentScope,
|
|
540
|
+
consentedLocalUserId: first.consentedLocalUserId,
|
|
481
541
|
consentEpoch: first.consentEpoch,
|
|
482
542
|
events: payloads,
|
|
483
543
|
};
|
|
@@ -496,7 +556,7 @@ export async function acknowledgeProductAnalyticsOutbox(db, input) {
|
|
|
496
556
|
attemptCount: sql `${productAnalyticsOutbox.attemptCount} + 1`,
|
|
497
557
|
deliveredAt: delivered ? now : null,
|
|
498
558
|
deadLetteredAt: deadLettered ? now : null,
|
|
499
|
-
nextAttemptAt: delivered || deadLettered ? now : new Date(now.getTime() + Math.min(60 * 60_000, 2 ** Math.min(maxAttemptCount, 6) * 60_000)),
|
|
559
|
+
nextAttemptAt: delivered || deadLettered ? now : new Date(now.getTime() + Math.round(Math.min(60 * 60_000, 2 ** Math.min(maxAttemptCount, 6) * 60_000) * (0.8 + Math.random() * 0.4))),
|
|
500
560
|
lastErrorCode: input.errorCode ?? null,
|
|
501
561
|
claimToken: null,
|
|
502
562
|
leaseExpiresAt: null,
|
|
@@ -511,7 +571,11 @@ export async function acknowledgeProductAnalyticsOutboxClaim(db, input) {
|
|
|
511
571
|
deliveryMode: productAnalyticsOutbox.deliveryMode,
|
|
512
572
|
consentEpoch: productAnalyticsOutbox.consentEpoch,
|
|
513
573
|
consentedLocalUserId: productAnalyticsOutbox.consentedLocalUserId,
|
|
514
|
-
}).from(productAnalyticsOutbox).where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.claimToken, input.claimToken), inArray(productAnalyticsOutbox.eventId, input.eventIds)
|
|
574
|
+
}).from(productAnalyticsOutbox).where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.claimToken, input.claimToken), inArray(productAnalyticsOutbox.eventId, input.eventIds), input.consentedLocalUserId === undefined
|
|
575
|
+
? undefined
|
|
576
|
+
: input.consentedLocalUserId
|
|
577
|
+
? eq(productAnalyticsOutbox.consentedLocalUserId, input.consentedLocalUserId)
|
|
578
|
+
: isNull(productAnalyticsOutbox.consentedLocalUserId)));
|
|
515
579
|
if (rows.length === 0)
|
|
516
580
|
return { updatedCount: 0, state: input.delivered === false ? "retry_wait" : "delivered" };
|
|
517
581
|
const first = rows[0];
|
|
@@ -527,6 +591,23 @@ export async function acknowledgeProductAnalyticsOutboxClaim(db, input) {
|
|
|
527
591
|
consentedLocalUserId: first.consentedLocalUserId,
|
|
528
592
|
});
|
|
529
593
|
}
|
|
594
|
+
export async function cleanupProductAnalyticsRetention(db, input) {
|
|
595
|
+
const now = input.now ?? new Date();
|
|
596
|
+
const retentionDays = Math.min(Math.max(Math.trunc(input.retentionDays ?? 90), 1), 3650);
|
|
597
|
+
const cutoff = new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1000);
|
|
598
|
+
const delivered = await db.select({ eventId: productAnalyticsOutbox.eventId }).from(productAnalyticsOutbox)
|
|
599
|
+
.where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.state, "delivered"), lt(productAnalyticsOutbox.updatedAt, cutoff)));
|
|
600
|
+
if (delivered.length === 0)
|
|
601
|
+
return { deletedEvents: 0, droppedDueToRetention: 0, cutoff: cutoff.toISOString() };
|
|
602
|
+
const eventIds = delivered.map((row) => row.eventId);
|
|
603
|
+
await db.delete(productAnalyticsOutbox).where(inArray(productAnalyticsOutbox.eventId, eventIds));
|
|
604
|
+
const deleted = await db.delete(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.installationId, input.installationId), inArray(productAnalyticsEvents.id, eventIds))).returning({ id: productAnalyticsEvents.id });
|
|
605
|
+
await db.update(productAnalyticsInstallations).set({
|
|
606
|
+
state: { retentionCleanupAt: now.toISOString(), droppedDueToRetention: 0 },
|
|
607
|
+
updatedAt: now,
|
|
608
|
+
}).where(eq(productAnalyticsInstallations.installationId, input.installationId));
|
|
609
|
+
return { deletedEvents: deleted.length, droppedDueToRetention: 0, cutoff: cutoff.toISOString() };
|
|
610
|
+
}
|
|
530
611
|
export function pseudonymizeProductAnalyticsId(secret, value) {
|
|
531
612
|
return createHmac("sha256", secret).update(value).digest("hex");
|
|
532
613
|
}
|
|
@@ -598,10 +679,11 @@ export function productAnalyticsService(db) {
|
|
|
598
679
|
.select({ count: sql `count(distinct ${productAnalyticsEvents.workCycleId} || ':' || ${productAnalyticsEvents.completionRevision})` })
|
|
599
680
|
.from(productAnalyticsEvents)
|
|
600
681
|
.where(and(where, eq(productAnalyticsEvents.eventName, "work_loop_completed"), eq(productAnalyticsEvents.origin, "human"), eq(productAnalyticsEvents.environment, "production"), sql `not exists (
|
|
601
|
-
select 1 from ${
|
|
682
|
+
select 1 from ${productAnalyticsWorkCycleRevisions} invalidated
|
|
602
683
|
where invalidated.org_id = ${productAnalyticsEvents.orgId}
|
|
603
684
|
and invalidated.work_cycle_id = ${productAnalyticsEvents.workCycleId}
|
|
604
|
-
and invalidated.
|
|
685
|
+
and invalidated.completion_revision = ${productAnalyticsEvents.completionRevision}
|
|
686
|
+
and invalidated.invalidated_at is not null
|
|
605
687
|
)`)),
|
|
606
688
|
db
|
|
607
689
|
.select({ count: sql `count(*)` })
|