@aglyn/shared-ui-email-campaigns 1.0.0-beta.220 → 1.0.0-beta.222
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/README.md +19 -40
- package/package.json +3 -3
- package/src/lib/model/index.d.ts +8 -18
- package/src/lib/model/index.js +8 -15
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/{campaign-report.d.ts → send-report.d.ts} +38 -163
- package/src/lib/model/send-report.js +187 -0
- package/src/lib/model/send-report.js.map +1 -0
- package/src/lib/model/campaign-container.d.ts +0 -503
- package/src/lib/model/campaign-container.js +0 -497
- package/src/lib/model/campaign-container.js.map +0 -1
- package/src/lib/model/campaign-report.js +0 -422
- package/src/lib/model/campaign-report.js.map +0 -1
- package/src/lib/model/email-record.d.ts +0 -175
- package/src/lib/model/email-record.js +0 -206
- package/src/lib/model/email-record.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @aglyn/shared-ui-email-campaigns
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The bulk-send reporting math, owned by none of the plugins that send in bulk.
|
|
4
4
|
|
|
5
5
|
> Beta. Published from the Aglyn monorepo under the `beta` dist-tag; APIs can change between beta releases.
|
|
6
6
|
|
|
@@ -12,59 +12,38 @@ npm install @aglyn/shared-ui-email-campaigns@beta
|
|
|
12
12
|
|
|
13
13
|
## Why it is not in a plugin
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
the
|
|
18
|
-
|
|
19
|
-
`
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
A shape that lives inside one of them is therefore a shape the other reaches
|
|
23
|
-
past a switch to get. Because every plugin compiles into the same console
|
|
24
|
-
bundle, such an import resolves at runtime whether or not the owning plugin is
|
|
25
|
-
enabled, and the plugin switchboard is bypassed silently rather than failing.
|
|
26
|
-
Putting the shared half here makes both plugins peers of it: each depends on
|
|
27
|
-
this library, neither depends on the other for a rate, a field name or a
|
|
28
|
-
`<Figure>`.
|
|
29
|
-
|
|
30
|
-
The boundary is enforced in
|
|
31
|
-
`libs/plugins/marketing/src/lib/plugin-email-boundary.spec.ts`, which fails if
|
|
32
|
-
Marketing re-acquires an import of anything this library owns from
|
|
33
|
-
`@aglyn/plugins-email`.
|
|
15
|
+
Two plugins mail in bulk and report what their mail did: the Marketing plugin
|
|
16
|
+
sends campaigns, and the Outreach plugin runs one-to-one sequences. Each send
|
|
17
|
+
records the same counters — sent, delivered, opens, clicks, bounces — and each
|
|
18
|
+
report divides them. A rate taken in two places is a rate that drifts: one
|
|
19
|
+
reader divides opens by `sent`, the other by `delivered`, and the same mail
|
|
20
|
+
reads two ways. So the division lives here, once, and both plugins are its
|
|
21
|
+
peers; neither imports the other.
|
|
34
22
|
|
|
35
23
|
## The model only
|
|
36
24
|
|
|
37
25
|
```typescript
|
|
38
26
|
// Pure. No React, no MUI — safe in a /server handler.
|
|
39
|
-
import {
|
|
40
|
-
campaignReport,
|
|
41
|
-
CAMPAIGN_SEND_CONTAINER_FIELD,
|
|
42
|
-
} from '@aglyn/shared-ui-email-campaigns/model'
|
|
27
|
+
import { sendRate, sendLinkReport, type SendStats } from '@aglyn/shared-ui-email-campaigns/model'
|
|
43
28
|
```
|
|
44
29
|
|
|
45
30
|
The library renders nothing. The figure renderers live in
|
|
46
|
-
`@aglyn/shared-ui-jsx
|
|
47
|
-
is the core's generic `ContainerPicker` (`@aglyn/tenant-feature-instance`),
|
|
48
|
-
handed the `campaign` kind the Marketing plugin declares.
|
|
31
|
+
`@aglyn/shared-ui-jsx`.
|
|
49
32
|
|
|
50
33
|
## What is here
|
|
51
34
|
|
|
52
|
-
| Module
|
|
53
|
-
|
|
|
54
|
-
| `model/
|
|
55
|
-
| `model/campaign-report` | The rate math, the population each rate describes, and the link rollup |
|
|
56
|
-
| `model/email-record` | One message: its state, its audience, and when it went out |
|
|
35
|
+
| Module | Answers |
|
|
36
|
+
| ------------------- | ----------------------------------------------------------------------------------------- |
|
|
37
|
+
| `model/send-report` | A send's stored counters, a rate with its denominator named, and the per-link click rollup |
|
|
57
38
|
|
|
58
39
|
## What is not here
|
|
59
40
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
send-time API all stay in `@aglyn/plugins-email`: they are behavior that the
|
|
67
|
-
Email plugin's switch is supposed to govern, not shapes that two plugins share.
|
|
41
|
+
A campaign's own model (AGL-3080): its container and window, a send's report
|
|
42
|
+
and the populations it measured, a message's record, what a design's sends
|
|
43
|
+
did, and what a campaign caused and earned. Only the Marketing plugin reads
|
|
44
|
+
them, so they are its model (`libs/plugins/marketing/src/lib/model/`), not a
|
|
45
|
+
shape two plugins share. The Email plugin's template page shows what a
|
|
46
|
+
design's sends did through a zone the Marketing plugin fills.
|
|
68
47
|
|
|
69
48
|
## License
|
|
70
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/shared-ui-email-campaigns",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.222",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"./package.json": "./package.json"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
32
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.222",
|
|
33
|
+
"@aglyn/shared-util-email": "1.0.0-beta.222",
|
|
34
34
|
"@swc/helpers": "0.5.23"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
package/src/lib/model/index.d.ts
CHANGED
|
@@ -15,24 +15,14 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
18
|
+
* The bulk-send reporting math: a send's stored counters, the rate a figure
|
|
19
|
+
* is taken as (always with its denominator named), and the link rollup.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Generic to every plugin that mails in bulk — the campaign sender, the
|
|
22
|
+
* sequence runner — and owned by none of them (AGL-3080). Nothing here
|
|
23
|
+
* imports React or MUI, so a server handler that needs a rate can take this
|
|
24
|
+
* barrel without dragging a component graph into its bundle. The renderers
|
|
25
|
+
* live one directory over, behind
|
|
24
26
|
* `@aglyn/shared-ui-jsx/components/measured-figures.component`.
|
|
25
27
|
*/
|
|
26
|
-
|
|
27
|
-
* Campaign reporting: the rate math, the populations the send recorded, and
|
|
28
|
-
* the link rollup — pure, so every denominator is named once and provable
|
|
29
|
-
* rather than chosen in JSX.
|
|
30
|
-
*/
|
|
31
|
-
export * from './campaign-report';
|
|
32
|
-
/** Reading one message record — its state, and when it went out. */
|
|
33
|
-
export * from './email-record';
|
|
34
|
-
/**
|
|
35
|
-
* The campaign CONTAINER — its window, its lists, and the arithmetic that
|
|
36
|
-
* rolls its sends into one set of figures.
|
|
37
|
-
*/
|
|
38
|
-
export * from './campaign-container';
|
|
28
|
+
export * from './send-report';
|
package/src/lib/model/index.js
CHANGED
|
@@ -14,22 +14,15 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ /**
|
|
17
|
-
* The
|
|
18
|
-
*
|
|
17
|
+
* The bulk-send reporting math: a send's stored counters, the rate a figure
|
|
18
|
+
* is taken as (always with its denominator named), and the link rollup.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Generic to every plugin that mails in bulk — the campaign sender, the
|
|
21
|
+
* sequence runner — and owned by none of them (AGL-3080). Nothing here
|
|
22
|
+
* imports React or MUI, so a server handler that needs a rate can take this
|
|
23
|
+
* barrel without dragging a component graph into its bundle. The renderers
|
|
24
|
+
* live one directory over, behind
|
|
23
25
|
* `@aglyn/shared-ui-jsx/components/measured-figures.component`.
|
|
24
|
-
*/
|
|
25
|
-
* Campaign reporting: the rate math, the populations the send recorded, and
|
|
26
|
-
* the link rollup — pure, so every denominator is named once and provable
|
|
27
|
-
* rather than chosen in JSX.
|
|
28
|
-
*/ export * from "./campaign-report.js";
|
|
29
|
-
/** Reading one message record — its state, and when it went out. */ export * from "./email-record.js";
|
|
30
|
-
/**
|
|
31
|
-
* The campaign CONTAINER — its window, its lists, and the arithmetic that
|
|
32
|
-
* rolls its sends into one set of figures.
|
|
33
|
-
*/ export * from "./campaign-container.js";
|
|
26
|
+
*/ export * from "./send-report.js";
|
|
34
27
|
|
|
35
28
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/email-campaigns/src/lib/model/index.ts"],"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\n/**\n * The
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/email-campaigns/src/lib/model/index.ts"],"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\n/**\n * The bulk-send reporting math: a send's stored counters, the rate a figure\n * is taken as (always with its denominator named), and the link rollup.\n *\n * Generic to every plugin that mails in bulk — the campaign sender, the\n * sequence runner — and owned by none of them (AGL-3080). Nothing here\n * imports React or MUI, so a server handler that needs a rate can take this\n * barrel without dragging a component graph into its bundle. The renderers\n * live one directory over, behind\n * `@aglyn/shared-ui-jsx/components/measured-figures.component`.\n */\nexport * from './send-report'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GACD,cAAc,mBAAe"}
|
|
@@ -15,11 +15,17 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* BULK SEND REPORTING MATH — the only place a rate is computed.
|
|
19
|
+
*
|
|
20
|
+
* Owned by none of the plugins that read it: the plugin that sends a
|
|
21
|
+
* campaign, the one that keeps the email templates a send is built from and
|
|
22
|
+
* the one that runs one-to-one sequences all report what their mail did, and
|
|
23
|
+
* each divides the same counters a send records. Putting the division here
|
|
24
|
+
* makes them peers of one rule rather than three copies of it.
|
|
19
25
|
*
|
|
20
26
|
* ## Why a pure module and not a component
|
|
21
27
|
*
|
|
22
|
-
* Every number on
|
|
28
|
+
* Every number on a report screen is a division, and a division is where
|
|
23
29
|
* email reporting goes wrong. Putting the arithmetic in JSX means the
|
|
24
30
|
* denominator is chosen by whoever writes the next card, in a file nobody
|
|
25
31
|
* tests for arithmetic; putting it here means each rate is named once,
|
|
@@ -32,47 +38,47 @@
|
|
|
32
38
|
* rate over `delivered` are different numbers with the same label, and the
|
|
33
39
|
* gap between them is exactly the mail that bounced. The industry convention
|
|
34
40
|
* is over `delivered`, and a report that quietly used `sent` would read
|
|
35
|
-
* higher than the same
|
|
41
|
+
* higher than the same send measured anywhere else.
|
|
36
42
|
*
|
|
37
|
-
* So {@link
|
|
38
|
-
*
|
|
43
|
+
* So {@link SendRate} carries `denominatorLabel`, and the screen is required
|
|
44
|
+
* to render it. There is no overload that omits it.
|
|
39
45
|
*
|
|
40
46
|
* ## Why some rates are deliberately absent
|
|
41
47
|
*
|
|
42
|
-
* {@link
|
|
48
|
+
* {@link sendRate} answers `null`, not zero, when it cannot divide:
|
|
43
49
|
*
|
|
44
50
|
* - **A zero denominator.** 0 opens out of 0 delivered is not a 0% open
|
|
45
51
|
* rate, it is no open rate. Rendering 0% invites the reader to compare it
|
|
46
|
-
* with a
|
|
52
|
+
* with a send that really did fail.
|
|
47
53
|
* - **An UNKNOWN denominator.** `delivered` is counted by the delivery
|
|
48
|
-
* webhook, which was connected after some
|
|
49
|
-
*
|
|
54
|
+
* webhook, which was connected after some sends went out. A send with 400
|
|
55
|
+
* messages and no delivery events has an unknown denominator, not a
|
|
50
56
|
* denominator of zero — and dividing by `sent` instead is precisely the
|
|
51
57
|
* flattering substitution above.
|
|
52
58
|
*
|
|
53
59
|
* ## The structural-zero window
|
|
54
60
|
*
|
|
55
61
|
* Click tracking rewrites links in the HTML part. Sends that carried no HTML
|
|
56
|
-
* part were therefore untrackable, and every one of
|
|
57
|
-
*
|
|
62
|
+
* part were therefore untrackable, and every one of them reports 0 clicks
|
|
63
|
+
* whatever the recipients actually did — a real 0 and a structural 0
|
|
58
64
|
* rendered identically. `send-email.ts` now synthesises an HTML part for a
|
|
59
|
-
* text-only send, so every send after that carries one, and
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* the audience and for those campaigns it is a measurement of the sender.
|
|
65
|
+
* text-only send, so every send after that carries one, and the sender
|
|
66
|
+
* records {@link SendStats.clickTracked} to say so. A send with no such
|
|
67
|
+
* marker predates the record: its click COUNT is still shown — it is a real
|
|
68
|
+
* count of real events — but no click RATE is computed from it, because a
|
|
69
|
+
* rate presents the number as a measurement of the audience and for those
|
|
70
|
+
* sends it is a measurement of the sender.
|
|
66
71
|
*/
|
|
67
72
|
/**
|
|
68
|
-
* The `stats` map on
|
|
73
|
+
* The `stats` map one bulk send keeps — a campaign send's on
|
|
74
|
+
* `orgs/{orgId}/campaigns/{sendId}`.
|
|
69
75
|
*
|
|
70
76
|
* Every field is optional and every reader defaults it, because these are
|
|
71
77
|
* written by three different writers at three different times — the send, the
|
|
72
78
|
* delivery webhook, the unsubscribe handler — and a campaign is a legitimate,
|
|
73
79
|
* readable document from the instant the first of them lands.
|
|
74
80
|
*/
|
|
75
|
-
export interface
|
|
81
|
+
export interface SendStats {
|
|
76
82
|
/** The whole audience the send was taken from, before the per-send cap. */
|
|
77
83
|
audienceSize?: number;
|
|
78
84
|
/** `audienceSize` is a FLOOR: audience resolution hit its read ceiling. */
|
|
@@ -139,7 +145,7 @@ export interface CampaignStats {
|
|
|
139
145
|
* numbers called "open rate" distinguishable, and the screen renders it
|
|
140
146
|
* beside the percentage for that reason.
|
|
141
147
|
*/
|
|
142
|
-
export interface
|
|
148
|
+
export interface SendRate {
|
|
143
149
|
/** 0–1. Multiply for display; the model never formats. */
|
|
144
150
|
value: number;
|
|
145
151
|
numerator: number;
|
|
@@ -153,98 +159,16 @@ export interface CampaignRate {
|
|
|
153
159
|
* `null` on a zero or unknown denominator — see the module header for why
|
|
154
160
|
* that is not the same as 0%.
|
|
155
161
|
*/
|
|
156
|
-
export declare function
|
|
157
|
-
/** Why a number the report would otherwise show is being withheld. */
|
|
158
|
-
export interface CampaignCaveat {
|
|
159
|
-
/** Stable id, so a spec can assert on the caveat rather than its prose. */
|
|
160
|
-
id: 'delivery-unrecorded' | 'click-tracking-unrecorded' | 'audience-truncated' | 'send-deferred' | 'revenue-denominator-unrecorded' | 'revenue-multi-currency' | 'revenue-mid-flight' | 'revenue-mixed-model' | 'conversions-kinds-overlap' | 'conversions-web-not-rolled-up' | 'conversions-unattributed-is-a-ceiling' | 'conversions-total-crosses-hosts' | 'sequences-funnel-not-summed';
|
|
161
|
-
message: string;
|
|
162
|
-
}
|
|
163
|
-
/** One population the send measured, for the audience breakdown. */
|
|
164
|
-
export interface CampaignPopulation {
|
|
165
|
-
id: string;
|
|
166
|
-
label: string;
|
|
167
|
-
count: number;
|
|
168
|
-
/** What this count is a part OF, named. */
|
|
169
|
-
ofLabel: string;
|
|
170
|
-
of: number;
|
|
171
|
-
}
|
|
172
|
-
/** Everything the report screen renders, decided here rather than in JSX. */
|
|
173
|
-
export interface CampaignReport {
|
|
174
|
-
sent: number;
|
|
175
|
-
recipients: number;
|
|
176
|
-
delivered: number | null;
|
|
177
|
-
opens: number;
|
|
178
|
-
clicks: number;
|
|
179
|
-
uniqueOpens: number | null;
|
|
180
|
-
uniqueClicks: number | null;
|
|
181
|
-
bounced: number;
|
|
182
|
-
complained: number;
|
|
183
|
-
unsubscribes: number;
|
|
184
|
-
/** Rates, each `null` when its denominator is zero or unrecorded. */
|
|
185
|
-
rates: {
|
|
186
|
-
/** Accepted by the receiving server, over what the provider accepted. */
|
|
187
|
-
delivery: CampaignRate | null;
|
|
188
|
-
/** Distinct readers who opened, over delivered. */
|
|
189
|
-
open: CampaignRate | null;
|
|
190
|
-
/** Distinct readers who clicked, over delivered. */
|
|
191
|
-
click: CampaignRate | null;
|
|
192
|
-
/**
|
|
193
|
-
* Distinct clickers over distinct OPENERS — a different question from
|
|
194
|
-
* `click`, and the one the two get confused for. It answers "of the
|
|
195
|
-
* people who read it, how many acted", not "of the people who received
|
|
196
|
-
* it". Reported separately and labelled separately, never as "click
|
|
197
|
-
* rate".
|
|
198
|
-
*/
|
|
199
|
-
clickToOpen: CampaignRate | null;
|
|
200
|
-
/** Bounced over what the provider accepted. */
|
|
201
|
-
bounce: CampaignRate | null;
|
|
202
|
-
/** Complaints over delivered — the number mailbox providers judge on. */
|
|
203
|
-
complaint: CampaignRate | null;
|
|
204
|
-
/** Unsubscribes through this campaign's link, over delivered. */
|
|
205
|
-
unsubscribe: CampaignRate | null;
|
|
206
|
-
};
|
|
207
|
-
populations: CampaignPopulation[];
|
|
208
|
-
caveats: CampaignCaveat[];
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Turns a stored `stats` map into the report.
|
|
212
|
-
*
|
|
213
|
-
* ## The denominator decisions, in one place
|
|
214
|
-
*
|
|
215
|
-
* - **`delivered`** carries the engagement rates — open, click, complaint,
|
|
216
|
-
* unsubscribe. Mail that bounced was never in front of a human, so
|
|
217
|
-
* including it in the denominator of an open rate depresses a number that
|
|
218
|
-
* describes the audience with a fact about the address list. This is also
|
|
219
|
-
* the convention every other tool reports, which matters: a merchant
|
|
220
|
-
* comparing our figure with their previous ESP's must be comparing the same
|
|
221
|
-
* quantity.
|
|
222
|
-
* - **`sent`** carries the delivery and bounce rates, because those describe
|
|
223
|
-
* what happened to what we handed the provider, and `delivered` is the
|
|
224
|
-
* numerator of one of them — a rate cannot be over itself.
|
|
225
|
-
* - **`uniqueOpens`** is the open-rate numerator, not `opens`. Open EVENTS
|
|
226
|
-
* over recipients can exceed 100% the moment one person opens twice, and a
|
|
227
|
-
* percentage above 100 is how a reader learns the number means something
|
|
228
|
-
* other than what it says. Both are shown; only the distinct count is
|
|
229
|
-
* divided.
|
|
230
|
-
*
|
|
231
|
-
* ## `delivered` unknown vs. zero
|
|
232
|
-
*
|
|
233
|
-
* A campaign predating the delivery webhook records no `delivered` at all.
|
|
234
|
-
* That is reported as `null` and every rate over it is withheld, with a
|
|
235
|
-
* caveat naming the reason — rather than substituting `sent`, which would
|
|
236
|
-
* silently publish the flattered number this module exists to refuse.
|
|
237
|
-
*/
|
|
238
|
-
export declare function campaignReport(stats: CampaignStats | undefined): CampaignReport;
|
|
162
|
+
export declare function sendRate(numerator: number | undefined, denominator: number | undefined, denominatorLabel: string): SendRate | null;
|
|
239
163
|
/**
|
|
240
164
|
* How many distinct destinations one campaign's rollup keeps.
|
|
241
165
|
*
|
|
242
166
|
* A CAP rather than a page size, and it exists because the map lives in a
|
|
243
167
|
* single document with a 1 MiB ceiling. Clicks past the cap are counted in
|
|
244
|
-
* {@link
|
|
168
|
+
* {@link SendLinkRollup.overflowClicks} rather than dropped, so the
|
|
245
169
|
* table's total still reconciles with `stats.clicks`.
|
|
246
170
|
*/
|
|
247
|
-
export declare const
|
|
171
|
+
export declare const SEND_LINK_ROLLUP_MAX = 50;
|
|
248
172
|
/**
|
|
249
173
|
* Reduces a clicked URL to the key the rollup counts under.
|
|
250
174
|
*
|
|
@@ -269,29 +193,29 @@ export declare const CAMPAIGN_LINK_ROLLUP_MAX = 50;
|
|
|
269
193
|
* @returns the normalized URL, or `null` for anything unparseable or not
|
|
270
194
|
* http(s). A rollup key must be a URL a merchant recognises.
|
|
271
195
|
*/
|
|
272
|
-
export declare function
|
|
196
|
+
export declare function sendLinkKey(link: string | null | undefined): string | null;
|
|
273
197
|
/** One destination in the rollup. */
|
|
274
|
-
export interface
|
|
198
|
+
export interface SendLinkRow {
|
|
275
199
|
url: string;
|
|
276
200
|
/** Click EVENTS on this destination. One reader clicking twice counts two. */
|
|
277
201
|
clicks: number;
|
|
278
202
|
/** Share of the campaign's counted link clicks. */
|
|
279
|
-
share:
|
|
203
|
+
share: SendRate | null;
|
|
280
204
|
}
|
|
281
205
|
/** The stored shape of `campaigns/{campaignId}/reports/links`. */
|
|
282
|
-
export interface
|
|
206
|
+
export interface SendLinkRollup {
|
|
283
207
|
links?: Record<string, {
|
|
284
208
|
url?: string;
|
|
285
209
|
clicks?: number;
|
|
286
210
|
}>;
|
|
287
|
-
/** Clicks on destinations past {@link
|
|
211
|
+
/** Clicks on destinations past {@link SEND_LINK_ROLLUP_MAX}. */
|
|
288
212
|
overflowClicks?: number;
|
|
289
213
|
/** Click events that arrived carrying no destination at all. */
|
|
290
214
|
unattributedClicks?: number;
|
|
291
215
|
}
|
|
292
216
|
/** What the link table renders. */
|
|
293
|
-
export interface
|
|
294
|
-
rows:
|
|
217
|
+
export interface SendLinkReport {
|
|
218
|
+
rows: SendLinkRow[];
|
|
295
219
|
/** Clicks counted against a named destination — the table's total. */
|
|
296
220
|
attributedClicks: number;
|
|
297
221
|
overflowClicks: number;
|
|
@@ -308,53 +232,4 @@ export interface CampaignLinkReport {
|
|
|
308
232
|
* that are not on screen is the kind of arithmetic a reader cannot check.
|
|
309
233
|
* Both excluded figures are returned so the screen can state them.
|
|
310
234
|
*/
|
|
311
|
-
export declare function
|
|
312
|
-
/**
|
|
313
|
-
* The outcomes the runtime credits, in funnel order. An array first, because
|
|
314
|
-
* the ORDER is the reading order and a second list is a second chance to
|
|
315
|
-
* leave one out; the union is derived from it.
|
|
316
|
-
*/
|
|
317
|
-
export declare const CAMPAIGN_SEQUENCE_OUTCOMES: readonly ["enrolled", "sent", "replied", "meetings", "converted"];
|
|
318
|
-
export type CampaignSequenceOutcome = (typeof CAMPAIGN_SEQUENCE_OUTCOMES)[number];
|
|
319
|
-
/**
|
|
320
|
-
* The org collection holding one document per campaign container,
|
|
321
|
-
* `orgs/{orgId}/campaignSequenceReports/{campaignId}`.
|
|
322
|
-
*/
|
|
323
|
-
export declare const CAMPAIGN_SEQUENCE_REPORTS_COLLECTION = "campaignSequenceReports";
|
|
324
|
-
/** What a reader calls each outcome, and what the count means. */
|
|
325
|
-
export declare const CAMPAIGN_SEQUENCE_OUTCOME_COPY: Readonly<Record<CampaignSequenceOutcome, {
|
|
326
|
-
label: string;
|
|
327
|
-
note: string;
|
|
328
|
-
}>>;
|
|
329
|
-
/**
|
|
330
|
-
* The stored shape of `campaignSequenceReports/{campaignId}`.
|
|
331
|
-
*
|
|
332
|
-
* Read-side only, every field optional: another plugin's runtime wrote it,
|
|
333
|
-
* and a reader that assumed a field was present would throw on the first
|
|
334
|
-
* document written before the field existed.
|
|
335
|
-
*/
|
|
336
|
-
export interface CampaignSequencesRollup {
|
|
337
|
-
byOutcome?: Partial<Record<CampaignSequenceOutcome, number>>;
|
|
338
|
-
/** When the runtime last credited anything, epoch ms. */
|
|
339
|
-
updatedAtMs?: number;
|
|
340
|
-
}
|
|
341
|
-
/** One outcome's figure on screen. */
|
|
342
|
-
export interface CampaignSequenceFigure {
|
|
343
|
-
outcome: CampaignSequenceOutcome;
|
|
344
|
-
label: string;
|
|
345
|
-
/** The count, or `null` when the rollup holds no entry for the outcome. */
|
|
346
|
-
value: number | null;
|
|
347
|
-
note: string;
|
|
348
|
-
}
|
|
349
|
-
/** Everything the Sequences section renders. */
|
|
350
|
-
export interface CampaignSequencesReport {
|
|
351
|
-
/** Always all five, always in {@link CAMPAIGN_SEQUENCE_OUTCOMES} order. */
|
|
352
|
-
figures: CampaignSequenceFigure[];
|
|
353
|
-
/** Whether the rollup document exists at all — see the block header. */
|
|
354
|
-
recorded: boolean;
|
|
355
|
-
/** At least one outcome holds a figure. */
|
|
356
|
-
any: boolean;
|
|
357
|
-
caveats: CampaignCaveat[];
|
|
358
|
-
}
|
|
359
|
-
/** Turns the stored rollup into the Sequences section. */
|
|
360
|
-
export declare function campaignSequencesReport(rollup: CampaignSequencesRollup | undefined): CampaignSequencesReport;
|
|
235
|
+
export declare function sendLinkReport(rollup: SendLinkRollup | undefined): SendLinkReport;
|