@aglyn/plugins-marketing 1.0.0-beta.198 → 1.0.0-beta.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -15
- package/src/lib/components/campaign-detail-card.js +106 -36
- package/src/lib/components/campaign-detail-card.js.map +1 -1
- package/src/lib/components/campaign-queries.d.ts +11 -0
- package/src/lib/components/campaign-queries.js +13 -0
- package/src/lib/components/campaign-queries.js.map +1 -1
- package/src/lib/components/campaigns-card.d.ts +8 -6
- package/src/lib/components/campaigns-card.js +283 -125
- package/src/lib/components/campaigns-card.js.map +1 -1
- package/src/lib/components/emails-list-card.d.ts +11 -15
- package/src/lib/components/emails-list-card.js +139 -142
- package/src/lib/components/emails-list-card.js.map +1 -1
- package/src/lib/components/host-experiments-card.component.js +107 -78
- package/src/lib/components/host-experiments-card.component.js.map +1 -1
- package/src/lib/components/org-experiments-card.js +7 -3
- package/src/lib/components/org-experiments-card.js.map +1 -1
- package/src/lib/model/campaign-list-query.d.ts +99 -0
- package/src/lib/model/campaign-list-query.js +373 -0
- package/src/lib/model/campaign-list-query.js.map +1 -0
- package/src/lib/model/experiment-list-query.d.ts +22 -0
- package/src/lib/model/experiment-list-query.js +85 -0
- package/src/lib/model/experiment-list-query.js.map +1 -0
- package/src/lib/server/campaign-manage.d.ts +1 -1
- package/src/lib/server/campaign-manage.js +30 -18
- package/src/lib/server/campaign-manage.js.map +1 -1
- package/src/lib/server/campaign-send.d.ts +7 -0
- package/src/lib/server/campaign-send.js +17 -5
- package/src/lib/server/campaign-send.js.map +1 -1
- package/src/lib/server/email-events.js +3 -1
- package/src/lib/server/email-events.js.map +1 -1
- package/src/lib/components/email-send-filter.d.ts +0 -32
- package/src/lib/components/email-send-filter.js +0 -55
- package/src/lib/components/email-send-filter.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-marketing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.200",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
41
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.200",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.200",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.200",
|
|
31
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.200",
|
|
32
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.200",
|
|
33
|
+
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.200",
|
|
34
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.200",
|
|
35
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.200",
|
|
36
|
+
"@aglyn/shared-util-email": "1.0.0-beta.200",
|
|
37
|
+
"@aglyn/shared-util-http": "1.0.0-beta.200",
|
|
38
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.200",
|
|
39
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.200",
|
|
40
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.200",
|
|
41
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.200",
|
|
42
42
|
"@swc/helpers": "0.5.23"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
@@ -20,11 +20,16 @@ import { mdiDeleteOutline, mdiEyeOutline, mdiPaletteOutline, mdiPencilOutline }
|
|
|
20
20
|
import { AppLink, CardDisplay, MdiIcon, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
21
21
|
import { Figure, RateRow, Section } from "@aglyn/shared-ui-jsx/components/measured-figures.component";
|
|
22
22
|
import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
|
|
23
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
24
|
+
import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
|
|
23
25
|
import { ListRowActions, ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
24
|
-
import {
|
|
26
|
+
import { hiddenFilterVisibility } from "@aglyn/shared-ui-jsx/const/list-filter";
|
|
27
|
+
import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
28
|
+
import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
|
|
25
29
|
import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
|
|
26
30
|
import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
27
31
|
import { ceilingedWindow } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
32
|
+
import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
|
|
28
33
|
import { PageHeaderRecord, pluginDocsHelp } from "@aglyn/aglyn";
|
|
29
34
|
import { Alert, Box, Button, Chip, Divider, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
30
35
|
import { collection, deleteField, documentId, limit, orderBy, query, updateDoc, where } from "firebase/firestore";
|
|
@@ -33,13 +38,13 @@ import { useRouter } from "next/navigation";
|
|
|
33
38
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
34
39
|
import { useFirestore, useFirestoreCollection, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
|
|
35
40
|
import { campaignForcedListUnsubscribeSends, campaignListUnsubscribe, campaignRollup, campaignSendDisplay, campaignSiteIds, campaignVisibleTo, campaignWindowState, emailListTimeMs } from "@aglyn/shared-ui-email-campaigns/model";
|
|
36
|
-
import {
|
|
41
|
+
import { CAMPAIGN_EMAILS_QUERY, CAMPAIGN_SEND_STATUS_OPTIONS, campaignContainerSearchFields, campaignSendsScope } from "../model/campaign-list-query.js";
|
|
37
42
|
import CampaignComposer from "./campaign-composer.js";
|
|
38
43
|
import CampaignEditDrawer from "./campaign-edit-drawer.js";
|
|
39
44
|
import { CampaignConversionsSection, CampaignDestinationsSection, CampaignRevenueSection, CampaignSequencesSection } from "./campaign-reach-sections.js";
|
|
40
45
|
import CampaignMembersSection from "./campaign-members-section.js";
|
|
41
46
|
import CampaignReportCard from "./campaign-report-card.js";
|
|
42
|
-
import { campaignContainerDoc, campaignSendsQuery } from "./campaign-queries.js";
|
|
47
|
+
import { campaignContainerDoc, campaignSendsCollection, campaignSendsQuery } from "./campaign-queries.js";
|
|
43
48
|
import { orgSiteHubPath, orgSiteName, orgSiteOptions, topicCatalogHostId, useMarketingOrgId, useMarketingOrgMount } from "./marketing-org-mount.js";
|
|
44
49
|
import { useCampaignManageApi } from "./use-campaign-send-api.js";
|
|
45
50
|
import { useEmailsHubPath } from "./use-emails-hub-path.js";
|
|
@@ -61,20 +66,20 @@ const detailDocsHelp = pluginDocsHelp('emailCampaigns', {
|
|
|
61
66
|
value: value.value,
|
|
62
67
|
note: value.value !== null && value.recorded < value.sends ? `across ${value.recorded} of ${value.sends} emails` : `across ${value.sends} email${value.sends === 1 ? '' : 's'}`
|
|
63
68
|
});
|
|
64
|
-
/*
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
/*
|
|
70
|
+
* What the campaign's emails table filters by, each clause and the search on
|
|
71
|
+
* the table's own Firestore query (AGL-3321, `CAMPAIGN_EMAILS_QUERY`). The
|
|
72
|
+
* State column is derived from an email's counters and cannot be asked of a
|
|
73
|
+
* query, so the panel offers the Status it stores.
|
|
74
|
+
*/ const SEND_FILTER_FIELDS = CAMPAIGN_EMAILS_QUERY.fields;
|
|
68
75
|
const SEND_FILTER_OPTIONS = {
|
|
69
|
-
|
|
76
|
+
status: CAMPAIGN_SEND_STATUS_OPTIONS
|
|
70
77
|
};
|
|
71
78
|
const SEND_FILTER_HEADERS = {
|
|
72
79
|
subject: 'Subject',
|
|
73
|
-
|
|
80
|
+
status: 'Status',
|
|
81
|
+
createdAtMs: 'Created'
|
|
74
82
|
};
|
|
75
|
-
const SEND_SEARCH_FIELDS = [
|
|
76
|
-
'subjectText'
|
|
77
|
-
];
|
|
78
83
|
/**
|
|
79
84
|
* ONE CAMPAIGN: its lists, what it caused, what it earned, where it sent
|
|
80
85
|
* people, and the emails it did all of that with.
|
|
@@ -232,17 +237,43 @@ const SEND_SEARCH_FIELDS = [
|
|
|
232
237
|
sends
|
|
233
238
|
]);
|
|
234
239
|
/*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
240
|
+
* THE EMAILS TABLE IS ITS OWN QUERY (AGL-3321).
|
|
241
|
+
*
|
|
242
|
+
* The figures above are a sum over the window read above; the table under
|
|
243
|
+
* them is a list a reader filters and searches, so each clause and the
|
|
244
|
+
* search word go onto a query of their own — this campaign's emails
|
|
245
|
+
* (`emailCampaignId == id`, and under a site the ones sent as it), newest
|
|
246
|
+
* first, paged by the query. A match on the fifth page is found; nothing
|
|
247
|
+
* is matched over the rows a read happened to hold. Gated on the container
|
|
248
|
+
* like the window: a single send's report reads none of it.
|
|
249
|
+
*/ const gridFilter = useListGridFilter({
|
|
250
|
+
selectFields: [
|
|
251
|
+
'status'
|
|
252
|
+
]
|
|
253
|
+
});
|
|
254
|
+
const sendPage = useListQuery({
|
|
255
|
+
collection: campaign ? campaignSendsCollection(firestore, orgId) : null,
|
|
256
|
+
declaration: CAMPAIGN_EMAILS_QUERY,
|
|
257
|
+
request: {
|
|
258
|
+
clauses: gridFilter.clauses,
|
|
259
|
+
search: gridFilter.searchWords,
|
|
260
|
+
base: [
|
|
261
|
+
{
|
|
262
|
+
path: 'emailCampaignId',
|
|
263
|
+
op: '==',
|
|
264
|
+
value: campaignId
|
|
265
|
+
},
|
|
266
|
+
...campaignSendsScope(hostId)
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
deps: [
|
|
270
|
+
firestore,
|
|
271
|
+
orgId,
|
|
272
|
+
hostId,
|
|
273
|
+
campaignId,
|
|
274
|
+
Boolean(campaign)
|
|
275
|
+
],
|
|
276
|
+
idField: '$id'
|
|
246
277
|
});
|
|
247
278
|
// The ids the two sections beneath the figures join on. Derived from the
|
|
248
279
|
// window this card already holds, so neither of them reads the send list
|
|
@@ -278,16 +309,18 @@ const SEND_SEARCH_FIELDS = [
|
|
|
278
309
|
setSaving(true);
|
|
279
310
|
setSaveError(null);
|
|
280
311
|
try {
|
|
312
|
+
const placement = !hostId && values.siteIds !== undefined ? {
|
|
313
|
+
visibleTo: campaignVisibleTo(values.siteIds)
|
|
314
|
+
} : {};
|
|
281
315
|
await updateDoc(campaignContainerDoc(firestore, orgId, campaignId), _extends({
|
|
282
|
-
name: values.name
|
|
316
|
+
name: values.name
|
|
317
|
+
}, campaignContainerSearchFields(values.name), {
|
|
283
318
|
startAtMs: values.startAtMs,
|
|
284
319
|
endAtMs: values.endAtMs,
|
|
285
320
|
listIds: values.listIds,
|
|
286
321
|
topicId: values.topicId ? values.topicId : deleteField(),
|
|
287
322
|
listUnsubscribe: values.listUnsubscribe
|
|
288
|
-
},
|
|
289
|
-
visibleTo: campaignVisibleTo(values.siteIds)
|
|
290
|
-
} : {}));
|
|
323
|
+
}, placement));
|
|
291
324
|
setEditing(false);
|
|
292
325
|
enqueueSnackbar('Campaign updated', {
|
|
293
326
|
variant: 'success',
|
|
@@ -470,7 +503,7 @@ const SEND_SEARCH_FIELDS = [
|
|
|
470
503
|
* The campaign's emails, on the surface's own row grammar: the row opens
|
|
471
504
|
* the email's report, its subject is also a real link so it can be
|
|
472
505
|
* middle-clicked and copied, and the trailing cluster holds the actions.
|
|
473
|
-
*
|
|
506
|
+
* A page of the table's own query, paged by that query.
|
|
474
507
|
*/ const sendColumns = [
|
|
475
508
|
{
|
|
476
509
|
field: 'subject',
|
|
@@ -845,22 +878,59 @@ const SEND_SEARCH_FIELDS = [
|
|
|
845
878
|
sends.length ? /*#__PURE__*/ _jsxs(Stack, {
|
|
846
879
|
spacing: 0.5,
|
|
847
880
|
children: [
|
|
848
|
-
/*#__PURE__*/ _jsx(ListFilterChips,
|
|
849
|
-
|
|
881
|
+
/*#__PURE__*/ _jsx(ListFilterChips, {
|
|
882
|
+
fields: SEND_FILTER_FIELDS,
|
|
883
|
+
headers: SEND_FILTER_HEADERS,
|
|
884
|
+
clauses: gridFilter.clauses,
|
|
885
|
+
onChange: gridFilter.setClauses,
|
|
886
|
+
options: SEND_FILTER_OPTIONS
|
|
887
|
+
}),
|
|
888
|
+
/*#__PURE__*/ _jsx(ListQueryNotices, {
|
|
889
|
+
refused: listQueryRefusals(sendPage.plan.refused, {
|
|
890
|
+
fields: SEND_FILTER_FIELDS,
|
|
891
|
+
headers: SEND_FILTER_HEADERS,
|
|
892
|
+
options: SEND_FILTER_OPTIONS
|
|
893
|
+
}),
|
|
894
|
+
notices: sendPage.plan.notices
|
|
895
|
+
}),
|
|
896
|
+
/*#__PURE__*/ _jsx(ListTable, {
|
|
850
897
|
"aria-label": "The campaign's emails",
|
|
851
|
-
rows:
|
|
852
|
-
columns:
|
|
898
|
+
rows: sendPage.rows,
|
|
899
|
+
columns: listFilterGridColumns(sendColumns, SEND_FILTER_FIELDS, SEND_FILTER_OPTIONS, SEND_FILTER_HEADERS),
|
|
853
900
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
854
901
|
onOpen: (_id, send)=>{
|
|
855
902
|
const href = sendHref(send);
|
|
856
903
|
if (href) router.push(href);
|
|
857
|
-
}
|
|
858
|
-
|
|
904
|
+
},
|
|
905
|
+
loading: sendPage.status === 'loading',
|
|
906
|
+
/*
|
|
907
|
+
* The panel and the search are the grid's; every clause and the
|
|
908
|
+
* search word are on the table's query (AGL-3321), and the grid
|
|
909
|
+
* neither filters nor sorts the page it is handed.
|
|
910
|
+
*/ filterMode: "server",
|
|
911
|
+
filterModel: gridFilter.filterModel,
|
|
912
|
+
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
913
|
+
quickFilter: true,
|
|
914
|
+
disableColumnSorting: true,
|
|
915
|
+
hideFooter: true,
|
|
916
|
+
initialState: {
|
|
917
|
+
columns: {
|
|
918
|
+
columnVisibilityModel: hiddenFilterVisibility(SEND_FILTER_FIELDS, sendColumns.map((column)=>String(column.field)))
|
|
919
|
+
}
|
|
920
|
+
},
|
|
859
921
|
noRowsLabel: "No emails match these filters"
|
|
860
|
-
})
|
|
922
|
+
}),
|
|
923
|
+
/*#__PURE__*/ _jsx(ListPagination, {
|
|
924
|
+
page: sendPage.page,
|
|
925
|
+
pageSize: sendPage.pageSize,
|
|
926
|
+
rowCount: sendPage.rows.length,
|
|
927
|
+
hasMore: sendPage.hasMore,
|
|
928
|
+
onPageChange: sendPage.setPage,
|
|
929
|
+
onPageSizeChange: sendPage.setPageSize
|
|
930
|
+
}),
|
|
861
931
|
sendsTruncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
862
932
|
severity: "info",
|
|
863
|
-
children: `
|
|
933
|
+
children: `The figures above cover ${CAMPAIGN_EMAIL_CEILING} of this ` + 'campaign’s emails. It has sent more; the table lists ' + 'every one of them.'
|
|
864
934
|
}) : null
|
|
865
935
|
]
|
|
866
936
|
}) : /*#__PURE__*/ _jsx(Typography, {
|