@aglyn/plugins-marketing 1.0.0-beta.178 → 1.0.0-beta.180

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.
Files changed (71) hide show
  1. package/README.md +3 -3
  2. package/package.json +15 -15
  3. package/src/lib/components/campaign-conversions-card.js +22 -4
  4. package/src/lib/components/campaign-conversions-card.js.map +1 -1
  5. package/src/lib/components/campaign-detail-card.js +3 -4
  6. package/src/lib/components/campaign-detail-card.js.map +1 -1
  7. package/src/lib/components/campaign-report-card.js +3 -3
  8. package/src/lib/components/campaign-report-card.js.map +1 -1
  9. package/src/lib/components/email-compose-card.d.ts +5 -2
  10. package/src/lib/components/email-compose-card.js +2 -3
  11. package/src/lib/components/email-compose-card.js.map +1 -1
  12. package/src/lib/components/email-detail.d.ts +5 -2
  13. package/src/lib/components/email-detail.js +4 -4
  14. package/src/lib/components/email-detail.js.map +1 -1
  15. package/src/lib/components/email-messages-widget.d.ts +17 -2
  16. package/src/lib/components/email-messages-widget.js +33 -2
  17. package/src/lib/components/email-messages-widget.js.map +1 -1
  18. package/src/lib/components/emails-list-card.d.ts +9 -6
  19. package/src/lib/components/emails-list-card.js +9 -9
  20. package/src/lib/components/emails-list-card.js.map +1 -1
  21. package/src/lib/components/host-experiments-card.component.d.ts +2 -0
  22. package/src/lib/components/host-experiments-card.component.js +20 -25
  23. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  24. package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
  25. package/src/lib/components/host-marketing-summary-card.component.js +59 -68
  26. package/src/lib/components/host-marketing-summary-card.component.js.map +1 -1
  27. package/src/lib/components/host-overlays-card.component.js +15 -37
  28. package/src/lib/components/host-overlays-card.component.js.map +1 -1
  29. package/src/lib/components/inbox-campaigns-widget.d.ts +23 -0
  30. package/src/lib/components/inbox-campaigns-widget.js +63 -0
  31. package/src/lib/components/inbox-campaigns-widget.js.map +1 -0
  32. package/src/lib/components/marketing-aggregates.d.ts +95 -0
  33. package/src/lib/components/marketing-aggregates.js +114 -0
  34. package/src/lib/components/marketing-aggregates.js.map +1 -0
  35. package/src/lib/components/marketing-console-page.js +32 -27
  36. package/src/lib/components/marketing-console-page.js.map +1 -1
  37. package/src/lib/components/marketing-console-sections.d.ts +24 -13
  38. package/src/lib/components/marketing-console-sections.js +37 -12
  39. package/src/lib/components/marketing-console-sections.js.map +1 -1
  40. package/src/lib/components/org-conversions-section.d.ts +32 -0
  41. package/src/lib/components/org-conversions-section.js +130 -0
  42. package/src/lib/components/org-conversions-section.js.map +1 -0
  43. package/src/lib/components/org-experiments-card.d.ts +32 -0
  44. package/src/lib/components/org-experiments-card.js +407 -0
  45. package/src/lib/components/org-experiments-card.js.map +1 -0
  46. package/src/lib/components/org-marketing-overview-card.d.ts +34 -0
  47. package/src/lib/components/org-marketing-overview-card.js +342 -0
  48. package/src/lib/components/org-marketing-overview-card.js.map +1 -0
  49. package/src/lib/components/org-overlays-card.d.ts +35 -0
  50. package/src/lib/components/org-overlays-card.js +345 -0
  51. package/src/lib/components/org-overlays-card.js.map +1 -0
  52. package/src/lib/components/org-site-picker-button.d.ts +26 -0
  53. package/src/lib/components/org-site-picker-button.js +104 -0
  54. package/src/lib/components/org-site-picker-button.js.map +1 -0
  55. package/src/lib/components/overlay-labels.d.ts +38 -0
  56. package/src/lib/components/overlay-labels.js +55 -0
  57. package/src/lib/components/overlay-labels.js.map +1 -0
  58. package/src/lib/components/use-emails-hub-path.d.ts +17 -8
  59. package/src/lib/components/use-emails-hub-path.js +29 -11
  60. package/src/lib/components/use-emails-hub-path.js.map +1 -1
  61. package/src/lib/components/use-one-shot-reads.d.ts +55 -0
  62. package/src/lib/components/use-one-shot-reads.js +168 -0
  63. package/src/lib/components/use-one-shot-reads.js.map +1 -0
  64. package/src/lib/model/experiments.d.ts +35 -0
  65. package/src/lib/model/experiments.js +34 -0
  66. package/src/lib/model/experiments.js.map +1 -1
  67. package/src/lib/model/overlays.d.ts +13 -2
  68. package/src/lib/model/overlays.js +18 -6
  69. package/src/lib/model/overlays.js.map +1 -1
  70. package/src/lib/plugin.js +13 -8
  71. package/src/lib/plugin.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/host-experiments-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n HOST_EVENT_TYPES,\n pluginDocsHelp,\n SITE_EVENT_TYPES,\n} from '@aglyn/aglyn'\nimport { compareVariants, summarizeVariantStats, validateExperiment, type ExperimentTarget, type ExperimentVariant, type HostExperiment } from '../model'\nimport {\n mdiChartBar,\n mdiDeleteOutline,\n mdiPause,\n mdiPencilOutline,\n mdiPlay,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n deleteDoc,\n doc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n} from 'firebase/firestore'\nimport { useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostActivityLogger,\n usePagedCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n ExperimentResultZone,\n ExperimentVariantsZone,\n marketingExperimentVariantDrafts,\n type MarketingExperimentVariantDraft,\n} from './experiment-zones'\n\nexport interface HostExperimentsCardProps {\n hostId: string\n /** Resolved entitlement source (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\ntype ExperimentDraft = HostExperiment & { $id?: string }\n\nconst STATUS_COLORS: Record<string, 'default' | 'success' | 'info' | 'warning'> =\n {\n draft: 'default',\n running: 'success',\n paused: 'warning',\n done: 'info',\n }\n\n/**\n * Experiments manager (AGL-252): create screen/section/email A/B tests\n * with weighted variants and a conversion goal; start/pause/finish them\n * and read exposures/conversions per variant. Business tier\n * (`ab-testing`).\n */\nexport function HostExperimentsCard(props: HostExperimentsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { org } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId)\n const entitled = checkEntitlement(org, 'abTesting')\n\n const {\n rows: experimentRows,\n hasMore: hasMoreExperiments,\n page: experimentPage,\n setPage: setExperimentPage,\n pageSize: experimentPageSize,\n setPageSize: setExperimentPageSize,\n status: experimentsStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored experiment into `editor`\n * and writes all of it back, so `merge: true` protects nothing — every\n * field is in the payload. `status` is the one that bites: it is\n * otherwise only ever moved by the start/pause/finish controls below, so\n * a cached seed can quietly restart a finished test, or stop a running\n * one, along with reverting its variant weights and goal.\n */\n fromCache: experimentsFromCache,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n /*\n * Ordered by the server and paged (AGL-2501, AGL-2292).\n *\n * `limit(50)` with no `orderBy` is answered in DOCUMENT-ID order over\n * ids from `createResourceUid()`, so the window was an arbitrary fifty\n * that a `localeCompare` in the browser arranged into a convincing A-to-Z\n * page. A site past fifty experiments could not reach the rest, and\n * the ones missing left no gap on screen.\n *\n * `name` is safe to order on, checked against the writers rather than\n * assumed: this card is the only thing that creates an experiment,\n * `newExperiment()` seeds `name: ''` and `validateExperiment` refuses\n * a save without one, the two server paths (`email-events`,\n * `campaign-send`) only ever `update` an existing document, and\n * `experiments` is not in `IMPORTABLE_FIELDS`. There is no writer that\n * can produce a nameless experiment for `orderBy` to drop.\n */\n orderBy('name'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Soft-deleted rows are dropped HERE and not in the query, which is why a\n * page can render fewer rows than its size: Firestore cannot ask for\n * documents that LACK a field, and a live experiment has no `deletedAt` at\n * all. The rows are not re-sorted — the server already ordered them, and\n * re-sorting a page of a name-ordered walk is the lie the old code told.\n */\n const experiments: ExperimentDraft[] = experimentRows.filter(\n (experiment: any) => !experiment.deletedAt,\n )\n const { data: screenDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'screens'), limit(100)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const screenOptions = [...(screenDocs ?? [])]\n .filter((screen: any) => !screen.deletedAt)\n .map((screen: any) => ({\n id: screen.$id as string,\n name: (screen.displayName ?? screen.name ?? screen.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const [editor, setEditor] = useState<ExperimentDraft | null>(null)\n // Versions of the screen under test (AGL-253): variants pin one each.\n const { data: versionDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'screens',\n editor?.screenId ?? '-none-',\n 'versions',\n ),\n limit(50),\n ),\n [firestore, hostId, editor?.screenId],\n { idField: '$id' },\n )\n const versionOptions = [...(versionDocs ?? [])]\n .filter((version: any) => !version.deletedAt)\n .map((version: any) => ({\n id: version.$id as string,\n name: (version.name ?? version.displayName ?? version.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n const [results, setResults] = useState<{\n experiment: ExperimentDraft\n stats: Record<string, { exposures?: number; conversions?: number }>\n } | null>(null)\n\n const patch = (partial: Partial<ExperimentDraft>) =>\n setEditor((previous) =>\n previous ? { ...previous, ...partial } : previous,\n )\n /**\n * What a widget in the variants zone proposes, put into the editor's own\n * fields as unsaved changes. Only the variants the editor already holds are\n * filled, in order: the test's shape — how many arms, their ids, their\n * weights, the version each screen variant pins — is the person's, and a\n * proposal that could add or drop an arm would be changing the test rather\n * than writing copy for it. A field a widget left empty is left alone, so\n * a proposal for an email's subject does not blank a body someone typed.\n */\n const proposeVariants = (\n proposed: readonly MarketingExperimentVariantDraft[],\n ) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index) => {\n const draft = proposed[index]\n if (!draft) return variant\n return {\n ...variant,\n ...(draft.name ? { name: draft.name } : {}),\n ...(draft.subject ? { subject: draft.subject } : {}),\n ...(draft.body ? { body: draft.body } : {}),\n }\n }),\n }\n : previous,\n )\n const patchVariant = (index: number, partial: Partial<ExperimentVariant>) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index2) =>\n index2 === index ? { ...variant, ...partial } : variant,\n ),\n }\n : previous,\n )\n\n const newExperiment = (): ExperimentDraft => ({\n name: '',\n status: 'draft',\n target: 'screen' as ExperimentTarget,\n variants: [\n { id: 'a', name: 'A (control)', weight: 1 },\n { id: 'b', name: 'B', weight: 1 },\n ],\n goal: { event: 'formSubmission' },\n })\n\n const handleSave = async () => {\n if (!editor) return\n const problem = validateExperiment(editor)\n if (problem) {\n return void enqueueSnackbar(problem, {\n variant: 'warning',\n persist: false,\n })\n }\n const id = editor.$id ?? createResourceUid()\n const { $id: _ignored, ...payload } = editor\n try {\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * `merge: true` is here so a cleared end date or auto-winner overwrites\n * (AGL-273), not to protect untouched fields — there are none, because\n * the payload is the whole stored row spread into `editor`. `status`\n * and `winnerVariantId` ride along with it, and those are otherwise\n * only moved by `setStatus`, so a cached seed can restart a test the\n * owner finished or halt one that is splitting live traffic.\n *\n * Only the edit path. A NEW experiment comes from `newExperiment()` at\n * a fresh uid and can overwrite nothing, and the first snapshot of any\n * listener is `fromCache: true`, so guarding a create would refuse a\n * save that was never unsafe.\n *\n * The guard WRAPS the write, and the activity log with it: a logged\n * \"Updated experiment\" whose write never happened leaves the history\n * disagreeing with the experiment it claims to explain.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'experiment',\n unreadable: Boolean(editor.$id) && experimentsStatus === 'error',\n fromCache: Boolean(editor.$id) && experimentsFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'experiments', id),\n {\n ...JSON.parse(JSON.stringify(payload)),\n // Clearing the end date / auto-winner must overwrite — a\n // merge-set keeps absent keys otherwise (AGL-273).\n endAtMs: editor.endAtMs ?? null,\n autoWinner: editor.autoWinner ?? null,\n updatedAt: Timestamp.now(),\n ...(editor.$id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n )\n logActivity(\n editor.$id ? 'Updated experiment' : 'Created experiment',\n { type: 'content', id, name: editor.name },\n )\n },\n )\n // A refusal keeps the editor open with everything that was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n enqueueSnackbar('Experiment saved', {\n variant: 'success',\n persist: false,\n })\n setEditor(null)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n }\n }\n\n const setStatus = async (\n experiment: ExperimentDraft,\n status: HostExperiment['status'],\n winnerVariantId?: string,\n ) => {\n if (!experiment.$id) return\n // One running experiment per screen (AGL-265): the page runner only\n // serves the first, so a second would silently never split traffic.\n if (status === 'running' && experiment.screenId) {\n const clash = experiments.find(\n (candidate) =>\n candidate.$id !== experiment.$id &&\n candidate.status === 'running' &&\n candidate.screenId === experiment.screenId,\n )\n if (clash) {\n return void enqueueSnackbar(\n `\"${clash.name}\" is already running on that screen — pause or ` +\n 'finish it first',\n { variant: 'warning', persist: false },\n )\n }\n }\n await setDoc(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n {\n status,\n ...(winnerVariantId ? { winnerVariantId } : {}),\n updatedAt: Timestamp.now(),\n },\n { merge: true },\n )\n logActivity(`Experiment ${status}`, {\n type: 'content',\n id: experiment.$id,\n name: experiment.name,\n })\n }\n\n const handleDelete = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const accepted = await confirm({\n title: 'Delete experiment?',\n description: `\"${experiment.name}\" and its results are removed.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n // Resolves with no value and rejects on cancel — gating on the\n // resolved value alone made this always return (AGL-950).\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n )\n }\n\n const openResults = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const snapshot = await getDocs(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'experiments',\n experiment.$id,\n 'stats',\n ),\n )\n const stats: Record<string, any> = {}\n snapshot.forEach((entry) => {\n stats[entry.id] = entry.data()\n })\n setResults({ experiment, stats })\n }\n\n /*\n * One row per experiment. The row opens its results, the one thing every\n * experiment has to show; starting, pausing, editing and deleting it are in\n * the row's menu rather than a row of buttons one mis-click from each other.\n */\n const experimentActions = (experiment: ExperimentDraft): RowActionsMenuItem[] => [\n ...(experiment.status === 'running'\n ? [\n {\n key: 'pause',\n label: 'Pause',\n icon: <MdiIcon path={mdiPause.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'paused'),\n },\n ]\n : experiment.status !== 'done'\n ? [\n {\n key: 'start',\n label: 'Start',\n icon: <MdiIcon path={mdiPlay.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'running'),\n },\n ]\n : []),\n {\n key: 'edit',\n label: 'Edit',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n onClick: () => setEditor({ ...experiment }),\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(experiment),\n },\n ]\n const experimentColumns: GridColDef<ExperimentDraft>[] = [\n { field: 'name', headerName: 'Experiment', flex: 1, minWidth: 200 },\n {\n field: 'target',\n headerName: 'Tests',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, experiment) =>\n `${experiment.target} · ${(experiment.variants ?? []).length} variants`,\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n renderCell: ({ row: experiment }) => (\n <Chip\n size=\"small\"\n color={STATUS_COLORS[experiment.status] ?? 'default'}\n label={experiment.status}\n />\n ),\n },\n listActionsColumn(\n (experiment: ExperimentDraft) => (\n <ListRowActions\n label={experiment.name || 'this experiment'}\n quick={{\n icon: mdiChartBar.path,\n label: 'Results',\n onClick: () => void openResults(experiment),\n }}\n items={experimentActions(experiment)}\n />\n ),\n ),\n ]\n\n return (\n <CardDisplay\n header=\"Experiments\"\n help={pluginDocsHelp('emailCampaigns', { anchor: '#experiments' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!entitled ? (\n <Alert severity=\"info\">\n {'A/B experiments are included in the Business plan — see ' +\n 'Billing to upgrade.'}\n </Alert>\n ) : (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Split traffic between variants of a screen, a section, or a ' +\n 'campaign email, and measure which one converts. Visitors ' +\n 'are assigned deterministically, so everyone keeps seeing ' +\n 'the same variant.'}\n </Typography>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => setEditor(newExperiment())}\n >\n {'New experiment'}\n </Button>\n {experiments.length === 0 && !hasMoreExperiments ? null : (\n <>\n <ListTable\n aria-label=\"Experiments\"\n rows={experiments}\n columns={experimentColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, experiment) => void openResults(experiment)}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n <ListPagination\n page={experimentPage}\n pageSize={experimentPageSize}\n // The rows LEFT after the soft-delete filter, not the page the\n // server returned: the footer must describe what is on screen.\n rowCount={experiments.length}\n hasMore={hasMoreExperiments}\n onPageChange={setExperimentPage}\n onPageSizeChange={setExperimentPageSize}\n />\n </>\n )}\n </Stack>\n )}\n\n <Dialog\n open={Boolean(editor)}\n onClose={() => setEditor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>\n {editor?.$id ? 'Edit experiment' : 'New experiment'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={editor?.name ?? ''}\n onChange={(event) => patch({ name: event.target.value })}\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n size=\"small\"\n label=\"Tests\"\n value={editor?.target ?? 'screen'}\n onChange={(event) =>\n patch({ target: event.target.value as ExperimentTarget })\n }\n sx={{ minWidth: 140 }}\n >\n <MenuItem value=\"screen\">{'A screen'}</MenuItem>\n <MenuItem value=\"section\">{'A section'}</MenuItem>\n <MenuItem value=\"email\">{'An email'}</MenuItem>\n </TextField>\n {editor?.target !== 'email' ? (\n <TextField\n select\n size=\"small\"\n label=\"Screen\"\n value={editor?.screenId ?? ''}\n onChange={(event) => patch({ screenId: event.target.value })}\n sx={{ flex: 1 }}\n >\n {screenOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'section' ? (\n <TextField\n size=\"small\"\n label=\"Element id\"\n helperText=\"The canvas element the variants swap (from the besigner)\"\n value={editor?.nodeId ?? ''}\n onChange={(event) => patch({ nodeId: event.target.value })}\n />\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"Conversion goal\"\n value={editor?.goal?.event ?? 'formSubmission'}\n onChange={(event) =>\n patch({ goal: { ...editor?.goal, event: event.target.value } })\n }\n sx={{ maxWidth: 260 }}\n >\n {[...HOST_EVENT_TYPES, ...SITE_EVENT_TYPES].map((eventType) => (\n <MenuItem key={eventType} value={eventType}>\n {eventType}\n </MenuItem>\n ))}\n </TextField>\n {/* Schedule + auto-winner (AGL-273). */}\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n type=\"datetime-local\"\n label=\"Ends at (optional)\"\n slotProps={{ inputLabel: { shrink: true } }}\n value={\n editor?.endAtMs\n ? new Date(\n editor.endAtMs -\n new Date().getTimezoneOffset() * 60000,\n )\n .toISOString()\n .slice(0, 16)\n : ''\n }\n onChange={(event) => {\n const ms = event.target.value\n ? new Date(event.target.value).getTime()\n : undefined\n patch({\n endAtMs: Number.isFinite(ms as number) ? ms : undefined,\n })\n }}\n helperText=\"Past this, visitors get the default\"\n />\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(editor?.autoWinner)}\n onChange={(event) =>\n patch({\n autoWinner: event.target.checked\n ? { minExposures: 200, confidence: 0.95 }\n : undefined,\n })\n }\n />\n }\n label=\"Auto-declare winner\"\n />\n {editor?.autoWinner ? (\n <>\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Min exposures / variant\"\n value={editor.autoWinner.minExposures}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n minExposures: Number(event.target.value),\n },\n })\n }\n sx={{ width: 170 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Confidence\"\n value={String(editor.autoWinner.confidence)}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n confidence: Number(event.target.value),\n },\n })\n }\n sx={{ width: 120 }}\n >\n <MenuItem value=\"0.9\">{'90%'}</MenuItem>\n <MenuItem value=\"0.95\">{'95%'}</MenuItem>\n <MenuItem value=\"0.99\">{'99%'}</MenuItem>\n </TextField>\n </>\n ) : null}\n </Stack>\n <Typography variant=\"subtitle2\">{'Variants'}</Typography>\n {(editor?.variants ?? []).map((variant, index) => (\n <Stack key={variant.id} spacing={1}>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label={`Variant ${variant.id.toUpperCase()}`}\n value={variant.name ?? ''}\n onChange={(event) =>\n patchVariant(index, { name: event.target.value })\n }\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Weight\"\n value={variant.weight ?? 1}\n onChange={(event) =>\n patchVariant(index, { weight: Number(event.target.value) })\n }\n sx={{ width: 100 }}\n />\n {editor?.target !== 'email' ? (\n // Screen/section variants pin a screen version\n // (AGL-253); empty = the published version.\n <TextField\n select\n size=\"small\"\n label=\"Version\"\n value={variant.versionId ?? ''}\n onChange={(event) =>\n patchVariant(index, { versionId: event.target.value })\n }\n sx={{ minWidth: 160 }}\n >\n <MenuItem value=\"\">{'Published (control)'}</MenuItem>\n {versionOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'email' ? (\n // Email variants override the campaign copy (AGL-255).\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Subject override\"\n value={variant.subject ?? ''}\n onChange={(event) =>\n patchVariant(index, { subject: event.target.value })\n }\n sx={{ width: 220 }}\n />\n <TextField\n size=\"small\"\n label=\"Body override\"\n value={variant.body ?? ''}\n onChange={(event) =>\n patchVariant(index, { body: event.target.value })\n }\n multiline\n maxRows={3}\n sx={{ flex: 1 }}\n />\n </Stack>\n ) : null}\n </Stack>\n ))}\n {/*\n Beneath the variants, never above them: a widget here writes copy\n FOR the list that precedes it, and the list is what the person\n came to the dialog for.\n */}\n {editor ? (\n <ExperimentVariantsZone\n hostId={hostId}\n experimentId={editor.$id ?? ''}\n name={editor.name ?? ''}\n target={editor.target ?? 'screen'}\n goal={editor.goal?.event ?? ''}\n variants={marketingExperimentVariantDrafts(editor.variants)}\n proposeVariants={proposeVariants}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setEditor(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => void handleSave()}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(results)}\n onClose={() => setResults(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {`Results — ${results?.experiment.name ?? ''}`}\n {results?.experiment.autoCompleted ? (\n // The track pipeline decided this one (AGL-273).\n <Chip\n size=\"small\"\n color=\"success\"\n label=\"Auto-completed\"\n sx={{ ml: 1 }}\n />\n ) : null}\n </DialogTitle>\n <DialogContent>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Variant'}</TableCell>\n <TableCell>{'Exposures'}</TableCell>\n <TableCell>{'Conversions'}</TableCell>\n <TableCell>{'Rate'}</TableCell>\n <TableCell>{'vs control'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {(results?.experiment.variants ?? []).map((variant, index) => {\n const summary = summarizeVariantStats(\n results?.stats[variant.id] ?? {},\n )\n // Lift + z-test confidence vs the first variant (AGL-265).\n const control = results?.experiment.variants[0]\n const comparison =\n index > 0 && control\n ? compareVariants(\n results?.stats[control.id] ?? {},\n results?.stats[variant.id] ?? {},\n )\n : null\n const leader =\n summary.exposures > 0 &&\n (results?.experiment.variants ?? []).every((other) => {\n const otherSummary = summarizeVariantStats(\n results?.stats[other.id] ?? {},\n )\n return summary.rate >= otherSummary.rate\n })\n return (\n <TableRow key={variant.id} selected={leader}>\n <TableCell>\n {variant.name ?? variant.id.toUpperCase()}\n {results?.experiment.winnerVariantId === variant.id\n ? ' 🏆'\n : leader\n ? ' ▲'\n : ''}\n </TableCell>\n <TableCell>{summary.exposures}</TableCell>\n <TableCell>{summary.conversions}</TableCell>\n <TableCell>\n {`${(summary.rate * 100).toFixed(1)}%`}\n </TableCell>\n <TableCell>\n {comparison\n ? `${\n comparison.lift != null\n ? `${comparison.lift >= 0 ? '+' : ''}${(comparison.lift * 100).toFixed(0)}%`\n : '—'\n }${\n comparison.confidence != null\n ? ` · ${(comparison.confidence * 100).toFixed(0)}% conf.`\n : ' · needs data'\n }`\n : 'control'}\n </TableCell>\n <TableCell align=\"right\">\n {results?.experiment.status !== 'done' ? (\n <Button\n size=\"small\"\n onClick={() => {\n if (results) {\n void setStatus(\n results.experiment,\n 'done',\n variant.id,\n )\n setResults(null)\n }\n }}\n >\n {'Pick winner'}\n </Button>\n ) : null}\n </TableCell>\n </TableRow>\n )\n })}\n </TableBody>\n </ScrollTable>\n {/*\n Below the figures, so anything a widget says about this test is\n read after the counts it is talking about, and the test is named\n as the results reader labels it — its name, or its id where it has\n none, which is the same fallback that reader takes.\n */}\n {results ? (\n <ExperimentResultZone\n hostId={hostId}\n experimentId={results.experiment.$id ?? ''}\n test={results.experiment.name?.trim() || (results.experiment.$id ?? '')}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setResults(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostExperimentsCard.displayName = 'HostExperimentsCard'\n\nexport default HostExperimentsCard\n"],"names":["checkEntitlement","createResourceUid","HOST_EVENT_TYPES","pluginDocsHelp","SITE_EVENT_TYPES","compareVariants","summarizeVariantStats","validateExperiment","mdiChartBar","mdiDeleteOutline","mdiPause","mdiPencilOutline","mdiPlay","CardDisplay","MdiIcon","useConfirmationContext","ListPagination","ListRowActions","ListTable","listActionsColumn","ScrollTable","TABLE_ROW_HEIGHT","useSnackbar","Timestamp","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","deleteDoc","doc","getDocs","limit","orderBy","query","setDoc","useState","useFirestore","useFirestoreCollection","useHostActivityLogger","usePagedCollection","writeGuardedBySeed","ExperimentResultZone","ExperimentVariantsZone","marketingExperimentVariantDrafts","STATUS_COLORS","draft","running","paused","done","HostExperimentsCard","props","editor","results","hostId","firestore","org","enqueueSnackbar","confirm","logActivity","entitled","rows","experimentRows","hasMore","hasMoreExperiments","page","experimentPage","setPage","setExperimentPage","pageSize","experimentPageSize","setPageSize","setExperimentPageSize","status","experimentsStatus","fromCache","experimentsFromCache","pageLimit","idField","experiments","filter","experiment","deletedAt","data","screenDocs","screenOptions","screen","map","id","$id","name","displayName","sort","a","b","localeCompare","setEditor","versionDocs","screenId","versionOptions","version","setResults","patch","partial","previous","proposeVariants","proposed","variants","variant","index","subject","body","patchVariant","index2","newExperiment","target","weight","goal","event","handleSave","problem","persist","_ignored","payload","verdict","unreadable","Boolean","JSON","parse","stringify","endAtMs","autoWinner","updatedAt","now","createdAt","merge","type","ok","message","error","console","setStatus","winnerVariantId","clash","find","candidate","handleDelete","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","openResults","snapshot","stats","forEach","entry","experimentActions","key","label","icon","path","size","onClick","destructive","experimentColumns","field","headerName","flex","minWidth","valueGetter","_value","length","width","renderCell","row","quick","items","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","spacing","sx","alignSelf","aria-label","columns","rowHeight","onOpen","_id","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","required","value","onChange","mt","direction","select","option","helperText","nodeId","eventType","alignItems","flexWrap","slotProps","inputLabel","shrink","Date","getTimezoneOffset","toISOString","slice","ms","getTime","undefined","Number","isFinite","control","checked","minExposures","confidence","String","toUpperCase","versionId","multiline","maxRows","experimentId","autoCompleted","ml","align","summary","comparison","leader","exposures","every","other","otherSummary","rate","selected","conversions","toFixed","lift","test","trim"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/host-experiments-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n HOST_EVENT_TYPES,\n pluginDocsHelp,\n SITE_EVENT_TYPES,\n} from '@aglyn/aglyn'\nimport { describeVariantComparison, experimentResultRows, validateExperiment, type ExperimentTarget, type ExperimentVariant, type HostExperiment } from '../model'\nimport {\n mdiChartBar,\n mdiDeleteOutline,\n mdiPause,\n mdiPencilOutline,\n mdiPlay,\n} from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n deleteDoc,\n doc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n} from 'firebase/firestore'\nimport { useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostActivityLogger,\n usePagedCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n ExperimentResultZone,\n ExperimentVariantsZone,\n marketingExperimentVariantDrafts,\n type MarketingExperimentVariantDraft,\n} from './experiment-zones'\n\nexport interface HostExperimentsCardProps {\n hostId: string\n /** Resolved entitlement source (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\ntype ExperimentDraft = HostExperiment & { $id?: string }\n\n/** An experiment's status as a chip color, in every list of experiments. */\nexport const EXPERIMENT_STATUS_COLORS: Record<\n string,\n 'default' | 'success' | 'info' | 'warning'\n> = {\n draft: 'default',\n running: 'success',\n paused: 'warning',\n done: 'info',\n}\n\n/**\n * Experiments manager (AGL-252): create screen/section/email A/B tests\n * with weighted variants and a conversion goal; start/pause/finish them\n * and read exposures/conversions per variant. Business tier\n * (`ab-testing`).\n */\nexport function HostExperimentsCard(props: HostExperimentsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { org } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId)\n const entitled = checkEntitlement(org, 'abTesting')\n\n const {\n rows: experimentRows,\n hasMore: hasMoreExperiments,\n page: experimentPage,\n setPage: setExperimentPage,\n pageSize: experimentPageSize,\n setPageSize: setExperimentPageSize,\n status: experimentsStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored experiment into `editor`\n * and writes all of it back, so `merge: true` protects nothing — every\n * field is in the payload. `status` is the one that bites: it is\n * otherwise only ever moved by the start/pause/finish controls below, so\n * a cached seed can quietly restart a finished test, or stop a running\n * one, along with reverting its variant weights and goal.\n */\n fromCache: experimentsFromCache,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n /*\n * Ordered by the server and paged (AGL-2501, AGL-2292).\n *\n * `limit(50)` with no `orderBy` is answered in DOCUMENT-ID order over\n * ids from `createResourceUid()`, so the window was an arbitrary fifty\n * that a `localeCompare` in the browser arranged into a convincing A-to-Z\n * page. A site past fifty experiments could not reach the rest, and\n * the ones missing left no gap on screen.\n *\n * `name` is safe to order on, checked against the writers rather than\n * assumed: this card is the only thing that creates an experiment,\n * `newExperiment()` seeds `name: ''` and `validateExperiment` refuses\n * a save without one, the two server paths (`email-events`,\n * `campaign-send`) only ever `update` an existing document, and\n * `experiments` is not in `IMPORTABLE_FIELDS`. There is no writer that\n * can produce a nameless experiment for `orderBy` to drop.\n */\n orderBy('name'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Soft-deleted rows are dropped HERE and not in the query, which is why a\n * page can render fewer rows than its size: Firestore cannot ask for\n * documents that LACK a field, and a live experiment has no `deletedAt` at\n * all. The rows are not re-sorted — the server already ordered them, and\n * re-sorting a page of a name-ordered walk is the lie the old code told.\n */\n const experiments: ExperimentDraft[] = experimentRows.filter(\n (experiment: any) => !experiment.deletedAt,\n )\n const [editor, setEditor] = useState<ExperimentDraft | null>(null)\n /*\n * The screens and their versions fill the EDITOR's pickers and nothing\n * else on the card, so they are read while the editor is open and not\n * before: a reader who came to see which tests are running pays for the\n * experiments list alone, not for a hundred screens they never pick from.\n */\n const editorOpen = editor !== null\n const { data: screenDocs } = useFirestoreCollection<any>(\n () =>\n editorOpen\n ? query(collection(firestore, 'hosts', hostId, 'screens'), limit(100))\n : null,\n [firestore, hostId, editorOpen],\n { idField: '$id' },\n )\n const screenOptions = [...(screenDocs ?? [])]\n .filter((screen: any) => !screen.deletedAt)\n .map((screen: any) => ({\n id: screen.$id as string,\n name: (screen.displayName ?? screen.name ?? screen.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n // Versions of the screen under test (AGL-253): variants pin one each.\n const { data: versionDocs } = useFirestoreCollection<any>(\n () =>\n editor?.screenId\n ? query(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'screens',\n editor.screenId,\n 'versions',\n ),\n limit(50),\n )\n : null,\n [firestore, hostId, editor?.screenId],\n { idField: '$id' },\n )\n const versionOptions = [...(versionDocs ?? [])]\n .filter((version: any) => !version.deletedAt)\n .map((version: any) => ({\n id: version.$id as string,\n name: (version.name ?? version.displayName ?? version.$id) as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n const [results, setResults] = useState<{\n experiment: ExperimentDraft\n stats: Record<string, { exposures?: number; conversions?: number }>\n } | null>(null)\n\n const patch = (partial: Partial<ExperimentDraft>) =>\n setEditor((previous) =>\n previous ? { ...previous, ...partial } : previous,\n )\n /**\n * What a widget in the variants zone proposes, put into the editor's own\n * fields as unsaved changes. Only the variants the editor already holds are\n * filled, in order: the test's shape — how many arms, their ids, their\n * weights, the version each screen variant pins — is the person's, and a\n * proposal that could add or drop an arm would be changing the test rather\n * than writing copy for it. A field a widget left empty is left alone, so\n * a proposal for an email's subject does not blank a body someone typed.\n */\n const proposeVariants = (\n proposed: readonly MarketingExperimentVariantDraft[],\n ) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index) => {\n const draft = proposed[index]\n if (!draft) return variant\n return {\n ...variant,\n ...(draft.name ? { name: draft.name } : {}),\n ...(draft.subject ? { subject: draft.subject } : {}),\n ...(draft.body ? { body: draft.body } : {}),\n }\n }),\n }\n : previous,\n )\n const patchVariant = (index: number, partial: Partial<ExperimentVariant>) =>\n setEditor((previous) =>\n previous\n ? {\n ...previous,\n variants: previous.variants.map((variant, index2) =>\n index2 === index ? { ...variant, ...partial } : variant,\n ),\n }\n : previous,\n )\n\n const newExperiment = (): ExperimentDraft => ({\n name: '',\n status: 'draft',\n target: 'screen' as ExperimentTarget,\n variants: [\n { id: 'a', name: 'A (control)', weight: 1 },\n { id: 'b', name: 'B', weight: 1 },\n ],\n goal: { event: 'formSubmission' },\n })\n\n const handleSave = async () => {\n if (!editor) return\n const problem = validateExperiment(editor)\n if (problem) {\n return void enqueueSnackbar(problem, {\n variant: 'warning',\n persist: false,\n })\n }\n const id = editor.$id ?? createResourceUid()\n const { $id: _ignored, ...payload } = editor\n try {\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * `merge: true` is here so a cleared end date or auto-winner overwrites\n * (AGL-273), not to protect untouched fields — there are none, because\n * the payload is the whole stored row spread into `editor`. `status`\n * and `winnerVariantId` ride along with it, and those are otherwise\n * only moved by `setStatus`, so a cached seed can restart a test the\n * owner finished or halt one that is splitting live traffic.\n *\n * Only the edit path. A NEW experiment comes from `newExperiment()` at\n * a fresh uid and can overwrite nothing, and the first snapshot of any\n * listener is `fromCache: true`, so guarding a create would refuse a\n * save that was never unsafe.\n *\n * The guard WRAPS the write, and the activity log with it: a logged\n * \"Updated experiment\" whose write never happened leaves the history\n * disagreeing with the experiment it claims to explain.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'experiment',\n unreadable: Boolean(editor.$id) && experimentsStatus === 'error',\n fromCache: Boolean(editor.$id) && experimentsFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'experiments', id),\n {\n ...JSON.parse(JSON.stringify(payload)),\n // Clearing the end date / auto-winner must overwrite — a\n // merge-set keeps absent keys otherwise (AGL-273).\n endAtMs: editor.endAtMs ?? null,\n autoWinner: editor.autoWinner ?? null,\n updatedAt: Timestamp.now(),\n ...(editor.$id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n )\n logActivity(\n editor.$id ? 'Updated experiment' : 'Created experiment',\n { type: 'content', id, name: editor.name },\n )\n },\n )\n // A refusal keeps the editor open with everything that was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n enqueueSnackbar('Experiment saved', {\n variant: 'success',\n persist: false,\n })\n setEditor(null)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n }\n }\n\n const setStatus = async (\n experiment: ExperimentDraft,\n status: HostExperiment['status'],\n winnerVariantId?: string,\n ) => {\n if (!experiment.$id) return\n // One running experiment per screen (AGL-265): the page runner only\n // serves the first, so a second would silently never split traffic.\n if (status === 'running' && experiment.screenId) {\n const clash = experiments.find(\n (candidate) =>\n candidate.$id !== experiment.$id &&\n candidate.status === 'running' &&\n candidate.screenId === experiment.screenId,\n )\n if (clash) {\n return void enqueueSnackbar(\n `\"${clash.name}\" is already running on that screen — pause or ` +\n 'finish it first',\n { variant: 'warning', persist: false },\n )\n }\n }\n await setDoc(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n {\n status,\n ...(winnerVariantId ? { winnerVariantId } : {}),\n updatedAt: Timestamp.now(),\n },\n { merge: true },\n )\n logActivity(`Experiment ${status}`, {\n type: 'content',\n id: experiment.$id,\n name: experiment.name,\n })\n }\n\n const handleDelete = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const accepted = await confirm({\n title: 'Delete experiment?',\n description: `\"${experiment.name}\" and its results are removed.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n // Resolves with no value and rejects on cancel — gating on the\n // resolved value alone made this always return (AGL-950).\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'experiments', experiment.$id),\n )\n }\n\n const openResults = async (experiment: ExperimentDraft) => {\n if (!experiment.$id) return\n const snapshot = await getDocs(\n collection(\n firestore,\n 'hosts',\n hostId,\n 'experiments',\n experiment.$id,\n 'stats',\n ),\n )\n const stats: Record<string, any> = {}\n snapshot.forEach((entry) => {\n stats[entry.id] = entry.data()\n })\n setResults({ experiment, stats })\n }\n\n /*\n * One row per experiment. The row opens its results, the one thing every\n * experiment has to show; starting, pausing, editing and deleting it are in\n * the row's menu rather than a row of buttons one mis-click from each other.\n */\n const experimentActions = (experiment: ExperimentDraft): RowActionsMenuItem[] => [\n ...(experiment.status === 'running'\n ? [\n {\n key: 'pause',\n label: 'Pause',\n icon: <MdiIcon path={mdiPause.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'paused'),\n },\n ]\n : experiment.status !== 'done'\n ? [\n {\n key: 'start',\n label: 'Start',\n icon: <MdiIcon path={mdiPlay.path} size={0.8} />,\n onClick: () => void setStatus(experiment, 'running'),\n },\n ]\n : []),\n {\n key: 'edit',\n label: 'Edit',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n onClick: () => setEditor({ ...experiment }),\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(experiment),\n },\n ]\n const experimentColumns: GridColDef<ExperimentDraft>[] = [\n { field: 'name', headerName: 'Experiment', flex: 1, minWidth: 200 },\n {\n field: 'target',\n headerName: 'Tests',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, experiment) =>\n `${experiment.target} · ${(experiment.variants ?? []).length} variants`,\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n renderCell: ({ row: experiment }) => (\n <Chip\n size=\"small\"\n color={EXPERIMENT_STATUS_COLORS[experiment.status] ?? 'default'}\n label={experiment.status}\n />\n ),\n },\n listActionsColumn(\n (experiment: ExperimentDraft) => (\n <ListRowActions\n label={experiment.name || 'this experiment'}\n quick={{\n icon: mdiChartBar.path,\n label: 'Results',\n onClick: () => void openResults(experiment),\n }}\n items={experimentActions(experiment)}\n />\n ),\n ),\n ]\n\n return (\n <CardDisplay\n header=\"Experiments\"\n help={pluginDocsHelp('emailCampaigns', { anchor: '#experiments' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!entitled ? (\n <Alert severity=\"info\">\n {'A/B experiments are included in the Business plan — see ' +\n 'Billing to upgrade.'}\n </Alert>\n ) : (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Split traffic between variants of a screen, a section, or a ' +\n 'campaign email, and measure which one converts. Visitors ' +\n 'are assigned deterministically, so everyone keeps seeing ' +\n 'the same variant.'}\n </Typography>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => setEditor(newExperiment())}\n >\n {'New experiment'}\n </Button>\n {experiments.length === 0 && !hasMoreExperiments ? null : (\n <>\n <ListTable\n aria-label=\"Experiments\"\n rows={experiments}\n columns={experimentColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, experiment) => void openResults(experiment)}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n <ListPagination\n page={experimentPage}\n pageSize={experimentPageSize}\n // The rows LEFT after the soft-delete filter, not the page the\n // server returned: the footer must describe what is on screen.\n rowCount={experiments.length}\n hasMore={hasMoreExperiments}\n onPageChange={setExperimentPage}\n onPageSizeChange={setExperimentPageSize}\n />\n </>\n )}\n </Stack>\n )}\n\n <Dialog\n open={Boolean(editor)}\n onClose={() => setEditor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>\n {editor?.$id ? 'Edit experiment' : 'New experiment'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n size=\"small\"\n label=\"Name\"\n required\n value={editor?.name ?? ''}\n onChange={(event) => patch({ name: event.target.value })}\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n size=\"small\"\n label=\"Tests\"\n value={editor?.target ?? 'screen'}\n onChange={(event) =>\n patch({ target: event.target.value as ExperimentTarget })\n }\n sx={{ minWidth: 140 }}\n >\n <MenuItem value=\"screen\">{'A screen'}</MenuItem>\n <MenuItem value=\"section\">{'A section'}</MenuItem>\n <MenuItem value=\"email\">{'An email'}</MenuItem>\n </TextField>\n {editor?.target !== 'email' ? (\n <TextField\n select\n size=\"small\"\n label=\"Screen\"\n value={editor?.screenId ?? ''}\n onChange={(event) => patch({ screenId: event.target.value })}\n sx={{ flex: 1 }}\n >\n {screenOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'section' ? (\n <TextField\n size=\"small\"\n label=\"Element id\"\n helperText=\"The canvas element the variants swap (from the besigner)\"\n value={editor?.nodeId ?? ''}\n onChange={(event) => patch({ nodeId: event.target.value })}\n />\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"Conversion goal\"\n value={editor?.goal?.event ?? 'formSubmission'}\n onChange={(event) =>\n patch({ goal: { ...editor?.goal, event: event.target.value } })\n }\n sx={{ maxWidth: 260 }}\n >\n {[...HOST_EVENT_TYPES, ...SITE_EVENT_TYPES].map((eventType) => (\n <MenuItem key={eventType} value={eventType}>\n {eventType}\n </MenuItem>\n ))}\n </TextField>\n {/* Schedule + auto-winner (AGL-273). */}\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n type=\"datetime-local\"\n label=\"Ends at (optional)\"\n slotProps={{ inputLabel: { shrink: true } }}\n value={\n editor?.endAtMs\n ? new Date(\n editor.endAtMs -\n new Date().getTimezoneOffset() * 60000,\n )\n .toISOString()\n .slice(0, 16)\n : ''\n }\n onChange={(event) => {\n const ms = event.target.value\n ? new Date(event.target.value).getTime()\n : undefined\n patch({\n endAtMs: Number.isFinite(ms as number) ? ms : undefined,\n })\n }}\n helperText=\"Past this, visitors get the default\"\n />\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(editor?.autoWinner)}\n onChange={(event) =>\n patch({\n autoWinner: event.target.checked\n ? { minExposures: 200, confidence: 0.95 }\n : undefined,\n })\n }\n />\n }\n label=\"Auto-declare winner\"\n />\n {editor?.autoWinner ? (\n <>\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Min exposures / variant\"\n value={editor.autoWinner.minExposures}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n minExposures: Number(event.target.value),\n },\n })\n }\n sx={{ width: 170 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Confidence\"\n value={String(editor.autoWinner.confidence)}\n onChange={(event) =>\n patch({\n autoWinner: {\n ...editor.autoWinner!,\n confidence: Number(event.target.value),\n },\n })\n }\n sx={{ width: 120 }}\n >\n <MenuItem value=\"0.9\">{'90%'}</MenuItem>\n <MenuItem value=\"0.95\">{'95%'}</MenuItem>\n <MenuItem value=\"0.99\">{'99%'}</MenuItem>\n </TextField>\n </>\n ) : null}\n </Stack>\n <Typography variant=\"subtitle2\">{'Variants'}</Typography>\n {(editor?.variants ?? []).map((variant, index) => (\n <Stack key={variant.id} spacing={1}>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label={`Variant ${variant.id.toUpperCase()}`}\n value={variant.name ?? ''}\n onChange={(event) =>\n patchVariant(index, { name: event.target.value })\n }\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Weight\"\n value={variant.weight ?? 1}\n onChange={(event) =>\n patchVariant(index, { weight: Number(event.target.value) })\n }\n sx={{ width: 100 }}\n />\n {editor?.target !== 'email' ? (\n // Screen/section variants pin a screen version\n // (AGL-253); empty = the published version.\n <TextField\n select\n size=\"small\"\n label=\"Version\"\n value={variant.versionId ?? ''}\n onChange={(event) =>\n patchVariant(index, { versionId: event.target.value })\n }\n sx={{ minWidth: 160 }}\n >\n <MenuItem value=\"\">{'Published (control)'}</MenuItem>\n {versionOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n {editor?.target === 'email' ? (\n // Email variants override the campaign copy (AGL-255).\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Subject override\"\n value={variant.subject ?? ''}\n onChange={(event) =>\n patchVariant(index, { subject: event.target.value })\n }\n sx={{ width: 220 }}\n />\n <TextField\n size=\"small\"\n label=\"Body override\"\n value={variant.body ?? ''}\n onChange={(event) =>\n patchVariant(index, { body: event.target.value })\n }\n multiline\n maxRows={3}\n sx={{ flex: 1 }}\n />\n </Stack>\n ) : null}\n </Stack>\n ))}\n {/*\n Beneath the variants, never above them: a widget here writes copy\n FOR the list that precedes it, and the list is what the person\n came to the dialog for.\n */}\n {editor ? (\n <ExperimentVariantsZone\n hostId={hostId}\n experimentId={editor.$id ?? ''}\n name={editor.name ?? ''}\n target={editor.target ?? 'screen'}\n goal={editor.goal?.event ?? ''}\n variants={marketingExperimentVariantDrafts(editor.variants)}\n proposeVariants={proposeVariants}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setEditor(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => void handleSave()}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(results)}\n onClose={() => setResults(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {`Results — ${results?.experiment.name ?? ''}`}\n {results?.experiment.autoCompleted ? (\n // The track pipeline decided this one (AGL-273).\n <Chip\n size=\"small\"\n color=\"success\"\n label=\"Auto-completed\"\n sx={{ ml: 1 }}\n />\n ) : null}\n </DialogTitle>\n <DialogContent>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Variant'}</TableCell>\n <TableCell>{'Exposures'}</TableCell>\n <TableCell>{'Conversions'}</TableCell>\n <TableCell>{'Rate'}</TableCell>\n <TableCell>{'vs control'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {/* Lift + z-test confidence vs the first variant (AGL-265). */}\n {(results\n ? experimentResultRows(results.experiment, results.stats)\n : []\n ).map(({ variant, summary, comparison, leader, winner }) => (\n <TableRow key={variant.id} selected={leader}>\n <TableCell>\n {variant.name ?? variant.id.toUpperCase()}\n {winner ? ' 🏆' : leader ? ' ▲' : ''}\n </TableCell>\n <TableCell>{summary.exposures}</TableCell>\n <TableCell>{summary.conversions}</TableCell>\n <TableCell>{`${(summary.rate * 100).toFixed(1)}%`}</TableCell>\n <TableCell>{describeVariantComparison(comparison)}</TableCell>\n <TableCell align=\"right\">\n {results?.experiment.status !== 'done' ? (\n <Button\n size=\"small\"\n onClick={() => {\n if (results) {\n void setStatus(results.experiment, 'done', variant.id)\n setResults(null)\n }\n }}\n >\n {'Pick winner'}\n </Button>\n ) : null}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {/*\n Below the figures, so anything a widget says about this test is\n read after the counts it is talking about, and the test is named\n as the results reader labels it — its name, or its id where it has\n none, which is the same fallback that reader takes.\n */}\n {results ? (\n <ExperimentResultZone\n hostId={hostId}\n experimentId={results.experiment.$id ?? ''}\n test={results.experiment.name?.trim() || (results.experiment.$id ?? '')}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setResults(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostExperimentsCard.displayName = 'HostExperimentsCard'\n\nexport default HostExperimentsCard\n"],"names":["checkEntitlement","createResourceUid","HOST_EVENT_TYPES","pluginDocsHelp","SITE_EVENT_TYPES","describeVariantComparison","experimentResultRows","validateExperiment","mdiChartBar","mdiDeleteOutline","mdiPause","mdiPencilOutline","mdiPlay","CardDisplay","MdiIcon","useConfirmationContext","ListPagination","ListRowActions","ListTable","listActionsColumn","ScrollTable","TABLE_ROW_HEIGHT","useSnackbar","Timestamp","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","deleteDoc","doc","getDocs","limit","orderBy","query","setDoc","useState","useFirestore","useFirestoreCollection","useHostActivityLogger","usePagedCollection","writeGuardedBySeed","ExperimentResultZone","ExperimentVariantsZone","marketingExperimentVariantDrafts","EXPERIMENT_STATUS_COLORS","draft","running","paused","done","HostExperimentsCard","props","editor","results","hostId","firestore","org","enqueueSnackbar","confirm","logActivity","entitled","rows","experimentRows","hasMore","hasMoreExperiments","page","experimentPage","setPage","setExperimentPage","pageSize","experimentPageSize","setPageSize","setExperimentPageSize","status","experimentsStatus","fromCache","experimentsFromCache","pageLimit","idField","experiments","filter","experiment","deletedAt","setEditor","editorOpen","data","screenDocs","screenOptions","screen","map","id","$id","name","displayName","sort","a","b","localeCompare","versionDocs","screenId","versionOptions","version","setResults","patch","partial","previous","proposeVariants","proposed","variants","variant","index","subject","body","patchVariant","index2","newExperiment","target","weight","goal","event","handleSave","problem","persist","_ignored","payload","verdict","unreadable","Boolean","JSON","parse","stringify","endAtMs","autoWinner","updatedAt","now","createdAt","merge","type","ok","message","error","console","setStatus","winnerVariantId","clash","find","candidate","handleDelete","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","openResults","snapshot","stats","forEach","entry","experimentActions","key","label","icon","path","size","onClick","destructive","experimentColumns","field","headerName","flex","minWidth","valueGetter","_value","length","width","renderCell","row","quick","items","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","spacing","sx","alignSelf","aria-label","columns","rowHeight","onOpen","_id","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","required","value","onChange","mt","direction","select","option","helperText","nodeId","eventType","alignItems","flexWrap","slotProps","inputLabel","shrink","Date","getTimezoneOffset","toISOString","slice","ms","getTime","undefined","Number","isFinite","control","checked","minExposures","confidence","String","toUpperCase","versionId","multiline","maxRows","experimentId","autoCompleted","ml","align","summary","comparison","leader","winner","selected","exposures","conversions","rate","toFixed","test","trim"],"mappings":"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"}
@@ -1,12 +1,27 @@
1
1
  export interface HostMarketingSummaryCardProps {
2
2
  hostId: string;
3
3
  }
4
+ /**
5
+ * How many overlays the live count reads.
6
+ *
7
+ * "Live" is the one figure here a server aggregate cannot answer — it needs
8
+ * `enabled` and the schedule window checked against the clock — so it is
9
+ * counted over a read of the overlays themselves, ordered and ceilinged the
10
+ * way the Overlays section reads them, with a probe that says when the site
11
+ * has more.
12
+ */
13
+ export declare const LIVE_OVERLAY_CEILING = 50;
4
14
  /**
5
15
  * Marketing at a glance (wave v8): one row of numbers across the
6
16
  * channels this page manages — live overlays and their lifetime
7
17
  * engagement, campaign sends/opens/clicks, and experiment states — so
8
18
  * the hub answers "is anything running and is it working" without
9
19
  * opening each card.
20
+ *
21
+ * Every total is a server aggregate over the whole collection (see
22
+ * `marketing-aggregates.ts`), read once rather than listened to: the
23
+ * overlays' counters move on every impression, and a listener over them
24
+ * would re-deliver a document per impression while the page stays open.
10
25
  */
11
26
  export declare function HostMarketingSummaryCard(props: HostMarketingSummaryCardProps): import("react").JSX.Element;
12
27
  export declare namespace HostMarketingSummaryCard {
@@ -15,121 +15,107 @@
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
- import { overlayActiveAt } from "../model/index.js";
18
+ import { overlayStatus } from "../model/index.js";
19
19
  import { CardDisplay } from "@aglyn/shared-ui-jsx";
20
20
  import { Stack, Typography } from "@mui/material";
21
- import { collection, limit, query } from "firebase/firestore";
22
- import { useMemo } from "react";
23
- import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
21
+ import { collection, getDocs, limit, orderBy, query } from "firebase/firestore";
22
+ import { useFirestore } from "@aglyn/tenant-feature-instance";
23
+ import { ceilingedWindow } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
24
24
  import { pluginDocsHelp } from "@aglyn/aglyn";
25
25
  import { campaignSendsQuery } from "./campaign-queries.js";
26
+ import { readSendFigures, readSiteExperimentFigures, readSiteOverlayFigures } from "./marketing-aggregates.js";
26
27
  import { useMarketingOrgId } from "./marketing-org-mount.js";
28
+ import { useOneShotRead } from "./use-one-shot-reads.js";
29
+ /**
30
+ * How many overlays the live count reads.
31
+ *
32
+ * "Live" is the one figure here a server aggregate cannot answer — it needs
33
+ * `enabled` and the schedule window checked against the clock — so it is
34
+ * counted over a read of the overlays themselves, ordered and ceilinged the
35
+ * way the Overlays section reads them, with a probe that says when the site
36
+ * has more.
37
+ */ export const LIVE_OVERLAY_CEILING = 50;
38
+ /** A figure as the row prints it: a dash for one the server did not answer. */ const figure = (value)=>value == null ? '—' : value.toLocaleString();
27
39
  /**
28
40
  * Marketing at a glance (wave v8): one row of numbers across the
29
41
  * channels this page manages — live overlays and their lifetime
30
42
  * engagement, campaign sends/opens/clicks, and experiment states — so
31
43
  * the hub answers "is anything running and is it working" without
32
44
  * opening each card.
45
+ *
46
+ * Every total is a server aggregate over the whole collection (see
47
+ * `marketing-aggregates.ts`), read once rather than listened to: the
48
+ * overlays' counters move on every impression, and a listener over them
49
+ * would re-deliver a document per impression while the page stays open.
33
50
  */ export function HostMarketingSummaryCard(props) {
51
+ var _live_value, _overlayFigures_value, _overlayFigures_value1;
34
52
  const { hostId } = props;
35
53
  const firestore = useFirestore();
36
54
  const { orgId } = useMarketingOrgId(hostId);
37
- const { data: overlayDocs } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'overlays'), limit(50)), [
55
+ const overlayFigures = useOneShotRead(()=>readSiteOverlayFigures(firestore, hostId), [
38
56
  firestore,
39
57
  hostId
40
- ], {
41
- idField: '$id'
42
- });
58
+ ]);
59
+ const live = useOneShotRead(()=>getDocs(query(collection(firestore, 'hosts', hostId, 'overlays'), // Ordered for the reason the Overlays section gives: `name` is on
60
+ // every overlay its only creator writes, and an unordered `limit`
61
+ // is an arbitrary window rather than the first N of anything.
62
+ orderBy('name'), limit(LIVE_OVERLAY_CEILING + 1))).then((snapshot)=>{
63
+ const { rows, truncated } = ceilingedWindow(snapshot.docs.map((entry)=>entry.data()), LIVE_OVERLAY_CEILING);
64
+ const nowMs = Date.now();
65
+ return {
66
+ count: rows.filter((overlay)=>!overlay.deletedAt && overlayStatus(overlay, nowMs) === 'live').length,
67
+ truncated
68
+ };
69
+ }), [
70
+ firestore,
71
+ hostId
72
+ ]);
43
73
  // The org's sends, narrowed to the ones this site sent.
44
- const { data: campaignDocs } = useFirestoreCollection(()=>{
74
+ const sendFigures = useOneShotRead(()=>{
45
75
  const sends = campaignSendsQuery(firestore, orgId, hostId);
46
- return sends ? query(sends, limit(50)) : null;
76
+ return sends ? readSendFigures(sends) : null;
47
77
  }, [
48
78
  firestore,
49
79
  orgId,
50
80
  hostId
51
- ], {
52
- idField: '$id'
53
- });
54
- const { data: experimentDocs } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'experiments'), limit(50)), [
81
+ ]);
82
+ const experimentFigures = useOneShotRead(()=>readSiteExperimentFigures(firestore, hostId), [
55
83
  firestore,
56
84
  hostId
57
- ], {
58
- idField: '$id'
59
- });
60
- const summary = useMemo(()=>{
61
- const overlays = (overlayDocs != null ? overlayDocs : []).filter((doc)=>!doc.deletedAt);
62
- const liveOverlays = overlays.filter((overlay)=>overlay.enabled !== false && overlayActiveAt(overlay, Date.now())).length;
63
- let impressions = 0;
64
- let overlayClicks = 0;
65
- for (const overlay of overlays){
66
- var _ref, _ref1;
67
- var _overlay_stats, _overlay_stats1;
68
- impressions += Number((_ref = (_overlay_stats = overlay.stats) == null ? void 0 : _overlay_stats.impressions) != null ? _ref : 0);
69
- overlayClicks += Number((_ref1 = (_overlay_stats1 = overlay.stats) == null ? void 0 : _overlay_stats1.clicks) != null ? _ref1 : 0);
70
- }
71
- const campaigns = (campaignDocs != null ? campaignDocs : []).filter((doc)=>!doc.deletedAt);
72
- let sent = 0;
73
- let opens = 0;
74
- let emailClicks = 0;
75
- let scheduled = 0;
76
- for (const campaign of campaigns){
77
- var _ref2, _ref3, _ref4;
78
- var _campaign_stats, _campaign_stats1, _campaign_stats2;
79
- sent += Number((_ref2 = (_campaign_stats = campaign.stats) == null ? void 0 : _campaign_stats.sent) != null ? _ref2 : 0);
80
- opens += Number((_ref3 = (_campaign_stats1 = campaign.stats) == null ? void 0 : _campaign_stats1.opens) != null ? _ref3 : 0);
81
- emailClicks += Number((_ref4 = (_campaign_stats2 = campaign.stats) == null ? void 0 : _campaign_stats2.clicks) != null ? _ref4 : 0);
82
- if (campaign.status === 'scheduled') scheduled += 1;
83
- }
84
- const experiments = (experimentDocs != null ? experimentDocs : []).filter((doc)=>!doc.deletedAt);
85
- const running = experiments.filter((experiment)=>experiment.status === 'running').length;
86
- const decided = experiments.filter((experiment)=>experiment.winnerVariantId).length;
87
- return {
88
- liveOverlays,
89
- impressions,
90
- overlayClicks,
91
- sent,
92
- opens,
93
- emailClicks,
94
- scheduled,
95
- running,
96
- decided
97
- };
98
- }, [
99
- overlayDocs,
100
- campaignDocs,
101
- experimentDocs
102
85
  ]);
86
+ const sends = sendFigures.value;
87
+ const tests = experimentFigures.value;
103
88
  const metrics = [
104
89
  {
105
90
  label: 'Live overlays',
106
- value: String(summary.liveOverlays)
91
+ value: live.value ? figure(live.value.count) : '—',
92
+ note: ((_live_value = live.value) == null ? void 0 : _live_value.truncated) ? `of the first ${LIVE_OVERLAY_CEILING} by name` : undefined
107
93
  },
108
94
  {
109
95
  label: 'Overlay views',
110
- value: summary.impressions.toLocaleString()
96
+ value: figure((_overlayFigures_value = overlayFigures.value) == null ? void 0 : _overlayFigures_value.views)
111
97
  },
112
98
  {
113
99
  label: 'Overlay clicks',
114
- value: summary.overlayClicks.toLocaleString()
100
+ value: figure((_overlayFigures_value1 = overlayFigures.value) == null ? void 0 : _overlayFigures_value1.clicks)
115
101
  },
116
102
  {
117
103
  label: 'Emails sent',
118
- value: summary.sent.toLocaleString()
104
+ value: figure(sends == null ? void 0 : sends.sent)
119
105
  },
120
106
  {
121
107
  label: 'Opens / clicks',
122
- value: `${summary.opens.toLocaleString()} / ${summary.emailClicks.toLocaleString()}`
108
+ value: `${figure(sends == null ? void 0 : sends.opens)} / ${figure(sends == null ? void 0 : sends.clicks)}`
123
109
  },
124
- ...summary.scheduled ? [
110
+ ...(sends == null ? void 0 : sends.scheduled) ? [
125
111
  {
126
112
  label: 'Scheduled sends',
127
- value: String(summary.scheduled)
113
+ value: figure(sends.scheduled)
128
114
  }
129
115
  ] : [],
130
116
  {
131
117
  label: 'Experiments',
132
- value: `${summary.running} running · ${summary.decided} decided`
118
+ value: `${figure(tests == null ? void 0 : tests.running)} running · ${figure(tests == null ? void 0 : tests.decided)} decided`
133
119
  }
134
120
  ];
135
121
  return /*#__PURE__*/ _jsx(CardDisplay, {
@@ -157,7 +143,12 @@ import { useMarketingOrgId } from "./marketing-org-mount.js";
157
143
  /*#__PURE__*/ _jsx(Typography, {
158
144
  variant: "h6",
159
145
  children: metric.value
160
- })
146
+ }),
147
+ metric.note ? /*#__PURE__*/ _jsx(Typography, {
148
+ variant: "caption",
149
+ color: "text.secondary",
150
+ children: metric.note
151
+ }) : null
161
152
  ]
162
153
  }, metric.label))
163
154
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/host-marketing-summary-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { overlayActiveAt } from '../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Stack, Typography } from '@mui/material'\nimport { collection, limit, query } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { campaignSendsQuery } from './campaign-queries'\nimport { useMarketingOrgId } from './marketing-org-mount'\n\nexport interface HostMarketingSummaryCardProps {\n hostId: string\n}\n\n/**\n * Marketing at a glance (wave v8): one row of numbers across the\n * channels this page manages — live overlays and their lifetime\n * engagement, campaign sends/opens/clicks, and experiment states — so\n * the hub answers \"is anything running and is it working\" without\n * opening each card.\n */\nexport function HostMarketingSummaryCard(props: HostMarketingSummaryCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { orgId } = useMarketingOrgId(hostId)\n const { data: overlayDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'overlays'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n // The org's sends, narrowed to the ones this site sent.\n const { data: campaignDocs } = useFirestoreCollection<any>(\n () => {\n const sends = campaignSendsQuery(firestore, orgId, hostId)\n return sends ? query(sends, limit(50)) : null\n },\n [firestore, orgId, hostId],\n { idField: '$id' },\n )\n const { data: experimentDocs } = useFirestoreCollection<any>(\n () =>\n query(collection(firestore, 'hosts', hostId, 'experiments'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n\n const summary = useMemo(() => {\n const overlays = (overlayDocs ?? []).filter((doc: any) => !doc.deletedAt)\n const liveOverlays = overlays.filter(\n (overlay: any) =>\n overlay.enabled !== false && overlayActiveAt(overlay, Date.now()),\n ).length\n let impressions = 0\n let overlayClicks = 0\n for (const overlay of overlays) {\n impressions += Number(overlay.stats?.impressions ?? 0)\n overlayClicks += Number(overlay.stats?.clicks ?? 0)\n }\n const campaigns = (campaignDocs ?? []).filter(\n (doc: any) => !doc.deletedAt,\n )\n let sent = 0\n let opens = 0\n let emailClicks = 0\n let scheduled = 0\n for (const campaign of campaigns) {\n sent += Number(campaign.stats?.sent ?? 0)\n opens += Number(campaign.stats?.opens ?? 0)\n emailClicks += Number(campaign.stats?.clicks ?? 0)\n if (campaign.status === 'scheduled') scheduled += 1\n }\n const experiments = (experimentDocs ?? []).filter(\n (doc: any) => !doc.deletedAt,\n )\n const running = experiments.filter(\n (experiment: any) => experiment.status === 'running',\n ).length\n const decided = experiments.filter(\n (experiment: any) => experiment.winnerVariantId,\n ).length\n return {\n liveOverlays,\n impressions,\n overlayClicks,\n sent,\n opens,\n emailClicks,\n scheduled,\n running,\n decided,\n }\n }, [overlayDocs, campaignDocs, experimentDocs])\n\n const metrics: Array<{ label: string; value: string }> = [\n { label: 'Live overlays', value: String(summary.liveOverlays) },\n {\n label: 'Overlay views',\n value: summary.impressions.toLocaleString(),\n },\n {\n label: 'Overlay clicks',\n value: summary.overlayClicks.toLocaleString(),\n },\n { label: 'Emails sent', value: summary.sent.toLocaleString() },\n {\n label: 'Opens / clicks',\n value: `${summary.opens.toLocaleString()} / ${summary.emailClicks.toLocaleString()}`,\n },\n ...(summary.scheduled\n ? [{ label: 'Scheduled sends', value: String(summary.scheduled) }]\n : []),\n {\n label: 'Experiments',\n value: `${summary.running} running · ${summary.decided} decided`,\n },\n ]\n\n return (\n <CardDisplay\n header={'At a glance'}\n help={pluginDocsHelp('marketingOverlays', {\n anchor: '#engagement-stats',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack\n direction=\"row\"\n spacing={3}\n sx={{ flexWrap: 'wrap', rowGap: 1.5 }}\n >\n {metrics.map((metric) => (\n <Stack key={metric.label} spacing={0}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {metric.label}\n </Typography>\n <Typography variant=\"h6\">{metric.value}</Typography>\n </Stack>\n ))}\n </Stack>\n </CardDisplay>\n )\n}\nHostMarketingSummaryCard.displayName = 'HostMarketingSummaryCard'\n\nexport default HostMarketingSummaryCard\n"],"names":["overlayActiveAt","CardDisplay","Stack","Typography","collection","limit","query","useMemo","useFirestore","useFirestoreCollection","pluginDocsHelp","campaignSendsQuery","useMarketingOrgId","HostMarketingSummaryCard","props","hostId","firestore","orgId","data","overlayDocs","idField","campaignDocs","sends","experimentDocs","summary","overlays","filter","doc","deletedAt","liveOverlays","overlay","enabled","Date","now","length","impressions","overlayClicks","Number","stats","clicks","campaigns","sent","opens","emailClicks","scheduled","campaign","status","experiments","running","experiment","decided","winnerVariantId","metrics","label","value","String","toLocaleString","header","help","anchor","contentGutterX","contentGutterY","direction","spacing","sx","flexWrap","rowGap","map","metric","variant","color","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/host-marketing-summary-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { overlayStatus } from '../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Stack, Typography } from '@mui/material'\nimport {\n collection,\n getDocs,\n limit,\n orderBy,\n query,\n} from 'firebase/firestore'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { campaignSendsQuery } from './campaign-queries'\nimport {\n readSendFigures,\n readSiteExperimentFigures,\n readSiteOverlayFigures,\n} from './marketing-aggregates'\nimport { useMarketingOrgId } from './marketing-org-mount'\nimport { useOneShotRead } from './use-one-shot-reads'\n\nexport interface HostMarketingSummaryCardProps {\n hostId: string\n}\n\n/**\n * How many overlays the live count reads.\n *\n * \"Live\" is the one figure here a server aggregate cannot answer — it needs\n * `enabled` and the schedule window checked against the clock — so it is\n * counted over a read of the overlays themselves, ordered and ceilinged the\n * way the Overlays section reads them, with a probe that says when the site\n * has more.\n */\nexport const LIVE_OVERLAY_CEILING = 50\n\n/** A figure as the row prints it: a dash for one the server did not answer. */\nconst figure = (value: number | null | undefined): string =>\n value == null ? '—' : value.toLocaleString()\n\n/**\n * Marketing at a glance (wave v8): one row of numbers across the\n * channels this page manages — live overlays and their lifetime\n * engagement, campaign sends/opens/clicks, and experiment states — so\n * the hub answers \"is anything running and is it working\" without\n * opening each card.\n *\n * Every total is a server aggregate over the whole collection (see\n * `marketing-aggregates.ts`), read once rather than listened to: the\n * overlays' counters move on every impression, and a listener over them\n * would re-deliver a document per impression while the page stays open.\n */\nexport function HostMarketingSummaryCard(props: HostMarketingSummaryCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { orgId } = useMarketingOrgId(hostId)\n\n const overlayFigures = useOneShotRead(\n () => readSiteOverlayFigures(firestore, hostId),\n [firestore, hostId],\n )\n const live = useOneShotRead(\n () =>\n getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'overlays'),\n // Ordered for the reason the Overlays section gives: `name` is on\n // every overlay its only creator writes, and an unordered `limit`\n // is an arbitrary window rather than the first N of anything.\n orderBy('name'),\n limit(LIVE_OVERLAY_CEILING + 1),\n ),\n ).then((snapshot) => {\n const { rows, truncated } = ceilingedWindow(\n snapshot.docs.map((entry) => entry.data() as Record<string, any>),\n LIVE_OVERLAY_CEILING,\n )\n const nowMs = Date.now()\n return {\n count: rows.filter(\n (overlay) => !overlay.deletedAt && overlayStatus(overlay, nowMs) === 'live',\n ).length,\n truncated,\n }\n }),\n [firestore, hostId],\n )\n // The org's sends, narrowed to the ones this site sent.\n const sendFigures = useOneShotRead(() => {\n const sends = campaignSendsQuery(firestore, orgId, hostId)\n return sends ? readSendFigures(sends) : null\n }, [firestore, orgId, hostId])\n const experimentFigures = useOneShotRead(\n () => readSiteExperimentFigures(firestore, hostId),\n [firestore, hostId],\n )\n\n const sends = sendFigures.value\n const tests = experimentFigures.value\n const metrics: Array<{ label: string; value: string; note?: string }> = [\n {\n label: 'Live overlays',\n value: live.value ? figure(live.value.count) : '—',\n note: live.value?.truncated\n ? `of the first ${LIVE_OVERLAY_CEILING} by name`\n : undefined,\n },\n { label: 'Overlay views', value: figure(overlayFigures.value?.views) },\n { label: 'Overlay clicks', value: figure(overlayFigures.value?.clicks) },\n { label: 'Emails sent', value: figure(sends?.sent) },\n {\n label: 'Opens / clicks',\n value: `${figure(sends?.opens)} / ${figure(sends?.clicks)}`,\n },\n ...(sends?.scheduled\n ? [{ label: 'Scheduled sends', value: figure(sends.scheduled) }]\n : []),\n {\n label: 'Experiments',\n value: `${figure(tests?.running)} running · ${figure(tests?.decided)} decided`,\n },\n ]\n\n return (\n <CardDisplay\n header={'At a glance'}\n help={pluginDocsHelp('marketingOverlays', {\n anchor: '#engagement-stats',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack\n direction=\"row\"\n spacing={3}\n sx={{ flexWrap: 'wrap', rowGap: 1.5 }}\n >\n {metrics.map((metric) => (\n <Stack key={metric.label} spacing={0}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {metric.label}\n </Typography>\n <Typography variant=\"h6\">{metric.value}</Typography>\n {metric.note ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {metric.note}\n </Typography>\n ) : null}\n </Stack>\n ))}\n </Stack>\n </CardDisplay>\n )\n}\nHostMarketingSummaryCard.displayName = 'HostMarketingSummaryCard'\n\nexport default HostMarketingSummaryCard\n"],"names":["overlayStatus","CardDisplay","Stack","Typography","collection","getDocs","limit","orderBy","query","useFirestore","ceilingedWindow","pluginDocsHelp","campaignSendsQuery","readSendFigures","readSiteExperimentFigures","readSiteOverlayFigures","useMarketingOrgId","useOneShotRead","LIVE_OVERLAY_CEILING","figure","value","toLocaleString","HostMarketingSummaryCard","props","live","overlayFigures","hostId","firestore","orgId","then","snapshot","rows","truncated","docs","map","entry","data","nowMs","Date","now","count","filter","overlay","deletedAt","length","sendFigures","sends","experimentFigures","tests","metrics","label","note","undefined","views","clicks","sent","opens","scheduled","running","decided","header","help","anchor","contentGutterX","contentGutterY","direction","spacing","sx","flexWrap","rowGap","metric","variant","color","displayName"],"mappings":"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"}