mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 08:02:04 +00:00
406 lines
14 KiB
Go
406 lines
14 KiB
Go
package models
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Seed panels a placement test can send to.
|
|
const (
|
|
// PlacementPanelInstance is the seed panel the operator runs for every
|
|
// workspace on the instance (on the hosted product, Warmbly's own).
|
|
PlacementPanelInstance = "instance"
|
|
// PlacementPanelWorkspace is the workspace's own test inboxes.
|
|
PlacementPanelWorkspace = "workspace"
|
|
// PlacementPanelCloud is Warmbly Cloud's panel, for a linked self-hosted
|
|
// instance.
|
|
PlacementPanelCloud = "cloud"
|
|
)
|
|
|
|
// ValidPlacementPanel reports whether p names a seed panel.
|
|
func ValidPlacementPanel(p string) bool {
|
|
switch p {
|
|
case PlacementPanelInstance, PlacementPanelWorkspace, PlacementPanelCloud:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Seed scopes, stored as email_accounts.seed_scope.
|
|
const (
|
|
SeedScopeInstance = "instance"
|
|
SeedScopeWorkspace = "workspace"
|
|
)
|
|
|
|
// Placement test statuses.
|
|
const (
|
|
PlacementStatusRunning = "running"
|
|
PlacementStatusCompleted = "completed"
|
|
PlacementStatusCancelled = "cancelled"
|
|
PlacementStatusFailed = "failed"
|
|
)
|
|
|
|
// What started a placement test.
|
|
const (
|
|
PlacementOriginManual = "manual"
|
|
PlacementOriginMonitor = "monitor"
|
|
PlacementOriginAdmin = "admin"
|
|
// PlacementOriginRemote is a test the cloud runs for a linked instance.
|
|
PlacementOriginRemote = "remote"
|
|
)
|
|
|
|
// Where one probe landed, stored as placement_results.folder.
|
|
const (
|
|
PlacementFolderPending = "pending"
|
|
PlacementFolderInbox = "inbox"
|
|
PlacementFolderPromotions = "promotions"
|
|
// PlacementFolderOther is a Gmail tab other than Promotions (Updates,
|
|
// Social, Forums).
|
|
PlacementFolderOther = "other"
|
|
PlacementFolderSpam = "spam"
|
|
// PlacementFolderMissing is a copy that left the sender and never showed
|
|
// up in the seed within the classify window.
|
|
PlacementFolderMissing = "missing"
|
|
// PlacementFolderFailed is a copy that never left the sender.
|
|
PlacementFolderFailed = "failed"
|
|
PlacementFolderCancelled = "cancelled"
|
|
)
|
|
|
|
// PlacementFolderResolved reports whether a folder is a final answer about
|
|
// where a delivered copy went (not pending, failed or cancelled).
|
|
func PlacementFolderResolved(folder string) bool {
|
|
switch folder {
|
|
case PlacementFolderInbox, PlacementFolderPromotions, PlacementFolderOther,
|
|
PlacementFolderSpam, PlacementFolderMissing:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ClassifyPlacementLanding reads where an arriving probe sits from its
|
|
// canonical folder and provider flags, splitting Gmail's Promotions tab from
|
|
// the other tabs because Promotions is where cold mail goes to be ignored.
|
|
func ClassifyPlacementLanding(folder string, flags []string) string {
|
|
if folder == FolderSpam || HasSpamFlag(flags) {
|
|
return PlacementFolderSpam
|
|
}
|
|
tab := ""
|
|
for _, f := range flags {
|
|
switch strings.ToUpper(f) {
|
|
case "CATEGORY_PROMOTIONS":
|
|
return PlacementFolderPromotions
|
|
case "CATEGORY_UPDATES", "CATEGORY_SOCIAL", "CATEGORY_FORUMS":
|
|
tab = PlacementFolderOther
|
|
}
|
|
}
|
|
if tab != "" {
|
|
return tab
|
|
}
|
|
return PlacementFolderInbox
|
|
}
|
|
|
|
// Tracking choices when a test is created.
|
|
const (
|
|
// PlacementTrackingCampaign copies the campaign's own open and click
|
|
// tracking; an ad-hoc test has none.
|
|
PlacementTrackingCampaign = "campaign"
|
|
PlacementTrackingOn = "on"
|
|
PlacementTrackingOff = "off"
|
|
// PlacementTrackingCompare runs the same copy twice, once tracked and once
|
|
// not, interleaved per seed so both see the same conditions.
|
|
PlacementTrackingCompare = "compare"
|
|
)
|
|
|
|
// Pacing of a test's copies from one sender.
|
|
const (
|
|
// PlacementPaceSpaced sends at the instance's spacing, about a minute apart.
|
|
PlacementPaceSpaced = "spaced"
|
|
// PlacementPaceQuick sends a few seconds apart, for a result in minutes.
|
|
PlacementPaceQuick = "quick"
|
|
)
|
|
|
|
// PlacementTest is one run: a template sent from one sender to a seed panel.
|
|
type PlacementTest struct {
|
|
ID uuid.UUID `json:"id"`
|
|
OrganizationID *uuid.UUID `json:"-"`
|
|
SenderAccountID *uuid.UUID `json:"sender_account_id"`
|
|
SenderEmail string `json:"sender_email"`
|
|
CreatedBy *uuid.UUID `json:"created_by"`
|
|
CampaignID *uuid.UUID `json:"campaign_id"`
|
|
SequenceID *uuid.UUID `json:"sequence_id"`
|
|
ContactID *uuid.UUID `json:"contact_id"`
|
|
MonitorID *uuid.UUID `json:"monitor_id"`
|
|
// BatchID and BatchSenderID name the batch that started the test.
|
|
BatchID *uuid.UUID `json:"batch_id"`
|
|
BatchSenderID *uuid.UUID `json:"-"`
|
|
Subject string `json:"subject"`
|
|
BodyHTML string `json:"body_html,omitempty"`
|
|
BodyPlain string `json:"body_plain,omitempty"`
|
|
OpenTracking bool `json:"open_tracking"`
|
|
LinkTracking bool `json:"link_tracking"`
|
|
CompareGroupID *uuid.UUID `json:"compare_group_id"`
|
|
Origin string `json:"origin"`
|
|
Panel string `json:"panel"`
|
|
Status string `json:"status"`
|
|
Error string `json:"error,omitempty"`
|
|
// RemoteInstanceID is set on the cloud's copy of a test it runs for a
|
|
// linked instance; RemoteTestID on the instance's copy, naming the cloud's.
|
|
RemoteInstanceID *uuid.UUID `json:"-"`
|
|
RemoteTestID *uuid.UUID `json:"-"`
|
|
Pace string `json:"pace"`
|
|
// CreditsCharged is what the test cost past the monthly free allowance.
|
|
// A paid test is settled once it finishes: CreditsRefunded is what came
|
|
// back because no copy was delivered, CreditsSettledAt when that was decided.
|
|
CreditsCharged int `json:"credits_charged"`
|
|
CreditsRefunded int `json:"credits_refunded"`
|
|
CreditsSettledAt *time.Time `json:"credits_settled_at"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
FinishedAt *time.Time `json:"finished_at"`
|
|
}
|
|
|
|
// Tracked reports whether the test's copies carry any tracking.
|
|
func (t PlacementTest) Tracked() bool { return t.OpenTracking || t.LinkTracking }
|
|
|
|
// PlacementResult is one probe: one copy to one seed.
|
|
type PlacementResult struct {
|
|
ID uuid.UUID `json:"id"`
|
|
TestID uuid.UUID `json:"test_id"`
|
|
SeedAccountID *uuid.UUID `json:"-"`
|
|
SeedAddress string `json:"seed_address"`
|
|
// Family is who hosts the seed, a mailhost value (google_workspace,
|
|
// gmail, microsoft365, outlook, yahoo, ...). Stored as provider.
|
|
Family string `json:"family"`
|
|
RemoteSeedID *uuid.UUID `json:"-"`
|
|
TaskID *uuid.UUID `json:"-"`
|
|
MessageID string `json:"-"`
|
|
Folder string `json:"folder"`
|
|
ScheduledAt *time.Time `json:"scheduled_at"`
|
|
SentAt *time.Time `json:"sent_at"`
|
|
DetectedAt *time.Time `json:"detected_at"`
|
|
RawFlags string `json:"-"`
|
|
Error string `json:"error,omitempty"`
|
|
RemoteSyncedAt *time.Time `json:"-"`
|
|
}
|
|
|
|
// PlacementCounts is where a set of probes landed.
|
|
type PlacementCounts struct {
|
|
Total int `json:"total"`
|
|
Pending int `json:"pending"`
|
|
Inbox int `json:"inbox"`
|
|
Promotions int `json:"promotions"`
|
|
Other int `json:"other"`
|
|
Spam int `json:"spam"`
|
|
Missing int `json:"missing"`
|
|
Failed int `json:"failed"`
|
|
Cancelled int `json:"cancelled"`
|
|
// Delivered is every copy that left and got a verdict:
|
|
// inbox + promotions + other + spam + missing.
|
|
Delivered int `json:"delivered"`
|
|
// InboxRate is the primary inbox share of Delivered, TabsRate the Gmail
|
|
// tabs, SpamRate spam and MissingRate the copies never seen. Nil until
|
|
// something is delivered.
|
|
InboxRate *float64 `json:"inbox_rate"`
|
|
TabsRate *float64 `json:"tabs_rate"`
|
|
SpamRate *float64 `json:"spam_rate"`
|
|
MissingRate *float64 `json:"missing_rate"`
|
|
}
|
|
|
|
// Add counts one probe.
|
|
func (c *PlacementCounts) Add(folder string) { c.AddN(folder, 1) }
|
|
|
|
// AddN counts n probes in one folder.
|
|
func (c *PlacementCounts) AddN(folder string, n int) {
|
|
c.Total += n
|
|
switch folder {
|
|
case PlacementFolderInbox:
|
|
c.Inbox += n
|
|
case PlacementFolderPromotions:
|
|
c.Promotions += n
|
|
case PlacementFolderOther:
|
|
c.Other += n
|
|
case PlacementFolderSpam:
|
|
c.Spam += n
|
|
case PlacementFolderMissing:
|
|
c.Missing += n
|
|
case PlacementFolderFailed:
|
|
c.Failed += n
|
|
case PlacementFolderCancelled:
|
|
c.Cancelled += n
|
|
default:
|
|
c.Pending += n
|
|
}
|
|
}
|
|
|
|
// Finish derives Delivered and the rates.
|
|
func (c *PlacementCounts) Finish() {
|
|
c.Delivered = c.Inbox + c.Promotions + c.Other + c.Spam + c.Missing
|
|
c.InboxRate, c.TabsRate, c.SpamRate, c.MissingRate = nil, nil, nil, nil
|
|
if c.Delivered == 0 {
|
|
return
|
|
}
|
|
rate := func(n int) *float64 {
|
|
r := float64(n) / float64(c.Delivered)
|
|
return &r
|
|
}
|
|
c.InboxRate = rate(c.Inbox)
|
|
c.TabsRate = rate(c.Promotions + c.Other)
|
|
c.SpamRate = rate(c.Spam)
|
|
c.MissingRate = rate(c.Missing)
|
|
}
|
|
|
|
// PlacementFamilyCounts is one host family's share of a test.
|
|
type PlacementFamilyCounts struct {
|
|
Family string `json:"family"`
|
|
Label string `json:"label"`
|
|
Counts PlacementCounts `json:"counts"`
|
|
}
|
|
|
|
// PlacementSeed is a seed mailbox as a test sees it.
|
|
type PlacementSeed struct {
|
|
AccountID *uuid.UUID
|
|
RemoteSeedID *uuid.UUID
|
|
Address string
|
|
Family string
|
|
OrganizationID *uuid.UUID
|
|
}
|
|
|
|
// PlacementPanelFamily counts the seeds one host family contributes.
|
|
type PlacementPanelFamily struct {
|
|
Family string `json:"family"`
|
|
Label string `json:"label"`
|
|
Seeds int `json:"seeds"`
|
|
}
|
|
|
|
// PlacementPanelInfo says whether a workspace can test on a panel.
|
|
type PlacementPanelInfo struct {
|
|
Panel string `json:"panel"`
|
|
Available bool `json:"available"`
|
|
// Reason explains an unavailable panel in one sentence.
|
|
Reason string `json:"reason,omitempty"`
|
|
Seeds int `json:"seeds"`
|
|
Families []PlacementPanelFamily `json:"families"`
|
|
// Metered panels count against the monthly allowance.
|
|
Metered bool `json:"metered"`
|
|
}
|
|
|
|
// PlacementUsage is a workspace's monthly allowance on the metered panels.
|
|
type PlacementUsage struct {
|
|
Used int `json:"used"`
|
|
// Limit is nil when the instance does not meter tests (self-hosted).
|
|
Limit *int `json:"limit"`
|
|
// CreditsPerTest is what a test past the free allowance costs in credits,
|
|
// zero when tests cannot be paid for; CreditBalance is the workspace's
|
|
// spendable balance, nil when credits are off.
|
|
CreditsPerTest int `json:"credits_per_test"`
|
|
CreditBalance *int `json:"credit_balance"`
|
|
PeriodStart time.Time `json:"period_start"`
|
|
PeriodEnd time.Time `json:"period_end"`
|
|
}
|
|
|
|
// Remaining is how many metered tests are left, -1 when unmetered.
|
|
func (u PlacementUsage) Remaining() int {
|
|
if u.Limit == nil {
|
|
return -1
|
|
}
|
|
return max(0, *u.Limit-u.Used)
|
|
}
|
|
|
|
// PlacementWorkspaceSeed is one of a workspace's own mailboxes as a seed
|
|
// candidate.
|
|
type PlacementWorkspaceSeed struct {
|
|
EmailAccountID uuid.UUID `json:"email_account_id"`
|
|
Email string `json:"email"`
|
|
Family string `json:"family"`
|
|
Label string `json:"label"`
|
|
Status string `json:"status"`
|
|
Seed bool `json:"seed"`
|
|
// Blocker is why the mailbox cannot become a seed, empty when it can.
|
|
Blocker string `json:"blocker,omitempty"`
|
|
}
|
|
|
|
// PlacementMonitor re-tests a campaign's first step on a schedule.
|
|
type PlacementMonitor struct {
|
|
ID uuid.UUID `json:"id"`
|
|
OrganizationID uuid.UUID `json:"-"`
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
CreatedBy *uuid.UUID `json:"created_by"`
|
|
Enabled bool `json:"enabled"`
|
|
IntervalDays int `json:"interval_days"`
|
|
Panel string `json:"panel"`
|
|
AlertBelow int `json:"alert_below"`
|
|
PauseOnAlert bool `json:"pause_on_alert"`
|
|
NextRunAt time.Time `json:"next_run_at"`
|
|
LastRunAt *time.Time `json:"last_run_at"`
|
|
LastTestID *uuid.UUID `json:"last_test_id"`
|
|
LastSenderID *uuid.UUID `json:"-"`
|
|
LastAlertAt *time.Time `json:"last_alert_at"`
|
|
LastError string `json:"last_error,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// PlacementCloudStartRequest is a linked instance asking Warmbly Cloud for
|
|
// seeds to run a test against.
|
|
type PlacementCloudStartRequest struct {
|
|
// SenderDomain keeps seeds on the sender's own domain out of the panel:
|
|
// mail inside one tenant skips the filtering the test is there to see.
|
|
SenderDomain string `json:"sender_domain"`
|
|
// Tests is how many tests the request covers (two for a tracking
|
|
// comparison), each charged against the cloud workspace's allowance.
|
|
Tests int `json:"tests"`
|
|
// MaxSeeds is how many seeds the sender's day can pay for; zero means the
|
|
// cloud's own cap.
|
|
MaxSeeds int `json:"max_seeds,omitempty"`
|
|
// Families keeps only seeds hosted by these providers; empty is all.
|
|
Families []string `json:"families,omitempty"`
|
|
}
|
|
|
|
// PlacementCloudSeed is one cloud seed a linked instance sends a copy to.
|
|
type PlacementCloudSeed struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Address string `json:"address"`
|
|
Family string `json:"family"`
|
|
}
|
|
|
|
// PlacementCloudStart is the cloud's answer: the seeds, once per test.
|
|
type PlacementCloudStart struct {
|
|
TestIDs []uuid.UUID `json:"test_ids"`
|
|
Seeds []PlacementCloudSeed `json:"seeds"`
|
|
Usage PlacementUsage `json:"usage"`
|
|
}
|
|
|
|
// PlacementCloudSend reports one copy the instance sent, or failed to send.
|
|
type PlacementCloudSend struct {
|
|
SeedID uuid.UUID `json:"seed_id"`
|
|
MessageID string `json:"message_id,omitempty"`
|
|
SentAt *time.Time `json:"sent_at,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// PlacementCloudSends is the batch an instance reports.
|
|
type PlacementCloudSends struct {
|
|
Sends []PlacementCloudSend `json:"sends"`
|
|
}
|
|
|
|
// PlacementCloudVerdict is where one cloud seed saw its copy land.
|
|
type PlacementCloudVerdict struct {
|
|
SeedID uuid.UUID `json:"seed_id"`
|
|
Folder string `json:"folder"`
|
|
DetectedAt *time.Time `json:"detected_at,omitempty"`
|
|
}
|
|
|
|
// PlacementCloudTest is the cloud's view of one test it runs for an instance.
|
|
type PlacementCloudTest struct {
|
|
TestID uuid.UUID `json:"test_id"`
|
|
Status string `json:"status"`
|
|
Verdicts []PlacementCloudVerdict `json:"verdicts"`
|
|
}
|
|
|
|
// PlacementCloudPanel is the cloud panel as a linked instance sees it.
|
|
type PlacementCloudPanel struct {
|
|
Panel PlacementPanelInfo `json:"panel"`
|
|
Usage PlacementUsage `json:"usage"`
|
|
}
|