@liiift-studio/sanity-visitor-insights 0.66.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 +169 -96
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +169 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/studio/Figure.tsx +100 -54
- package/src/studio/palette.ts +2 -0
- package/src/studio/panels.test.tsx +88 -5
- package/src/studio/panels.tsx +115 -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,
|
|
@@ -313,7 +312,9 @@ var MARKS = {
|
|
|
313
312
|
"estimate.rule": { key: "neutral", alpha: 0.45, role: "furniture", note: "The 100% reference a capture rate is read against" },
|
|
314
313
|
"shift.now": { key: "vercel", alpha: 1, role: "data", note: "A channel this period" },
|
|
315
314
|
"shift.before": { key: "neutral", alpha: 0.55, role: "furniture", note: "The same channel last period \u2014 context, not a second answer" },
|
|
316
|
-
"shift.link": { key: "neutral", alpha: 0.35, role: "furniture", note: "The distance between the two, which is the finding" }
|
|
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" }
|
|
317
318
|
};
|
|
318
319
|
function mark(name) {
|
|
319
320
|
const m = MARKS[name];
|
|
@@ -499,30 +500,6 @@ function MetricFigure({ metric, label, size = 4, unit = "count" }) {
|
|
|
499
500
|
}
|
|
500
501
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size, children: formatted });
|
|
501
502
|
}
|
|
502
|
-
function ComparisonBar({ label, metric, max, outOf }) {
|
|
503
|
-
const value = metric.status === "unavailable" ? null : metric.value;
|
|
504
|
-
const width = value !== null && max > 0 ? Math.max(2, value / max * 100) : 0;
|
|
505
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
506
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
507
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: label }),
|
|
508
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricFigure, { metric, label, size: 1 })
|
|
509
|
-
] }),
|
|
510
|
-
value === null ? (
|
|
511
|
-
// A dashed rail, not a zero-width bar: absence must not read as a measured zero.
|
|
512
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
513
|
-
import_sanity_ui_compat.Card,
|
|
514
|
-
{
|
|
515
|
-
"aria-hidden": "true",
|
|
516
|
-
radius: 2,
|
|
517
|
-
tone: "transparent",
|
|
518
|
-
border: true,
|
|
519
|
-
style: { height: 8, borderStyle: "dashed" }
|
|
520
|
-
}
|
|
521
|
-
)
|
|
522
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: barTrack, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...barFill, width: `${width}%` } }) }),
|
|
523
|
-
outOf && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: outOf })
|
|
524
|
-
] });
|
|
525
|
-
}
|
|
526
503
|
function ChartData({ label, rows, columns, rowKey, exportName, note, filterOn }) {
|
|
527
504
|
if (rows.length === 0) return null;
|
|
528
505
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { style: disclosureBlock, children: [
|
|
@@ -896,6 +873,73 @@ function EstimateDotPlot({
|
|
|
896
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%." })
|
|
897
874
|
] });
|
|
898
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
|
+
}
|
|
899
943
|
function SortableTable({
|
|
900
944
|
caption,
|
|
901
945
|
columns,
|
|
@@ -1173,6 +1217,7 @@ var shiftNow = {
|
|
|
1173
1217
|
borderRadius: "50%",
|
|
1174
1218
|
background: mark("shift.now")
|
|
1175
1219
|
};
|
|
1220
|
+
var survivalLegend = { display: "flex", gap: 14, flexWrap: "wrap" };
|
|
1176
1221
|
var filterInput = {
|
|
1177
1222
|
font: "inherit",
|
|
1178
1223
|
fontSize: "0.85em",
|
|
@@ -2029,24 +2074,6 @@ var figureRow = {
|
|
|
2029
2074
|
gap: 10,
|
|
2030
2075
|
flexWrap: "wrap"
|
|
2031
2076
|
};
|
|
2032
|
-
var segmentHeadCell = {
|
|
2033
|
-
textAlign: "left",
|
|
2034
|
-
fontSize: "0.78em",
|
|
2035
|
-
letterSpacing: "0.06em",
|
|
2036
|
-
textTransform: "uppercase",
|
|
2037
|
-
fontWeight: 500,
|
|
2038
|
-
opacity: 0.7,
|
|
2039
|
-
padding: "8px 12px",
|
|
2040
|
-
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.3))",
|
|
2041
|
-
whiteSpace: "nowrap"
|
|
2042
|
-
};
|
|
2043
|
-
var segmentRowCell = {
|
|
2044
|
-
textAlign: "left",
|
|
2045
|
-
fontWeight: 400,
|
|
2046
|
-
padding: "8px 12px",
|
|
2047
|
-
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.18))",
|
|
2048
|
-
verticalAlign: "baseline"
|
|
2049
|
-
};
|
|
2050
2077
|
var sourceLink = {
|
|
2051
2078
|
color: "inherit",
|
|
2052
2079
|
textDecoration: "underline",
|
|
@@ -2484,10 +2511,45 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2484
2511
|
part: data.ga4Pageviews,
|
|
2485
2512
|
missingLabel: "it missed"
|
|
2486
2513
|
}
|
|
2487
|
-
) :
|
|
2488
|
-
/*
|
|
2489
|
-
|
|
2490
|
-
|
|
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
|
+
)
|
|
2491
2553
|
] }),
|
|
2492
2554
|
diagnostics && /* Open, and primary. This is the longest block on the tab and the obvious candidate
|
|
2493
2555
|
for folding away — but the tab it sits on exists to answer "what should I fix
|
|
@@ -2526,10 +2588,18 @@ function DataHealthPanel({ data, diagnostics }) {
|
|
|
2526
2588
|
Object.keys(data.orderStatuses ?? {}).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2527
2589
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Order statuses in this range", tone: "secondary" }),
|
|
2528
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." }),
|
|
2529
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
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
|
+
)
|
|
2533
2603
|
] }),
|
|
2534
2604
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2535
2605
|
Section,
|
|
@@ -2776,48 +2846,6 @@ function rankLine(series, pick) {
|
|
|
2776
2846
|
const ranked = weeklyRank(series.map((d) => ({ date: d.date, value: pick(d) })));
|
|
2777
2847
|
return ranked ? describeRank(ranked) : null;
|
|
2778
2848
|
}
|
|
2779
|
-
function SegmentTable({ segments, dimension }) {
|
|
2780
|
-
if (segments.length < 2) return null;
|
|
2781
|
-
const spine = [...segments].sort((a, b) => b.steps.length - a.steps.length)[0]?.steps ?? [];
|
|
2782
|
-
if (spine.length === 0) return null;
|
|
2783
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2784
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
|
|
2785
|
-
"Every ",
|
|
2786
|
-
dimension,
|
|
2787
|
-
" at once. A rate needs ",
|
|
2788
|
-
MIN_RATE_DENOMINATOR,
|
|
2789
|
-
" people at the step before it; below that only the count is shown."
|
|
2790
|
-
] }),
|
|
2791
|
-
/* @__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: [
|
|
2792
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("caption", { style: visuallyHidden, children: [
|
|
2793
|
-
"Funnel steps by ",
|
|
2794
|
-
dimension
|
|
2795
|
-
] }),
|
|
2796
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
2797
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "col", style: segmentHeadCell, children: "Step" }),
|
|
2798
|
-
segments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "col", style: { ...segmentHeadCell, textAlign: "right" }, children: segment.label }, segment.key))
|
|
2799
|
-
] }) }),
|
|
2800
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: spine.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
2801
|
-
/* @__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 }) }),
|
|
2802
|
-
segments.map((segment) => {
|
|
2803
|
-
const cell = segment.steps.find((s) => s.key === step.key);
|
|
2804
|
-
const count = cell ? metricSortValue(cell.count) : null;
|
|
2805
|
-
const before = index > 0 ? metricSortValue(segment.steps.find((s) => s.key === spine[index - 1].key)?.count) : null;
|
|
2806
|
-
const rate = cell?.conversionFromPrevious ?? null;
|
|
2807
|
-
const showRate = index > 0 && rate !== null && before !== null && before >= MIN_RATE_DENOMINATOR;
|
|
2808
|
-
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: [
|
|
2809
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(count) }),
|
|
2810
|
-
showRate && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
2811
|
-
formatPercent(rate, 1),
|
|
2812
|
-
" of ",
|
|
2813
|
-
formatCount(before)
|
|
2814
|
-
] })
|
|
2815
|
-
] }) }, segment.key);
|
|
2816
|
-
})
|
|
2817
|
-
] }, step.key)) })
|
|
2818
|
-
] }) })
|
|
2819
|
-
] });
|
|
2820
|
-
}
|
|
2821
2849
|
function revenueCoversEveryOrder(data) {
|
|
2822
2850
|
const counted = metricSortValue(data.orders);
|
|
2823
2851
|
const withTotal = finiteOrNull(data.ordersWithTotal);
|
|
@@ -2826,6 +2854,7 @@ function revenueCoversEveryOrder(data) {
|
|
|
2826
2854
|
}
|
|
2827
2855
|
function JourneyPanel({ data }) {
|
|
2828
2856
|
const segments = data.segments ?? [];
|
|
2857
|
+
const spine = [...segments].sort((a, b) => b.steps.length - a.steps.length)[0]?.steps ?? [];
|
|
2829
2858
|
const allSteps = data.steps ?? [];
|
|
2830
2859
|
const shown = allSteps.filter((step) => step.count.status !== "unavailable");
|
|
2831
2860
|
const hiddenSteps = allSteps.filter((step) => step.count.status === "unavailable");
|
|
@@ -2838,7 +2867,52 @@ function JourneyPanel({ data }) {
|
|
|
2838
2867
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked path" : "Not a tracked path" }),
|
|
2839
2868
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: tracked, children: data.approximationNote })
|
|
2840
2869
|
] }) }),
|
|
2841
|
-
/* @__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
|
+
),
|
|
2842
2916
|
spread(segments) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) }),
|
|
2843
2917
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FunnelChart, { stages, measurement: data.measurement ?? "independent-totals" }),
|
|
2844
2918
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: splitGrid, children: [
|
|
@@ -3895,7 +3969,6 @@ var index_default = visitorInsights;
|
|
|
3895
3969
|
0 && (module.exports = {
|
|
3896
3970
|
AcquisitionPanel,
|
|
3897
3971
|
ChartData,
|
|
3898
|
-
ComparisonBar,
|
|
3899
3972
|
CrossSourceTimeline,
|
|
3900
3973
|
DataHealthPanel,
|
|
3901
3974
|
Delta,
|