@liiift-studio/sanity-visitor-insights 0.7.0 → 0.8.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/README.md +2 -0
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{ranges-D0k0f-FF.d.mts → ranges-B1YwjGGt.d.mts} +23 -5
- package/dist/{ranges-D0k0f-FF.d.ts → ranges-B1YwjGGt.d.ts} +23 -5
- package/dist/server.d.mts +6 -11
- package/dist/server.d.ts +6 -11
- package/dist/server.js +63 -23
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +63 -23
- package/dist/server.mjs.map +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{vercel-B6d1Ng48.d.mts → vercel-BnuGVKWS.d.mts} +14 -0
- package/dist/{vercel-B6d1Ng48.d.ts → vercel-BnuGVKWS.d.ts} +14 -0
- package/package.json +1 -1
- package/src/core/siteConfig.ts +10 -0
- package/src/reportData.ts +13 -4
- package/src/server/auth.ts +38 -4
- package/src/server/createHandler.ts +1 -1
- package/src/server/ga4.ts +22 -0
- package/src/server/reports/acquisition.ts +15 -3
- package/src/server/reports/journey.ts +25 -13
- package/src/server/reports/reports.test.ts +102 -2
- package/src/server/reports/typefaceInterest.ts +19 -3
- package/src/server.ts +1 -1
- package/src/studio/panels.test.tsx +44 -2
- package/src/studio/panels.tsx +20 -11
package/README.md
CHANGED
|
@@ -169,6 +169,8 @@ export default createVisitorInsightsHandler({
|
|
|
169
169
|
tester_engaged: null,
|
|
170
170
|
},
|
|
171
171
|
// Only needed where the Studio is on a different origin.
|
|
172
|
+
// Enforced server-side, unlike the plugin option of the same name. Set both, to the same list.
|
|
173
|
+
roles: ['administrator'],
|
|
172
174
|
allowedStudioOrigins: ['https://mckl.sanity.studio'],
|
|
173
175
|
},
|
|
174
176
|
})
|
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, A as AcquisitionData, D as DiagnosticReport, J as JourneyData, c as MeasurementHealthData, T as TypefaceInterestData } from './ranges-
|
|
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-
|
|
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-B1YwjGGt.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-B1YwjGGt.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The Visitor Insights Studio tool.
|
|
@@ -144,6 +144,13 @@ declare function NoticeList({ notices }: NoticeListProps): React.ReactElement |
|
|
|
144
144
|
* Every panel renders a table or labelled bars rather than a chart, and every one surfaces its own
|
|
145
145
|
* caveats inline. Tables are the accessible representation as well as the visual one, so there is
|
|
146
146
|
* no separate "view as data" toggle that could drift out of sync with what is displayed.
|
|
147
|
+
*
|
|
148
|
+
* Every array these panels read is accessed defensively, and that is not paranoia about the server.
|
|
149
|
+
* The Studio bundle and the site's API route are separate deployments on separate schedules: a
|
|
150
|
+
* Studio can be upgraded while its route still runs an older package, and then a field added in the
|
|
151
|
+
* newer version simply is not in the response. On 2026-09-01 a Studio on 0.8.0 read `data.daily`
|
|
152
|
+
* from a route still on 0.6.2 and took the whole tool down with "Cannot read properties of
|
|
153
|
+
* undefined". A panel must degrade to showing less, never to a stack trace.
|
|
147
154
|
*/
|
|
148
155
|
|
|
149
156
|
/**
|
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, A as AcquisitionData, D as DiagnosticReport, J as JourneyData, c as MeasurementHealthData, T as TypefaceInterestData } from './ranges-
|
|
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-
|
|
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-B1YwjGGt.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-B1YwjGGt.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The Visitor Insights Studio tool.
|
|
@@ -144,6 +144,13 @@ declare function NoticeList({ notices }: NoticeListProps): React.ReactElement |
|
|
|
144
144
|
* Every panel renders a table or labelled bars rather than a chart, and every one surfaces its own
|
|
145
145
|
* caveats inline. Tables are the accessible representation as well as the visual one, so there is
|
|
146
146
|
* no separate "view as data" toggle that could drift out of sync with what is displayed.
|
|
147
|
+
*
|
|
148
|
+
* Every array these panels read is accessed defensively, and that is not paranoia about the server.
|
|
149
|
+
* The Studio bundle and the site's API route are separate deployments on separate schedules: a
|
|
150
|
+
* Studio can be upgraded while its route still runs an older package, and then a field added in the
|
|
151
|
+
* newer version simply is not in the response. On 2026-09-01 a Studio on 0.8.0 read `data.daily`
|
|
152
|
+
* from a route still on 0.6.2 and took the whole tool down with "Cannot read properties of
|
|
153
|
+
* undefined". A panel must degrade to showing less, never to a stack trace.
|
|
147
154
|
*/
|
|
148
155
|
|
|
149
156
|
/**
|
package/dist/index.js
CHANGED
|
@@ -259,10 +259,10 @@ function MeasurementHealthPanel({ data }) {
|
|
|
259
259
|
data.shortfallRatio !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, weight: "semibold", children: data.shortfallRatio >= 0 ? `GA4 saw ${formatPercent(data.shortfallRatio, 1)} fewer pageviews than Vercel` : `GA4 saw ${formatPercent(-data.shortfallRatio, 1)} more pageviews than Vercel` }),
|
|
260
260
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: data.interpretation })
|
|
261
261
|
] }) }),
|
|
262
|
-
data.daily
|
|
262
|
+
(data.daily?.length ?? 0) >= 3 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
263
263
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Day by day" }),
|
|
264
264
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "A gap that has always been there is consent and blocking. A gap that opens on one day is an incident." }),
|
|
265
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrendChart, { points: data.daily })
|
|
265
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrendChart, { points: data.daily ?? [] })
|
|
266
266
|
] }),
|
|
267
267
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
268
268
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Context" }),
|
|
@@ -307,7 +307,7 @@ function AcquisitionPanel({ data }) {
|
|
|
307
307
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Channel" }) }),
|
|
308
308
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Sessions" }) })
|
|
309
309
|
] }) }),
|
|
310
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
310
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: (data.rows ?? []).map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
311
311
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Flex, { gap: 2, align: "center", children: [
|
|
312
312
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.source }),
|
|
313
313
|
row.designIndustry && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: "primary", fontSize: 0, children: "design industry" }),
|
|
@@ -321,26 +321,26 @@ function AcquisitionPanel({ data }) {
|
|
|
321
321
|
] });
|
|
322
322
|
}
|
|
323
323
|
function JourneyPanel({ data }) {
|
|
324
|
-
const max = maxOf(data.steps.map((step) => step.count));
|
|
324
|
+
const max = maxOf((data.steps ?? []).map((step) => step.count));
|
|
325
325
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
326
326
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: data.approximationNote }) }),
|
|
327
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: data.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 2, children: [
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: (data.steps ?? []).map((step) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 2, children: [
|
|
328
328
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComparisonBar, { label: step.label, metric: step.count, max, tone: "primary" }),
|
|
329
329
|
step.conversionFromPrevious !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Text, { size: 0, muted: true, children: [
|
|
330
330
|
formatPercent(step.conversionFromPrevious, 1),
|
|
331
331
|
" of the previous measurable step"
|
|
332
332
|
] })
|
|
333
333
|
] }, step.key)) }),
|
|
334
|
-
data.
|
|
335
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Where sessions
|
|
334
|
+
(data.topLandingPages?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Where sessions began" }),
|
|
336
336
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { radius: 2, tone: "transparent", border: true, style: tableWrap, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { style: table, children: [
|
|
337
337
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
338
338
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Page" }) }),
|
|
339
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "
|
|
339
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Sessions" }) })
|
|
340
340
|
] }) }),
|
|
341
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.
|
|
341
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: (data.topLandingPages ?? []).map((page) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
342
342
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: page.path }) }),
|
|
343
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: formatCount(page.
|
|
343
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: formatCount(page.sessions) }) })
|
|
344
344
|
] }, page.path)) })
|
|
345
345
|
] }) })
|
|
346
346
|
] })
|
|
@@ -358,7 +358,7 @@ function TypefaceInterestPanel({ data }) {
|
|
|
358
358
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Bought" }) }),
|
|
359
359
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Test rate" }) })
|
|
360
360
|
] }) }),
|
|
361
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
361
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: (data.rows ?? []).map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
362
362
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.typeface }) }),
|
|
363
363
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.viewed, label: `${row.typeface} viewed`, size: 1 }) }),
|
|
364
364
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.tested, label: `${row.typeface} tested`, size: 1 }) }),
|