@liiift-studio/sanity-visitor-insights 0.65.0 → 0.67.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 +1 -25
- package/dist/index.d.ts +1 -25
- package/dist/index.js +203 -96
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/studio/Figure.tsx +206 -54
- package/src/studio/palette.ts +5 -0
- package/src/studio/panels.test.tsx +165 -5
- package/src/studio/panels.tsx +193 -18
package/dist/index.d.mts
CHANGED
|
@@ -214,30 +214,6 @@ interface MetricFigureProps {
|
|
|
214
214
|
* Screen readers get the reason text rather than an unexplained dash.
|
|
215
215
|
*/
|
|
216
216
|
declare function MetricFigure({ metric, label, size, unit }: MetricFigureProps): React.ReactElement;
|
|
217
|
-
/** Props for ComparisonBar. */
|
|
218
|
-
interface ComparisonBarProps {
|
|
219
|
-
label: string;
|
|
220
|
-
metric: MetricValue;
|
|
221
|
-
/** Largest value across the sibling bars, used to scale width. */
|
|
222
|
-
max: number;
|
|
223
|
-
/**
|
|
224
|
-
* What full width means, named.
|
|
225
|
-
*
|
|
226
|
-
* A ComparisonBar scales to the largest sibling, so a full bar means only "biggest of these" —
|
|
227
|
-
* and unlike the other two bar idioms, which print their share and their total, this one said
|
|
228
|
-
* nothing at all. There was a `tone` prop instead, which both call sites passed different
|
|
229
|
-
* values to and which the component destructured and never used.
|
|
230
|
-
*/
|
|
231
|
-
outOf?: string;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* A horizontal bar with its value printed alongside.
|
|
235
|
-
*
|
|
236
|
-
* Deliberately CSS rather than a charting library: these panels compare and rank a handful of
|
|
237
|
-
* values, which a labelled bar does as well as a chart while avoiding a large dependency in the
|
|
238
|
-
* Studio bundle and the theme-token bridging that a chart library would need for light and dark.
|
|
239
|
-
*/
|
|
240
|
-
declare function ComparisonBar({ label, metric, max, outOf }: ComparisonBarProps): React.ReactElement;
|
|
241
217
|
/** Props for ChartData. */
|
|
242
218
|
interface ChartDataProps<Row> {
|
|
243
219
|
/** What the disclosure is called. Names the chart it belongs to. */
|
|
@@ -655,4 +631,4 @@ interface VisitorInsightsPluginOptions {
|
|
|
655
631
|
*/
|
|
656
632
|
declare const visitorInsights: sanity.Plugin<VisitorInsightsPluginOptions>;
|
|
657
633
|
|
|
658
|
-
export { AcquisitionData, AcquisitionPanel, ChartData, type ChartDataProps,
|
|
634
|
+
export { AcquisitionData, AcquisitionPanel, ChartData, type ChartDataProps, CrossSourceTimeline, type CrossSourceTimelineProps, DataHealthPanel, Delta, type DeltaProps, DiagnosticReport, DiagnosticsPanel, FunnelChart, JourneyData, JourneyPanel, MeasurementHealthData, DataHealthPanel as MeasurementHealthPanel, MetricFigure, MetricValue, NoticeList, OverviewPanel, type ProportionBar, ProportionChart, type ProportionChartProps, RangeKey, ReportEnvelope, ReportName, type ReportState, type Series, type SeriesPoint, type SortColumn, SortableTable, type SortableTableProps, type TimelineMarker, TypefaceInterestData, TypefaceInterestPanel, type UseReportOptions, type ViewState, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, decodeView, visitorInsights as default, encodeView, formatCount, formatMoney, formatPercent, knownShortfall, mergeIntoHash, useReport, visitorInsights };
|
package/dist/index.d.ts
CHANGED
|
@@ -214,30 +214,6 @@ interface MetricFigureProps {
|
|
|
214
214
|
* Screen readers get the reason text rather than an unexplained dash.
|
|
215
215
|
*/
|
|
216
216
|
declare function MetricFigure({ metric, label, size, unit }: MetricFigureProps): React.ReactElement;
|
|
217
|
-
/** Props for ComparisonBar. */
|
|
218
|
-
interface ComparisonBarProps {
|
|
219
|
-
label: string;
|
|
220
|
-
metric: MetricValue;
|
|
221
|
-
/** Largest value across the sibling bars, used to scale width. */
|
|
222
|
-
max: number;
|
|
223
|
-
/**
|
|
224
|
-
* What full width means, named.
|
|
225
|
-
*
|
|
226
|
-
* A ComparisonBar scales to the largest sibling, so a full bar means only "biggest of these" —
|
|
227
|
-
* and unlike the other two bar idioms, which print their share and their total, this one said
|
|
228
|
-
* nothing at all. There was a `tone` prop instead, which both call sites passed different
|
|
229
|
-
* values to and which the component destructured and never used.
|
|
230
|
-
*/
|
|
231
|
-
outOf?: string;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* A horizontal bar with its value printed alongside.
|
|
235
|
-
*
|
|
236
|
-
* Deliberately CSS rather than a charting library: these panels compare and rank a handful of
|
|
237
|
-
* values, which a labelled bar does as well as a chart while avoiding a large dependency in the
|
|
238
|
-
* Studio bundle and the theme-token bridging that a chart library would need for light and dark.
|
|
239
|
-
*/
|
|
240
|
-
declare function ComparisonBar({ label, metric, max, outOf }: ComparisonBarProps): React.ReactElement;
|
|
241
217
|
/** Props for ChartData. */
|
|
242
218
|
interface ChartDataProps<Row> {
|
|
243
219
|
/** What the disclosure is called. Names the chart it belongs to. */
|
|
@@ -655,4 +631,4 @@ interface VisitorInsightsPluginOptions {
|
|
|
655
631
|
*/
|
|
656
632
|
declare const visitorInsights: sanity.Plugin<VisitorInsightsPluginOptions>;
|
|
657
633
|
|
|
658
|
-
export { AcquisitionData, AcquisitionPanel, ChartData, type ChartDataProps,
|
|
634
|
+
export { AcquisitionData, AcquisitionPanel, ChartData, type ChartDataProps, CrossSourceTimeline, type CrossSourceTimelineProps, DataHealthPanel, Delta, type DeltaProps, DiagnosticReport, DiagnosticsPanel, FunnelChart, JourneyData, JourneyPanel, MeasurementHealthData, DataHealthPanel as MeasurementHealthPanel, MetricFigure, MetricValue, NoticeList, OverviewPanel, type ProportionBar, ProportionChart, type ProportionChartProps, RangeKey, ReportEnvelope, ReportName, type ReportState, type Series, type SeriesPoint, type SortColumn, SortableTable, type SortableTableProps, type TimelineMarker, TypefaceInterestData, TypefaceInterestPanel, type UseReportOptions, type ViewState, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, decodeView, visitorInsights as default, encodeView, formatCount, formatMoney, formatPercent, knownShortfall, mergeIntoHash, useReport, visitorInsights };
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,6 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AcquisitionPanel: () => AcquisitionPanel,
|
|
34
34
|
ChartData: () => ChartData,
|
|
35
|
-
ComparisonBar: () => ComparisonBar,
|
|
36
35
|
CrossSourceTimeline: () => CrossSourceTimeline,
|
|
37
36
|
DataHealthPanel: () => DataHealthPanel,
|
|
38
37
|
Delta: () => Delta,
|
|
@@ -310,7 +309,12 @@ var MARKS = {
|
|
|
310
309
|
"bar.seen": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The share of a complete count that the lossy source saw" },
|
|
311
310
|
"estimate.dot": { key: "ga4Pageviews", alpha: 1, role: "data", note: "One independent estimate of how much GA4 sees" },
|
|
312
311
|
"estimate.interval": { key: "ga4Pageviews", alpha: 0.35, role: "fill", boundary: "estimate.dot", note: "How wide the sample leaves that estimate \u2014 the dot is its own boundary" },
|
|
313
|
-
"estimate.rule": { key: "neutral", alpha: 0.45, role: "furniture", note: "The 100% reference a capture rate is read against" }
|
|
312
|
+
"estimate.rule": { key: "neutral", alpha: 0.45, role: "furniture", note: "The 100% reference a capture rate is read against" },
|
|
313
|
+
"shift.now": { key: "vercel", alpha: 1, role: "data", note: "A channel this period" },
|
|
314
|
+
"shift.before": { key: "neutral", alpha: 0.55, role: "furniture", note: "The same channel last period \u2014 context, not a second answer" },
|
|
315
|
+
"shift.link": { key: "neutral", alpha: 0.35, role: "furniture", note: "The distance between the two, which is the finding" },
|
|
316
|
+
"survival.line": { key: "vercel", alpha: 1, role: "data", note: "One audience segment falling through a funnel" },
|
|
317
|
+
"survival.grid": { key: "neutral", alpha: 0.18, role: "furniture", note: "The 50% and 100% references a survival line is read against" }
|
|
314
318
|
};
|
|
315
319
|
function mark(name) {
|
|
316
320
|
const m = MARKS[name];
|
|
@@ -496,30 +500,6 @@ function MetricFigure({ metric, label, size = 4, unit = "count" }) {
|
|
|
496
500
|
}
|
|
497
501
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size, children: formatted });
|
|
498
502
|
}
|
|
499
|
-
function ComparisonBar({ label, metric, max, outOf }) {
|
|
500
|
-
const value = metric.status === "unavailable" ? null : metric.value;
|
|
501
|
-
const width = value !== null && max > 0 ? Math.max(2, value / max * 100) : 0;
|
|
502
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
503
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
504
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: label }),
|
|
505
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricFigure, { metric, label, size: 1 })
|
|
506
|
-
] }),
|
|
507
|
-
value === null ? (
|
|
508
|
-
// A dashed rail, not a zero-width bar: absence must not read as a measured zero.
|
|
509
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
510
|
-
import_sanity_ui_compat.Card,
|
|
511
|
-
{
|
|
512
|
-
"aria-hidden": "true",
|
|
513
|
-
radius: 2,
|
|
514
|
-
tone: "transparent",
|
|
515
|
-
border: true,
|
|
516
|
-
style: { height: 8, borderStyle: "dashed" }
|
|
517
|
-
}
|
|
518
|
-
)
|
|
519
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: barTrack, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...barFill, width: `${width}%` } }) }),
|
|
520
|
-
outOf && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: outOf })
|
|
521
|
-
] });
|
|
522
|
-
}
|
|
523
503
|
function ChartData({ label, rows, columns, rowKey, exportName, note, filterOn }) {
|
|
524
504
|
if (rows.length === 0) return null;
|
|
525
505
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { style: disclosureBlock, children: [
|
|
@@ -893,6 +873,73 @@ function EstimateDotPlot({
|
|
|
893
873
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "Each is a separate way of asking the same question. Where they disagree, the spread is the answer\u2019s uncertainty \u2014 the rule marks 100%." })
|
|
894
874
|
] });
|
|
895
875
|
}
|
|
876
|
+
function SurvivalLines({
|
|
877
|
+
segments,
|
|
878
|
+
stepLabels,
|
|
879
|
+
minDenominator = MIN_RATE_DENOMINATOR
|
|
880
|
+
}) {
|
|
881
|
+
if (segments.length < 2 || stepLabels.length < 2) return null;
|
|
882
|
+
const width = 100;
|
|
883
|
+
const height = 46;
|
|
884
|
+
const x = (i) => i / (stepLabels.length - 1) * width;
|
|
885
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 3, children: [
|
|
886
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
887
|
+
"svg",
|
|
888
|
+
{
|
|
889
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
890
|
+
preserveAspectRatio: "none",
|
|
891
|
+
style: { width: "100%", height: 150, overflow: "visible" },
|
|
892
|
+
role: "img",
|
|
893
|
+
"aria-label": `Share of each segment surviving to each step, ${stepLabels.join(", then ")}`,
|
|
894
|
+
children: [
|
|
895
|
+
[0, 0.5].map((level) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
896
|
+
"line",
|
|
897
|
+
{
|
|
898
|
+
x1: 0,
|
|
899
|
+
x2: width,
|
|
900
|
+
y1: height * level,
|
|
901
|
+
y2: height * level,
|
|
902
|
+
stroke: mark("survival.grid"),
|
|
903
|
+
strokeWidth: 0.4
|
|
904
|
+
},
|
|
905
|
+
level
|
|
906
|
+
)),
|
|
907
|
+
segments.map((segment, index) => {
|
|
908
|
+
const entry = segment.values[0];
|
|
909
|
+
if (entry === null || entry === void 0 || entry <= 0) return null;
|
|
910
|
+
const points = [];
|
|
911
|
+
for (let i = 0; i < segment.values.length && i < stepLabels.length; i++) {
|
|
912
|
+
const value = segment.values[i];
|
|
913
|
+
const previous = i === 0 ? entry : segment.values[i - 1];
|
|
914
|
+
if (value === null || value === void 0) break;
|
|
915
|
+
if (i > 0 && (previous === null || previous === void 0 || previous < minDenominator)) break;
|
|
916
|
+
points.push([x(i), height - Math.min(1, value / entry) * height]);
|
|
917
|
+
}
|
|
918
|
+
if (points.length < 2) return null;
|
|
919
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
920
|
+
"polyline",
|
|
921
|
+
{
|
|
922
|
+
points: points.map(([px, py]) => `${px},${py}`).join(" "),
|
|
923
|
+
fill: "none",
|
|
924
|
+
stroke: mark("survival.line"),
|
|
925
|
+
strokeWidth: 0.8,
|
|
926
|
+
vectorEffect: "non-scaling-stroke",
|
|
927
|
+
strokeDasharray: index === 0 ? void 0 : index === 1 ? "4 3" : "1 2",
|
|
928
|
+
opacity: index === 0 ? 1 : 0.75
|
|
929
|
+
},
|
|
930
|
+
segment.key
|
|
931
|
+
);
|
|
932
|
+
})
|
|
933
|
+
]
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: survivalLegend, children: segments.map((segment, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
937
|
+
index === 0 ? "\u2014\u2014" : index === 1 ? "\u2013 \u2013" : "\xB7 \xB7",
|
|
938
|
+
" ",
|
|
939
|
+
segment.label
|
|
940
|
+
] }, segment.key)) })
|
|
941
|
+
] });
|
|
942
|
+
}
|
|
896
943
|
function SortableTable({
|
|
897
944
|
caption,
|
|
898
945
|
columns,
|
|
@@ -1139,6 +1186,38 @@ var estimateDot = {
|
|
|
1139
1186
|
borderRadius: "50%",
|
|
1140
1187
|
background: mark("estimate.dot")
|
|
1141
1188
|
};
|
|
1189
|
+
var shiftTrack = {
|
|
1190
|
+
position: "relative",
|
|
1191
|
+
height: 16,
|
|
1192
|
+
borderRadius: 2,
|
|
1193
|
+
background: mark("bar.track")
|
|
1194
|
+
};
|
|
1195
|
+
var shiftLink = {
|
|
1196
|
+
position: "absolute",
|
|
1197
|
+
top: 7,
|
|
1198
|
+
height: 2,
|
|
1199
|
+
background: mark("shift.link")
|
|
1200
|
+
};
|
|
1201
|
+
var shiftBefore = {
|
|
1202
|
+
position: "absolute",
|
|
1203
|
+
top: 3,
|
|
1204
|
+
width: 10,
|
|
1205
|
+
height: 10,
|
|
1206
|
+
marginLeft: -5,
|
|
1207
|
+
borderRadius: "50%",
|
|
1208
|
+
border: `2px solid ${mark("shift.before")}`,
|
|
1209
|
+
background: "transparent"
|
|
1210
|
+
};
|
|
1211
|
+
var shiftNow = {
|
|
1212
|
+
position: "absolute",
|
|
1213
|
+
top: 3,
|
|
1214
|
+
width: 10,
|
|
1215
|
+
height: 10,
|
|
1216
|
+
marginLeft: -5,
|
|
1217
|
+
borderRadius: "50%",
|
|
1218
|
+
background: mark("shift.now")
|
|
1219
|
+
};
|
|
1220
|
+
var survivalLegend = { display: "flex", gap: 14, flexWrap: "wrap" };
|
|
1142
1221
|
var filterInput = {
|
|
1143
1222
|
font: "inherit",
|
|
1144
1223
|
fontSize: "0.85em",
|
|
@@ -1995,24 +2074,6 @@ var figureRow = {
|
|
|
1995
2074
|
gap: 10,
|
|
1996
2075
|
flexWrap: "wrap"
|
|
1997
2076
|
};
|
|
1998
|
-
var segmentHeadCell = {
|
|
1999
|
-
textAlign: "left",
|
|
2000
|
-
fontSize: "0.78em",
|
|
2001
|
-
letterSpacing: "0.06em",
|
|
2002
|
-
textTransform: "uppercase",
|
|
2003
|
-
fontWeight: 500,
|
|
2004
|
-
opacity: 0.7,
|
|
2005
|
-
padding: "8px 12px",
|
|
2006
|
-
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.3))",
|
|
2007
|
-
whiteSpace: "nowrap"
|
|
2008
|
-
};
|
|
2009
|
-
var segmentRowCell = {
|
|
2010
|
-
textAlign: "left",
|
|
2011
|
-
fontWeight: 400,
|
|
2012
|
-
padding: "8px 12px",
|
|
2013
|
-
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.18))",
|
|
2014
|
-
verticalAlign: "baseline"
|
|
2015
|
-
};
|
|
2016
2077
|
var sourceLink = {
|
|
2017
2078
|
color: "inherit",
|
|
2018
2079
|
textDecoration: "underline",
|
|
@@ -2450,10 +2511,45 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2450
2511
|
part: data.ga4Pageviews,
|
|
2451
2512
|
missingLabel: "it missed"
|
|
2452
2513
|
}
|
|
2453
|
-
) :
|
|
2454
|
-
/*
|
|
2455
|
-
|
|
2456
|
-
|
|
2514
|
+
) : isContainment(data.ga4Pageviews, data.vercelPageviews) ? (
|
|
2515
|
+
/* Still a containment — the operands are simply the other way round.
|
|
2516
|
+
|
|
2517
|
+
When GA4 counts MORE than Vercel, it is because Vercel started collecting after
|
|
2518
|
+
the range began, so Vercel's count is the subset. Falling back to two peer bars
|
|
2519
|
+
scaled to the larger put GA4 at full width saying nothing, which is verbatim the
|
|
2520
|
+
defect ContainmentBar was built to kill. */
|
|
2521
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 2, children: [
|
|
2522
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2523
|
+
ContainmentBar,
|
|
2524
|
+
{
|
|
2525
|
+
wholeLabel: "Pageviews Google Analytics counted",
|
|
2526
|
+
whole: data.ga4Pageviews,
|
|
2527
|
+
partLabel: "Also counted by Vercel",
|
|
2528
|
+
part: data.vercelPageviews,
|
|
2529
|
+
missingLabel: "Vercel did not see"
|
|
2530
|
+
}
|
|
2531
|
+
),
|
|
2532
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Vercel counted fewer because its collection started after this range began, not because the traffic was not there." })
|
|
2533
|
+
] })
|
|
2534
|
+
) : (
|
|
2535
|
+
/* Neither containment holds, because only one source answered.
|
|
2536
|
+
|
|
2537
|
+
Deleting ComparisonBar took this case with it: with GA4 down, both containments
|
|
2538
|
+
return null and the section rendered NOTHING — not even the figure Vercel did
|
|
2539
|
+
report. That is the state where the reader most needs to see what survived, and
|
|
2540
|
+
it is the one the card below spends a comment explaining. */
|
|
2541
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2542
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
2543
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Pageviews Vercel counted" }),
|
|
2544
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.vercelPageviews, label: "Vercel pageviews", size: 3 })
|
|
2545
|
+
] }),
|
|
2546
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
2547
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Seen by Google Analytics" }),
|
|
2548
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.ga4Pageviews, label: "GA4 pageviews", size: 3 })
|
|
2549
|
+
] }),
|
|
2550
|
+
/* @__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." })
|
|
2551
|
+
] })
|
|
2552
|
+
)
|
|
2457
2553
|
] }),
|
|
2458
2554
|
diagnostics && /* Open, and primary. This is the longest block on the tab and the obvious candidate
|
|
2459
2555
|
for folding away — but the tab it sits on exists to answer "what should I fix
|
|
@@ -2492,10 +2588,18 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2492
2588
|
Object.keys(data.orderStatuses ?? {}).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2493
2589
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Order statuses in this range", tone: "secondary" }),
|
|
2494
2590
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "What the orders actually say, before any filtering. Use these values to set which statuses count as a sale." }),
|
|
2495
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2591
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2592
|
+
ProportionChart,
|
|
2593
|
+
{
|
|
2594
|
+
bars: Object.entries(data.orderStatuses ?? {}).map(([status, count]) => ({
|
|
2595
|
+
key: status,
|
|
2596
|
+
label: status,
|
|
2597
|
+
value: count
|
|
2598
|
+
})),
|
|
2599
|
+
format: (n) => formatCount(n),
|
|
2600
|
+
totalLabel: "Orders in this range"
|
|
2601
|
+
}
|
|
2602
|
+
)
|
|
2499
2603
|
] }),
|
|
2500
2604
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2501
2605
|
Section,
|
|
@@ -2742,48 +2846,6 @@ function rankLine(series, pick) {
|
|
|
2742
2846
|
const ranked = weeklyRank(series.map((d) => ({ date: d.date, value: pick(d) })));
|
|
2743
2847
|
return ranked ? describeRank(ranked) : null;
|
|
2744
2848
|
}
|
|
2745
|
-
function SegmentTable({ segments, dimension }) {
|
|
2746
|
-
if (segments.length < 2) return null;
|
|
2747
|
-
const spine = [...segments].sort((a, b) => b.steps.length - a.steps.length)[0]?.steps ?? [];
|
|
2748
|
-
if (spine.length === 0) return null;
|
|
2749
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2750
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
|
|
2751
|
-
"Every ",
|
|
2752
|
-
dimension,
|
|
2753
|
-
" at once. A rate needs ",
|
|
2754
|
-
MIN_RATE_DENOMINATOR,
|
|
2755
|
-
" people at the step before it; below that only the count is shown."
|
|
2756
|
-
] }),
|
|
2757
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { radius: 2, tone: "transparent", border: true, style: { overflowX: "auto", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { style: { width: "100%", borderCollapse: "collapse", minWidth: 360 }, children: [
|
|
2758
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("caption", { style: visuallyHidden, children: [
|
|
2759
|
-
"Funnel steps by ",
|
|
2760
|
-
dimension
|
|
2761
|
-
] }),
|
|
2762
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
2763
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "col", style: segmentHeadCell, children: "Step" }),
|
|
2764
|
-
segments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "col", style: { ...segmentHeadCell, textAlign: "right" }, children: segment.label }, segment.key))
|
|
2765
|
-
] }) }),
|
|
2766
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: spine.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
2767
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "row", style: segmentRowCell, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: step.label }) }),
|
|
2768
|
-
segments.map((segment) => {
|
|
2769
|
-
const cell = segment.steps.find((s) => s.key === step.key);
|
|
2770
|
-
const count = cell ? metricSortValue(cell.count) : null;
|
|
2771
|
-
const before = index > 0 ? metricSortValue(segment.steps.find((s) => s.key === spine[index - 1].key)?.count) : null;
|
|
2772
|
-
const rate = cell?.conversionFromPrevious ?? null;
|
|
2773
|
-
const showRate = index > 0 && rate !== null && before !== null && before >= MIN_RATE_DENOMINATOR;
|
|
2774
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { style: { ...segmentRowCell, textAlign: "right", fontVariantNumeric: "tabular-nums" }, children: count === null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
|
|
2775
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(count) }),
|
|
2776
|
-
showRate && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
2777
|
-
formatPercent(rate, 1),
|
|
2778
|
-
" of ",
|
|
2779
|
-
formatCount(before)
|
|
2780
|
-
] })
|
|
2781
|
-
] }) }, segment.key);
|
|
2782
|
-
})
|
|
2783
|
-
] }, step.key)) })
|
|
2784
|
-
] }) })
|
|
2785
|
-
] });
|
|
2786
|
-
}
|
|
2787
2849
|
function revenueCoversEveryOrder(data) {
|
|
2788
2850
|
const counted = metricSortValue(data.orders);
|
|
2789
2851
|
const withTotal = finiteOrNull(data.ordersWithTotal);
|
|
@@ -2792,6 +2854,7 @@ function revenueCoversEveryOrder(data) {
|
|
|
2792
2854
|
}
|
|
2793
2855
|
function JourneyPanel({ data }) {
|
|
2794
2856
|
const segments = data.segments ?? [];
|
|
2857
|
+
const spine = [...segments].sort((a, b) => b.steps.length - a.steps.length)[0]?.steps ?? [];
|
|
2795
2858
|
const allSteps = data.steps ?? [];
|
|
2796
2859
|
const shown = allSteps.filter((step) => step.count.status !== "unavailable");
|
|
2797
2860
|
const hiddenSteps = allSteps.filter((step) => step.count.status === "unavailable");
|
|
@@ -2804,7 +2867,52 @@ function JourneyPanel({ data }) {
|
|
|
2804
2867
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked path" : "Not a tracked path" }),
|
|
2805
2868
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: tracked, children: data.approximationNote })
|
|
2806
2869
|
] }) }),
|
|
2807
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2870
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2871
|
+
SurvivalLines,
|
|
2872
|
+
{
|
|
2873
|
+
segments: segments.map((segment) => ({
|
|
2874
|
+
key: segment.key,
|
|
2875
|
+
label: segment.label,
|
|
2876
|
+
values: spine.map((step) => {
|
|
2877
|
+
const cell = segment.steps.find((s) => s.key === step.key);
|
|
2878
|
+
return cell ? metricSortValue(cell.count) : null;
|
|
2879
|
+
})
|
|
2880
|
+
})),
|
|
2881
|
+
stepLabels: spine.map((step) => step.label)
|
|
2882
|
+
}
|
|
2883
|
+
),
|
|
2884
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2885
|
+
ChartData,
|
|
2886
|
+
{
|
|
2887
|
+
label: "Show the figures behind this",
|
|
2888
|
+
rows: segments,
|
|
2889
|
+
rowKey: (segment) => segment.key,
|
|
2890
|
+
columns: [
|
|
2891
|
+
{
|
|
2892
|
+
key: "segment",
|
|
2893
|
+
label: data.segmentDimension ?? "Segment",
|
|
2894
|
+
sortValue: (segment) => segment.label,
|
|
2895
|
+
render: (segment) => segment.label
|
|
2896
|
+
},
|
|
2897
|
+
...spine.map((step) => ({
|
|
2898
|
+
key: step.key,
|
|
2899
|
+
label: step.label,
|
|
2900
|
+
numeric: true,
|
|
2901
|
+
sortValue: (segment) => metricSortValue(segment.steps.find((s) => s.key === step.key)?.count),
|
|
2902
|
+
render: (segment) => {
|
|
2903
|
+
const cell = segment.steps.find((s) => s.key === step.key);
|
|
2904
|
+
const count = metricSortValue(cell?.count);
|
|
2905
|
+
if (count === null) return "\u2014";
|
|
2906
|
+
const index = spine.findIndex((sp) => sp.key === step.key);
|
|
2907
|
+
const before = index > 0 ? metricSortValue(segment.steps.find((s) => s.key === spine[index - 1].key)?.count) : null;
|
|
2908
|
+
const rate = cell?.conversionFromPrevious ?? null;
|
|
2909
|
+
const showRate = index > 0 && rate !== null && before !== null && before >= MIN_RATE_DENOMINATOR;
|
|
2910
|
+
return showRate ? `${formatCount(count)} \xB7 ${formatPercent(rate, 1)} of ${formatCount(before)}` : formatCount(count);
|
|
2911
|
+
}
|
|
2912
|
+
}))
|
|
2913
|
+
]
|
|
2914
|
+
}
|
|
2915
|
+
),
|
|
2808
2916
|
spread(segments) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) }),
|
|
2809
2917
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FunnelChart, { stages, measurement: data.measurement ?? "independent-totals" }),
|
|
2810
2918
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: splitGrid, children: [
|
|
@@ -3861,7 +3969,6 @@ var index_default = visitorInsights;
|
|
|
3861
3969
|
0 && (module.exports = {
|
|
3862
3970
|
AcquisitionPanel,
|
|
3863
3971
|
ChartData,
|
|
3864
|
-
ComparisonBar,
|
|
3865
3972
|
CrossSourceTimeline,
|
|
3866
3973
|
DataHealthPanel,
|
|
3867
3974
|
Delta,
|