@liiift-studio/sanity-visitor-insights 0.1.0 → 0.2.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 +48 -0
- package/dist/index.d.mts +42 -3
- package/dist/index.d.ts +42 -3
- package/dist/index.js +43 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{ranges-D6AZwpmm.d.mts → ranges-DSFapooC.d.mts} +116 -2
- package/dist/{ranges-D6AZwpmm.d.ts → ranges-DSFapooC.d.ts} +116 -2
- package/dist/server.d.mts +35 -104
- package/dist/server.d.ts +35 -104
- package/dist/server.js +280 -2
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +279 -2
- package/dist/server.mjs.map +1 -1
- package/dist/testing.d.mts +73 -0
- package/dist/testing.d.ts +73 -0
- package/dist/testing.js +106 -0
- package/dist/testing.js.map +1 -0
- package/dist/testing.mjs +73 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/vercel-D19ArNAY.d.mts +80 -0
- package/dist/vercel-D19ArNAY.d.ts +80 -0
- package/package.json +8 -3
- package/src/index.ts +7 -0
- package/src/reportData.ts +25 -0
- package/src/server/createHandler.ts +5 -0
- package/src/server/diagnostics.test.ts +249 -0
- package/src/server/diagnostics.ts +358 -0
- package/src/server/ga4.ts +7 -1
- package/src/server/reports/reports.test.ts +344 -0
- package/src/server.ts +2 -0
- package/src/studio/VisitorInsightsTool.tsx +3 -1
- package/src/studio/panels.tsx +60 -0
- package/src/testing/fakes.ts +135 -0
- package/src/testing.ts +11 -0
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ only as a conversion anchor and a count; no revenue, and no customer data.
|
|
|
17
17
|
| **Acquisition** | Where do visitors come from, and how many are design-industry referrals? |
|
|
18
18
|
| **Journey** | How far do visitors get, and where do they stop? |
|
|
19
19
|
| **Typeface interest** | Which families get viewed, tested and bought? |
|
|
20
|
+
| **Diagnostics** | What to fix before trusting any of the above? |
|
|
20
21
|
|
|
21
22
|
Each panel offers a week, quarter or year range.
|
|
22
23
|
|
|
@@ -60,6 +61,53 @@ aggregate statistics into personal-data processing.
|
|
|
60
61
|
|
|
61
62
|
---
|
|
62
63
|
|
|
64
|
+
## Diagnostics — run this first
|
|
65
|
+
|
|
66
|
+
The panels are only as trustworthy as the property behind them, and the ways a GA4 property
|
|
67
|
+
quietly produces wrong numbers are not visible from the numbers themselves. The Diagnostics panel
|
|
68
|
+
measures them:
|
|
69
|
+
|
|
70
|
+
- **GA4 reachable** — distinguishes a bad service-account key from missing Viewer access.
|
|
71
|
+
- **Timezone matches config** — a mismatch shifts day and week boundaries, so GA4, Vercel and
|
|
72
|
+
Sanity stop agreeing which period an event belongs to.
|
|
73
|
+
- **Retention covers a year** — probes a window beyond the 2-month default rather than asking you
|
|
74
|
+
to read the Admin screen. This is the check most likely to explain an empty year range.
|
|
75
|
+
- **Configured events fire** — catches a cutover map claiming an event is live when GA4 has never
|
|
76
|
+
seen it, which otherwise produces figures that look real but are not.
|
|
77
|
+
- **Purchases carry `transaction_id`** — without it a GA4 purchase cannot be reconciled against a
|
|
78
|
+
Sanity order at all.
|
|
79
|
+
- **GA4 purchases match orders** — a large divergence usually means `purchase` fires on a page some
|
|
80
|
+
buyers never reach, fires twice, or is being blocked.
|
|
81
|
+
|
|
82
|
+
It is also the one panel that still says something useful with nothing configured, so it is worth
|
|
83
|
+
opening the moment credentials land. Available in the Studio, or headless:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { runDiagnostics } from '@liiift-studio/sanity-visitor-insights/server'
|
|
87
|
+
|
|
88
|
+
const report = await runDiagnostics({ config, ga4, vercel, sanity })
|
|
89
|
+
console.log(report.verdict, report.checks)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Testing without credentials
|
|
95
|
+
|
|
96
|
+
Test doubles ship from a separate subpath, so a consuming site can exercise these reports before
|
|
97
|
+
it has anything to point at:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createFakeGa4Client, makeGa4Total } from '@liiift-studio/sanity-visitor-insights/testing'
|
|
101
|
+
|
|
102
|
+
const ga4 = createFakeGa4Client({ batch: () => [makeGa4Total(800), makeGa4Total(300), makeGa4Total(0)] })
|
|
103
|
+
const data = await measurementHealth({ config, range, ga4, vercel: null, sanity: null })
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The fakes record what they were asked, so a test can assert on the GROQ that ran — which is how
|
|
107
|
+
this package proves no customer field is ever projected.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
63
111
|
## Installing
|
|
64
112
|
|
|
65
113
|
```bash
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as sanity from 'sanity';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue } from './ranges-
|
|
4
|
-
export { C as Coverage,
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, D as DiagnosticReport, J as JourneyData, c as MeasurementHealthData, T as TypefaceInterestData } from './ranges-DSFapooC.mjs';
|
|
4
|
+
export { C as Coverage, d as DateRange, E as EventCutover, P as PREEXISTING, e as REPORT_NAMES, f as ReportError, S as SiteAnalyticsConfig, g as SourceName, h as SourceStatus, U as UnavailableReason, i as coverageForRange, j as isReportName, o as ok, p as partial, k as previousRange, r as resolveRange, u as unavailable, v as validateSiteConfig, l as valueOrNull } from './ranges-DSFapooC.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The Visitor Insights Studio tool.
|
|
@@ -110,6 +110,45 @@ interface NoticeListProps {
|
|
|
110
110
|
*/
|
|
111
111
|
declare function NoticeList({ notices }: NoticeListProps): React.ReactElement | null;
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The four report panels.
|
|
115
|
+
*
|
|
116
|
+
* Every panel renders a table or labelled bars rather than a chart, and every one surfaces its own
|
|
117
|
+
* caveats inline. Tables are the accessible representation as well as the visual one, so there is
|
|
118
|
+
* no separate "view as data" toggle that could drift out of sync with what is displayed.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Measurement Health — how much of reality each source sees.
|
|
123
|
+
* Compares pageviews to pageviews. Sessions and orders sit alongside as context and are never
|
|
124
|
+
* subtracted from a pageview count.
|
|
125
|
+
*/
|
|
126
|
+
declare function MeasurementHealthPanel({ data }: {
|
|
127
|
+
data: MeasurementHealthData;
|
|
128
|
+
}): React.ReactElement;
|
|
129
|
+
/** Acquisition — where visitors came from, with design-industry referrers called out. */
|
|
130
|
+
declare function AcquisitionPanel({ data }: {
|
|
131
|
+
data: AcquisitionData;
|
|
132
|
+
}): React.ReactElement;
|
|
133
|
+
/** Journey — per-step totals with adjacent drop-off, explicitly not a tracked path. */
|
|
134
|
+
declare function JourneyPanel({ data }: {
|
|
135
|
+
data: JourneyData;
|
|
136
|
+
}): React.ReactElement;
|
|
137
|
+
/** Typeface interest — viewed, tested and bought per family. */
|
|
138
|
+
declare function TypefaceInterestPanel({ data }: {
|
|
139
|
+
data: TypefaceInterestData;
|
|
140
|
+
}): React.ReactElement;
|
|
141
|
+
/**
|
|
142
|
+
* Diagnostics — what to fix before trusting anything else here.
|
|
143
|
+
*
|
|
144
|
+
* Deliberately the panel that still works with nothing configured: on a site without credentials
|
|
145
|
+
* it is the only one that can say something useful, and it is the first thing worth opening once
|
|
146
|
+
* credentials land.
|
|
147
|
+
*/
|
|
148
|
+
declare function DiagnosticsPanel({ data }: {
|
|
149
|
+
data: DiagnosticReport;
|
|
150
|
+
}): React.ReactElement;
|
|
151
|
+
|
|
113
152
|
/**
|
|
114
153
|
* Studio entry point — browser only.
|
|
115
154
|
*
|
|
@@ -152,4 +191,4 @@ interface VisitorInsightsPluginOptions {
|
|
|
152
191
|
*/
|
|
153
192
|
declare const visitorInsights: sanity.Plugin<VisitorInsightsPluginOptions>;
|
|
154
193
|
|
|
155
|
-
export { ComparisonBar, MetricFigure, MetricValue, NoticeList, RangeKey, ReportEnvelope, ReportName, type ReportState, type UseReportOptions, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, visitorInsights as default, formatCount, formatPercent, useReport, visitorInsights };
|
|
194
|
+
export { AcquisitionPanel, ComparisonBar, DiagnosticsPanel, JourneyPanel, MeasurementHealthPanel, MetricFigure, MetricValue, NoticeList, RangeKey, ReportEnvelope, ReportName, type ReportState, TypefaceInterestPanel, type UseReportOptions, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, visitorInsights as default, formatCount, formatPercent, useReport, visitorInsights };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as sanity from 'sanity';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue } from './ranges-
|
|
4
|
-
export { C as Coverage,
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, D as DiagnosticReport, J as JourneyData, c as MeasurementHealthData, T as TypefaceInterestData } from './ranges-DSFapooC.js';
|
|
4
|
+
export { C as Coverage, d as DateRange, E as EventCutover, P as PREEXISTING, e as REPORT_NAMES, f as ReportError, S as SiteAnalyticsConfig, g as SourceName, h as SourceStatus, U as UnavailableReason, i as coverageForRange, j as isReportName, o as ok, p as partial, k as previousRange, r as resolveRange, u as unavailable, v as validateSiteConfig, l as valueOrNull } from './ranges-DSFapooC.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The Visitor Insights Studio tool.
|
|
@@ -110,6 +110,45 @@ interface NoticeListProps {
|
|
|
110
110
|
*/
|
|
111
111
|
declare function NoticeList({ notices }: NoticeListProps): React.ReactElement | null;
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The four report panels.
|
|
115
|
+
*
|
|
116
|
+
* Every panel renders a table or labelled bars rather than a chart, and every one surfaces its own
|
|
117
|
+
* caveats inline. Tables are the accessible representation as well as the visual one, so there is
|
|
118
|
+
* no separate "view as data" toggle that could drift out of sync with what is displayed.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Measurement Health — how much of reality each source sees.
|
|
123
|
+
* Compares pageviews to pageviews. Sessions and orders sit alongside as context and are never
|
|
124
|
+
* subtracted from a pageview count.
|
|
125
|
+
*/
|
|
126
|
+
declare function MeasurementHealthPanel({ data }: {
|
|
127
|
+
data: MeasurementHealthData;
|
|
128
|
+
}): React.ReactElement;
|
|
129
|
+
/** Acquisition — where visitors came from, with design-industry referrers called out. */
|
|
130
|
+
declare function AcquisitionPanel({ data }: {
|
|
131
|
+
data: AcquisitionData;
|
|
132
|
+
}): React.ReactElement;
|
|
133
|
+
/** Journey — per-step totals with adjacent drop-off, explicitly not a tracked path. */
|
|
134
|
+
declare function JourneyPanel({ data }: {
|
|
135
|
+
data: JourneyData;
|
|
136
|
+
}): React.ReactElement;
|
|
137
|
+
/** Typeface interest — viewed, tested and bought per family. */
|
|
138
|
+
declare function TypefaceInterestPanel({ data }: {
|
|
139
|
+
data: TypefaceInterestData;
|
|
140
|
+
}): React.ReactElement;
|
|
141
|
+
/**
|
|
142
|
+
* Diagnostics — what to fix before trusting anything else here.
|
|
143
|
+
*
|
|
144
|
+
* Deliberately the panel that still works with nothing configured: on a site without credentials
|
|
145
|
+
* it is the only one that can say something useful, and it is the first thing worth opening once
|
|
146
|
+
* credentials land.
|
|
147
|
+
*/
|
|
148
|
+
declare function DiagnosticsPanel({ data }: {
|
|
149
|
+
data: DiagnosticReport;
|
|
150
|
+
}): React.ReactElement;
|
|
151
|
+
|
|
113
152
|
/**
|
|
114
153
|
* Studio entry point — browser only.
|
|
115
154
|
*
|
|
@@ -152,4 +191,4 @@ interface VisitorInsightsPluginOptions {
|
|
|
152
191
|
*/
|
|
153
192
|
declare const visitorInsights: sanity.Plugin<VisitorInsightsPluginOptions>;
|
|
154
193
|
|
|
155
|
-
export { ComparisonBar, MetricFigure, MetricValue, NoticeList, RangeKey, ReportEnvelope, ReportName, type ReportState, type UseReportOptions, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, visitorInsights as default, formatCount, formatPercent, useReport, visitorInsights };
|
|
194
|
+
export { AcquisitionPanel, ComparisonBar, DiagnosticsPanel, JourneyPanel, MeasurementHealthPanel, MetricFigure, MetricValue, NoticeList, RangeKey, ReportEnvelope, ReportName, type ReportState, TypefaceInterestPanel, type UseReportOptions, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, visitorInsights as default, formatCount, formatPercent, useReport, visitorInsights };
|
package/dist/index.js
CHANGED
|
@@ -20,11 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AcquisitionPanel: () => AcquisitionPanel,
|
|
23
24
|
ComparisonBar: () => ComparisonBar,
|
|
25
|
+
DiagnosticsPanel: () => DiagnosticsPanel,
|
|
26
|
+
JourneyPanel: () => JourneyPanel,
|
|
27
|
+
MeasurementHealthPanel: () => MeasurementHealthPanel,
|
|
24
28
|
MetricFigure: () => MetricFigure,
|
|
25
29
|
NoticeList: () => NoticeList,
|
|
26
30
|
PREEXISTING: () => PREEXISTING,
|
|
27
31
|
REPORT_NAMES: () => REPORT_NAMES,
|
|
32
|
+
TypefaceInterestPanel: () => TypefaceInterestPanel,
|
|
28
33
|
VisitorInsightsTool: () => VisitorInsightsTool,
|
|
29
34
|
coverageForRange: () => coverageForRange,
|
|
30
35
|
default: () => index_default,
|
|
@@ -313,6 +318,34 @@ function TypefaceInterestPanel({ data }) {
|
|
|
313
318
|
] }) })
|
|
314
319
|
] });
|
|
315
320
|
}
|
|
321
|
+
var CHECK_TONE = {
|
|
322
|
+
pass: "positive",
|
|
323
|
+
warn: "caution",
|
|
324
|
+
fail: "critical",
|
|
325
|
+
skipped: "default"
|
|
326
|
+
};
|
|
327
|
+
var CHECK_WORD = {
|
|
328
|
+
pass: "Pass",
|
|
329
|
+
warn: "Check",
|
|
330
|
+
fail: "Fail",
|
|
331
|
+
skipped: "Skipped"
|
|
332
|
+
};
|
|
333
|
+
function DiagnosticsPanel({ data }) {
|
|
334
|
+
const failing = data.checks.filter((c) => c.status === "fail").length;
|
|
335
|
+
const warning = data.checks.filter((c) => c.status === "warn").length;
|
|
336
|
+
const summary = data.verdict === "pass" ? "Everything checked out. The figures in the other panels can be taken at face value." : `${failing} failing, ${warning} worth a look. Panels depending on these will be wrong or incomplete until they are resolved.`;
|
|
337
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
338
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: CHECK_TONE[data.verdict], border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: summary }) }),
|
|
339
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: data.checks.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
340
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
341
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, weight: "semibold", children: item.label }),
|
|
342
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 2, radius: 2, tone: CHECK_TONE[item.status], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 0, weight: "medium", children: CHECK_WORD[item.status] }) })
|
|
343
|
+
] }),
|
|
344
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: item.detail }),
|
|
345
|
+
item.remedy && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: item.remedy })
|
|
346
|
+
] }) }, item.id)) })
|
|
347
|
+
] });
|
|
348
|
+
}
|
|
316
349
|
|
|
317
350
|
// src/studio/VisitorInsightsTool.tsx
|
|
318
351
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -325,7 +358,8 @@ var PANELS = [
|
|
|
325
358
|
{ report: "measurement-health", label: "Measurement health", blurb: "How much of reality each source actually sees" },
|
|
326
359
|
{ report: "acquisition", label: "Acquisition", blurb: "Where visitors come from" },
|
|
327
360
|
{ report: "journey", label: "Journey", blurb: "How far visitors get, and where they stop" },
|
|
328
|
-
{ report: "typeface-interest", label: "Typeface interest", blurb: "Viewed, tested and bought, by family" }
|
|
361
|
+
{ report: "typeface-interest", label: "Typeface interest", blurb: "Viewed, tested and bought, by family" },
|
|
362
|
+
{ report: "diagnostics", label: "Diagnostics", blurb: "What to fix before trusting the numbers above" }
|
|
329
363
|
];
|
|
330
364
|
function RangeSelector({ value, onChange }) {
|
|
331
365
|
const refs = (0, import_react2.useRef)([]);
|
|
@@ -430,6 +464,7 @@ function ReportPanel({ report, apiBaseUrl, range }) {
|
|
|
430
464
|
report === "acquisition" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AcquisitionPanel, { data: state.envelope.data }),
|
|
431
465
|
report === "journey" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JourneyPanel, { data: state.envelope.data }),
|
|
432
466
|
report === "typeface-interest" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TypefaceInterestPanel, { data: state.envelope.data }),
|
|
467
|
+
report === "diagnostics" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DiagnosticsPanel, { data: state.envelope.data }),
|
|
433
468
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
434
469
|
state.envelope.range.start,
|
|
435
470
|
" to ",
|
|
@@ -488,7 +523,8 @@ var REPORT_NAMES = [
|
|
|
488
523
|
"measurement-health",
|
|
489
524
|
"acquisition",
|
|
490
525
|
"journey",
|
|
491
|
-
"typeface-interest"
|
|
526
|
+
"typeface-interest",
|
|
527
|
+
"diagnostics"
|
|
492
528
|
];
|
|
493
529
|
function isReportName(value) {
|
|
494
530
|
return typeof value === "string" && REPORT_NAMES.includes(value);
|
|
@@ -629,11 +665,16 @@ var visitorInsights = (0, import_sanity2.definePlugin)((options) => {
|
|
|
629
665
|
var index_default = visitorInsights;
|
|
630
666
|
// Annotate the CommonJS export names for ESM import in node:
|
|
631
667
|
0 && (module.exports = {
|
|
668
|
+
AcquisitionPanel,
|
|
632
669
|
ComparisonBar,
|
|
670
|
+
DiagnosticsPanel,
|
|
671
|
+
JourneyPanel,
|
|
672
|
+
MeasurementHealthPanel,
|
|
633
673
|
MetricFigure,
|
|
634
674
|
NoticeList,
|
|
635
675
|
PREEXISTING,
|
|
636
676
|
REPORT_NAMES,
|
|
677
|
+
TypefaceInterestPanel,
|
|
637
678
|
VisitorInsightsTool,
|
|
638
679
|
coverageForRange,
|
|
639
680
|
formatCount,
|