Skip to content

Commit

Permalink
Mailchimp report plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
sparrc committed Dec 2, 2015
1 parent 6fb7d28 commit 5238885
Show file tree
Hide file tree
Showing 5 changed files with 603 additions and 1 deletion.
1 change: 1 addition & 0 deletions plugins/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
_ "github.com/influxdb/telegraf/plugins/kafka_consumer"
_ "github.com/influxdb/telegraf/plugins/leofs"
_ "github.com/influxdb/telegraf/plugins/lustre2"
_ "github.com/influxdb/telegraf/plugins/mailchimp"
_ "github.com/influxdb/telegraf/plugins/memcached"
_ "github.com/influxdb/telegraf/plugins/mongodb"
_ "github.com/influxdb/telegraf/plugins/mysql"
Expand Down
2 changes: 1 addition & 1 deletion plugins/apache/apache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ConnsAsyncClosing: 205
Scoreboard: WW_____W_RW_R_W__RRR____WR_W___WW________W_WW_W_____R__R_WR__WRWR_RRRW___R_RWW__WWWRW__R_RW___RR_RW_R__W__WR_WWW______WWR__R___R_WR_W___RW______RR________________W______R__RR______W________________R____R__________________________RW_W____R_____W_R_________________R____RR__W___R_R____RW______R____W______W_W_R_R______R__R_R__________R____W_______WW____W____RR__W_____W_R_______W__________W___W____________W_______WRR_R_W____W_____R____W_WW_R____RRW__W............................................................................................................................................................................................................................................................................................................WRRWR____WR__RR_R___RWR_________W_R____RWRRR____R_R__RW_R___WWW_RW__WR_RRR____W___R____WW_R__R___RR_W_W_RRRRWR__RRWR__RRW_W_RRRW_R_RR_W__RR_RWRR_R__R___RR_RR______R__RR____R_____W_R_R_R__R__R__________W____WW_R___R_R___R_________RR__RR____RWWWW___W_R________R_R____R_W___W___R___W_WRRWW_______R__W_RW_______R________RR__R________W_______________________W_W______________RW_________WR__R___R__R_______________WR_R_________W___RW_____R____________W____......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
`

func TestHTTPInflux(t *testing.T) {
func TestHTTPApache(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, apacheStatus)
Expand Down
173 changes: 173 additions & 0 deletions plugins/mailchimp/chimp_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package mailchimp

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"regexp"
)

const (
reports_summary_endpoint string = "/3.0/reports"
)

var mailchimp_datacenter = regexp.MustCompile("[a-z]+[0-9]+$")

type ChimpAPI struct {
Transport http.RoundTripper
endpoint string
Debug bool
}

func NewChimp(apiKey string) *ChimpAPI {
u := url.URL{}
u.Scheme = "https"
u.Host = fmt.Sprintf("%s.api.mailchimp.com", mailchimp_datacenter.FindString(apiKey))
u.Path = reports_summary_endpoint
u.User = url.UserPassword("", apiKey)
return &ChimpAPI{endpoint: u.String()}
}

type APIError struct {
Status int `json:"status"`
Type string `json:"type"`
Title string `json:"title"`
Detail string `json:"detail"`
Instance string `json:"instance"`
}

func (e APIError) Error() string {
return fmt.Sprintf("ERROR %v: %v. See %v", e.Status, e.Title, e.Type)
}

func chimpErrorCheck(body []byte) error {
var e APIError
json.Unmarshal(body, &e)
if e.Title != "" || e.Status != 0 {
return e
}
return nil
}

func (a *ChimpAPI) GetReports() (ReportsResponse, error) {
var response ReportsResponse
rawjson, err := runChimp(a)
if err != nil {
return response, err
}

err = json.Unmarshal(rawjson, &response)
if err != nil {
return response, err
}

return response, nil
}

func runChimp(api *ChimpAPI) ([]byte, error) {
if api.Debug {
log.Printf("Request URL: %s", api.endpoint)
}
client := &http.Client{Transport: api.Transport}
resp, err := client.Get(api.endpoint)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if api.Debug {
log.Printf("Response Body:%s", string(body))
}

if err = chimpErrorCheck(body); err != nil {
return nil, err
}
return body, nil
}

type ReportsResponse struct {
Reports []Report `json:"reports"`
TotalItems int `json:"total_items"`
}

type Report struct {
ID string `json:"id"`
CampaignTitle string `json:"campaign_title"`
Type string `json:"type"`
EmailsSent int `json:"emails_sent"`
AbuseReports int `json:"abuse_reports"`
Unsubscribed int `json:"unsubscribed"`
SendTime string `json:"send_time"`

TimeSeries []TimeSerie
Bounces Bounces `json:"bounces"`
Forwards Forwards `json:"forwards"`
Opens Opens `json:"opens"`
Clicks Clicks `json:"clicks"`
FacebookLikes FacebookLikes `json:"facebook_likes"`
IndustryStats IndustryStats `json:"industry_stats"`
ListStats ListStats `json:"list_stats"`
}

type Bounces struct {
HardBounces int `json:"hard_bounces"`
SoftBounces int `json:"soft_bounces"`
SyntaxErrors int `json:"syntax_errors"`
}

type Forwards struct {
ForwardsCount int `json:"forwards_count"`
ForwardsOpens int `json:"forwards_opens"`
}

type Opens struct {
OpensTotal int `json:"opens_total"`
UniqueOpens int `json:"unique_opens"`
OpenRate float64 `json:"open_rate"`
LastOpen string `json:"last_open"`
}

type Clicks struct {
ClicksTotal int `json:"clicks_total"`
UniqueClicks int `json:"unique_clicks"`
UniqueSubscriberClicks int `json:"unique_subscriber_clicks"`
ClickRate float64 `json:"click_rate"`
LastClick string `json:"last_click"`
}

type FacebookLikes struct {
RecipientLikes int `json:"recipient_likes"`
UniqueLikes int `json:"unique_likes"`
FacebookLikes int `json:"facebook_likes"`
}

type IndustryStats struct {
Type string `json:"type"`
OpenRate float64 `json:"open_rate"`
ClickRate float64 `json:"click_rate"`
BounceRate float64 `json:"bounce_rate"`
UnopenRate float64 `json:"unopen_rate"`
UnsubRate float64 `json:"unsub_rate"`
AbuseRate float64 `json:"abuse_rate"`
}

type ListStats struct {
SubRate float64 `json:"sub_rate"`
UnsubRate float64 `json:"unsub_rate"`
OpenRate float64 `json:"open_rate"`
ClickRate float64 `json:"click_rate"`
}

type TimeSerie struct {
TimeStamp string `json:"timestamp"`
EmailsSent int `json:"emails_sent"`
UniqueOpens int `json:"unique_opens"`
RecipientsClick int `json:"recipients_click"`
}
86 changes: 86 additions & 0 deletions plugins/mailchimp/mailchimp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package mailchimp

import (
"time"

"github.com/influxdb/telegraf/plugins"
)

type MailChimp struct {
api *ChimpAPI

ApiKey string
}

var sampleConfig = `
# MailChimp API key
api_key = ""
`

func (m *MailChimp) SampleConfig() string {
return sampleConfig
}

func (m *MailChimp) Description() string {
return "Gathers metrics from the /reports MailChimp API"
}

func (m *MailChimp) Gather(acc plugins.Accumulator) error {
if m.api == nil {
m.api = NewChimp(m.ApiKey)
}
m.api.Debug = false

reports, err := m.api.GetReports()
if err != nil {
return err
}
now := time.Now()

for _, report := range reports.Reports {
tags := make(map[string]string)
tags["id"] = report.ID
tags["campaign_title"] = report.CampaignTitle
acc.Add("emails_sent", report.EmailsSent, tags, now)
acc.Add("abuse_reports", report.AbuseReports, tags, now)
acc.Add("unsubscribed", report.Unsubscribed, tags, now)
acc.Add("hard_bounces", report.Bounces.HardBounces, tags, now)
acc.Add("soft_bounces", report.Bounces.SoftBounces, tags, now)
acc.Add("syntax_errors", report.Bounces.SyntaxErrors, tags, now)
acc.Add("forwards_count", report.Forwards.ForwardsCount, tags, now)
acc.Add("forwards_opens", report.Forwards.ForwardsOpens, tags, now)
acc.Add("opens_total", report.Opens.OpensTotal, tags, now)
acc.Add("unique_opens", report.Opens.UniqueOpens, tags, now)
acc.Add("open_rate", report.Opens.OpenRate, tags, now)
acc.Add("last_open", report.Opens.LastOpen, tags, now)
acc.Add("clicks_total", report.Clicks.ClicksTotal, tags, now)
acc.Add("unique_clicks", report.Clicks.UniqueClicks, tags, now)
acc.Add("unique_subscriber_clicks", report.Clicks.UniqueSubscriberClicks, tags, now)
acc.Add("click_rate", report.Clicks.ClickRate, tags, now)
acc.Add("last_click", report.Clicks.LastClick, tags, now)
acc.Add("facebook_recipient_likes", report.FacebookLikes.RecipientLikes, tags, now)
acc.Add("facebook_unique_likes", report.FacebookLikes.UniqueLikes, tags, now)
acc.Add("facebook_likes", report.FacebookLikes.FacebookLikes, tags, now)
acc.Add("industry_type", report.IndustryStats.Type, tags, now)
acc.Add("industry_open_rate", report.IndustryStats.OpenRate, tags, now)
acc.Add("industry_click_rate", report.IndustryStats.ClickRate, tags, now)
acc.Add("industry_bounce_rate", report.IndustryStats.BounceRate, tags, now)
acc.Add("industry_unopen_rate", report.IndustryStats.UnopenRate, tags, now)
acc.Add("industry_unsub_rate", report.IndustryStats.UnsubRate, tags, now)
acc.Add("industry_abuse_rate", report.IndustryStats.AbuseRate, tags, now)
acc.Add("list_stats_sub_rate", report.ListStats.SubRate, tags, now)
acc.Add("list_stats_unsub_rate", report.ListStats.UnsubRate, tags, now)
acc.Add("list_stats_open_rate", report.ListStats.OpenRate, tags, now)
acc.Add("list_stats_click_rate", report.ListStats.ClickRate, tags, now)

// TODO(cam) parse and add TimeSeries data here too
}

return nil
}

func init() {
plugins.Add("mailchimp", func() plugins.Plugin {
return &MailChimp{}
})
}
Loading

0 comments on commit 5238885

Please sign in to comment.