@liiift-studio/sanity-visitor-insights 0.56.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +119 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +119 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/rank.test.ts +103 -0
- package/src/core/rank.ts +94 -0
- package/src/studio/Figure.tsx +83 -0
- package/src/studio/palette.ts +1 -0
- package/src/studio/panels.test.tsx +118 -1
- package/src/studio/panels.tsx +64 -5
package/dist/index.js
CHANGED
|
@@ -306,7 +306,8 @@ var MARKS = {
|
|
|
306
306
|
"chart.grid": { key: "neutral", alpha: 0.18, role: "furniture", note: "Grid rules" },
|
|
307
307
|
"bar.fill": { key: "vercel", alpha: 1, role: "data", note: "Every proportion, comparison and funnel bar" },
|
|
308
308
|
"bar.track": { key: "neutral", alpha: 0.12, role: "furniture", note: "The rail a bar sits in" },
|
|
309
|
-
"bar.lost": { key: "revenue", alpha: 1, role: "data", note: "People who did not continue past a funnel rung" }
|
|
309
|
+
"bar.lost": { key: "revenue", alpha: 1, role: "data", note: "People who did not continue past a funnel rung" },
|
|
310
|
+
"bar.seen": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The share of a complete count that the lossy source saw" }
|
|
310
311
|
};
|
|
311
312
|
function mark(name) {
|
|
312
313
|
const m = MARKS[name];
|
|
@@ -315,6 +316,33 @@ function mark(name) {
|
|
|
315
316
|
return seriesFill(m.key, m.alpha);
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
// src/types.ts
|
|
320
|
+
function estimated(value, low, high, basis) {
|
|
321
|
+
return { status: "estimated", value, low, high, basis };
|
|
322
|
+
}
|
|
323
|
+
function ok(value) {
|
|
324
|
+
return { status: "ok", value };
|
|
325
|
+
}
|
|
326
|
+
function unavailable(reason, detail) {
|
|
327
|
+
return { status: "unavailable", reason, detail };
|
|
328
|
+
}
|
|
329
|
+
function partial(value, coveredFrom, note) {
|
|
330
|
+
return { status: "partial", value, coveredFrom, note };
|
|
331
|
+
}
|
|
332
|
+
function valueOrNull(metric) {
|
|
333
|
+
return metric.status === "unavailable" ? null : metric.value;
|
|
334
|
+
}
|
|
335
|
+
var REPORT_NAMES = [
|
|
336
|
+
"measurement-health",
|
|
337
|
+
"acquisition",
|
|
338
|
+
"journey",
|
|
339
|
+
"typeface-interest",
|
|
340
|
+
"diagnostics"
|
|
341
|
+
];
|
|
342
|
+
function isReportName(value) {
|
|
343
|
+
return typeof value === "string" && REPORT_NAMES.includes(value);
|
|
344
|
+
}
|
|
345
|
+
|
|
318
346
|
// src/studio/Figure.tsx
|
|
319
347
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
320
348
|
var REASON_TEXT = {
|
|
@@ -773,6 +801,47 @@ function SectionTitle({
|
|
|
773
801
|
}) {
|
|
774
802
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: sectionHeader, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Heading, { size: tone === "primary" ? 1 : 0, style: tone === "primary" ? primaryTitle : secondaryTitle, children: title }) });
|
|
775
803
|
}
|
|
804
|
+
function isContainment(whole, part) {
|
|
805
|
+
const w = valueOrNull(whole);
|
|
806
|
+
const p = valueOrNull(part);
|
|
807
|
+
return w !== null && p !== null && w > 0 && p <= w;
|
|
808
|
+
}
|
|
809
|
+
function ContainmentBar({
|
|
810
|
+
wholeLabel,
|
|
811
|
+
whole,
|
|
812
|
+
partLabel,
|
|
813
|
+
part,
|
|
814
|
+
missingLabel
|
|
815
|
+
}) {
|
|
816
|
+
const wholeValue = valueOrNull(whole);
|
|
817
|
+
const partValue = valueOrNull(part);
|
|
818
|
+
if (!isContainment(whole, part)) return null;
|
|
819
|
+
if (wholeValue === null || partValue === null) return null;
|
|
820
|
+
const share = partValue / wholeValue;
|
|
821
|
+
const missing = wholeValue - partValue;
|
|
822
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
824
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, children: wholeLabel }),
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: formatCount(wholeValue) })
|
|
826
|
+
] }),
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, background: mark("bar.fill"), height: 14 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: "100%", width: `${share * 100}%`, background: mark("bar.seen"), borderRadius: 2 } }) }),
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
830
|
+
partLabel,
|
|
831
|
+
": ",
|
|
832
|
+
formatCount(partValue),
|
|
833
|
+
" (",
|
|
834
|
+
formatPercent(share, 0),
|
|
835
|
+
")"
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
838
|
+
formatCount(missing),
|
|
839
|
+
" ",
|
|
840
|
+
missingLabel
|
|
841
|
+
] })
|
|
842
|
+
] })
|
|
843
|
+
] });
|
|
844
|
+
}
|
|
776
845
|
function SortableTable({
|
|
777
846
|
caption,
|
|
778
847
|
columns,
|
|
@@ -1785,31 +1854,31 @@ var legendRow = {
|
|
|
1785
1854
|
alignItems: "center"
|
|
1786
1855
|
};
|
|
1787
1856
|
|
|
1788
|
-
// src/
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
return
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
return
|
|
1857
|
+
// src/core/rank.ts
|
|
1858
|
+
var MIN_WEEKS_TO_RANK = 4;
|
|
1859
|
+
function weeklyRank(series) {
|
|
1860
|
+
if (series.length < 7 * MIN_WEEKS_TO_RANK) return null;
|
|
1861
|
+
const weeks = [];
|
|
1862
|
+
for (let end = series.length; end - 7 >= 0; end -= 7) {
|
|
1863
|
+
const slice = series.slice(end - 7, end);
|
|
1864
|
+
weeks.push(slice.some((d) => d.value === null) ? null : slice.reduce((sum, d) => sum + d.value, 0));
|
|
1865
|
+
}
|
|
1866
|
+
const latest = weeks[0];
|
|
1867
|
+
if (latest === null || latest === void 0) return null;
|
|
1868
|
+
const comparable = weeks.filter((w) => w !== null);
|
|
1869
|
+
if (comparable.length < MIN_WEEKS_TO_RANK) return null;
|
|
1870
|
+
const better = comparable.filter((w) => w > latest).length;
|
|
1871
|
+
return { rank: better + 1, of: comparable.length, value: latest };
|
|
1872
|
+
}
|
|
1873
|
+
function describeRank(rank) {
|
|
1874
|
+
const ordinal = (n) => {
|
|
1875
|
+
const tens = n % 100;
|
|
1876
|
+
if (tens >= 11 && tens <= 13) return `${n}th`;
|
|
1877
|
+
return `${n}${["th", "st", "nd", "rd"][n % 10] ?? "th"}`;
|
|
1878
|
+
};
|
|
1879
|
+
if (rank.rank === 1) return `best of the last ${rank.of} weeks`;
|
|
1880
|
+
if (rank.rank === rank.of) return `lowest of the last ${rank.of} weeks`;
|
|
1881
|
+
return `${ordinal(rank.rank)} best of the last ${rank.of} weeks`;
|
|
1813
1882
|
}
|
|
1814
1883
|
|
|
1815
1884
|
// src/studio/panels.tsx
|
|
@@ -1887,14 +1956,16 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
1887
1956
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
1888
1957
|
metricOr(data.revenue, OLDER_ROUTE).status === "ok" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: formatMoney(data.revenue.value, data.currency ?? null) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.revenue, OLDER_ROUTE), label: "Revenue" }),
|
|
1889
1958
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.revenue), previous: metricSortValue(previous?.revenue) })
|
|
1890
|
-
] })
|
|
1959
|
+
] }),
|
|
1960
|
+
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) })
|
|
1891
1961
|
] }) }),
|
|
1892
1962
|
/* @__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: [
|
|
1893
1963
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Orders" }),
|
|
1894
1964
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
|
|
1895
1965
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: data.orders, label: "Orders" }),
|
|
1896
1966
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.orders), previous: metricSortValue(previous?.orders) })
|
|
1897
|
-
] })
|
|
1967
|
+
] }),
|
|
1968
|
+
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) })
|
|
1898
1969
|
] }) }),
|
|
1899
1970
|
/* @__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: [
|
|
1900
1971
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Traffic" }),
|
|
@@ -2240,12 +2311,24 @@ function Verdict({ data, previous }) {
|
|
|
2240
2311
|
}
|
|
2241
2312
|
function DataHealthPanel({ data, diagnostics }) {
|
|
2242
2313
|
const pageviewMax = maxOf([data.ga4Pageviews, data.vercelPageviews]);
|
|
2314
|
+
const contained = isContainment(data.vercelPageviews, data.ga4Pageviews);
|
|
2243
2315
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
|
|
2244
2316
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2245
2317
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Pageviews, source against source" }),
|
|
2246
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "
|
|
2247
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2248
|
-
|
|
2318
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Both counting pageviews. Google Analytics is blockable, so seeing fewer is normal." }),
|
|
2319
|
+
contained ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2320
|
+
ContainmentBar,
|
|
2321
|
+
{
|
|
2322
|
+
wholeLabel: "Pageviews Vercel counted",
|
|
2323
|
+
whole: data.vercelPageviews,
|
|
2324
|
+
partLabel: "Seen by Google Analytics",
|
|
2325
|
+
part: data.ga4Pageviews,
|
|
2326
|
+
missingLabel: "it missed"
|
|
2327
|
+
}
|
|
2328
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2329
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComparisonBar, { label: "Vercel pageviews", metric: data.vercelPageviews, max: pageviewMax }),
|
|
2330
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComparisonBar, { label: "GA4 pageviews", metric: data.ga4Pageviews, max: pageviewMax })
|
|
2331
|
+
] })
|
|
2249
2332
|
] }),
|
|
2250
2333
|
/* @__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: [
|
|
2251
2334
|
data.shortfallRatio !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "semibold", children: data.shortfallRatio >= 0 ? `GA4 saw ${formatPercent(data.shortfallRatio, 1)} fewer pageviews than Vercel` : `GA4 saw ${formatPercent(-data.shortfallRatio, 1)} more pageviews than Vercel` }),
|
|
@@ -2537,6 +2620,11 @@ function shortListNote(truncated, withheld, tail) {
|
|
|
2537
2620
|
const cause = truncated && withheld ? "GA4 returned only its top rows, and withheld others with too few people to report" : truncated ? "GA4 returned only its top rows" : "GA4 withheld rows with too few people to report";
|
|
2538
2621
|
return `${cause}, so this list is shorter than reality. ${tail}`;
|
|
2539
2622
|
}
|
|
2623
|
+
function rankLine(series, pick) {
|
|
2624
|
+
if (!series || series.length === 0) return null;
|
|
2625
|
+
const ranked = weeklyRank(series.map((d) => ({ date: d.date, value: pick(d) })));
|
|
2626
|
+
return ranked ? describeRank(ranked) : null;
|
|
2627
|
+
}
|
|
2540
2628
|
function JourneyPanel({ data }) {
|
|
2541
2629
|
const [segmentKey, setSegmentKey] = import_react4.default.useState(null);
|
|
2542
2630
|
const segments = data.segments ?? [];
|