@liiift-studio/sanity-visitor-insights 0.70.0 → 0.71.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 +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +92 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -46
- package/dist/index.mjs.map +1 -1
- package/dist/{ranges-CMADS44I.d.mts → ranges-CMhL0Chn.d.mts} +1 -1
- package/dist/{ranges-CMADS44I.d.ts → ranges-CMhL0Chn.d.ts} +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +2 -2
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/server/reports/journey.ts +3 -2
- package/src/server/reports/reports.test.ts +8 -2
- package/src/studio/CrossSourceTimeline.tsx +24 -3
- package/src/studio/Figure.tsx +107 -33
- package/src/studio/palette.test.ts +37 -1
- package/src/studio/palette.ts +21 -10
- package/src/studio/panels.test.tsx +330 -13
- package/src/studio/panels.tsx +63 -24
- package/src/types.ts +6 -2
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-CMhL0Chn.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-CMhL0Chn.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
|
@@ -206,14 +206,21 @@ interface MetricFigureProps {
|
|
|
206
206
|
* suffix inverted the conclusion.
|
|
207
207
|
*
|
|
208
208
|
* 'percent' expects a 0-100 value, matching what measurementHealth produces.
|
|
209
|
+
*
|
|
210
|
+
* 'money' writes the figure in the range's currency. Without it a PARTIAL revenue reached the
|
|
211
|
+
* count formatter, so Darden's default tab — where revenue is permanently partial, because most
|
|
212
|
+
* orders predate the amount field — rendered its headline money as a bare "12,346", beside an
|
|
213
|
+
* "Average order US$455" that did carry a currency.
|
|
209
214
|
*/
|
|
210
|
-
unit?: 'count' | 'percent';
|
|
215
|
+
unit?: 'count' | 'percent' | 'money';
|
|
216
|
+
/** ISO 4217 code. Read only when `unit` is 'money'. */
|
|
217
|
+
currency?: string | null;
|
|
211
218
|
}
|
|
212
219
|
/**
|
|
213
220
|
* Render a metric value, handling the absent case visibly.
|
|
214
221
|
* Screen readers get the reason text rather than an unexplained dash.
|
|
215
222
|
*/
|
|
216
|
-
declare function MetricFigure({ metric, label, size, unit }: MetricFigureProps): React.ReactElement;
|
|
223
|
+
declare function MetricFigure({ metric, label, size, unit, currency }: MetricFigureProps): React.ReactElement;
|
|
217
224
|
/** Props for ChartData. */
|
|
218
225
|
interface ChartDataProps<Row> {
|
|
219
226
|
/** What the disclosure is called. Names the chart it belongs to. */
|
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-CMhL0Chn.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-CMhL0Chn.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
|
@@ -206,14 +206,21 @@ interface MetricFigureProps {
|
|
|
206
206
|
* suffix inverted the conclusion.
|
|
207
207
|
*
|
|
208
208
|
* 'percent' expects a 0-100 value, matching what measurementHealth produces.
|
|
209
|
+
*
|
|
210
|
+
* 'money' writes the figure in the range's currency. Without it a PARTIAL revenue reached the
|
|
211
|
+
* count formatter, so Darden's default tab — where revenue is permanently partial, because most
|
|
212
|
+
* orders predate the amount field — rendered its headline money as a bare "12,346", beside an
|
|
213
|
+
* "Average order US$455" that did carry a currency.
|
|
209
214
|
*/
|
|
210
|
-
unit?: 'count' | 'percent';
|
|
215
|
+
unit?: 'count' | 'percent' | 'money';
|
|
216
|
+
/** ISO 4217 code. Read only when `unit` is 'money'. */
|
|
217
|
+
currency?: string | null;
|
|
211
218
|
}
|
|
212
219
|
/**
|
|
213
220
|
* Render a metric value, handling the absent case visibly.
|
|
214
221
|
* Screen readers get the reason text rather than an unexplained dash.
|
|
215
222
|
*/
|
|
216
|
-
declare function MetricFigure({ metric, label, size, unit }: MetricFigureProps): React.ReactElement;
|
|
223
|
+
declare function MetricFigure({ metric, label, size, unit, currency }: MetricFigureProps): React.ReactElement;
|
|
217
224
|
/** Props for ChartData. */
|
|
218
225
|
interface ChartDataProps<Row> {
|
|
219
226
|
/** What the disclosure is called. Names the chart it belongs to. */
|
package/dist/index.js
CHANGED
|
@@ -295,9 +295,8 @@ var COMPARISON_ON_LIGHT = "#6A4FC4";
|
|
|
295
295
|
var COMPARISON_ON_DARK = "#A594E8";
|
|
296
296
|
var COMPARISON_STYLE = `
|
|
297
297
|
:root { --vi-comparison: ${COMPARISON}; }
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
298
|
+
[data-scheme="light"] { --vi-comparison: ${COMPARISON_ON_LIGHT}; }
|
|
299
|
+
[data-scheme="dark"] { --vi-comparison: ${COMPARISON_ON_DARK}; }
|
|
301
300
|
`.trim();
|
|
302
301
|
var MARKS = {
|
|
303
302
|
"chart.line": { key: "vercel", alpha: 1, role: "data", note: "A source series in the timeline" },
|
|
@@ -316,6 +315,10 @@ var MARKS = {
|
|
|
316
315
|
"chart.ghost": { key: "comparison", alpha: 1, role: "data", note: "The same window last period, behind everything" },
|
|
317
316
|
"chart.grid": { key: "neutral", alpha: 0.18, role: "furniture", note: "Grid rules" },
|
|
318
317
|
"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" },
|
|
319
322
|
"bar.track": { key: "neutral", alpha: 0.12, role: "furniture", note: "The rail a bar sits in" },
|
|
320
323
|
// Neither a source nor money. It was drawn in the REVENUE hue, so a magenta bar between funnel
|
|
321
324
|
// rungs read as takings; GA4's hue was rejected for the opposite reason, since that hue means
|
|
@@ -366,7 +369,7 @@ function partial(value, coveredFrom, note) {
|
|
|
366
369
|
return { status: "partial", value, coveredFrom, note };
|
|
367
370
|
}
|
|
368
371
|
function valueOrNull(metric) {
|
|
369
|
-
return metric.status === "unavailable" ? null : metric.value;
|
|
372
|
+
return !metric || metric.status === "unavailable" ? null : metric.value;
|
|
370
373
|
}
|
|
371
374
|
var REPORT_NAMES = [
|
|
372
375
|
"measurement-health",
|
|
@@ -530,7 +533,10 @@ var hoverLabel = { opacity: 0.75, whiteSpace: "nowrap", overflow: "hidden", text
|
|
|
530
533
|
var hoverValue = { marginLeft: "auto", fontVariantNumeric: "tabular-nums", fontWeight: 500, whiteSpace: "nowrap" };
|
|
531
534
|
var hoverSeen = { opacity: 0.6, fontWeight: 400 };
|
|
532
535
|
var hoverEvent = { fontSize: 11, opacity: 0.8, borderTop: "1px solid var(--card-border-color, rgba(128,128,128,0.25))", paddingTop: 4, marginTop: 2 };
|
|
533
|
-
function
|
|
536
|
+
function partialBadge(metric) {
|
|
537
|
+
return metric.coveredFrom ? `From ${formatDay(metric.coveredFrom)} only` : "Some orders only";
|
|
538
|
+
}
|
|
539
|
+
function MetricFigure({ metric, label, size = 4, unit = "count", currency = null }) {
|
|
534
540
|
if (metric.status === "unavailable") {
|
|
535
541
|
const reason = REASON_TEXT[metric.reason] ?? "No figure available";
|
|
536
542
|
const detail = metric.detail ? `${reason}. ${metric.detail}` : reason;
|
|
@@ -557,7 +563,7 @@ function MetricFigure({ metric, label, size = 4, unit = "count" }) {
|
|
|
557
563
|
] }) })
|
|
558
564
|
);
|
|
559
565
|
}
|
|
560
|
-
const formatted = unit === "percent" ? `${metric.value.toFixed(1)}%` : formatCount(metric.value);
|
|
566
|
+
const formatted = unit === "percent" ? `${metric.value.toFixed(1)}%` : unit === "money" ? formatMoney(metric.value, currency) : formatCount(metric.value);
|
|
561
567
|
if (metric.status === "partial") {
|
|
562
568
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
563
569
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size, children: [
|
|
@@ -567,11 +573,11 @@ function MetricFigure({ metric, label, size = 4, unit = "count" }) {
|
|
|
567
573
|
metric.note
|
|
568
574
|
] })
|
|
569
575
|
] }),
|
|
570
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children:
|
|
576
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: partialBadge(metric) })
|
|
571
577
|
] });
|
|
572
578
|
}
|
|
573
579
|
if (metric.status === "estimated") {
|
|
574
|
-
const range = unit === "percent" ? `${metric.low.toFixed(1)}% to ${metric.high.toFixed(1)}%` : `${formatCount(metric.low)} to ${formatCount(metric.high)}`;
|
|
580
|
+
const range = unit === "percent" ? `${metric.low.toFixed(1)}% to ${metric.high.toFixed(1)}%` : unit === "money" ? `${formatMoney(metric.low, currency)} to ${formatMoney(metric.high, currency)}` : `${formatCount(metric.low)} to ${formatCount(metric.high)}`;
|
|
575
581
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
576
582
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size, children: [
|
|
577
583
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "~" }),
|
|
@@ -671,9 +677,9 @@ function rateLine(stage, index, entry, share, entryLabel) {
|
|
|
671
677
|
if (index === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "100% \u2014 everyone who arrived" });
|
|
672
678
|
if (stage.partial) {
|
|
673
679
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
674
|
-
"
|
|
680
|
+
"tracking added ",
|
|
675
681
|
stage.partial.from,
|
|
676
|
-
", so no share is shown"
|
|
682
|
+
", so this step covers fewer days than the ones above \u2014 no share is shown"
|
|
677
683
|
] });
|
|
678
684
|
}
|
|
679
685
|
const canRate = entry >= MIN_RATE_DENOMINATOR && stage.value >= MIN_RATE_DENOMINATOR;
|
|
@@ -747,24 +753,24 @@ function FunnelChart({ stages, measurement }) {
|
|
|
747
753
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, height: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
748
754
|
"div",
|
|
749
755
|
{
|
|
750
|
-
style: {
|
|
751
|
-
|
|
752
|
-
width:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
outline: `1px solid ${mark("bar.fill")}`,
|
|
757
|
-
outlineOffset: -1
|
|
758
|
-
} : {}
|
|
759
|
-
}
|
|
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}%` }
|
|
760
762
|
}
|
|
761
763
|
) }),
|
|
762
|
-
|
|
764
|
+
rateLine(stage, index, entry, share, stages[0]?.label ?? "")
|
|
763
765
|
]
|
|
764
766
|
}
|
|
765
767
|
)
|
|
766
768
|
] }, stage.key);
|
|
767
769
|
}) }),
|
|
770
|
+
stages.some((stage) => stage.partial) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: funnelKey, children: [
|
|
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
|
+
] }),
|
|
768
774
|
withheldAny && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
769
775
|
"Shares are shown only for steps at least ",
|
|
770
776
|
MIN_RATE_DENOMINATOR,
|
|
@@ -781,6 +787,15 @@ function gapLabel(delta, measurement, entry = 0) {
|
|
|
781
787
|
}
|
|
782
788
|
return `${formatCount(-delta)} more \u2014 not a subset of the step above`;
|
|
783
789
|
}
|
|
790
|
+
var funnelKey = { display: "flex", alignItems: "center", gap: 8, paddingTop: 4 };
|
|
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
|
+
};
|
|
784
799
|
var funnelList = { listStyle: "none", margin: 0, padding: 0 };
|
|
785
800
|
var funnelItem = { display: "grid", gap: 4 };
|
|
786
801
|
function funnelGapStyle(hasBar) {
|
|
@@ -1008,6 +1023,7 @@ function EstimateDotPlot({
|
|
|
1008
1023
|
function SurvivalLines({
|
|
1009
1024
|
segments,
|
|
1010
1025
|
stepLabels,
|
|
1026
|
+
partialSteps = [],
|
|
1011
1027
|
minDenominator = MIN_RATE_DENOMINATOR
|
|
1012
1028
|
}) {
|
|
1013
1029
|
const [active, setActive] = import_react2.default.useState(null);
|
|
@@ -1026,6 +1042,7 @@ function SurvivalLines({
|
|
|
1026
1042
|
const previous = i === 0 ? entry : segment.values[i - 1];
|
|
1027
1043
|
if (value === null || value === void 0) break;
|
|
1028
1044
|
if (i > 0 && (previous === null || previous === void 0 || previous < minDenominator)) break;
|
|
1045
|
+
if (partialSteps.includes(i)) break;
|
|
1029
1046
|
const share = Math.min(1, value / entry);
|
|
1030
1047
|
points.push([x(i), height - share * height]);
|
|
1031
1048
|
shares.push(share);
|
|
@@ -1068,7 +1085,10 @@ function SurvivalLines({
|
|
|
1068
1085
|
width: width / (stepLabels.length - 1) / (i === 0 || i === stepLabels.length - 1 ? 2 : 1),
|
|
1069
1086
|
height,
|
|
1070
1087
|
fill: "transparent",
|
|
1071
|
-
onMouseEnter: () =>
|
|
1088
|
+
onMouseEnter: () => {
|
|
1089
|
+
setSteppedByKey(false);
|
|
1090
|
+
setActive(i);
|
|
1091
|
+
},
|
|
1072
1092
|
onMouseLeave: () => setActive(null)
|
|
1073
1093
|
},
|
|
1074
1094
|
label
|
|
@@ -1123,7 +1143,7 @@ function SurvivalLines({
|
|
|
1123
1143
|
{
|
|
1124
1144
|
type: "button",
|
|
1125
1145
|
style: survivalTick(i === active, i, stepLabels.length),
|
|
1126
|
-
"aria-
|
|
1146
|
+
"aria-label": `Read ${label}`,
|
|
1127
1147
|
onMouseEnter: () => {
|
|
1128
1148
|
setSteppedByKey(false);
|
|
1129
1149
|
setActive(i);
|
|
@@ -2235,7 +2255,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
2235
2255
|
markers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2236
2256
|
LegendKey,
|
|
2237
2257
|
{
|
|
2238
|
-
swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendMarker }),
|
|
2258
|
+
swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendMarker, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendMarkerDot }) }),
|
|
2239
2259
|
label: "Dashed rules with a dot mark campaign sends"
|
|
2240
2260
|
}
|
|
2241
2261
|
),
|
|
@@ -2273,11 +2293,23 @@ function legendBlock(fill, edge) {
|
|
|
2273
2293
|
var legendMarker = {
|
|
2274
2294
|
display: "inline-block",
|
|
2275
2295
|
position: "relative",
|
|
2276
|
-
width:
|
|
2296
|
+
width: 1,
|
|
2277
2297
|
height: 12,
|
|
2278
2298
|
borderLeft: "1px dashed currentColor",
|
|
2279
|
-
|
|
2280
|
-
|
|
2299
|
+
// The rule's OWN weight. The chart draws the line at 0.5 and only the dot at 0.7, so putting 0.7
|
|
2300
|
+
// on the whole element made the legend's rule heavier than the rule it names — the same class of
|
|
2301
|
+
// mismatch this legend work exists to remove.
|
|
2302
|
+
opacity: 0.5
|
|
2303
|
+
};
|
|
2304
|
+
var legendMarkerDot = {
|
|
2305
|
+
position: "absolute",
|
|
2306
|
+
top: -3,
|
|
2307
|
+
left: -2.5,
|
|
2308
|
+
width: 6,
|
|
2309
|
+
height: 6,
|
|
2310
|
+
borderRadius: "50%",
|
|
2311
|
+
background: "currentColor",
|
|
2312
|
+
opacity: 0.7
|
|
2281
2313
|
};
|
|
2282
2314
|
function shortfallKey(series) {
|
|
2283
2315
|
const row = series.find((r) => r.shortfall);
|
|
@@ -2352,7 +2384,7 @@ function metricOr(metric, detail) {
|
|
|
2352
2384
|
}
|
|
2353
2385
|
var OLDER_ROUTE = "This site\u2019s analytics route is older than this figure \u2014 it will appear after the site is next deployed.";
|
|
2354
2386
|
function maxOf(metrics) {
|
|
2355
|
-
return metrics.reduce((max, metric) => metric.status === "unavailable" ? max : Math.max(max, metric.value), 0);
|
|
2387
|
+
return metrics.reduce((max, metric) => !metric || metric.status === "unavailable" ? max : Math.max(max, metric.value), 0);
|
|
2356
2388
|
}
|
|
2357
2389
|
function finiteOrNull(value) {
|
|
2358
2390
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
@@ -2395,7 +2427,15 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2395
2427
|
/* @__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: [
|
|
2396
2428
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Revenue" }),
|
|
2397
2429
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
2398
|
-
|
|
2430
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2431
|
+
MetricFigure,
|
|
2432
|
+
{
|
|
2433
|
+
metric: metricOr(data.revenue, OLDER_ROUTE),
|
|
2434
|
+
label: "Revenue",
|
|
2435
|
+
unit: "money",
|
|
2436
|
+
currency: data.currency ?? null
|
|
2437
|
+
}
|
|
2438
|
+
),
|
|
2399
2439
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.revenue), previous: metricSortValue(previous?.revenue) })
|
|
2400
2440
|
] }),
|
|
2401
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) })
|
|
@@ -2403,7 +2443,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2403
2443
|
/* @__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: [
|
|
2404
2444
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Orders" }),
|
|
2405
2445
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
2406
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.orders, label: "Orders" }),
|
|
2446
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.orders, OLDER_ROUTE), label: "Orders" }),
|
|
2407
2447
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.orders), previous: metricSortValue(previous?.orders) })
|
|
2408
2448
|
] }),
|
|
2409
2449
|
rankLine(data.crossSource, (d) => d.orders) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: rankLine(data.crossSource, (d) => d.orders) })
|
|
@@ -2414,7 +2454,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2414
2454
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.vercelPageviews, OLDER_ROUTE), label: "Pageviews" }),
|
|
2415
2455
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.vercelPageviews), previous: metricSortValue(previous?.vercelPageviews) })
|
|
2416
2456
|
] }),
|
|
2417
|
-
data.vercelPageviews
|
|
2457
|
+
data.vercelPageviews && data.vercelPageviews.status !== "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Pageviews, from Vercel\u2019s own counter." })
|
|
2418
2458
|
] }) }),
|
|
2419
2459
|
/* @__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: [
|
|
2420
2460
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Mailing list, total" }),
|
|
@@ -2433,7 +2473,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2433
2473
|
] });
|
|
2434
2474
|
})()
|
|
2435
2475
|
] }),
|
|
2436
|
-
data.audience
|
|
2476
|
+
data.audience && data.audience.status !== "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Everyone subscribed today, not just this period. Mailchimp\u2019s own count, so it is not consent-gated or blockable." })
|
|
2437
2477
|
] }) })
|
|
2438
2478
|
] }),
|
|
2439
2479
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: cardGrid, children: [
|
|
@@ -2452,7 +2492,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2452
2492
|
}
|
|
2453
2493
|
),
|
|
2454
2494
|
averageOrderValue(data).status === "partial" && averageOrderValue(data).note && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: averageOrderValue(data).note }),
|
|
2455
|
-
averageOrderValue(data).status
|
|
2495
|
+
averageOrderValue(data).status === "ok" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "From your orders, so exact." })
|
|
2456
2496
|
] }) }),
|
|
2457
2497
|
/* @__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: [
|
|
2458
2498
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Visitors per order" }),
|
|
@@ -2468,7 +2508,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
2468
2508
|
unavailable: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: visitorsPerOrder(data), label: "Visitors per order" })
|
|
2469
2509
|
}
|
|
2470
2510
|
),
|
|
2471
|
-
data.vercelVisitors
|
|
2511
|
+
data.vercelVisitors && data.vercelVisitors.status !== "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Vercel\u2019s visitor count. A ceiling \u2014 some are bots or the same person twice." })
|
|
2472
2512
|
] }) })
|
|
2473
2513
|
] }),
|
|
2474
2514
|
(data.crossSource?.length ?? 0) >= 3 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
@@ -2783,8 +2823,7 @@ function Verdict({ data, previous }) {
|
|
|
2783
2823
|
const shortfall = data.shortfallRatio;
|
|
2784
2824
|
const seeing = shortfall !== null && shortfall !== void 0 ? `GA4 is seeing ${formatPercent(1 - shortfall, 0)} of your traffic` : null;
|
|
2785
2825
|
const broken = shortfall === null || shortfall === void 0 ? data.capture?.discrepancy ? "measurement disagrees between sources" : null : shortfall > 0.6 ? `${seeing} \u2014 treat its figures as broken` : shortfall > 0.35 ? `${seeing} \u2014 its figures are undercounts, not what happened` : data.capture?.discrepancy ? "measurement disagrees between sources" : null;
|
|
2786
|
-
if (
|
|
2787
|
-
if (parts.length === 0) {
|
|
2826
|
+
if (parts.length === 0 && !broken) {
|
|
2788
2827
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, style: { borderLeftWidth: 3, borderLeftStyle: "solid" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: "No figures arrived for this window." }) });
|
|
2789
2828
|
}
|
|
2790
2829
|
const noticeable = shortfall !== null && shortfall !== void 0 && shortfall > 0.2;
|
|
@@ -2797,9 +2836,12 @@ function Verdict({ data, previous }) {
|
|
|
2797
2836
|
tone: broken ? "caution" : "transparent",
|
|
2798
2837
|
border: true,
|
|
2799
2838
|
style: broken ? { borderLeftWidth: 3, borderLeftStyle: "solid" } : void 0,
|
|
2800
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.
|
|
2801
|
-
parts.
|
|
2802
|
-
|
|
2839
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 2, children: [
|
|
2840
|
+
parts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 3, weight: broken ? "semibold" : "medium", children: [
|
|
2841
|
+
parts.join(" \xB7 "),
|
|
2842
|
+
closing
|
|
2843
|
+
] }),
|
|
2844
|
+
broken && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: broken })
|
|
2803
2845
|
] })
|
|
2804
2846
|
}
|
|
2805
2847
|
);
|
|
@@ -2854,11 +2896,11 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2854
2896
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2855
2897
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
2856
2898
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Pageviews Vercel counted" }),
|
|
2857
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.vercelPageviews, label: "Vercel pageviews", size: 3 })
|
|
2899
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.vercelPageviews, OLDER_ROUTE), label: "Vercel pageviews", size: 3 })
|
|
2858
2900
|
] }),
|
|
2859
2901
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
2860
2902
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Seen by Google Analytics" }),
|
|
2861
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.ga4Pageviews, label: "GA4 pageviews", size: 3 })
|
|
2903
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.ga4Pageviews, OLDER_ROUTE), label: "GA4 pageviews", size: 3 })
|
|
2862
2904
|
] }),
|
|
2863
2905
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Only one source answered for this range, so there is nothing to compare." })
|
|
2864
2906
|
] })
|
|
@@ -2895,7 +2937,7 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2895
2937
|
] }) }),
|
|
2896
2938
|
data.estimatedSessions && data.estimatedSessions.status === "estimated" && /* @__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: [
|
|
2897
2939
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Sessions, corrected for what GA4 misses" }),
|
|
2898
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.estimatedSessions, label: "Estimated sessions" })
|
|
2940
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.estimatedSessions, OLDER_ROUTE), label: "Estimated sessions" })
|
|
2899
2941
|
] }) })
|
|
2900
2942
|
] }),
|
|
2901
2943
|
Object.keys(data.orderStatuses ?? {}).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
@@ -2929,11 +2971,11 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2929
2971
|
] }) }),
|
|
2930
2972
|
/* @__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: [
|
|
2931
2973
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "GA4 sessions" }),
|
|
2932
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.ga4Sessions, label: "GA4 sessions" })
|
|
2974
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.ga4Sessions, OLDER_ROUTE), label: "GA4 sessions" })
|
|
2933
2975
|
] }) }),
|
|
2934
2976
|
/* @__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: [
|
|
2935
2977
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Consent granted" }),
|
|
2936
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.consentRate, label: "Consent granted, share of visitors GA4 saw", unit: "percent" })
|
|
2978
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.consentRate, OLDER_ROUTE), label: "Consent granted, share of visitors GA4 saw", unit: "percent" })
|
|
2937
2979
|
] }) })
|
|
2938
2980
|
] })
|
|
2939
2981
|
}
|
|
@@ -3187,6 +3229,10 @@ function JourneyPanel({ data }) {
|
|
|
3187
3229
|
...step.count.status === "partial" && step.count.coveredFrom ? { partial: { from: formatDay(step.count.coveredFrom) } } : {}
|
|
3188
3230
|
}]
|
|
3189
3231
|
);
|
|
3232
|
+
const wholeSpine = spine.filter((step) => {
|
|
3233
|
+
const cell = allSteps.find((s) => s.key === step.key);
|
|
3234
|
+
return cell?.count.status !== "partial";
|
|
3235
|
+
});
|
|
3190
3236
|
const tracked = data.measurement === "sequence";
|
|
3191
3237
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
|
|
3192
3238
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: tracked ? "transparent" : "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 2, children: [
|
|
@@ -3199,12 +3245,12 @@ function JourneyPanel({ data }) {
|
|
|
3199
3245
|
segments: segments.map((segment) => ({
|
|
3200
3246
|
key: segment.key,
|
|
3201
3247
|
label: segment.label,
|
|
3202
|
-
values:
|
|
3248
|
+
values: wholeSpine.map((step) => {
|
|
3203
3249
|
const cell = segment.steps.find((s) => s.key === step.key);
|
|
3204
3250
|
return cell ? metricSortValue(cell.count) : null;
|
|
3205
3251
|
})
|
|
3206
3252
|
})),
|
|
3207
|
-
stepLabels:
|
|
3253
|
+
stepLabels: wholeSpine.map((step) => step.label)
|
|
3208
3254
|
}
|
|
3209
3255
|
),
|
|
3210
3256
|
segments.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|