@aglyn/plugins-marketing 1.0.0-beta.199 → 1.0.0-beta.200

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 (33) hide show
  1. package/package.json +15 -15
  2. package/src/lib/components/campaign-detail-card.js +106 -36
  3. package/src/lib/components/campaign-detail-card.js.map +1 -1
  4. package/src/lib/components/campaign-queries.d.ts +11 -0
  5. package/src/lib/components/campaign-queries.js +13 -0
  6. package/src/lib/components/campaign-queries.js.map +1 -1
  7. package/src/lib/components/campaigns-card.d.ts +8 -6
  8. package/src/lib/components/campaigns-card.js +283 -125
  9. package/src/lib/components/campaigns-card.js.map +1 -1
  10. package/src/lib/components/emails-list-card.d.ts +11 -15
  11. package/src/lib/components/emails-list-card.js +139 -142
  12. package/src/lib/components/emails-list-card.js.map +1 -1
  13. package/src/lib/components/host-experiments-card.component.js +107 -78
  14. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  15. package/src/lib/components/org-experiments-card.js +7 -3
  16. package/src/lib/components/org-experiments-card.js.map +1 -1
  17. package/src/lib/model/campaign-list-query.d.ts +99 -0
  18. package/src/lib/model/campaign-list-query.js +373 -0
  19. package/src/lib/model/campaign-list-query.js.map +1 -0
  20. package/src/lib/model/experiment-list-query.d.ts +22 -0
  21. package/src/lib/model/experiment-list-query.js +85 -0
  22. package/src/lib/model/experiment-list-query.js.map +1 -0
  23. package/src/lib/server/campaign-manage.d.ts +1 -1
  24. package/src/lib/server/campaign-manage.js +30 -18
  25. package/src/lib/server/campaign-manage.js.map +1 -1
  26. package/src/lib/server/campaign-send.d.ts +7 -0
  27. package/src/lib/server/campaign-send.js +17 -5
  28. package/src/lib/server/campaign-send.js.map +1 -1
  29. package/src/lib/server/email-events.js +3 -1
  30. package/src/lib/server/email-events.js.map +1 -1
  31. package/src/lib/components/email-send-filter.d.ts +0 -32
  32. package/src/lib/components/email-send-filter.js +0 -55
  33. package/src/lib/components/email-send-filter.js.map +0 -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 { 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 ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\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 { inMemoryListField } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { usePagedRowsFilter } from '@aglyn/shared-ui-jsx/hooks/use-paged-rows-filter'\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 * What the experiments grid's Filters panel offers (AGL-3317). The list is a\n * paged listener, so a filter matches over what its window has read, which\n * the first filter widens (`usePagedRowsFilter`).\n */\nconst EXPERIMENT_FILTER_FIELDS = [\n inMemoryListField('name', 'text'),\n inMemoryListField('target', 'select'),\n inMemoryListField('status', 'select'),\n]\nconst EXPERIMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Experiment',\n target: 'Tests',\n status: 'Status',\n}\nconst EXPERIMENT_FILTER_OPTIONS = {\n target: [\n { value: 'screen', label: 'Screen' },\n { value: 'section', label: 'Section' },\n { value: 'email', label: 'Email' },\n ],\n status: Object.keys(EXPERIMENT_STATUS_COLORS).map((status) => ({\n value: status,\n label: status.charAt(0).toUpperCase() + status.slice(1),\n })),\n}\nconst EXPERIMENT_SEARCH_FIELDS = ['name'] as const\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 data: experimentData,\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 experimentsLive: ExperimentDraft[] = experimentRows.filter(\n (experiment: any) => !experiment.deletedAt,\n )\n const experimentFilter = usePagedRowsFilter<ExperimentDraft>(\n {\n data: (experimentData ?? []).filter((experiment: any) => !experiment.deletedAt),\n rows: experimentsLive,\n hasMore: hasMoreExperiments,\n page: experimentPage,\n setPage: setExperimentPage,\n pageSize: experimentPageSize,\n setPageSize: setExperimentPageSize,\n },\n {\n fields: EXPERIMENT_FILTER_FIELDS,\n options: EXPERIMENT_FILTER_OPTIONS,\n headers: EXPERIMENT_FILTER_HEADERS,\n search: EXPERIMENT_SEARCH_FIELDS,\n },\n )\n const experiments = experimentFilter.rows\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 = experimentsLive.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 {experimentsLive.length === 0 && !hasMoreExperiments && !experimentFilter.filtering ? null : (\n <>\n <ListFilterChips {...experimentFilter.chipsProps} />\n {experimentFilter.filtering && hasMoreExperiments ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Filtering the ${experimentFilter.read} experiments read so far — the next page reads more.`}\n </Typography>\n ) : null}\n <ListTable\n aria-label=\"Experiments\"\n rows={experiments}\n columns={experimentFilter.filterColumns(experimentColumns as GridColDef[])}\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 // The panel and the search are the grid's; the card answers\n // them over what its window read (AGL-3317).\n {...experimentFilter.gridProps}\n noRowsLabel=\"No experiments match these filters\"\n />\n <ListPagination\n // The rows LEFT after the soft-delete filter (and any grid\n // filter), not the page the server returned: the footer must\n // describe what is on screen.\n {...experimentFilter.pagination}\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","ListFilterChips","ListPagination","ListRowActions","ListTable","listActionsColumn","inMemoryListField","usePagedRowsFilter","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","EXPERIMENT_FILTER_FIELDS","EXPERIMENT_FILTER_HEADERS","name","target","status","EXPERIMENT_FILTER_OPTIONS","value","label","Object","keys","map","charAt","toUpperCase","slice","EXPERIMENT_SEARCH_FIELDS","HostExperimentsCard","props","editor","results","hostId","firestore","org","enqueueSnackbar","confirm","logActivity","entitled","data","experimentData","rows","experimentRows","hasMore","hasMoreExperiments","page","experimentPage","setPage","setExperimentPage","pageSize","experimentPageSize","setPageSize","setExperimentPageSize","experimentsStatus","fromCache","experimentsFromCache","pageLimit","idField","experimentsLive","filter","experiment","deletedAt","experimentFilter","fields","options","headers","search","experiments","setEditor","editorOpen","screenDocs","screenOptions","screen","id","$id","displayName","sort","a","b","localeCompare","versionDocs","screenId","versionOptions","version","setResults","patch","partial","previous","proposeVariants","proposed","variants","variant","index","subject","body","patchVariant","index2","newExperiment","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","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","filtering","chipsProps","read","aria-label","columns","filterColumns","rowHeight","onOpen","_id","hideFooter","gridProps","noRowsLabel","pagination","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","required","onChange","mt","direction","select","option","helperText","nodeId","eventType","alignItems","flexWrap","slotProps","inputLabel","shrink","Date","getTimezoneOffset","toISOString","ms","getTime","undefined","Number","isFinite","control","checked","minExposures","confidence","String","versionId","multiline","maxRows","experimentId","autoCompleted","ml","align","summary","comparison","leader","winner","selected","exposures","conversions","rate","toFixed","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 { nameSearchFields } from '@aglyn/aglyn/app-utils/name-search'\nimport { describeVariantComparison, experimentResultRows, validateExperiment, type ExperimentTarget, type ExperimentVariant, type HostExperiment } from '../model'\nimport { EXPERIMENT_LIST_QUERY } from '../model/experiment-list-query'\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 ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\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 { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\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 query,\n setDoc,\n where,\n} from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostActivityLogger,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\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 * What the experiments grid's Filters panel offers: the fields of the list's\n * query declaration (`EXPERIMENT_LIST_QUERY`), every one served by Firestore\n * (AGL-3321).\n */\nconst EXPERIMENT_FILTER_FIELDS = EXPERIMENT_LIST_QUERY.fields\nconst EXPERIMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Experiment',\n target: 'Tests',\n status: 'Status',\n}\nconst EXPERIMENT_FILTER_OPTIONS = {\n target: [\n { value: 'screen', label: 'Screen' },\n { value: 'section', label: 'Section' },\n { value: 'email', label: 'Email' },\n ],\n status: Object.keys(EXPERIMENT_STATUS_COLORS).map((status) => ({\n value: status,\n label: status.charAt(0).toUpperCase() + status.slice(1),\n })),\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 /*\n * The Filters panel and the search box are the grid's; the clauses and the\n * words go onto ONE query over the site's experiments, ordered by name, and\n * each page is a page of that query's matches (AGL-3321). A clause the\n * query cannot hold beside the others is not applied, and the notice above\n * the list says which and why — never answered over the experiments that\n * happen to be loaded.\n */\n const gridFilter = useListGridFilter({ selectFields: ['target', 'status'] })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\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 plan: experimentPlan,\n } = useListQuery<ExperimentDraft>({\n collection: collection(firestore, 'hosts', hostId, 'experiments'),\n /*\n * Ordered by the server and paged (AGL-2501, AGL-2292): the declaration's\n * one order is `name`, which every writer sets — the editor refuses a save\n * without one (`validateExperiment`), the besigner's section shortcut\n * names its draft, the two send-path readers only ever write the stats\n * beneath an experiment, and `experiments` is not in `IMPORTABLE_FIELDS`.\n * There is no writer that can produce a nameless experiment for `orderBy`\n * to drop.\n *\n * Nothing is dropped after the query either. An experiment is never\n * soft-deleted — Delete below is a `deleteDoc` — so every row the query\n * returns is live, and a page holds as many rows as its size.\n */\n declaration: EXPERIMENT_LIST_QUERY,\n request: { clauses: gridFilter.clauses, search: gridFilter.searchWords },\n deps: [firestore, hostId],\n idField: '$id',\n })\n const experiments: ExperimentDraft[] = experimentRows\n const experimentColumnsFor = (columns: GridColDef[]) =>\n listFilterGridColumns(\n columns,\n EXPERIMENT_FILTER_FIELDS,\n EXPERIMENT_FILTER_OPTIONS,\n EXPERIMENT_FILTER_HEADERS,\n )\n const experimentRefusals = useMemo(\n () =>\n listQueryRefusals(experimentPlan.refused, {\n fields: EXPERIMENT_FILTER_FIELDS,\n headers: EXPERIMENT_FILTER_HEADERS,\n options: EXPERIMENT_FILTER_OPTIONS,\n }),\n [experimentPlan.refused],\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 /*\n * The name's search keys, from the name being written — the\n * list's Experiment filter and its search box read them\n * (AGL-3321). After the payload, so keys copied from the stored\n * row cannot outlive a rename.\n */\n ...nameSearchFields(editor.name ?? ''),\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 /*\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 *\n * Asked of Firestore, not of the rows on screen: the list is one page of\n * a query the reader may have narrowed, and the running test on the same\n * screen is as likely to be on another page, or filtered out, as on this\n * one. Two equalities, served by merging their single-field indexes.\n */\n if (status === 'running' && experiment.screenId) {\n const running = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n where('status', '==', 'running'),\n where('screenId', '==', experiment.screenId),\n limit(2),\n ),\n )\n const clash = running.docs\n .map((entry) => ({ ...(entry.data() as HostExperiment), $id: entry.id }))\n .find((candidate) => candidate.$id !== experiment.$id)\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 && experimentPage === 0 && !filtering ? null : (\n <>\n <ListFilterChips\n fields={EXPERIMENT_FILTER_FIELDS}\n headers={EXPERIMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={EXPERIMENT_FILTER_OPTIONS}\n />\n <ListQueryNotices refused={experimentRefusals} notices={experimentPlan.notices} />\n <ListTable\n aria-label=\"Experiments\"\n rows={experiments}\n columns={experimentColumnsFor(experimentColumns as GridColDef[])}\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 * The panel and the search are the grid's; every clause and the\n * search word are on the list's query (AGL-3321), so the grid\n * never narrows the page it is handed.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel=\"No experiments match these filters\"\n />\n <ListPagination\n page={experimentPage}\n pageSize={experimentPageSize}\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","nameSearchFields","describeVariantComparison","experimentResultRows","validateExperiment","EXPERIMENT_LIST_QUERY","mdiChartBar","mdiDeleteOutline","mdiPause","mdiPencilOutline","mdiPlay","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","listFilterGridColumns","useListGridFilter","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","query","setDoc","where","useMemo","useState","useFirestore","useFirestoreCollection","useHostActivityLogger","writeGuardedBySeed","useListQuery","ExperimentResultZone","ExperimentVariantsZone","marketingExperimentVariantDrafts","EXPERIMENT_STATUS_COLORS","draft","running","paused","done","EXPERIMENT_FILTER_FIELDS","fields","EXPERIMENT_FILTER_HEADERS","name","target","status","EXPERIMENT_FILTER_OPTIONS","value","label","Object","keys","map","charAt","toUpperCase","slice","HostExperimentsCard","props","editor","results","hostId","firestore","org","enqueueSnackbar","confirm","logActivity","entitled","gridFilter","selectFields","filtering","clauses","length","searchWords","some","word","trim","rows","experimentRows","hasMore","hasMoreExperiments","page","experimentPage","setPage","setExperimentPage","pageSize","experimentPageSize","setPageSize","setExperimentPageSize","experimentsStatus","fromCache","experimentsFromCache","plan","experimentPlan","declaration","request","search","deps","idField","experiments","experimentColumnsFor","columns","experimentRefusals","refused","headers","options","setEditor","editorOpen","data","screenDocs","screenOptions","filter","screen","deletedAt","id","$id","displayName","sort","a","b","localeCompare","versionDocs","screenId","versionOptions","version","setResults","patch","partial","previous","proposeVariants","proposed","variants","variant","index","subject","body","patchVariant","index2","newExperiment","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","experiment","winnerVariantId","clash","docs","entry","find","candidate","handleDelete","accepted","title","description","confirmationText","confirmationButtonProps","color","then","catch","openResults","snapshot","stats","forEach","experimentActions","key","icon","path","size","onClick","destructive","experimentColumns","field","headerName","flex","minWidth","valueGetter","_value","width","renderCell","row","quick","items","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","spacing","sx","alignSelf","onChange","setClauses","notices","aria-label","rowHeight","onOpen","_id","hideFooter","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","required","mt","direction","select","option","helperText","nodeId","eventType","alignItems","flexWrap","slotProps","inputLabel","shrink","Date","getTimezoneOffset","toISOString","ms","getTime","undefined","Number","isFinite","control","checked","minExposures","confidence","String","versionId","multiline","maxRows","experimentId","autoCompleted","ml","align","summary","comparison","leader","winner","selected","exposures","conversions","rate","toFixed","test"],"mappings":"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"}
@@ -42,15 +42,19 @@ import { ORG_SITES_PER_PAGE, useOrgSiteReads } from "./use-one-shot-reads.js";
42
42
  */ export const ORG_EXPERIMENTS_PER_SITE = 10;
43
43
  /**
44
44
  * One site's tests, ordered by name as the site's own list pages them —
45
- * `name` is on every experiment, since the one editor that creates them
46
- * refuses a save without it.
45
+ * `name` is on every experiment, since every writer of one sets it.
46
+ *
47
+ * Every row the query returns is shown. An experiment is never soft-deleted
48
+ * — the site list's Delete is a `deleteDoc` — so there is nothing to drop
49
+ * after the read, and the ceiling's "more than ten" is a count of live tests
50
+ * rather than of whatever the window happened to hold (AGL-3321).
47
51
  */ async function readSiteExperiments(firestore, hostId) {
48
52
  const snapshot = await getDocs(query(collection(firestore, 'hosts', hostId, 'experiments'), orderBy('name'), limit(ORG_EXPERIMENTS_PER_SITE + 1)));
49
53
  const { rows, truncated } = ceilingedWindow(snapshot.docs.map((entry)=>_extends({}, entry.data(), {
50
54
  $id: entry.id
51
55
  })), ORG_EXPERIMENTS_PER_SITE);
52
56
  return {
53
- rows: rows.filter((row)=>!row.deletedAt),
57
+ rows,
54
58
  truncated
55
59
  };
56
60
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/org-experiments-card.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 pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n describeVariantComparison,\n experimentResultRows,\n type ExperimentStatsByVariant,\n type HostExperiment,\n} from '../model'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n getDocs,\n limit,\n orderBy,\n query,\n type Firestore,\n} from 'firebase/firestore'\nimport { Fragment, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { ExperimentResultZone } from './experiment-zones'\nimport { EXPERIMENT_STATUS_COLORS } from './host-experiments-card.component'\nimport { useMarketingOrgMount } from './marketing-org-mount'\nimport {\n OrgSitePickerButton,\n orgSiteSectionHref,\n} from './org-site-picker-button'\nimport { ORG_SITES_PER_PAGE, useOrgSiteReads } from './use-one-shot-reads'\n\n/**\n * How many A/B tests are read for each site.\n *\n * The same bargain as the org overlay list: a site runs a few tests at a\n * time, a page here holds a page of sites' worth of them, and a site with\n * more says so on its row and links to its own list, which pages through\n * every one.\n */\nexport const ORG_EXPERIMENTS_PER_SITE = 10\n\ntype ExperimentRow = HostExperiment & { $id: string; deletedAt?: unknown }\n\ninterface SiteExperiments {\n rows: ExperimentRow[]\n truncated: boolean\n}\n\n/**\n * One site's tests, ordered by name as the site's own list pages them —\n * `name` is on every experiment, since the one editor that creates them\n * refuses a save without it.\n */\nasync function readSiteExperiments(\n firestore: Firestore,\n hostId: string,\n): Promise<SiteExperiments> {\n const snapshot = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n orderBy('name'),\n limit(ORG_EXPERIMENTS_PER_SITE + 1),\n ),\n )\n const { rows, truncated } = ceilingedWindow(\n snapshot.docs.map(\n (entry) => ({ ...(entry.data() as HostExperiment), $id: entry.id }) as ExperimentRow,\n ),\n ORG_EXPERIMENTS_PER_SITE,\n )\n return { rows: rows.filter((row) => !row.deletedAt), truncated }\n}\n\nconst TARGET_LABEL: Record<string, string> = {\n screen: 'Screen',\n section: 'Section',\n email: 'Email',\n}\n\n/** Columns in a row, so a site's own line can span all of them. */\nconst COLUMNS = 5\n\nexport interface OrgExperimentsCardProps {\n /** The org's plan, for the `abTesting` entitlement. */\n org?: Partial<AglynOrgBilling>\n}\n\n/**\n * Every site's A/B tests, on the organization's Marketing hub.\n *\n * A test splits ONE site's traffic between versions of that site's screens,\n * sections or emails, so it is created, started and decided on the site:\n * the New button and each row's Open go to the site's own A/B testing\n * section. What the org hub adds is the view across sites, and each test's\n * results — read when somebody asks for them, one document per variant,\n * rather than for every test on every visit.\n *\n * Grouped by site and paged by site, like the org overlay list, so turning\n * the page reads the next sites and nothing else.\n */\nexport function OrgExperimentsCard(props: OrgExperimentsCardProps) {\n const { org } = props\n const mount = useMarketingOrgMount()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const entitled = checkEntitlement(org, 'abTesting')\n\n const sites = mount?.hosts ?? []\n const [page, setPage] = useState(0)\n const pageSites = sites.slice(\n page * ORG_SITES_PER_PAGE,\n (page + 1) * ORG_SITES_PER_PAGE,\n )\n const { reads } = useOrgSiteReads<SiteExperiments>(\n entitled ? pageSites.map((site) => site.id) : [],\n (hostId) => readSiteExperiments(firestore, hostId),\n [firestore, entitled],\n )\n\n const [results, setResults] = useState<{\n hostId: string\n siteName: string\n experiment: ExperimentRow\n stats: ExperimentStatsByVariant\n } | null>(null)\n const [opening, setOpening] = useState<string | null>(null)\n\n /** The per-variant counters of one test, `experiments/{id}/stats`. */\n const openResults = async (\n hostId: string,\n siteName: string,\n experiment: ExperimentRow,\n ) => {\n setOpening(experiment.$id)\n try {\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: ExperimentStatsByVariant = {}\n snapshot.forEach((entry) => {\n stats[entry.id] = entry.data()\n })\n setResults({ hostId, siteName, experiment, stats })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The results could not be read', { variant: 'error' })\n } finally {\n setOpening(null)\n }\n }\n\n const resultsHref =\n results && mount\n ? orgSiteSectionHref(mount, results.hostId, 'experiments')\n : null\n\n return (\n <CardDisplay\n header=\"Experiments\"\n subheader=\"Every site in this organization\"\n help={pluginDocsHelp('emailCampaigns', {\n anchor: '#experiments-across-sites',\n })}\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 ) : !mount || !mount.hostsReady ? null : !sites.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet. A test splits one site’s ' +\n 'traffic, so it is created on a site.'}\n </Typography>\n ) : (\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'center', justifyContent: 'space-between' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Each test splits one site’s traffic. Read any test’s results ' +\n 'here; create, start and decide them on the site.'}\n </Typography>\n <OrgSitePickerButton\n mount={mount}\n section=\"experiments\"\n label=\"New experiment\"\n />\n </Stack>\n <ScrollTable size=\"small\" aria-label=\"Experiments by site\">\n <TableHead>\n <TableRow>\n <TableCell>{'Experiment'}</TableCell>\n <TableCell>{'Tests'}</TableCell>\n <TableCell>{'Variants'}</TableCell>\n <TableCell>{'Status'}</TableCell>\n <TableCell align=\"right\">{'Actions'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {pageSites.map((site) => {\n const read = reads.get(site.id)\n const answer = read?.value ?? null\n const href = orgSiteSectionHref(mount, site.id, 'experiments')\n const name = site.name || site.subdomain || site.id\n return (\n <Fragment key={site.id}>\n {/* The site's own line: its name, and what was not read. */}\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}\n >\n <Typography variant=\"subtitle2\">\n {href ? <AppLink href={href}>{name}</AppLink> : name}\n </Typography>\n {answer?.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`More than ${ORG_EXPERIMENTS_PER_SITE} on this ` +\n 'site — these are the first by name; the ' +\n 'site’s own list has every one.'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n </TableRow>\n {read?.status === 'error' ? (\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This site’s experiments could not be read.'}\n </Typography>\n </TableCell>\n </TableRow>\n ) : answer && !answer.rows.length ? (\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No experiments on this site.'}\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n (answer?.rows ?? []).map((experiment) => (\n <TableRow key={experiment.$id}>\n <TableCell>{experiment.name || experiment.$id}</TableCell>\n <TableCell>\n {TARGET_LABEL[experiment.target] ?? experiment.target}\n </TableCell>\n <TableCell>\n {(experiment.variants ?? []).length.toLocaleString()}\n </TableCell>\n <TableCell>\n <Chip\n size=\"small\"\n color={\n EXPERIMENT_STATUS_COLORS[experiment.status] ??\n 'default'\n }\n label={experiment.status}\n />\n </TableCell>\n <TableCell align=\"right\">\n <Button\n size=\"small\"\n color=\"primary\"\n disabled={opening === experiment.$id}\n aria-label={`Results for ${experiment.name || experiment.$id} on ${name}`}\n onClick={() =>\n void openResults(site.id, name, experiment)\n }\n >\n {'Results'}\n </Button>\n {/* A site the shell could not address has no section to open. */}\n {href ? (\n <AppLink\n componentVariant=\"button\"\n href={href}\n size=\"small\"\n color=\"primary\"\n aria-label={`Open ${experiment.name || experiment.$id} on ${name}`}\n >\n {'Open'}\n </AppLink>\n ) : null}\n </TableCell>\n </TableRow>\n ))\n )}\n </Fragment>\n )\n })}\n </TableBody>\n </ScrollTable>\n <ListPagination\n page={page}\n pageSize={ORG_SITES_PER_PAGE}\n rowCount={pageSites.length}\n count={sites.length}\n onPageChange={setPage}\n labelDisplayedRows={({ from, to, count }) =>\n `Sites ${from}–${to} of ${count}`\n }\n />\n </Stack>\n )}\n\n {/*\n The site card's results, read-only: picking a winner changes what a\n site serves, so it is done on the site, one link away.\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 <Chip\n size=\"small\"\n color=\"success\"\n label=\"Auto-completed\"\n sx={{ ml: 1 }}\n />\n ) : null}\n </DialogTitle>\n <DialogContent>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {results ? `On ${results.siteName}` : ''}\n </Typography>\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 </TableRow>\n </TableHead>\n <TableBody>\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 </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {results ? (\n <ExperimentResultZone\n hostId={results.hostId}\n experimentId={results.experiment.$id}\n test={results.experiment.name?.trim() || results.experiment.$id}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n {resultsHref ? (\n <AppLink\n componentVariant=\"button\"\n href={resultsHref}\n size=\"small\"\n color=\"primary\"\n >\n {'Open on the site'}\n </AppLink>\n ) : null}\n <Button color=\"inherit\" onClick={() => setResults(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nOrgExperimentsCard.displayName = 'OrgExperimentsCard'\n\nexport default OrgExperimentsCard\n"],"names":["checkEntitlement","pluginDocsHelp","describeVariantComparison","experimentResultRows","AppLink","CardDisplay","ListPagination","ScrollTable","useSnackbar","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TableBody","TableCell","TableHead","TableRow","Typography","collection","getDocs","limit","orderBy","query","Fragment","useState","useFirestore","ceilingedWindow","ExperimentResultZone","EXPERIMENT_STATUS_COLORS","useMarketingOrgMount","OrgSitePickerButton","orgSiteSectionHref","ORG_SITES_PER_PAGE","useOrgSiteReads","ORG_EXPERIMENTS_PER_SITE","readSiteExperiments","firestore","hostId","snapshot","rows","truncated","docs","map","entry","data","$id","id","filter","row","deletedAt","TARGET_LABEL","screen","section","email","COLUMNS","OrgExperimentsCard","props","results","org","mount","enqueueSnackbar","entitled","sites","hosts","page","setPage","pageSites","slice","reads","site","setResults","opening","setOpening","openResults","siteName","experiment","stats","forEach","error","console","variant","resultsHref","header","subheader","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","hostsReady","length","color","spacing","direction","sx","alignItems","justifyContent","label","size","aria-label","align","read","get","answer","value","href","name","subdomain","colSpan","flexWrap","status","target","variants","toLocaleString","disabled","onClick","componentVariant","pageSize","rowCount","count","onPageChange","labelDisplayedRows","from","to","open","Boolean","onClose","maxWidth","fullWidth","autoCompleted","ml","summary","comparison","leader","winner","selected","toUpperCase","exposures","conversions","rate","toFixed","experimentId","test","trim","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/org-experiments-card.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 pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n describeVariantComparison,\n experimentResultRows,\n type ExperimentStatsByVariant,\n type HostExperiment,\n} from '../model'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n getDocs,\n limit,\n orderBy,\n query,\n type Firestore,\n} from 'firebase/firestore'\nimport { Fragment, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { ExperimentResultZone } from './experiment-zones'\nimport { EXPERIMENT_STATUS_COLORS } from './host-experiments-card.component'\nimport { useMarketingOrgMount } from './marketing-org-mount'\nimport {\n OrgSitePickerButton,\n orgSiteSectionHref,\n} from './org-site-picker-button'\nimport { ORG_SITES_PER_PAGE, useOrgSiteReads } from './use-one-shot-reads'\n\n/**\n * How many A/B tests are read for each site.\n *\n * The same bargain as the org overlay list: a site runs a few tests at a\n * time, a page here holds a page of sites' worth of them, and a site with\n * more says so on its row and links to its own list, which pages through\n * every one.\n */\nexport const ORG_EXPERIMENTS_PER_SITE = 10\n\ntype ExperimentRow = HostExperiment & { $id: string }\n\ninterface SiteExperiments {\n rows: ExperimentRow[]\n truncated: boolean\n}\n\n/**\n * One site's tests, ordered by name as the site's own list pages them —\n * `name` is on every experiment, since every writer of one sets it.\n *\n * Every row the query returns is shown. An experiment is never soft-deleted\n * — the site list's Delete is a `deleteDoc` — so there is nothing to drop\n * after the read, and the ceiling's \"more than ten\" is a count of live tests\n * rather than of whatever the window happened to hold (AGL-3321).\n */\nasync function readSiteExperiments(\n firestore: Firestore,\n hostId: string,\n): Promise<SiteExperiments> {\n const snapshot = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'experiments'),\n orderBy('name'),\n limit(ORG_EXPERIMENTS_PER_SITE + 1),\n ),\n )\n const { rows, truncated } = ceilingedWindow(\n snapshot.docs.map(\n (entry) => ({ ...(entry.data() as HostExperiment), $id: entry.id }) as ExperimentRow,\n ),\n ORG_EXPERIMENTS_PER_SITE,\n )\n return { rows, truncated }\n}\n\nconst TARGET_LABEL: Record<string, string> = {\n screen: 'Screen',\n section: 'Section',\n email: 'Email',\n}\n\n/** Columns in a row, so a site's own line can span all of them. */\nconst COLUMNS = 5\n\nexport interface OrgExperimentsCardProps {\n /** The org's plan, for the `abTesting` entitlement. */\n org?: Partial<AglynOrgBilling>\n}\n\n/**\n * Every site's A/B tests, on the organization's Marketing hub.\n *\n * A test splits ONE site's traffic between versions of that site's screens,\n * sections or emails, so it is created, started and decided on the site:\n * the New button and each row's Open go to the site's own A/B testing\n * section. What the org hub adds is the view across sites, and each test's\n * results — read when somebody asks for them, one document per variant,\n * rather than for every test on every visit.\n *\n * Grouped by site and paged by site, like the org overlay list, so turning\n * the page reads the next sites and nothing else.\n */\nexport function OrgExperimentsCard(props: OrgExperimentsCardProps) {\n const { org } = props\n const mount = useMarketingOrgMount()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const entitled = checkEntitlement(org, 'abTesting')\n\n const sites = mount?.hosts ?? []\n const [page, setPage] = useState(0)\n const pageSites = sites.slice(\n page * ORG_SITES_PER_PAGE,\n (page + 1) * ORG_SITES_PER_PAGE,\n )\n const { reads } = useOrgSiteReads<SiteExperiments>(\n entitled ? pageSites.map((site) => site.id) : [],\n (hostId) => readSiteExperiments(firestore, hostId),\n [firestore, entitled],\n )\n\n const [results, setResults] = useState<{\n hostId: string\n siteName: string\n experiment: ExperimentRow\n stats: ExperimentStatsByVariant\n } | null>(null)\n const [opening, setOpening] = useState<string | null>(null)\n\n /** The per-variant counters of one test, `experiments/{id}/stats`. */\n const openResults = async (\n hostId: string,\n siteName: string,\n experiment: ExperimentRow,\n ) => {\n setOpening(experiment.$id)\n try {\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: ExperimentStatsByVariant = {}\n snapshot.forEach((entry) => {\n stats[entry.id] = entry.data()\n })\n setResults({ hostId, siteName, experiment, stats })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The results could not be read', { variant: 'error' })\n } finally {\n setOpening(null)\n }\n }\n\n const resultsHref =\n results && mount\n ? orgSiteSectionHref(mount, results.hostId, 'experiments')\n : null\n\n return (\n <CardDisplay\n header=\"Experiments\"\n subheader=\"Every site in this organization\"\n help={pluginDocsHelp('emailCampaigns', {\n anchor: '#experiments-across-sites',\n })}\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 ) : !mount || !mount.hostsReady ? null : !sites.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet. A test splits one site’s ' +\n 'traffic, so it is created on a site.'}\n </Typography>\n ) : (\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'center', justifyContent: 'space-between' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Each test splits one site’s traffic. Read any test’s results ' +\n 'here; create, start and decide them on the site.'}\n </Typography>\n <OrgSitePickerButton\n mount={mount}\n section=\"experiments\"\n label=\"New experiment\"\n />\n </Stack>\n <ScrollTable size=\"small\" aria-label=\"Experiments by site\">\n <TableHead>\n <TableRow>\n <TableCell>{'Experiment'}</TableCell>\n <TableCell>{'Tests'}</TableCell>\n <TableCell>{'Variants'}</TableCell>\n <TableCell>{'Status'}</TableCell>\n <TableCell align=\"right\">{'Actions'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {pageSites.map((site) => {\n const read = reads.get(site.id)\n const answer = read?.value ?? null\n const href = orgSiteSectionHref(mount, site.id, 'experiments')\n const name = site.name || site.subdomain || site.id\n return (\n <Fragment key={site.id}>\n {/* The site's own line: its name, and what was not read. */}\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}\n >\n <Typography variant=\"subtitle2\">\n {href ? <AppLink href={href}>{name}</AppLink> : name}\n </Typography>\n {answer?.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`More than ${ORG_EXPERIMENTS_PER_SITE} on this ` +\n 'site — these are the first by name; the ' +\n 'site’s own list has every one.'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n </TableRow>\n {read?.status === 'error' ? (\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This site’s experiments could not be read.'}\n </Typography>\n </TableCell>\n </TableRow>\n ) : answer && !answer.rows.length ? (\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No experiments on this site.'}\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n (answer?.rows ?? []).map((experiment) => (\n <TableRow key={experiment.$id}>\n <TableCell>{experiment.name || experiment.$id}</TableCell>\n <TableCell>\n {TARGET_LABEL[experiment.target] ?? experiment.target}\n </TableCell>\n <TableCell>\n {(experiment.variants ?? []).length.toLocaleString()}\n </TableCell>\n <TableCell>\n <Chip\n size=\"small\"\n color={\n EXPERIMENT_STATUS_COLORS[experiment.status] ??\n 'default'\n }\n label={experiment.status}\n />\n </TableCell>\n <TableCell align=\"right\">\n <Button\n size=\"small\"\n color=\"primary\"\n disabled={opening === experiment.$id}\n aria-label={`Results for ${experiment.name || experiment.$id} on ${name}`}\n onClick={() =>\n void openResults(site.id, name, experiment)\n }\n >\n {'Results'}\n </Button>\n {/* A site the shell could not address has no section to open. */}\n {href ? (\n <AppLink\n componentVariant=\"button\"\n href={href}\n size=\"small\"\n color=\"primary\"\n aria-label={`Open ${experiment.name || experiment.$id} on ${name}`}\n >\n {'Open'}\n </AppLink>\n ) : null}\n </TableCell>\n </TableRow>\n ))\n )}\n </Fragment>\n )\n })}\n </TableBody>\n </ScrollTable>\n <ListPagination\n page={page}\n pageSize={ORG_SITES_PER_PAGE}\n rowCount={pageSites.length}\n count={sites.length}\n onPageChange={setPage}\n labelDisplayedRows={({ from, to, count }) =>\n `Sites ${from}–${to} of ${count}`\n }\n />\n </Stack>\n )}\n\n {/*\n The site card's results, read-only: picking a winner changes what a\n site serves, so it is done on the site, one link away.\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 <Chip\n size=\"small\"\n color=\"success\"\n label=\"Auto-completed\"\n sx={{ ml: 1 }}\n />\n ) : null}\n </DialogTitle>\n <DialogContent>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {results ? `On ${results.siteName}` : ''}\n </Typography>\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 </TableRow>\n </TableHead>\n <TableBody>\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 </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {results ? (\n <ExperimentResultZone\n hostId={results.hostId}\n experimentId={results.experiment.$id}\n test={results.experiment.name?.trim() || results.experiment.$id}\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n {resultsHref ? (\n <AppLink\n componentVariant=\"button\"\n href={resultsHref}\n size=\"small\"\n color=\"primary\"\n >\n {'Open on the site'}\n </AppLink>\n ) : null}\n <Button color=\"inherit\" onClick={() => setResults(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nOrgExperimentsCard.displayName = 'OrgExperimentsCard'\n\nexport default OrgExperimentsCard\n"],"names":["checkEntitlement","pluginDocsHelp","describeVariantComparison","experimentResultRows","AppLink","CardDisplay","ListPagination","ScrollTable","useSnackbar","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TableBody","TableCell","TableHead","TableRow","Typography","collection","getDocs","limit","orderBy","query","Fragment","useState","useFirestore","ceilingedWindow","ExperimentResultZone","EXPERIMENT_STATUS_COLORS","useMarketingOrgMount","OrgSitePickerButton","orgSiteSectionHref","ORG_SITES_PER_PAGE","useOrgSiteReads","ORG_EXPERIMENTS_PER_SITE","readSiteExperiments","firestore","hostId","snapshot","rows","truncated","docs","map","entry","data","$id","id","TARGET_LABEL","screen","section","email","COLUMNS","OrgExperimentsCard","props","results","org","mount","enqueueSnackbar","entitled","sites","hosts","page","setPage","pageSites","slice","reads","site","setResults","opening","setOpening","openResults","siteName","experiment","stats","forEach","error","console","variant","resultsHref","header","subheader","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","hostsReady","length","color","spacing","direction","sx","alignItems","justifyContent","label","size","aria-label","align","read","get","answer","value","href","name","subdomain","colSpan","flexWrap","status","target","variants","toLocaleString","disabled","onClick","componentVariant","pageSize","rowCount","count","onPageChange","labelDisplayedRows","from","to","open","Boolean","onClose","maxWidth","fullWidth","autoCompleted","ml","summary","comparison","leader","winner","selected","toUpperCase","exposures","conversions","rate","toFixed","experimentId","test","trim","displayName"],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
18
+ import type { ListQueryDeclaration, ListQueryFilter } from '@aglyn/shared-ui-jsx/const/list-query-plan';
19
+ /** The send's subject, as word prefixes. */
20
+ export declare const CAMPAIGN_SEND_SUBJECT_TOKENS = "subjectTokens";
21
+ /** The container's name, as word prefixes. */
22
+ export declare const CAMPAIGN_NAME_TOKENS = "nameTokens";
23
+ /** The container's name, lower-cased whole, for a site hub's prefix search. */
24
+ export declare const CAMPAIGN_NAME_LOWER = "nameLower";
25
+ /** The one date on every send and container, and every list's order. */
26
+ export declare const CAMPAIGN_LIST_ORDER_FIELD = "createdAtMs";
27
+ /**
28
+ * The Campaign filter's value for an email filed under no campaign. An
29
+ * empty value reads as "no value" to the panel, so the choice needs one of
30
+ * its own; the query asks `emailCampaignId == null` for it.
31
+ */
32
+ export declare const SINGLE_SEND_FILTER_VALUE = "__single__";
33
+ /**
34
+ * The search field a send carries beside its `subject`, stamped by every
35
+ * write that sets the subject.
36
+ */
37
+ export declare function campaignSendSearchFields(subject: unknown): {
38
+ subjectTokens: string[];
39
+ };
40
+ /**
41
+ * The search fields a container carries beside its `name`, stamped by every
42
+ * write that sets the name.
43
+ */
44
+ export declare function campaignContainerSearchFields(name: unknown): {
45
+ nameLower: string;
46
+ nameTokens: string[];
47
+ };
48
+ /**
49
+ * The stored statuses a send can hold, for the Status filter. Stored
50
+ * strings, not the State column's display states: an email between batches
51
+ * is stored `scheduled` and SHOWN as Sending, which is why the filter is
52
+ * headed Status and not State.
53
+ */
54
+ export declare const CAMPAIGN_SEND_STATUS_OPTIONS: ReadonlyArray<{
55
+ value: string;
56
+ label: string;
57
+ }>;
58
+ /**
59
+ * The Emails list: every send, under a site or across the org.
60
+ *
61
+ * `site` is the org hub's only — under a site every row is that site's.
62
+ */
63
+ export declare function campaignEmailsListQuery(orgHub: boolean): ListQueryDeclaration;
64
+ /** A campaign's own emails: the container is the query's base. */
65
+ export declare const CAMPAIGN_EMAILS_QUERY: ListQueryDeclaration;
66
+ /**
67
+ * The Campaigns list's CAMPAIGNS: the containers. The columns are the
68
+ * table's (`name`, `listIds`, `sitesLabel`); Lists and Sites are the org
69
+ * hub's only — under a site the scope clause is the query's one array
70
+ * clause, so an any-of on another array could never be served beside it.
71
+ */
72
+ export declare function campaignContainersListQuery(orgHub: boolean): ListQueryDeclaration;
73
+ /**
74
+ * The Campaigns list's SINGLE SENDS: sends filed under no campaign, drawn
75
+ * in the same table. The `name` column is the send's subject; `sitesLabel`
76
+ * is the site it was sent as, on the org hub.
77
+ */
78
+ export declare function campaignSingleSendsListQuery(orgHub: boolean): ListQueryDeclaration;
79
+ /** A site hub's sends: the ones sent as it. Nothing on the org hub. */
80
+ export declare function campaignSendsScope(hostId: string | null): ListQueryFilter[];
81
+ /** Said above a site hub's campaigns while its search is applied. */
82
+ export declare const CAMPAIGN_SITE_SEARCH_NOTICE = "Search on a site\u2019s campaigns matches the start of a campaign\u2019s name.";
83
+ /**
84
+ * A site hub's campaign search, as the clause its query can serve: the typed
85
+ * words as "Campaign starts with", or null when nothing was typed. The list
86
+ * passes it among its clauses and no search words — see the file header for
87
+ * why a site's search is a prefix range and not a token lookup.
88
+ */
89
+ export declare function campaignSiteSearchClause(words: readonly string[]): ListFilterClause | null;
90
+ /** A site hub's containers: the ones placed on every site or on it. */
91
+ export declare function campaignContainersScope(hostId: string | null): ListQueryFilter[];
92
+ /** The sends filed under no campaign. */
93
+ export declare const SINGLE_SENDS_BASE: ListQueryFilter;
94
+ /**
95
+ * The Campaign filter's clauses as the query asks them: "Single send" is
96
+ * `emailCampaignId == null`, which the grammar spells `isEmpty`. The chips
97
+ * keep reading the clause as the reader set it.
98
+ */
99
+ export declare function emailCampaignQueryClauses(clauses: readonly ListFilterClause[]): ListFilterClause[];