diff --git a/cmd/lists.go b/cmd/lists.go index 578f9d8fc..a580f5acb 100644 --- a/cmd/lists.go +++ b/cmd/lists.go @@ -93,6 +93,11 @@ func handleGetLists(c echo.Context) error { if v.Tags == nil { out.Results[i].Tags = make(pq.StringArray, 0) } + + // Total counts. + for _, c := range v.SubscriberCounts { + out.Results[i].SubscriberCount += c + } } if single { diff --git a/frontend/src/views/Lists.vue b/frontend/src/views/Lists.vue index 8497315b9..df5661ccd 100644 --- a/frontend/src/views/Lists.vue +++ b/frontend/src/views/Lists.vue @@ -61,7 +61,7 @@ + header-class="cy-type" sortable width="15%">
{{ $t(`lists.types.${props.row.type}`) }} @@ -94,6 +94,16 @@ + +
+

+ + {{ $utils.formatNumber(count) }} +

+
+
+ {{ $utils.niceDate(props.row.createdAt) }} diff --git a/models/models.go b/models/models.go index 387fbdc7f..402c672ca 100644 --- a/models/models.go +++ b/models/models.go @@ -149,6 +149,9 @@ type subLists struct { // SubscriberAttribs is the map of key:value attributes of a subscriber. type SubscriberAttribs map[string]interface{} +// StringIntMap is used to define DB Scan()s. +type StringIntMap map[string]int + // Subscribers represents a slice of Subscriber. type Subscribers []Subscriber @@ -167,13 +170,14 @@ type SubscriberExport struct { type List struct { Base - UUID string `db:"uuid" json:"uuid"` - Name string `db:"name" json:"name"` - Type string `db:"type" json:"type"` - Optin string `db:"optin" json:"optin"` - Tags pq.StringArray `db:"tags" json:"tags"` - SubscriberCount int `db:"subscriber_count" json:"subscriber_count"` - SubscriberID int `db:"subscriber_id" json:"-"` + UUID string `db:"uuid" json:"uuid"` + Name string `db:"name" json:"name"` + Type string `db:"type" json:"type"` + Optin string `db:"optin" json:"optin"` + Tags pq.StringArray `db:"tags" json:"tags"` + SubscriberCount int `db:"-" json:"subscriber_count"` + SubscriberCounts StringIntMap `db:"subscriber_statuses" json:"subscriber_statuses"` + SubscriberID int `db:"subscriber_id" json:"-"` // This is only relevant when querying the lists of a subscriber. SubscriptionStatus string `db:"subscription_status" json:"subscription_status,omitempty"` @@ -319,12 +323,20 @@ func (s SubscriberAttribs) Value() (driver.Value, error) { return json.Marshal(s) } -// Scan unmarshals JSON into SubscriberAttribs. +// Scan unmarshals JSONB from the DB. func (s SubscriberAttribs) Scan(src interface{}) error { if data, ok := src.([]byte); ok { return json.Unmarshal(data, &s) } - return fmt.Errorf("Could not not decode type %T -> %T", src, s) + return fmt.Errorf("could not not decode type %T -> %T", src, s) +} + +// Scan unmarshals JSONB from the DB. +func (s StringIntMap) Scan(src interface{}) error { + if data, ok := src.([]byte); ok { + return json.Unmarshal(data, &s) + } + return fmt.Errorf("could not not decode type %T -> %T", src, s) } // GetIDs returns the list of campaign IDs. diff --git a/queries.sql b/queries.sql index 5e2189d74..bc5f1973a 100644 --- a/queries.sql +++ b/queries.sql @@ -351,12 +351,13 @@ WITH ls AS ( OFFSET $3 LIMIT (CASE WHEN $4 = 0 THEN NULL ELSE $4 END) ), counts AS ( - SELECT COUNT(*) as subscriber_count, list_id FROM subscriber_lists - WHERE status != 'unsubscribed' - AND ($1 = 0 OR list_id = $1) - GROUP BY list_id + SELECT list_id, JSON_OBJECT_AGG(status, subscriber_count) AS subscriber_statuses FROM ( + SELECT COUNT(*) as subscriber_count, list_id, status FROM subscriber_lists + WHERE ($1 = 0 OR list_id = $1) + GROUP BY list_id, status + ) row GROUP BY list_id ) -SELECT ls.*, COALESCE(subscriber_count, 0) AS subscriber_count FROM ls +SELECT ls.*, subscriber_statuses FROM ls LEFT JOIN counts ON (counts.list_id = ls.id) ORDER BY %s %s;