@liiift-studio/sanity-visitor-insights 0.72.0 → 0.74.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.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +41 -87
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -88
- package/dist/index.mjs.map +1 -1
- package/dist/{ranges-CMhL0Chn.d.mts → ranges-DfjCK0XY.d.mts} +17 -2
- package/dist/{ranges-CMhL0Chn.d.ts → ranges-DfjCK0XY.d.ts} +17 -2
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +25 -14
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +25 -14
- package/dist/server.mjs.map +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{vercel-pqyL1I1o.d.mts → vercel-CNoYeZgN.d.mts} +0 -2
- package/dist/{vercel-pqyL1I1o.d.ts → vercel-CNoYeZgN.d.ts} +0 -2
- package/package.json +1 -1
- package/src/reportData.ts +17 -2
- package/src/server/createHandler.ts +6 -1
- package/src/server/orders.ts +0 -2
- package/src/server/reports/acquisition.ts +2 -1
- package/src/server/reports/measurementHealth.ts +25 -13
- package/src/server/reports/reports.test.ts +114 -2
- package/src/server/reports/typefaceInterest.ts +24 -2
- package/src/studio/CrossSourceTimeline.tsx +15 -40
- package/src/studio/Figure.tsx +50 -43
- package/src/studio/palette.test.ts +3 -1
- package/src/studio/palette.ts +5 -9
- package/src/studio/panels.test.tsx +202 -32
- package/src/studio/panels.tsx +47 -21
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, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-
|
|
4
|
-
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-DfjCK0XY.mjs';
|
|
4
|
+
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-DfjCK0XY.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
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, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-
|
|
4
|
-
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-DfjCK0XY.js';
|
|
4
|
+
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-DfjCK0XY.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
package/dist/index.js
CHANGED
|
@@ -279,10 +279,6 @@ var SERIES = {
|
|
|
279
279
|
/** Revenue, same source, different unit. */
|
|
280
280
|
revenue: "#BF5D9B"
|
|
281
281
|
};
|
|
282
|
-
var REGION_ALPHA = 0.18;
|
|
283
|
-
function seriesFill(key, alpha = REGION_ALPHA) {
|
|
284
|
-
return withAlpha(SERIES[key], alpha);
|
|
285
|
-
}
|
|
286
282
|
function withAlpha(hex, alpha) {
|
|
287
283
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
288
284
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
@@ -304,21 +300,14 @@ var MARKS = {
|
|
|
304
300
|
"chart.coverage": { key: "ga4Sessions", alpha: 1, role: "data", note: "Share one source saw of another" },
|
|
305
301
|
"chart.stem": { key: "orders", alpha: 1, role: "data", note: "One day that had orders" },
|
|
306
302
|
"chart.zeroTick": { key: "orders", alpha: 1, role: "data", note: "A day MEASURED at zero \u2014 the package's founding distinction" },
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
boundary: "chart.regionEdge",
|
|
313
|
-
note: "What the lossier source missed. Light on purpose \u2014 the line it covers must stay readable"
|
|
314
|
-
},
|
|
303
|
+
// Renamed from `chart.regionEdge`. It was the boundary of a filled region; the region is gone —
|
|
304
|
+
// its width was the absolute gap, which under a flat coverage rate is the traffic curve scaled
|
|
305
|
+
// down, so it widened on busy days with no change in the instrument. This is now simply the
|
|
306
|
+
// lossier source's own line, which is what the collapse detector and the spoken summary read.
|
|
307
|
+
"chart.lossy": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The lossier source's own level, dashed beneath the complete one" },
|
|
315
308
|
"chart.ghost": { key: "comparison", alpha: 1, role: "data", note: "The same window last period, behind everything" },
|
|
316
309
|
"chart.grid": { key: "neutral", alpha: 0.18, role: "furniture", note: "Grid rules" },
|
|
317
310
|
"bar.fill": { key: "vercel", alpha: 1, role: "data", note: "Every proportion, comparison and funnel bar" },
|
|
318
|
-
// The hatch on a rung whose count covers fewer days than the rail it sits in. A `data` mark, not
|
|
319
|
-
// furniture: it is the only thing on that rung saying the figure is not comparable, so it has to
|
|
320
|
-
// be as visible as a bar would have been.
|
|
321
|
-
"bar.partial": { key: "vercel", alpha: 1, role: "data", note: "A funnel rung measured over part of the window" },
|
|
322
311
|
"bar.track": { key: "neutral", alpha: 0.12, role: "furniture", note: "The rail a bar sits in" },
|
|
323
312
|
// Neither a source nor money. It was drawn in the REVENUE hue, so a magenta bar between funnel
|
|
324
313
|
// rungs read as takings; GA4's hue was rejected for the opposite reason, since that hue means
|
|
@@ -750,27 +739,14 @@ function FunnelChart({ stages, measurement }) {
|
|
|
750
739
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: stage.label }),
|
|
751
740
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "semibold", children: formatCount(stage.value) })
|
|
752
741
|
] }),
|
|
753
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, height: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
754
|
-
"div",
|
|
755
|
-
{
|
|
756
|
-
style: stage.partial ? {
|
|
757
|
-
height: "100%",
|
|
758
|
-
width: "100%",
|
|
759
|
-
borderRadius: 3,
|
|
760
|
-
backgroundImage: `repeating-linear-gradient(135deg, ${mark("bar.partial")} 0 4px, transparent 4px 8px)`
|
|
761
|
-
} : { ...barFill, width: `${width}%` }
|
|
762
|
-
}
|
|
763
|
-
) }),
|
|
742
|
+
stage.partial ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: partialSlot }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, height: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...barFill, width: `${width}%` } }) }),
|
|
764
743
|
rateLine(stage, index, entry, share, stages[0]?.label ?? "")
|
|
765
744
|
]
|
|
766
745
|
}
|
|
767
746
|
)
|
|
768
747
|
] }, stage.key);
|
|
769
748
|
}) }),
|
|
770
|
-
stages.some((stage) => stage.partial) && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
771
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: funnelKeySwatch }),
|
|
772
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "Striped: tracking started partway through this period, so the step covers fewer days than the ones above it and cannot be compared with them." })
|
|
773
|
-
] }),
|
|
749
|
+
stages.some((stage) => stage.partial) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "Steps with no bar were only tracked for part of this period, so they cover fewer days than the ones above and cannot be drawn against them. Their counts are exact for the days they do cover." }),
|
|
774
750
|
withheldAny && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
775
751
|
"Shares are shown only for steps at least ",
|
|
776
752
|
MIN_RATE_DENOMINATOR,
|
|
@@ -787,15 +763,7 @@ function gapLabel(delta, measurement, entry = 0) {
|
|
|
787
763
|
}
|
|
788
764
|
return `${formatCount(-delta)} more \u2014 not a subset of the step above`;
|
|
789
765
|
}
|
|
790
|
-
var
|
|
791
|
-
var funnelKeySwatch = {
|
|
792
|
-
display: "inline-block",
|
|
793
|
-
width: 24,
|
|
794
|
-
height: 10,
|
|
795
|
-
flex: "0 0 auto",
|
|
796
|
-
borderRadius: 3,
|
|
797
|
-
backgroundImage: `repeating-linear-gradient(135deg, ${mark("bar.partial")} 0 4px, transparent 4px 8px)`
|
|
798
|
-
};
|
|
766
|
+
var partialSlot = { height: 10 };
|
|
799
767
|
var funnelList = { listStyle: "none", margin: 0, padding: 0 };
|
|
800
768
|
var funnelItem = { display: "grid", gap: 4 };
|
|
801
769
|
function funnelGapStyle(hasBar) {
|
|
@@ -1013,11 +981,12 @@ function EstimateDotPlot({
|
|
|
1013
981
|
" of ",
|
|
1014
982
|
formatCount(estimate.actual),
|
|
1015
983
|
wide && " \u2014 too small a sample to lean on",
|
|
984
|
+
estimate.note && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: estimateNote, children: estimate.note }),
|
|
1016
985
|
estimate.rate > 1.05 && " \u2014 over 100%, so GA4 is counting more than the source it is checked against. That is usually a tag firing twice, not extra traffic."
|
|
1017
986
|
] })
|
|
1018
987
|
] }, estimate.basis);
|
|
1019
988
|
}),
|
|
1020
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "The rule marks 100%." })
|
|
1021
990
|
] });
|
|
1022
991
|
}
|
|
1023
992
|
function SurvivalLines({
|
|
@@ -1435,6 +1404,7 @@ var estimateTrack = {
|
|
|
1435
1404
|
borderRadius: 2,
|
|
1436
1405
|
background: mark("bar.track")
|
|
1437
1406
|
};
|
|
1407
|
+
var estimateNote = { display: "block", marginTop: 2, opacity: 0.85 };
|
|
1438
1408
|
var estimateRule = {
|
|
1439
1409
|
position: "absolute",
|
|
1440
1410
|
top: 0,
|
|
@@ -2008,8 +1978,6 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
2008
1978
|
const defined = (p) => p.value !== null;
|
|
2009
1979
|
const lineGen = (0, import_d3_shape.line)().defined(defined).x(at).y((p) => y(p.value)).curve(import_d3_shape.curveLinear);
|
|
2010
1980
|
const shortfallPoints = row.shortfall?.points ?? [];
|
|
2011
|
-
const byDate = new Map(shortfallPoints.map((p) => [p.date, p.value]));
|
|
2012
|
-
const gapGen = (0, import_d3_shape.area)().defined((p) => p.value !== null && byDate.get(p.date) != null).x(at).y0((p) => y(byDate.get(p.date))).y1((p) => y(p.value)).curve(import_d3_shape.curveLinear);
|
|
2013
1981
|
const path = row.mark === "events" ? "" : lineGen(row.points) ?? "";
|
|
2014
1982
|
const ghost = row.comparison && row.comparison.length > 1 ? lineGen(row.comparison.slice(0, dates.length).map((point, i) => ({
|
|
2015
1983
|
date: dates[i] ?? point.date,
|
|
@@ -2019,7 +1987,6 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
2019
1987
|
const stemWidth = Math.max(1, Math.min(9, pitch > 2 ? pitch - 1 : pitch));
|
|
2020
1988
|
const stems = row.mark === "events" ? row.points.filter((p) => p.value !== null && p.value !== 0) : [];
|
|
2021
1989
|
const zeroes = row.mark === "events" && pitch >= 6 ? row.points.filter((p) => p.value === 0) : [];
|
|
2022
|
-
const gap = row.shortfall ? gapGen(row.points) ?? "" : "";
|
|
2023
1990
|
const shortfallLine = row.shortfall ? lineGen(shortfallPoints) ?? "" : "";
|
|
2024
1991
|
const clipId = `${instanceId}-row-${rowIndex}`;
|
|
2025
1992
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
@@ -2044,20 +2011,12 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
2044
2011
|
strokeDasharray: "2 3"
|
|
2045
2012
|
}
|
|
2046
2013
|
),
|
|
2047
|
-
gap && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2048
|
-
"path",
|
|
2049
|
-
{
|
|
2050
|
-
d: gap,
|
|
2051
|
-
fill: shortfallStroke ? seriesFill(colorFor(row.shortfall.source, row.unit, row.shortfall.color), MARKS["chart.region"].alpha) : "currentColor",
|
|
2052
|
-
opacity: shortfallStroke ? 1 : 0.48
|
|
2053
|
-
}
|
|
2054
|
-
),
|
|
2055
2014
|
shortfallLine && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2056
2015
|
"path",
|
|
2057
2016
|
{
|
|
2058
2017
|
d: shortfallLine,
|
|
2059
2018
|
fill: "none",
|
|
2060
|
-
stroke: mark("chart.
|
|
2019
|
+
stroke: mark("chart.lossy"),
|
|
2061
2020
|
strokeWidth: 1.5,
|
|
2062
2021
|
strokeDasharray: "6 4"
|
|
2063
2022
|
}
|
|
@@ -2229,22 +2188,13 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
2229
2188
|
] }, m.label))
|
|
2230
2189
|
] }),
|
|
2231
2190
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: legendRow, children: [
|
|
2232
|
-
series.some((row) => row.shortfall) && /* @__PURE__ */ (0, import_jsx_runtime2.
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
{
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
),
|
|
2240
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2241
|
-
LegendKey,
|
|
2242
|
-
{
|
|
2243
|
-
swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendBlock(seriesFill(shortfallKey(series), MARKS["chart.region"].alpha), shortfallColour(series)) }),
|
|
2244
|
-
label: "Shaded: what your analytics did not see"
|
|
2245
|
-
}
|
|
2246
|
-
)
|
|
2247
|
-
] }),
|
|
2191
|
+
series.some((row) => row.shortfall) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2192
|
+
LegendKey,
|
|
2193
|
+
{
|
|
2194
|
+
swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendDash(shortfallColour(series)) }),
|
|
2195
|
+
label: `${shortfallLabel(series)} \u2014 the dashed line`
|
|
2196
|
+
}
|
|
2197
|
+
) }),
|
|
2248
2198
|
series.some((row) => (row.comparison?.length ?? 0) > 1) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2249
2199
|
LegendKey,
|
|
2250
2200
|
{
|
|
@@ -2281,15 +2231,6 @@ function legendDash(colour) {
|
|
|
2281
2231
|
borderTop: `2px dashed ${colour}`
|
|
2282
2232
|
};
|
|
2283
2233
|
}
|
|
2284
|
-
function legendBlock(fill, edge) {
|
|
2285
|
-
return {
|
|
2286
|
-
display: "inline-block",
|
|
2287
|
-
width: 18,
|
|
2288
|
-
height: 10,
|
|
2289
|
-
background: fill,
|
|
2290
|
-
borderTop: `1.5px dashed ${edge}`
|
|
2291
|
-
};
|
|
2292
|
-
}
|
|
2293
2234
|
var legendMarker = {
|
|
2294
2235
|
display: "inline-block",
|
|
2295
2236
|
position: "relative",
|
|
@@ -2383,9 +2324,6 @@ function metricOr(metric, detail) {
|
|
|
2383
2324
|
return { status: "unavailable", reason: "route_outdated", detail };
|
|
2384
2325
|
}
|
|
2385
2326
|
var OLDER_ROUTE = "This site\u2019s analytics route is older than this figure \u2014 it will appear after the site is next deployed.";
|
|
2386
|
-
function maxOf(metrics) {
|
|
2387
|
-
return metrics.reduce((max, metric) => !metric || metric.status === "unavailable" ? max : Math.max(max, metric.value), 0);
|
|
2388
|
-
}
|
|
2389
2327
|
function finiteOrNull(value) {
|
|
2390
2328
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2391
2329
|
}
|
|
@@ -2438,7 +2376,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2438
2376
|
),
|
|
2439
2377
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.revenue), previous: metricSortValue(previous?.revenue) })
|
|
2440
2378
|
] }),
|
|
2441
|
-
rankLine(data.crossSource, (d) => d.revenue) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: rankLine(data.crossSource, (d) => d.revenue) })
|
|
2379
|
+
revenueCoversEveryOrder(data) && rankLine(data.crossSource, (d) => d.revenue) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: rankLine(data.crossSource, (d) => d.revenue) })
|
|
2442
2380
|
] }) }),
|
|
2443
2381
|
/* @__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.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2444
2382
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Orders" }),
|
|
@@ -2847,7 +2785,6 @@ function Verdict({ data, previous }) {
|
|
|
2847
2785
|
);
|
|
2848
2786
|
}
|
|
2849
2787
|
function DataHealthPanel({ data, diagnostics }) {
|
|
2850
|
-
const pageviewMax = maxOf([data.ga4Pageviews, data.vercelPageviews]);
|
|
2851
2788
|
const contained = isContainment(data.vercelPageviews, data.ga4Pageviews);
|
|
2852
2789
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
|
|
2853
2790
|
/* @__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.jsxs)(import_sanity_ui_compat3.Stack, { space: 2, children: [
|
|
@@ -3160,8 +3097,9 @@ function AcquisitionPanel({ data, previous }) {
|
|
|
3160
3097
|
formatMoney(finiteOrNull(data.actualRevenue) ?? 0, data.currency ?? null),
|
|
3161
3098
|
" from",
|
|
3162
3099
|
" ",
|
|
3163
|
-
finiteOrNull(data.actualOrders) === null ? "your orders" : `${formatCount(data.actualOrders)} orders`,
|
|
3164
|
-
"
|
|
3100
|
+
finiteOrNull(data.actualOrders) === null ? "your orders" : (data.ordersMissingTotal ?? 0) > 0 ? `the ${formatCount(data.actualOrders - data.ordersMissingTotal)} of ${formatCount(data.actualOrders)} orders that carry an amount` : `${formatCount(data.actualOrders)} orders`,
|
|
3101
|
+
" ",
|
|
3102
|
+
"in Sanity, split across the ",
|
|
3165
3103
|
formatCount(data.trackedPurchases ?? 0),
|
|
3166
3104
|
" purchase",
|
|
3167
3105
|
data.trackedPurchases === 1 ? "" : "s",
|
|
@@ -3416,7 +3354,7 @@ function TypefaceInterestPanel({ data }) {
|
|
|
3416
3354
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
|
|
3417
3355
|
"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.",
|
|
3418
3356
|
" ",
|
|
3419
|
-
"Bought and Revenue come from your own orders
|
|
3357
|
+
"Bought and Revenue come from your own orders, not from Google Analytics \u2014 do not scale those up.",
|
|
3420
3358
|
" ",
|
|
3421
3359
|
"A family needs a reasonable number of views before it gets a comparison at all; below that it says so rather than ranking one order against the catalogue."
|
|
3422
3360
|
] }),
|
|
@@ -3464,6 +3402,8 @@ function TypefaceInterestPanel({ data }) {
|
|
|
3464
3402
|
},
|
|
3465
3403
|
{
|
|
3466
3404
|
key: "bought",
|
|
3405
|
+
// Never folded. Zero sales is the finding, not an empty column.
|
|
3406
|
+
alwaysShow: true,
|
|
3467
3407
|
label: "Bought (orders)",
|
|
3468
3408
|
numeric: true,
|
|
3469
3409
|
sortValue: (row) => metricSortValue(row.bought),
|
|
@@ -3471,6 +3411,8 @@ function TypefaceInterestPanel({ data }) {
|
|
|
3471
3411
|
},
|
|
3472
3412
|
{
|
|
3473
3413
|
key: "revenue",
|
|
3414
|
+
// Same reason as Bought: an exact zero from the order book is a measurement.
|
|
3415
|
+
alwaysShow: true,
|
|
3474
3416
|
label: "Revenue (orders)",
|
|
3475
3417
|
numeric: true,
|
|
3476
3418
|
sortValue: (row) => metricSortValue(row.revenue),
|
|
@@ -3564,7 +3506,19 @@ function TypefaceInterestPanel({ data }) {
|
|
|
3564
3506
|
}
|
|
3565
3507
|
)
|
|
3566
3508
|
] }),
|
|
3567
|
-
data.revenueIsApportioned && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "
|
|
3509
|
+
data.revenueIsApportioned && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "An order covering several families is split evenly between them, because the order documents carry no per-line value." }),
|
|
3510
|
+
finiteOrNull(data.unattributedRevenue) !== null && data.unattributedRevenue > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
3511
|
+
formatMoney(data.unattributedRevenue, data.currency ?? null),
|
|
3512
|
+
" of this range\u2019s takings is not in the column above \u2014 those orders name no family in this catalogue."
|
|
3513
|
+
] }),
|
|
3514
|
+
finiteOrNull(data.ordersMissingTotal) !== null && data.ordersMissingTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
3515
|
+
formatCount(data.ordersMissingTotal),
|
|
3516
|
+
" order",
|
|
3517
|
+
data.ordersMissingTotal === 1 ? "" : "s",
|
|
3518
|
+
" in this range carry no amount, so the revenue column does not cover ",
|
|
3519
|
+
data.ordersMissingTotal === 1 ? "it" : "them",
|
|
3520
|
+
"."
|
|
3521
|
+
] })
|
|
3568
3522
|
] });
|
|
3569
3523
|
}
|
|
3570
3524
|
var CHECK_TONE = {
|