@liiift-studio/sanity-visitor-insights 0.40.0 → 0.41.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/dist/index.js +32 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -8
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +48 -7
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +48 -7
- package/dist/server.mjs.map +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{vercel-BG4eJo0f.d.mts → vercel-Da9lu56v.d.mts} +9 -1
- package/dist/{vercel-BG4eJo0f.d.ts → vercel-Da9lu56v.d.ts} +9 -1
- package/package.json +1 -1
- package/src/core/core.test.ts +23 -0
- package/src/core/cutover.ts +16 -2
- package/src/server/ga4.ts +12 -3
- package/src/server/reports/journey.ts +16 -3
- package/src/server/reports/measurementHealth.ts +32 -3
- package/src/server/reports/reports.test.ts +75 -0
- package/src/server/reports/typefaceInterest.ts +31 -3
- package/src/studio/CrossSourceTimeline.tsx +32 -2
- package/src/studio/panels.test.tsx +64 -2
- package/src/studio/panels.tsx +23 -4
package/dist/index.js
CHANGED
|
@@ -943,7 +943,15 @@ function dayIndexAt(clientX, left, boxWidth, viewBoxWidth, plotWidth, count) {
|
|
|
943
943
|
const index = Math.round((clamped - GUTTER) / plotWidth * (count - 1));
|
|
944
944
|
return Math.max(0, Math.min(count - 1, index));
|
|
945
945
|
}
|
|
946
|
-
function findCoverageIncident(days, dates) {
|
|
946
|
+
function findCoverageIncident(days, dates, volumes = []) {
|
|
947
|
+
const busiest = volumes.reduce((best, v) => Math.max(best, v ?? 0), 0);
|
|
948
|
+
const floor = busiest > 0 ? Math.max(1, busiest * 0.1) : 0;
|
|
949
|
+
const usable = days.map((value, i) => {
|
|
950
|
+
const volume = volumes[i];
|
|
951
|
+
if (floor > 0 && (volume === null || volume === void 0 || volume < floor)) return null;
|
|
952
|
+
return value;
|
|
953
|
+
});
|
|
954
|
+
days = usable;
|
|
947
955
|
const measured = days.filter((d) => d !== null);
|
|
948
956
|
if (measured.length < 21) return null;
|
|
949
957
|
const sorted = [...measured].sort((a, b) => a - b);
|
|
@@ -1127,7 +1135,11 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1127
1135
|
const saw = seen.get(point.date);
|
|
1128
1136
|
return saw == null ? null : saw / point.value;
|
|
1129
1137
|
});
|
|
1130
|
-
const incident = findCoverageIncident(
|
|
1138
|
+
const incident = findCoverageIncident(
|
|
1139
|
+
coverageByDay,
|
|
1140
|
+
row.points.map((p) => p.date),
|
|
1141
|
+
row.points.map((p) => p.value)
|
|
1142
|
+
);
|
|
1131
1143
|
if (incident) {
|
|
1132
1144
|
sentences.push(
|
|
1133
1145
|
`${row.shortfall.source} coverage fell from about ${formatPercent(incident.before, 0)} to ${formatPercent(incident.during, 0)} on ${tickLabel(/* @__PURE__ */ new Date(`${incident.onset}T00:00:00Z`))}` + (incident.ongoing ? `, and has stayed there for ${incident.days} days. That is a change on a date, not a standing shortfall \u2014 something altered on or around then.` : ` for ${incident.days} days, then recovered.`)
|
|
@@ -1810,6 +1822,8 @@ function Verdict({ data, previous }) {
|
|
|
1810
1822
|
const change = (a - b) / Math.abs(b);
|
|
1811
1823
|
if (Math.abs(change) < 0.05) {
|
|
1812
1824
|
parts.push(Math.abs(change) < 5e-3 ? `${label} flat` : `${label} little changed (${change > 0 ? "+" : "\u2212"}${formatPercent(Math.abs(change), 0)})`);
|
|
1825
|
+
} else if (unit === "money") {
|
|
1826
|
+
parts.push(`${label} ${change > 0 ? "up" : "down"} to ${formatMoney(a, data.currency ?? null)} from ${formatMoney(b, data.currency ?? null)}`);
|
|
1813
1827
|
} else parts.push(`${label} ${change > 0 ? "up" : "down"} ${formatPercent(Math.abs(change), 0)}`);
|
|
1814
1828
|
};
|
|
1815
1829
|
say("Revenue", data.revenue, previous?.revenue, "money");
|
|
@@ -2191,7 +2205,11 @@ function TypefaceInterestPanel({ data }) {
|
|
|
2191
2205
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: data.interpretationNote }) }),
|
|
2192
2206
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2193
2207
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Heading, { size: 1, style: sectionHeading, children: "Engagement by typeface" }),
|
|
2194
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
2208
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
|
|
2209
|
+
"Sort by any column. \u201CSells vs catalogue\u201D compares each family\u2019s sales-per-view against the catalogue as a whole \u2014 1.0\xD7 is average, and sorting up finds the families that are looked at and do not sell. It is a comparison between your families, not the share of visitors who buy: Google Analytics sees only a fraction of the views, which moves every family together and so cancels out here. With one or two sales a family will still swing a long way, so read it alongside the order counts beside it.",
|
|
2210
|
+
" ",
|
|
2211
|
+
"Bought and Revenue come from your own orders and are exact \u2014 do not scale those up."
|
|
2212
|
+
] }),
|
|
2195
2213
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2196
2214
|
SortableTable,
|
|
2197
2215
|
{
|
|
@@ -2212,28 +2230,34 @@ function TypefaceInterestPanel({ data }) {
|
|
|
2212
2230
|
},
|
|
2213
2231
|
{
|
|
2214
2232
|
key: "viewed",
|
|
2215
|
-
|
|
2233
|
+
// The source is in the HEADER, because the ribbon at the top of this tab
|
|
2234
|
+
// instructs the reader to multiply its figures by about five — and two of
|
|
2235
|
+
// these four columns come from the order book and are exact. Following that
|
|
2236
|
+
// instruction across the whole table multiplies real revenue fivefold. The
|
|
2237
|
+
// sentence that prevents it already exists on the licence block below; it was
|
|
2238
|
+
// simply never applied where the mistake is made.
|
|
2239
|
+
label: "Viewed (GA4)",
|
|
2216
2240
|
numeric: true,
|
|
2217
2241
|
sortValue: (row) => metricSortValue(row.viewed),
|
|
2218
2242
|
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: row.viewed, label: `${row.typeface} viewed`, size: 1 })
|
|
2219
2243
|
},
|
|
2220
2244
|
{
|
|
2221
2245
|
key: "tested",
|
|
2222
|
-
label: "Tested",
|
|
2246
|
+
label: "Tested (GA4)",
|
|
2223
2247
|
numeric: true,
|
|
2224
2248
|
sortValue: (row) => metricSortValue(row.tested),
|
|
2225
2249
|
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: row.tested, label: `${row.typeface} tested`, size: 1 })
|
|
2226
2250
|
},
|
|
2227
2251
|
{
|
|
2228
2252
|
key: "bought",
|
|
2229
|
-
label: "Bought",
|
|
2253
|
+
label: "Bought (orders)",
|
|
2230
2254
|
numeric: true,
|
|
2231
2255
|
sortValue: (row) => metricSortValue(row.bought),
|
|
2232
2256
|
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: row.bought, label: `${row.typeface} bought`, size: 1 })
|
|
2233
2257
|
},
|
|
2234
2258
|
{
|
|
2235
2259
|
key: "revenue",
|
|
2236
|
-
label: "Revenue",
|
|
2260
|
+
label: "Revenue (orders)",
|
|
2237
2261
|
numeric: true,
|
|
2238
2262
|
sortValue: (row) => metricSortValue(row.revenue),
|
|
2239
2263
|
exportValue: (row) => {
|
|
@@ -2946,7 +2970,7 @@ function toHistory(cutover) {
|
|
|
2946
2970
|
return cutover;
|
|
2947
2971
|
}
|
|
2948
2972
|
function overlaps(aStart, aEnd, bStart, bEnd) {
|
|
2949
|
-
return aStart <= bEnd && (aEnd === null || aEnd
|
|
2973
|
+
return aStart <= bEnd && (aEnd === null || aEnd > bStart);
|
|
2950
2974
|
}
|
|
2951
2975
|
function coverageForRange(cutovers, eventName, range) {
|
|
2952
2976
|
if (!(eventName in cutovers)) return { status: "none", reason: "unknown_event" };
|