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

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 (35) hide show
  1. package/package.json +15 -15
  2. package/src/lib/components/campaign-composer.js +14 -6
  3. package/src/lib/components/campaign-composer.js.map +1 -1
  4. package/src/lib/components/campaign-conversions-card.js +6 -0
  5. package/src/lib/components/campaign-conversions-card.js.map +1 -1
  6. package/src/lib/components/campaign-detail-card.js +1 -1
  7. package/src/lib/components/campaign-detail-card.js.map +1 -1
  8. package/src/lib/components/campaign-members-section.d.ts +16 -4
  9. package/src/lib/components/campaign-members-section.js +171 -105
  10. package/src/lib/components/campaign-members-section.js.map +1 -1
  11. package/src/lib/components/campaigns-card.js +19 -9
  12. package/src/lib/components/campaigns-card.js.map +1 -1
  13. package/src/lib/components/email-compose-card.js +4 -3
  14. package/src/lib/components/email-compose-card.js.map +1 -1
  15. package/src/lib/components/email-detail.js +16 -5
  16. package/src/lib/components/email-detail.js.map +1 -1
  17. package/src/lib/components/email-recipients-card.js +3 -1
  18. package/src/lib/components/email-recipients-card.js.map +1 -1
  19. package/src/lib/components/emails-list-card.js +3 -1
  20. package/src/lib/components/emails-list-card.js.map +1 -1
  21. package/src/lib/components/host-experiments-card.component.js +3 -0
  22. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  23. package/src/lib/model/campaign-members-query.d.ts +37 -0
  24. package/src/lib/model/campaign-members-query.js +120 -0
  25. package/src/lib/model/campaign-members-query.js.map +1 -0
  26. package/src/lib/server/campaign-form-flags.d.ts +35 -0
  27. package/src/lib/server/campaign-form-flags.js +52 -0
  28. package/src/lib/server/campaign-form-flags.js.map +1 -0
  29. package/src/lib/server/campaign-manage.js +12 -2
  30. package/src/lib/server/campaign-manage.js.map +1 -1
  31. package/src/lib/server/campaign-send.d.ts +35 -1
  32. package/src/lib/server/campaign-send.js +230 -11
  33. package/src/lib/server/campaign-send.js.map +1 -1
  34. package/src/lib/server/email-events.js +10 -0
  35. package/src/lib/server/email-events.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/emails-list-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 { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n mdiBullhornOutline,\n mdiContentCopy,\n mdiDeleteOutline,\n mdiEyeOutline,\n mdiPaletteOutline,\n} from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} 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 ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\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 { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { CreateArtifactDrawer } from '@aglyn/shared-ui-jsx-forms'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { collectionCeiling } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n DUPLICATE_MENU_LABEL,\n useDuplicateResource,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n campaignPlacedOnHost,\n campaignSendDisplay,\n CAMPAIGN_SEND_CONTAINER_FIELD,\n type CampaignSendDisplayState,\n} from '@aglyn/shared-ui-email-campaigns/model/campaign-container'\nimport {\n emailListTimeMs,\n emailSendTimeMs,\n} from '@aglyn/shared-ui-email-campaigns/model/email-record'\nimport {\n useCampaignManageApi,\n useCampaignSendApi,\n} from './use-campaign-send-api'\nimport {\n CAMPAIGN_SEND_STATUS_OPTIONS,\n SINGLE_SEND_FILTER_VALUE,\n campaignEmailsListQuery,\n campaignSendsScope,\n emailCampaignQueryClauses,\n} from '../model/campaign-list-query'\nimport { useMarketingHubPath } from './use-marketing-hub-path'\nimport {\n campaignContainersQuery,\n campaignSendsCollection,\n} from './campaign-queries'\nimport {\n orgSiteHubPath,\n orgSiteName,\n orgSiteOptions,\n useMarketingOrgId,\n useMarketingOrgMount,\n} from './marketing-org-mount'\n\n/**\n * How many campaigns the Campaign filter and the create drawer offer — the\n * same read serves both. A picker's options, not a list.\n */\nconst CONTAINER_CEILING = 50\n\n/**\n * Why discard is refused, keyed by the state that refuses it.\n *\n * The KEYS are persisted status values written by the send path. Each line\n * says what to do instead, because \"you cannot\" on its own leaves a merchant\n * with an email they wanted rid of and no next step — and for a scheduled one\n * the next step is a different act with a different consequence, so naming it\n * is the difference between withdrawing mail and losing the record of it.\n *\n * A state absent from here falls through to the generic refusal rather than\n * being flattened into one of these: a state this list cannot name is worth\n * seeing.\n */\nconst DISCARD_REFUSAL: Record<string, string> = {\n sent: 'This email has been sent. Its report and its unsubscribe links have to go on resolving.',\n scheduled: 'Cancel the send first — cancelling takes it off the clock and keeps the email.',\n sending: 'This email is being sent right now.',\n canceled: 'A canceled email is kept as the record that it was withdrawn.',\n}\n\n/**\n * The chip color for one display state.\n *\n * `sending` is the one worth a color: an email part way through an audience\n * larger than one batch is doing something right now, and a merchant scanning\n * the list for what needs attention should find it without reading. The rest\n * are neutral, because a finished send and a draft are not events.\n */\nconst STATE_COLOR: Partial<\n Record<CampaignSendDisplayState, 'info' | 'warning'>\n> = {\n sending: 'info',\n stopped: 'warning',\n}\n\n/*\n * What the messages grid's Filters panel offers, each clause and the search\n * on the list's Firestore query (AGL-3321, `campaignEmailsListQuery`). The\n * State column is what the email is DOING (`campaignSendDisplay`), which no\n * query can ask, so the panel offers the Status it STORES instead.\n */\nconst EMAIL_FILTER_HEADERS: Readonly<Record<string, string>> = {\n subject: 'Subject',\n status: 'Status',\n emailCampaignId: 'Campaign',\n site: 'Site',\n createdAtMs: 'Created',\n}\n\nconst emailsDocsHelp = pluginDocsHelp('emailCampaigns', {\n anchor: '#opens--clicks',\n excerpt:\n 'Every message this site has sent or has scheduled, each with its own ' +\n 'report: what was delivered, who opened it, and which links they followed.',\n})\n\nexport interface EmailsListCardProps {\n /** The site, or `null` on the organization's Emails page. */\n hostId: string | null\n /**\n * The Emails page's own URL, under the site or the organization, so a row\n * can link to the message's own page beneath it.\n */\n basePath: string\n}\n\n/**\n * EVERY MESSAGE, AS AGAINST EVERY CAMPAIGN.\n *\n * A campaign groups messages; this is the messages. The two are different\n * questions — \"how did the spring promotion do\" and \"what went out on the\n * 14th, and to whom\" — and they were previously the same list because a\n * campaign document WAS a single send.\n *\n * ## Asked of Firestore, a page at a time\n *\n * No SEND date is on every message — a sent one carries `sentAt`, a\n * scheduled one `sendAtMs`, a draft neither — so the list is ordered on\n * `createdAtMs`, the one date every writer stamps when it mints a record\n * (and `backfill-campaign-list-fields.mjs` stamped on the ones before).\n * Newest first, paged by the query itself.\n *\n * The Filters panel and the search box are the grid's; every clause and the\n * search word go onto that ONE query (AGL-3321, `campaignEmailsListQuery`),\n * so each page is a page of matches — an email that matches on page four is\n * found — and a combination one query cannot hold is refused by name above\n * the table, never answered over the rows that happen to be loaded.\n *\n * ## Under a site, and over the organization\n *\n * Sends are the org's. Under a site this lists the ones sent as that site;\n * on the organization's Emails page it lists every site's, with a Site column,\n * and every action on a row names the site that row is sent as — the only\n * site that can duplicate it, discard it or open its template. Creating one\n * there first asks which site it is sent as.\n */\nexport function EmailsListCard(props: EmailsListCardProps) {\n const { hostId, basePath } = props\n const firestore = useFirestore()\n const orgMount = useMarketingOrgMount()\n const { orgId } = useMarketingOrgId(hostId)\n // The sibling hub: a campaign's page belongs to the Marketing console —\n // over the org, the organization's own Marketing page the mount names.\n const siteMarketingHub = useMarketingHubPath()\n const marketingHub = orgMount ? orgMount.basePath : siteMarketingHub\n const router = useRouter()\n\n /*\n * The Filters panel and the search, bound to the grid. Held here rather\n * than in a saved view: this list keeps no views.\n */\n const declaration = campaignEmailsListQuery(!hostId)\n const gridFilter = useListGridFilter({\n selectFields: ['status', 'emailCampaignId', 'site'],\n })\n const emailPage = useListQuery<any>({\n collection: campaignSendsCollection(firestore, orgId),\n declaration,\n request: {\n clauses: emailCampaignQueryClauses(gridFilter.clauses),\n search: gridFilter.searchWords,\n base: campaignSendsScope(hostId),\n },\n deps: [firestore, orgId, hostId],\n idField: '$id',\n })\n const emails = emailPage.rows\n const filtering =\n gridFilter.clauses.length > 0 ||\n gridFilter.searchWords.some((word) => word.trim())\n\n /*\n * A message's own pages, beneath the Emails page this list is on — the\n * site's or the organization's. A template is a site's design, so over the\n * org it opens on the site the row is sent as.\n */\n const messagesPath = `${basePath}/messages`\n const emailHref = (email: any) => `${messagesPath}/${email.$id}`\n const templatesHub = (email: any): string | null =>\n orgMount ? orgSiteHubPath(orgMount, email?.hostId, 'emails') : basePath\n /** The site a row is sent as, which every action on it must name. */\n const rowHostId = useCallback(\n (email: any): string | null =>\n hostId ?? (email?.hostId ? String(email.hostId) : null),\n [hostId],\n )\n const hostOfEmail = useMemo(\n () =>\n new Map<string, string | null>(\n emails.map((email: any) => [\n String(email.$id),\n email?.hostId ? String(email.hostId) : null,\n ]),\n ),\n [emails],\n )\n\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n // Duplicate (AGL-2936): a new draft with the message and its design, and\n // nobody to send to until the person chooses — through the manage door,\n // which is the plugin's own.\n const manageForCopy = useCampaignManageApi(hostId)\n const duplicate = useDuplicateResource({\n hostId: hostId ?? '',\n perform: async ({ sourceId, name, attemptKey }) => {\n const { response, payload } = await manageForCopy({\n action: 'duplicate',\n campaignId: sourceId,\n name,\n attemptKey,\n // The copy is made as the site the source is sent as.\n ...(hostId ? {} : { hostId: hostOfEmail.get(sourceId) ?? undefined }),\n })\n if (!response.ok) throw new Error(payload?.error ?? 'Duplicate failed')\n return {\n id: String(payload.emailId),\n versionId: null,\n name: String(payload.name ?? name),\n }\n },\n onDuplicated: (_kind, copy) =>\n enqueueSnackbar(`Duplicated as “${copy.name}” — a draft with no audience yet`, {\n variant: 'success',\n }),\n })\n const manageApi = useCampaignManageApi(hostId)\n /** The email a discard is in flight for, so its row menu can say so. */\n const [discardingId, setDiscardingId] = useState('')\n\n /*==========================================\n * THROWING AWAY A DRAFT.\n *\n * The one removal this surface has, and it is deliberately the narrowest\n * one: an email that was never sent, whose record nobody outside the\n * console has ever seen. Everything else on this list is evidence — a sent\n * message's report is what a merchant answers a complaint with, and its id\n * is inside the HMAC of every unsubscribe footer it delivered — and a\n * scheduled one is withdrawn with Cancel, which keeps the record and takes\n * it off the clock.\n *\n * The menu already refuses anything but a draft, and so does the route.\n * That is not belt and braces: the state on screen is a snapshot that can\n * be seconds old, and the record can be claimed by `sendNow` in between.\n * The route's refusal is the one that decides, inside the transaction that\n * does the delete.\n *=========================================*/\n const handleDiscard = useCallback(\n async (email: any) => {\n if (discardingId) return\n const id = String(email?.$id ?? '')\n const name = String(email?.subject || email?.displayName || 'this draft')\n const agreed = await confirm({\n title: 'Discard this draft?',\n description:\n `${name} has not been sent to anybody, and discarding it removes ` +\n 'it for good — the subject, the message and everything else ' +\n 'written on it. There is no undo.',\n confirmationText: 'Discard',\n })\n .then(() => true)\n .catch(() => false)\n if (!agreed) return\n setDiscardingId(id)\n try {\n const { response, payload } = await manageApi({\n action: 'discardEmail',\n campaignId: id,\n ...(hostId ? {} : { hostId: rowHostId(email) ?? undefined }),\n })\n if (!response.ok) {\n return void enqueueSnackbar(\n payload?.error ?? 'This draft could not be discarded',\n { variant: 'warning', allowDuplicate: true },\n )\n }\n enqueueSnackbar('Draft discarded', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('This draft could not be discarded', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setDiscardingId('')\n }\n },\n [confirm, discardingId, enqueueSnackbar, hostId, manageApi, rowHostId],\n )\n\n /**\n * What a message can be opened INTO, from the list.\n *\n * All three are places the message's own report already links to, moved one\n * screen earlier: the report itself, the campaign it belongs to, and the\n * template it was rendered from. A message need have neither of the last\n * two — one sent before campaigns grouped anything names no container, and\n * one composed inline was built from no template — so those entries are\n * shown DISABLED with the reason rather than hidden. A control that\n * disappears and a control that does not apply look identical, and only one\n * of them tells the reader which case they are in.\n *\n * DISCARD is the fourth entry and is offered on the same terms: a draft can\n * be thrown away, and nothing else can. It stays visible on a sent or\n * scheduled message carrying the reason it is refused, because that is the\n * honest answer to “how do I get rid of this” — and the route refuses it\n * too, so the menu is describing a rule rather than being one.\n */\n const rowActions = (email: any): RowActionsMenuItem[] => {\n const containerId = String(email?.[CAMPAIGN_SEND_CONTAINER_FIELD] ?? '')\n const templateScreenId = String(email?.templateScreenId ?? '')\n const state = String(email?.status ?? '')\n const templates = templatesHub(email)\n /** On the org hub, a send that names no site has nothing to act as. */\n const siteless = !rowHostId(email)\n const sitelessReason =\n 'This email does not record which site it was sent as. Manage it from ' +\n 'that site’s own Emails page.'\n return [\n {\n key: 'details',\n label: 'Open report',\n icon: <MdiIcon path={mdiEyeOutline.path} size={0.8} />,\n href: emailHref(email),\n },\n {\n key: 'campaign',\n label: 'Open its campaign',\n icon: <MdiIcon path={mdiBullhornOutline.path} size={0.8} />,\n // The campaign's page belongs to the Marketing console, so this one\n // href is built from the sibling hub rather than this surface's own.\n href:\n containerId && marketingHub\n ? `${marketingHub}/campaigns/${containerId}`\n : undefined,\n disabled: !containerId || !marketingHub,\n disabledReason: containerId\n ? 'This site’s console URL has not resolved yet'\n : 'Sent before campaigns grouped their emails, so it belongs to none',\n },\n {\n key: 'template',\n label: 'Open its template',\n icon: <MdiIcon path={mdiPaletteOutline.path} size={0.8} />,\n href:\n templateScreenId && templates\n ? `${templates}/templates/${templateScreenId}`\n : undefined,\n disabled: !templateScreenId || !templates,\n disabledReason: templateScreenId\n ? 'This site’s console URL has not resolved yet'\n : 'This message was not built from a template',\n },\n {\n key: 'duplicate',\n label: DUPLICATE_MENU_LABEL,\n icon: <MdiIcon path={mdiContentCopy.path} size={0.8} />,\n disabled: siteless,\n disabledReason: sitelessReason,\n onClick: () =>\n duplicate.request('campaign', {\n id: String(email.$id),\n name: String(email?.displayName ?? email?.subject ?? ''),\n }),\n },\n {\n key: 'discard',\n label: 'Discard draft',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: siteless || state !== 'draft' || Boolean(discardingId),\n disabledReason: siteless\n ? sitelessReason\n : (DISCARD_REFUSAL[state] ?? 'Only a draft can be discarded'),\n onClick: () => void handleDiscard(email),\n },\n ]\n }\n\n const [createOpen, setCreateOpen] = useState(false)\n const [creating, setCreating] = useState(false)\n const campaignSendApi = useCampaignSendApi(hostId)\n /*\n * The sites a new email can be sent as, on the org hub. One site answers\n * the question itself, so the field is asked only when there is a choice.\n */\n const orgSites = useMemo(\n () => (orgMount ? orgSiteOptions(orgMount) : []),\n [orgMount],\n )\n const askSite = !hostId && orgSites.length > 1\n\n /*\n * The campaigns, read while the list is shown: the Campaign filter offers\n * them by name, and the create drawer offers them to file a new email\n * under. One capped read of the containers serves both.\n */\n const { data: campaignDocs } = useFirestoreCollection<any>(\n () => {\n const containers = campaignContainersQuery(firestore, orgId, hostId)\n return containers ? collectionCeiling(containers, CONTAINER_CEILING) : null\n },\n [firestore, orgId, hostId],\n { idField: '$id' },\n )\n const campaignOptions = useMemo(\n () =>\n [...(campaignDocs ?? [])]\n .map((campaign: any) => ({\n value: String(campaign.$id),\n label: String(campaign.name || 'Untitled campaign'),\n }))\n .sort((a, b) => a.label.localeCompare(b.label)),\n [campaignDocs],\n )\n\n const filterOptions = useMemo(\n () => ({\n status: CAMPAIGN_SEND_STATUS_OPTIONS,\n emailCampaignId: [\n { value: SINGLE_SEND_FILTER_VALUE, label: 'Single send' },\n ...campaignOptions,\n ],\n ...(hostId ? {} : { site: orgSites }),\n }),\n [campaignOptions, hostId, orgSites],\n )\n\n /*==========================================\n * CREATE, THEN GO TO THE EMAIL'S OWN PAGE.\n *\n * The drawer collects only what it takes to MINT the record — the friendly\n * name, and the campaign it belongs to. Everything else about the email is\n * written on the email's own page, which is where a record is edited\n * throughout this console: a list page carries no form.\n *\n * The record is real from this moment: `/marketing/campaigns/{id}` resolves,\n * the row appears in the table below as a Draft, and the id it is created\n * under is the id it keeps when it is eventually sent. It costs nothing to\n * exist — the route reserves no allowance and moves no meter for a draft,\n * and the scheduled processor only ever picks up `scheduled`.\n *=========================================*/\n const handleCreate = useCallback(\n async (values: Record<string, any>) => {\n if (creating) return\n const sendAs =\n hostId ?? (askSite ? String(values.hostId ?? '') : orgSites[0]?.value)\n if (!sendAs) {\n return void enqueueSnackbar('Choose the site this email is sent as', {\n variant: 'warning',\n allowDuplicate: true,\n })\n }\n /*\n * The organization hub offers every campaign, because the site is\n * chosen in the same form. An email can only join a campaign placed on\n * the site it is sent as — the send route refuses anything else — so\n * the mismatch is named here, where it can be fixed, rather than\n * arriving as the route's bare refusal. A site hub lists only the\n * campaigns placed on it, so there is nothing to check there.\n */\n const chosenCampaign =\n !hostId && values.emailCampaignId\n ? (campaignDocs ?? []).find(\n (campaign: any) =>\n String(campaign.$id) === String(values.emailCampaignId),\n )\n : null\n if (chosenCampaign && !campaignPlacedOnHost(chosenCampaign, sendAs)) {\n return void enqueueSnackbar(\n 'That campaign is not offered on the site this email is sent as. ' +\n 'Choose another, or add the site to the campaign first.',\n { variant: 'warning', allowDuplicate: true },\n )\n }\n setCreating(true)\n try {\n const { response, payload } = await campaignSendApi({\n action: 'draft',\n ...(hostId ? {} : { hostId: sendAs }),\n displayName: String(values.displayName ?? '').trim(),\n ...(values.emailCampaignId\n ? { emailCampaignId: String(values.emailCampaignId) }\n : {}),\n })\n if (!response.ok || !payload?.campaignId) {\n return void enqueueSnackbar(\n payload?.error ?? 'This email could not be created',\n { variant: 'warning', allowDuplicate: true },\n )\n }\n setCreateOpen(false)\n /*\n * Straight to where it is WRITTEN. The drawer collected the name and\n * the campaign; the record now exists and holds nothing else, so the\n * next thing to do with it is compose it — and its own page is a\n * report of a send that has not happened.\n */\n router.push(`${messagesPath}/${payload.campaignId}/edit`)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('This email could not be created', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setCreating(false)\n }\n },\n [\n askSite,\n campaignDocs,\n campaignSendApi,\n creating,\n enqueueSnackbar,\n hostId,\n messagesPath,\n orgSites,\n router,\n ],\n )\n\n /*\n * One row per message, in the query's order. The subject is a link AND the\n * row opens the report: a click handler cannot be middle-clicked or opened\n * in a new tab.\n */\n const columns: GridColDef[] = [\n {\n field: 'subject',\n headerName: 'Subject',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => row.subject || 'Untitled email',\n renderCell: ({ row, value }) => (\n <AppLink\n href={emailHref(row)}\n // The row's own handler would fire too and push the same route\n // twice — one history entry per back press.\n onClick={(event: { stopPropagation: () => void }) =>\n event.stopPropagation()\n }\n >\n {value}\n </AppLink>\n ),\n },\n // Which site each row is sent as, on the org hub only.\n ...(orgMount\n ? [\n {\n field: 'site',\n headerName: 'Site',\n width: 150,\n valueGetter: (_value: unknown, row: any) =>\n row?.hostId ? orgSiteName(orgMount, row.hostId) : '—',\n } satisfies GridColDef,\n ]\n : []),\n {\n /*\n * WHAT THIS EMAIL IS DOING, not what field it stores.\n *\n * An email delivering an audience larger than one batch is written back\n * as `scheduled` between runs, so a chip rendering the status said\n * \"Scheduled\" about a send that had already reached five hundred people.\n * The derivation reads the counters beside the status and says which of\n * the two it is.\n */\n field: 'state',\n headerName: 'State',\n width: 150,\n valueGetter: (_value, row) => campaignSendDisplay(row).label,\n renderCell: ({ row }) => {\n const display = campaignSendDisplay(row)\n return (\n <Chip\n size=\"small\"\n color={STATE_COLOR[display.state]}\n label={display.label}\n />\n )\n },\n },\n {\n field: 'when',\n headerName: 'When',\n width: 190,\n valueGetter: (_value, row) => emailListTimeMs(row),\n renderCell: ({ row }) => {\n const at = emailSendTimeMs(row)\n return at ? new Date(at).toLocaleString() : '—'\n },\n },\n ...(\n [\n ['recipients', 'Addressed'],\n ['opens', 'Opens'],\n ['clicks', 'Clicks'],\n ] as const\n ).map(\n ([stat, headerName]): GridColDef => ({\n field: stat,\n headerName,\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueGetter: (_value, row) => Number(row.stats?.[stat] ?? 0),\n valueFormatter: (value: number) => value.toLocaleString(),\n }),\n ),\n listActionsColumn(\n (row) => (\n <ListRowActions\n label={String(row.subject || 'Untitled email')}\n items={rowActions(row)}\n />\n ),\n { width: 72 },\n ),\n ]\n\n const gridColumns = listFilterGridColumns(\n columns,\n declaration.fields,\n filterOptions,\n EMAIL_FILTER_HEADERS,\n )\n /** Nothing stored at all — not a filter that matched nothing. */\n const none =\n emailPage.status !== 'loading' &&\n emails.length === 0 &&\n !filtering &&\n emailPage.page === 0\n\n return (\n <CardDisplay\n header={'Messages'}\n help={emailsDocsHelp}\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => setCreateOpen(true)}\n >\n {'New email'}\n </Button>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {duplicate.dialog}\n <Stack spacing={2}>\n {none ? (\n <Stack spacing={2} sx={{ alignItems: 'flex-start' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing has been sent or scheduled yet. Write one here, or ' +\n 'from a campaign, and it appears in this list with its own ' +\n 'report.'}\n </Typography>\n <Button variant=\"contained\" onClick={() => setCreateOpen(true)}>\n {'New email'}\n </Button>\n </Stack>\n ) : (\n <>\n <ListFilterChips\n fields={declaration.fields}\n headers={EMAIL_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n <ListQueryNotices\n refused={listQueryRefusals(emailPage.plan.refused, {\n fields: declaration.fields,\n headers: EMAIL_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={emailPage.plan.notices}\n />\n <ListTable\n aria-label=\"Messages\"\n rows={emails}\n columns={gridColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row) => router.push(emailHref(row))}\n loading={emailPage.status === 'loading'}\n /*\n * The panel and the search are the grid's; every clause and\n * the search word are on the list's query (AGL-3321), and the\n * grid neither filters nor sorts the page it is handed: the\n * query's one order, newest first, is the list's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n hideFooter\n initialState={{\n columns: {\n columnVisibilityModel: hiddenFilterVisibility(\n declaration.fields,\n columns.map((column) => column.field),\n ),\n },\n }}\n noRowsLabel=\"No messages match these filters\"\n />\n <ListPagination\n page={emailPage.page}\n pageSize={emailPage.pageSize}\n rowCount={emails.length}\n hasMore={emailPage.hasMore}\n onPageChange={emailPage.setPage}\n onPageSizeChange={emailPage.setPageSize}\n />\n </>\n )}\n </Stack>\n {/*\n AN EMAIL WRITTEN HERE BELONGS TO A CAMPAIGN, OR TO NO CAMPAIGN.\n\n The second is a real answer and not a gap in the model. A send with no\n container is what the product has always had — every message that\n predates campaigns is one — and `campaignListRows` adopts each of them\n as a campaign of one at read time, which is the \"Single send\" chip on\n the campaigns table. So composing from this list mints nothing, files\n nothing, and requires nobody to invent a campaign first: it leaves the\n container empty, and the send that results is presented the way every\n containerless send already is.\n\n Offering the campaigns anyway is what stops the opposite mistake — a\n merchant who DOES have a spring campaign writing its third email into\n a single send that never joins the rollup.\n */}\n <CreateArtifactDrawer\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"New email\"\n submitLabel={creating ? 'Creating…' : 'Start writing'}\n includeDescription={false}\n onSubmit={handleCreate}\n extraFields={[\n ...(askSite\n ? [\n {\n component: 'select',\n name: 'hostId',\n label: 'Send as',\n isRequired: true,\n initialValue: '',\n helperText:\n 'The site this email is sent from — its sender, designs ' +\n 'and unsubscribe page',\n disableDefaultOption: true,\n options: orgSites,\n },\n ]\n : []),\n {\n component: 'select',\n name: 'emailCampaignId',\n label: 'Campaign',\n initialValue: '',\n helperText:\n 'Leave this as a single send unless the email belongs with ' +\n 'others',\n disableDefaultOption: true,\n options: [\n { value: '', label: 'Single send — not part of a campaign' },\n ...campaignOptions,\n ],\n },\n ]}\n />\n </CardDisplay>\n )\n}\nEmailsListCard.displayName = 'EmailsListCard'\n\nexport default EmailsListCard\n"],"names":["pluginDocsHelp","mdiBullhornOutline","mdiContentCopy","mdiDeleteOutline","mdiEyeOutline","mdiPaletteOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","TABLE_ROW_HEIGHT","CreateArtifactDrawer","useSnackbar","collectionCeiling","useListQuery","DUPLICATE_MENU_LABEL","useDuplicateResource","useFirestore","useFirestoreCollection","Button","Chip","Stack","Typography","useRouter","useCallback","useMemo","useState","campaignPlacedOnHost","campaignSendDisplay","CAMPAIGN_SEND_CONTAINER_FIELD","emailListTimeMs","emailSendTimeMs","useCampaignManageApi","useCampaignSendApi","CAMPAIGN_SEND_STATUS_OPTIONS","SINGLE_SEND_FILTER_VALUE","campaignEmailsListQuery","campaignSendsScope","emailCampaignQueryClauses","useMarketingHubPath","campaignContainersQuery","campaignSendsCollection","orgSiteHubPath","orgSiteName","orgSiteOptions","useMarketingOrgId","useMarketingOrgMount","CONTAINER_CEILING","DISCARD_REFUSAL","sent","scheduled","sending","canceled","STATE_COLOR","stopped","EMAIL_FILTER_HEADERS","subject","status","emailCampaignId","site","createdAtMs","emailsDocsHelp","anchor","excerpt","EmailsListCard","props","hostId","basePath","firestore","orgMount","orgId","siteMarketingHub","marketingHub","router","declaration","gridFilter","selectFields","emailPage","collection","request","clauses","search","searchWords","base","deps","idField","emails","rows","filtering","length","some","word","trim","messagesPath","emailHref","email","$id","templatesHub","rowHostId","String","hostOfEmail","Map","map","confirm","enqueueSnackbar","manageForCopy","duplicate","perform","sourceId","name","attemptKey","payload","response","action","campaignId","get","undefined","ok","Error","error","id","emailId","versionId","onDuplicated","_kind","copy","variant","manageApi","discardingId","setDiscardingId","handleDiscard","displayName","agreed","title","description","confirmationText","then","catch","allowDuplicate","persist","console","rowActions","containerId","templateScreenId","state","templates","siteless","sitelessReason","key","label","icon","path","size","href","disabled","disabledReason","onClick","destructive","Boolean","createOpen","setCreateOpen","creating","setCreating","campaignSendApi","orgSites","askSite","data","campaignDocs","containers","campaignOptions","campaign","value","sort","a","b","localeCompare","filterOptions","handleCreate","values","sendAs","chosenCampaign","find","push","columns","field","headerName","flex","minWidth","valueGetter","_value","row","renderCell","event","stopPropagation","width","display","color","at","Date","toLocaleString","stat","type","align","headerAlign","Number","stats","valueFormatter","items","gridColumns","fields","none","page","header","help","HeaderProps","contentGutterX","contentGutterY","dialog","spacing","sx","alignItems","headers","onChange","setClauses","options","refused","plan","notices","aria-label","rowHeight","onOpen","_id","loading","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","hideFooter","initialState","columnVisibilityModel","column","noRowsLabel","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","open","onClose","submitLabel","includeDescription","onSubmit","extraFields","component","isRequired","initialValue","helperText","disableDefaultOption"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/emails-list-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 { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n mdiBullhornOutline,\n mdiContentCopy,\n mdiDeleteOutline,\n mdiEyeOutline,\n mdiPaletteOutline,\n} from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} 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 ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\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 { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { CreateArtifactDrawer } from '@aglyn/shared-ui-jsx-forms'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { collectionCeiling } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n DUPLICATE_MENU_LABEL,\n useDuplicateResource,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n campaignPlacedOnHost,\n campaignSendDisplay,\n CAMPAIGN_SEND_CONTAINER_FIELD,\n type CampaignSendDisplayState,\n} from '@aglyn/shared-ui-email-campaigns/model/campaign-container'\nimport {\n emailListTimeMs,\n emailSendTimeMs,\n} from '@aglyn/shared-ui-email-campaigns/model/email-record'\nimport {\n useCampaignManageApi,\n useCampaignSendApi,\n} from './use-campaign-send-api'\nimport {\n CAMPAIGN_SEND_STATUS_OPTIONS,\n SINGLE_SEND_FILTER_VALUE,\n campaignEmailsListQuery,\n campaignSendsScope,\n emailCampaignQueryClauses,\n} from '../model/campaign-list-query'\nimport { useMarketingHubPath } from './use-marketing-hub-path'\nimport {\n campaignContainersQuery,\n campaignSendsCollection,\n} from './campaign-queries'\nimport {\n orgSiteHubPath,\n orgSiteName,\n orgSiteOptions,\n useMarketingOrgId,\n useMarketingOrgMount,\n} from './marketing-org-mount'\n\n/**\n * How many campaigns the Campaign filter and the create drawer offer — the\n * same read serves both. A picker's options, not a list.\n */\nconst CONTAINER_CEILING = 50\n\n/**\n * Why discard is refused, keyed by the state that refuses it.\n *\n * The KEYS are persisted status values written by the send path. Each line\n * says what to do instead, because \"you cannot\" on its own leaves a merchant\n * with an email they wanted rid of and no next step — and for a scheduled one\n * the next step is a different act with a different consequence, so naming it\n * is the difference between withdrawing mail and losing the record of it.\n *\n * A state absent from here falls through to the generic refusal rather than\n * being flattened into one of these: a state this list cannot name is worth\n * seeing.\n */\nconst DISCARD_REFUSAL: Record<string, string> = {\n sent: 'This email has been sent. Its report and its unsubscribe links have to go on resolving.',\n scheduled: 'Cancel the send first — cancelling takes it off the clock and keeps the email.',\n sending: 'This email is being sent right now.',\n canceled: 'A canceled email is kept as the record that it was withdrawn.',\n}\n\n/**\n * The chip color for one display state.\n *\n * `sending` is the one worth a color: an email part way through an audience\n * larger than one batch is doing something right now, and a merchant scanning\n * the list for what needs attention should find it without reading. The rest\n * are neutral, because a finished send and a draft are not events.\n */\nconst STATE_COLOR: Partial<\n Record<CampaignSendDisplayState, 'info' | 'warning'>\n> = {\n sending: 'info',\n stopped: 'warning',\n // Waiting on staff, not on the merchant (AGL-3356): worth finding on a scan.\n held: 'warning',\n}\n\n/*\n * What the messages grid's Filters panel offers, each clause and the search\n * on the list's Firestore query (AGL-3321, `campaignEmailsListQuery`). The\n * State column is what the email is DOING (`campaignSendDisplay`), which no\n * query can ask, so the panel offers the Status it STORES instead.\n */\nconst EMAIL_FILTER_HEADERS: Readonly<Record<string, string>> = {\n subject: 'Subject',\n status: 'Status',\n emailCampaignId: 'Campaign',\n site: 'Site',\n createdAtMs: 'Created',\n}\n\nconst emailsDocsHelp = pluginDocsHelp('emailCampaigns', {\n anchor: '#opens--clicks',\n excerpt:\n 'Every message this site has sent or has scheduled, each with its own ' +\n 'report: what was delivered, who opened it, and which links they followed.',\n})\n\nexport interface EmailsListCardProps {\n /** The site, or `null` on the organization's Emails page. */\n hostId: string | null\n /**\n * The Emails page's own URL, under the site or the organization, so a row\n * can link to the message's own page beneath it.\n */\n basePath: string\n}\n\n/**\n * EVERY MESSAGE, AS AGAINST EVERY CAMPAIGN.\n *\n * A campaign groups messages; this is the messages. The two are different\n * questions — \"how did the spring promotion do\" and \"what went out on the\n * 14th, and to whom\" — and they were previously the same list because a\n * campaign document WAS a single send.\n *\n * ## Asked of Firestore, a page at a time\n *\n * No SEND date is on every message — a sent one carries `sentAt`, a\n * scheduled one `sendAtMs`, a draft neither — so the list is ordered on\n * `createdAtMs`, the one date every writer stamps when it mints a record\n * (and `backfill-campaign-list-fields.mjs` stamped on the ones before).\n * Newest first, paged by the query itself.\n *\n * The Filters panel and the search box are the grid's; every clause and the\n * search word go onto that ONE query (AGL-3321, `campaignEmailsListQuery`),\n * so each page is a page of matches — an email that matches on page four is\n * found — and a combination one query cannot hold is refused by name above\n * the table, never answered over the rows that happen to be loaded.\n *\n * ## Under a site, and over the organization\n *\n * Sends are the org's. Under a site this lists the ones sent as that site;\n * on the organization's Emails page it lists every site's, with a Site column,\n * and every action on a row names the site that row is sent as — the only\n * site that can duplicate it, discard it or open its template. Creating one\n * there first asks which site it is sent as.\n */\nexport function EmailsListCard(props: EmailsListCardProps) {\n const { hostId, basePath } = props\n const firestore = useFirestore()\n const orgMount = useMarketingOrgMount()\n const { orgId } = useMarketingOrgId(hostId)\n // The sibling hub: a campaign's page belongs to the Marketing console —\n // over the org, the organization's own Marketing page the mount names.\n const siteMarketingHub = useMarketingHubPath()\n const marketingHub = orgMount ? orgMount.basePath : siteMarketingHub\n const router = useRouter()\n\n /*\n * The Filters panel and the search, bound to the grid. Held here rather\n * than in a saved view: this list keeps no views.\n */\n const declaration = campaignEmailsListQuery(!hostId)\n const gridFilter = useListGridFilter({\n selectFields: ['status', 'emailCampaignId', 'site'],\n })\n const emailPage = useListQuery<any>({\n collection: campaignSendsCollection(firestore, orgId),\n declaration,\n request: {\n clauses: emailCampaignQueryClauses(gridFilter.clauses),\n search: gridFilter.searchWords,\n base: campaignSendsScope(hostId),\n },\n deps: [firestore, orgId, hostId],\n idField: '$id',\n })\n const emails = emailPage.rows\n const filtering =\n gridFilter.clauses.length > 0 ||\n gridFilter.searchWords.some((word) => word.trim())\n\n /*\n * A message's own pages, beneath the Emails page this list is on — the\n * site's or the organization's. A template is a site's design, so over the\n * org it opens on the site the row is sent as.\n */\n const messagesPath = `${basePath}/messages`\n const emailHref = (email: any) => `${messagesPath}/${email.$id}`\n const templatesHub = (email: any): string | null =>\n orgMount ? orgSiteHubPath(orgMount, email?.hostId, 'emails') : basePath\n /** The site a row is sent as, which every action on it must name. */\n const rowHostId = useCallback(\n (email: any): string | null =>\n hostId ?? (email?.hostId ? String(email.hostId) : null),\n [hostId],\n )\n const hostOfEmail = useMemo(\n () =>\n new Map<string, string | null>(\n emails.map((email: any) => [\n String(email.$id),\n email?.hostId ? String(email.hostId) : null,\n ]),\n ),\n [emails],\n )\n\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n // Duplicate (AGL-2936): a new draft with the message and its design, and\n // nobody to send to until the person chooses — through the manage door,\n // which is the plugin's own.\n const manageForCopy = useCampaignManageApi(hostId)\n const duplicate = useDuplicateResource({\n hostId: hostId ?? '',\n perform: async ({ sourceId, name, attemptKey }) => {\n const { response, payload } = await manageForCopy({\n action: 'duplicate',\n campaignId: sourceId,\n name,\n attemptKey,\n // The copy is made as the site the source is sent as.\n ...(hostId ? {} : { hostId: hostOfEmail.get(sourceId) ?? undefined }),\n })\n if (!response.ok) throw new Error(payload?.error ?? 'Duplicate failed')\n return {\n id: String(payload.emailId),\n versionId: null,\n name: String(payload.name ?? name),\n }\n },\n onDuplicated: (_kind, copy) =>\n enqueueSnackbar(`Duplicated as “${copy.name}” — a draft with no audience yet`, {\n variant: 'success',\n }),\n })\n const manageApi = useCampaignManageApi(hostId)\n /** The email a discard is in flight for, so its row menu can say so. */\n const [discardingId, setDiscardingId] = useState('')\n\n /*==========================================\n * THROWING AWAY A DRAFT.\n *\n * The one removal this surface has, and it is deliberately the narrowest\n * one: an email that was never sent, whose record nobody outside the\n * console has ever seen. Everything else on this list is evidence — a sent\n * message's report is what a merchant answers a complaint with, and its id\n * is inside the HMAC of every unsubscribe footer it delivered — and a\n * scheduled one is withdrawn with Cancel, which keeps the record and takes\n * it off the clock.\n *\n * The menu already refuses anything but a draft, and so does the route.\n * That is not belt and braces: the state on screen is a snapshot that can\n * be seconds old, and the record can be claimed by `sendNow` in between.\n * The route's refusal is the one that decides, inside the transaction that\n * does the delete.\n *=========================================*/\n const handleDiscard = useCallback(\n async (email: any) => {\n if (discardingId) return\n const id = String(email?.$id ?? '')\n const name = String(email?.subject || email?.displayName || 'this draft')\n const agreed = await confirm({\n title: 'Discard this draft?',\n description:\n `${name} has not been sent to anybody, and discarding it removes ` +\n 'it for good — the subject, the message and everything else ' +\n 'written on it. There is no undo.',\n confirmationText: 'Discard',\n })\n .then(() => true)\n .catch(() => false)\n if (!agreed) return\n setDiscardingId(id)\n try {\n const { response, payload } = await manageApi({\n action: 'discardEmail',\n campaignId: id,\n ...(hostId ? {} : { hostId: rowHostId(email) ?? undefined }),\n })\n if (!response.ok) {\n return void enqueueSnackbar(\n payload?.error ?? 'This draft could not be discarded',\n { variant: 'warning', allowDuplicate: true },\n )\n }\n enqueueSnackbar('Draft discarded', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('This draft could not be discarded', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setDiscardingId('')\n }\n },\n [confirm, discardingId, enqueueSnackbar, hostId, manageApi, rowHostId],\n )\n\n /**\n * What a message can be opened INTO, from the list.\n *\n * All three are places the message's own report already links to, moved one\n * screen earlier: the report itself, the campaign it belongs to, and the\n * template it was rendered from. A message need have neither of the last\n * two — one sent before campaigns grouped anything names no container, and\n * one composed inline was built from no template — so those entries are\n * shown DISABLED with the reason rather than hidden. A control that\n * disappears and a control that does not apply look identical, and only one\n * of them tells the reader which case they are in.\n *\n * DISCARD is the fourth entry and is offered on the same terms: a draft can\n * be thrown away, and nothing else can. It stays visible on a sent or\n * scheduled message carrying the reason it is refused, because that is the\n * honest answer to “how do I get rid of this” — and the route refuses it\n * too, so the menu is describing a rule rather than being one.\n */\n const rowActions = (email: any): RowActionsMenuItem[] => {\n const containerId = String(email?.[CAMPAIGN_SEND_CONTAINER_FIELD] ?? '')\n const templateScreenId = String(email?.templateScreenId ?? '')\n const state = String(email?.status ?? '')\n const templates = templatesHub(email)\n /** On the org hub, a send that names no site has nothing to act as. */\n const siteless = !rowHostId(email)\n const sitelessReason =\n 'This email does not record which site it was sent as. Manage it from ' +\n 'that site’s own Emails page.'\n return [\n {\n key: 'details',\n label: 'Open report',\n icon: <MdiIcon path={mdiEyeOutline.path} size={0.8} />,\n href: emailHref(email),\n },\n {\n key: 'campaign',\n label: 'Open its campaign',\n icon: <MdiIcon path={mdiBullhornOutline.path} size={0.8} />,\n // The campaign's page belongs to the Marketing console, so this one\n // href is built from the sibling hub rather than this surface's own.\n href:\n containerId && marketingHub\n ? `${marketingHub}/campaigns/${containerId}`\n : undefined,\n disabled: !containerId || !marketingHub,\n disabledReason: containerId\n ? 'This site’s console URL has not resolved yet'\n : 'Sent before campaigns grouped their emails, so it belongs to none',\n },\n {\n key: 'template',\n label: 'Open its template',\n icon: <MdiIcon path={mdiPaletteOutline.path} size={0.8} />,\n href:\n templateScreenId && templates\n ? `${templates}/templates/${templateScreenId}`\n : undefined,\n disabled: !templateScreenId || !templates,\n disabledReason: templateScreenId\n ? 'This site’s console URL has not resolved yet'\n : 'This message was not built from a template',\n },\n {\n key: 'duplicate',\n label: DUPLICATE_MENU_LABEL,\n icon: <MdiIcon path={mdiContentCopy.path} size={0.8} />,\n disabled: siteless,\n disabledReason: sitelessReason,\n onClick: () =>\n duplicate.request('campaign', {\n id: String(email.$id),\n name: String(email?.displayName ?? email?.subject ?? ''),\n }),\n },\n {\n key: 'discard',\n label: 'Discard draft',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: siteless || state !== 'draft' || Boolean(discardingId),\n disabledReason: siteless\n ? sitelessReason\n : (DISCARD_REFUSAL[state] ?? 'Only a draft can be discarded'),\n onClick: () => void handleDiscard(email),\n },\n ]\n }\n\n const [createOpen, setCreateOpen] = useState(false)\n const [creating, setCreating] = useState(false)\n const campaignSendApi = useCampaignSendApi(hostId)\n /*\n * The sites a new email can be sent as, on the org hub. One site answers\n * the question itself, so the field is asked only when there is a choice.\n */\n const orgSites = useMemo(\n () => (orgMount ? orgSiteOptions(orgMount) : []),\n [orgMount],\n )\n const askSite = !hostId && orgSites.length > 1\n\n /*\n * The campaigns, read while the list is shown: the Campaign filter offers\n * them by name, and the create drawer offers them to file a new email\n * under. One capped read of the containers serves both.\n */\n const { data: campaignDocs } = useFirestoreCollection<any>(\n () => {\n const containers = campaignContainersQuery(firestore, orgId, hostId)\n return containers ? collectionCeiling(containers, CONTAINER_CEILING) : null\n },\n [firestore, orgId, hostId],\n { idField: '$id' },\n )\n const campaignOptions = useMemo(\n () =>\n [...(campaignDocs ?? [])]\n .map((campaign: any) => ({\n value: String(campaign.$id),\n label: String(campaign.name || 'Untitled campaign'),\n }))\n .sort((a, b) => a.label.localeCompare(b.label)),\n [campaignDocs],\n )\n\n const filterOptions = useMemo(\n () => ({\n status: CAMPAIGN_SEND_STATUS_OPTIONS,\n emailCampaignId: [\n { value: SINGLE_SEND_FILTER_VALUE, label: 'Single send' },\n ...campaignOptions,\n ],\n ...(hostId ? {} : { site: orgSites }),\n }),\n [campaignOptions, hostId, orgSites],\n )\n\n /*==========================================\n * CREATE, THEN GO TO THE EMAIL'S OWN PAGE.\n *\n * The drawer collects only what it takes to MINT the record — the friendly\n * name, and the campaign it belongs to. Everything else about the email is\n * written on the email's own page, which is where a record is edited\n * throughout this console: a list page carries no form.\n *\n * The record is real from this moment: `/marketing/campaigns/{id}` resolves,\n * the row appears in the table below as a Draft, and the id it is created\n * under is the id it keeps when it is eventually sent. It costs nothing to\n * exist — the route reserves no allowance and moves no meter for a draft,\n * and the scheduled processor only ever picks up `scheduled`.\n *=========================================*/\n const handleCreate = useCallback(\n async (values: Record<string, any>) => {\n if (creating) return\n const sendAs =\n hostId ?? (askSite ? String(values.hostId ?? '') : orgSites[0]?.value)\n if (!sendAs) {\n return void enqueueSnackbar('Choose the site this email is sent as', {\n variant: 'warning',\n allowDuplicate: true,\n })\n }\n /*\n * The organization hub offers every campaign, because the site is\n * chosen in the same form. An email can only join a campaign placed on\n * the site it is sent as — the send route refuses anything else — so\n * the mismatch is named here, where it can be fixed, rather than\n * arriving as the route's bare refusal. A site hub lists only the\n * campaigns placed on it, so there is nothing to check there.\n */\n const chosenCampaign =\n !hostId && values.emailCampaignId\n ? (campaignDocs ?? []).find(\n (campaign: any) =>\n String(campaign.$id) === String(values.emailCampaignId),\n )\n : null\n if (chosenCampaign && !campaignPlacedOnHost(chosenCampaign, sendAs)) {\n return void enqueueSnackbar(\n 'That campaign is not offered on the site this email is sent as. ' +\n 'Choose another, or add the site to the campaign first.',\n { variant: 'warning', allowDuplicate: true },\n )\n }\n setCreating(true)\n try {\n const { response, payload } = await campaignSendApi({\n action: 'draft',\n ...(hostId ? {} : { hostId: sendAs }),\n displayName: String(values.displayName ?? '').trim(),\n ...(values.emailCampaignId\n ? { emailCampaignId: String(values.emailCampaignId) }\n : {}),\n })\n if (!response.ok || !payload?.campaignId) {\n return void enqueueSnackbar(\n payload?.error ?? 'This email could not be created',\n { variant: 'warning', allowDuplicate: true },\n )\n }\n setCreateOpen(false)\n /*\n * Straight to where it is WRITTEN. The drawer collected the name and\n * the campaign; the record now exists and holds nothing else, so the\n * next thing to do with it is compose it — and its own page is a\n * report of a send that has not happened.\n */\n router.push(`${messagesPath}/${payload.campaignId}/edit`)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('This email could not be created', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setCreating(false)\n }\n },\n [\n askSite,\n campaignDocs,\n campaignSendApi,\n creating,\n enqueueSnackbar,\n hostId,\n messagesPath,\n orgSites,\n router,\n ],\n )\n\n /*\n * One row per message, in the query's order. The subject is a link AND the\n * row opens the report: a click handler cannot be middle-clicked or opened\n * in a new tab.\n */\n const columns: GridColDef[] = [\n {\n field: 'subject',\n headerName: 'Subject',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => row.subject || 'Untitled email',\n renderCell: ({ row, value }) => (\n <AppLink\n href={emailHref(row)}\n // The row's own handler would fire too and push the same route\n // twice — one history entry per back press.\n onClick={(event: { stopPropagation: () => void }) =>\n event.stopPropagation()\n }\n >\n {value}\n </AppLink>\n ),\n },\n // Which site each row is sent as, on the org hub only.\n ...(orgMount\n ? [\n {\n field: 'site',\n headerName: 'Site',\n width: 150,\n valueGetter: (_value: unknown, row: any) =>\n row?.hostId ? orgSiteName(orgMount, row.hostId) : '—',\n } satisfies GridColDef,\n ]\n : []),\n {\n /*\n * WHAT THIS EMAIL IS DOING, not what field it stores.\n *\n * An email delivering an audience larger than one batch is written back\n * as `scheduled` between runs, so a chip rendering the status said\n * \"Scheduled\" about a send that had already reached five hundred people.\n * The derivation reads the counters beside the status and says which of\n * the two it is.\n */\n field: 'state',\n headerName: 'State',\n width: 150,\n valueGetter: (_value, row) => campaignSendDisplay(row).label,\n renderCell: ({ row }) => {\n const display = campaignSendDisplay(row)\n return (\n <Chip\n size=\"small\"\n color={STATE_COLOR[display.state]}\n label={display.label}\n />\n )\n },\n },\n {\n field: 'when',\n headerName: 'When',\n width: 190,\n valueGetter: (_value, row) => emailListTimeMs(row),\n renderCell: ({ row }) => {\n const at = emailSendTimeMs(row)\n return at ? new Date(at).toLocaleString() : '—'\n },\n },\n ...(\n [\n ['recipients', 'Addressed'],\n ['opens', 'Opens'],\n ['clicks', 'Clicks'],\n ] as const\n ).map(\n ([stat, headerName]): GridColDef => ({\n field: stat,\n headerName,\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueGetter: (_value, row) => Number(row.stats?.[stat] ?? 0),\n valueFormatter: (value: number) => value.toLocaleString(),\n }),\n ),\n listActionsColumn(\n (row) => (\n <ListRowActions\n label={String(row.subject || 'Untitled email')}\n items={rowActions(row)}\n />\n ),\n { width: 72 },\n ),\n ]\n\n const gridColumns = listFilterGridColumns(\n columns,\n declaration.fields,\n filterOptions,\n EMAIL_FILTER_HEADERS,\n )\n /** Nothing stored at all — not a filter that matched nothing. */\n const none =\n emailPage.status !== 'loading' &&\n emails.length === 0 &&\n !filtering &&\n emailPage.page === 0\n\n return (\n <CardDisplay\n header={'Messages'}\n help={emailsDocsHelp}\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => setCreateOpen(true)}\n >\n {'New email'}\n </Button>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {duplicate.dialog}\n <Stack spacing={2}>\n {none ? (\n <Stack spacing={2} sx={{ alignItems: 'flex-start' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing has been sent or scheduled yet. Write one here, or ' +\n 'from a campaign, and it appears in this list with its own ' +\n 'report.'}\n </Typography>\n <Button variant=\"contained\" onClick={() => setCreateOpen(true)}>\n {'New email'}\n </Button>\n </Stack>\n ) : (\n <>\n <ListFilterChips\n fields={declaration.fields}\n headers={EMAIL_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n <ListQueryNotices\n refused={listQueryRefusals(emailPage.plan.refused, {\n fields: declaration.fields,\n headers: EMAIL_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={emailPage.plan.notices}\n />\n <ListTable\n aria-label=\"Messages\"\n rows={emails}\n columns={gridColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row) => router.push(emailHref(row))}\n loading={emailPage.status === 'loading'}\n /*\n * The panel and the search are the grid's; every clause and\n * the search word are on the list's query (AGL-3321), and the\n * grid neither filters nor sorts the page it is handed: the\n * query's one order, newest first, is the list's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n hideFooter\n initialState={{\n columns: {\n columnVisibilityModel: hiddenFilterVisibility(\n declaration.fields,\n columns.map((column) => column.field),\n ),\n },\n }}\n noRowsLabel=\"No messages match these filters\"\n />\n <ListPagination\n page={emailPage.page}\n pageSize={emailPage.pageSize}\n rowCount={emails.length}\n hasMore={emailPage.hasMore}\n onPageChange={emailPage.setPage}\n onPageSizeChange={emailPage.setPageSize}\n />\n </>\n )}\n </Stack>\n {/*\n AN EMAIL WRITTEN HERE BELONGS TO A CAMPAIGN, OR TO NO CAMPAIGN.\n\n The second is a real answer and not a gap in the model. A send with no\n container is what the product has always had — every message that\n predates campaigns is one — and `campaignListRows` adopts each of them\n as a campaign of one at read time, which is the \"Single send\" chip on\n the campaigns table. So composing from this list mints nothing, files\n nothing, and requires nobody to invent a campaign first: it leaves the\n container empty, and the send that results is presented the way every\n containerless send already is.\n\n Offering the campaigns anyway is what stops the opposite mistake — a\n merchant who DOES have a spring campaign writing its third email into\n a single send that never joins the rollup.\n */}\n <CreateArtifactDrawer\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"New email\"\n submitLabel={creating ? 'Creating…' : 'Start writing'}\n includeDescription={false}\n onSubmit={handleCreate}\n extraFields={[\n ...(askSite\n ? [\n {\n component: 'select',\n name: 'hostId',\n label: 'Send as',\n isRequired: true,\n initialValue: '',\n helperText:\n 'The site this email is sent from — its sender, designs ' +\n 'and unsubscribe page',\n disableDefaultOption: true,\n options: orgSites,\n },\n ]\n : []),\n {\n component: 'select',\n name: 'emailCampaignId',\n label: 'Campaign',\n initialValue: '',\n helperText:\n 'Leave this as a single send unless the email belongs with ' +\n 'others',\n disableDefaultOption: true,\n options: [\n { value: '', label: 'Single send — not part of a campaign' },\n ...campaignOptions,\n ],\n },\n ]}\n />\n </CardDisplay>\n )\n}\nEmailsListCard.displayName = 'EmailsListCard'\n\nexport default EmailsListCard\n"],"names":["pluginDocsHelp","mdiBullhornOutline","mdiContentCopy","mdiDeleteOutline","mdiEyeOutline","mdiPaletteOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","TABLE_ROW_HEIGHT","CreateArtifactDrawer","useSnackbar","collectionCeiling","useListQuery","DUPLICATE_MENU_LABEL","useDuplicateResource","useFirestore","useFirestoreCollection","Button","Chip","Stack","Typography","useRouter","useCallback","useMemo","useState","campaignPlacedOnHost","campaignSendDisplay","CAMPAIGN_SEND_CONTAINER_FIELD","emailListTimeMs","emailSendTimeMs","useCampaignManageApi","useCampaignSendApi","CAMPAIGN_SEND_STATUS_OPTIONS","SINGLE_SEND_FILTER_VALUE","campaignEmailsListQuery","campaignSendsScope","emailCampaignQueryClauses","useMarketingHubPath","campaignContainersQuery","campaignSendsCollection","orgSiteHubPath","orgSiteName","orgSiteOptions","useMarketingOrgId","useMarketingOrgMount","CONTAINER_CEILING","DISCARD_REFUSAL","sent","scheduled","sending","canceled","STATE_COLOR","stopped","held","EMAIL_FILTER_HEADERS","subject","status","emailCampaignId","site","createdAtMs","emailsDocsHelp","anchor","excerpt","EmailsListCard","props","hostId","basePath","firestore","orgMount","orgId","siteMarketingHub","marketingHub","router","declaration","gridFilter","selectFields","emailPage","collection","request","clauses","search","searchWords","base","deps","idField","emails","rows","filtering","length","some","word","trim","messagesPath","emailHref","email","$id","templatesHub","rowHostId","String","hostOfEmail","Map","map","confirm","enqueueSnackbar","manageForCopy","duplicate","perform","sourceId","name","attemptKey","payload","response","action","campaignId","get","undefined","ok","Error","error","id","emailId","versionId","onDuplicated","_kind","copy","variant","manageApi","discardingId","setDiscardingId","handleDiscard","displayName","agreed","title","description","confirmationText","then","catch","allowDuplicate","persist","console","rowActions","containerId","templateScreenId","state","templates","siteless","sitelessReason","key","label","icon","path","size","href","disabled","disabledReason","onClick","destructive","Boolean","createOpen","setCreateOpen","creating","setCreating","campaignSendApi","orgSites","askSite","data","campaignDocs","containers","campaignOptions","campaign","value","sort","a","b","localeCompare","filterOptions","handleCreate","values","sendAs","chosenCampaign","find","push","columns","field","headerName","flex","minWidth","valueGetter","_value","row","renderCell","event","stopPropagation","width","display","color","at","Date","toLocaleString","stat","type","align","headerAlign","Number","stats","valueFormatter","items","gridColumns","fields","none","page","header","help","HeaderProps","contentGutterX","contentGutterY","dialog","spacing","sx","alignItems","headers","onChange","setClauses","options","refused","plan","notices","aria-label","rowHeight","onOpen","_id","loading","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","hideFooter","initialState","columnVisibilityModel","column","noRowsLabel","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","open","onClose","submitLabel","includeDescription","onSubmit","extraFields","component","isRequired","initialValue","helperText","disableDefaultOption"],"mappings":"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"}
@@ -508,6 +508,9 @@ const EXPERIMENT_FILTER_OPTIONS = {
508
508
  onOpen: (_id, experiment)=>void openResults(experiment),
509
509
  // Paged by the footer below, so the grid must not also slice.
510
510
  hideFooter: true,
511
+ // The rows keep the query's order; a header sort would order
512
+ // only the page on screen and read as the whole list's.
513
+ disableColumnSorting: true,
511
514
  /*
512
515
  * The panel and the search are the grid's; every clause and the
513
516
  * search word are on the list's query (AGL-3321), so the grid
@@ -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 { 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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"}
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 // The rows keep the query's order; a header sort would order\n // only the page on screen and read as the whole list's.\n disableColumnSorting\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","disableColumnSorting","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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"}