Created
January 7, 2014 17:49
-
-
Save imjasonh/8303394 to your computer and use it in GitHub Desktop.
Simple client for the Google Analytics Measurement Protocol (https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters), to report events to Google Analytics. Also a half-baked example of using reflection and struct tags to generate URL query parameters.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters | |
// | |
// Usage: | |
// req, _ := analytics.Request{ | |
// HitType: analytics.PageView, | |
// TrackingID: "UA-XXXX-Y", | |
// ClientID: "35009a79-1a05-49d7-b876-2b884d0f825b", | |
// // ... | |
// }.Request() | |
// resp, _ := req.Do() | |
package analytics | |
import ( | |
"fmt" | |
"net/http" | |
"net/url" | |
"reflect" | |
"time" | |
) | |
var required = []string{"t", "tid", "cid"} | |
type HitType string | |
const ( | |
PageView HitType = "pageview" | |
AppView HitType = "appview" | |
Event HitType = "event" | |
Transaction HitType = "transaction" | |
Item HitType = "item" | |
Social HitType = "social" | |
Exception HitType = "exception" | |
Timing HitType = "timing" | |
) | |
type SessionEvent string | |
const ( | |
Start SessionEvent = "start" | |
End SessionEvent = "end" | |
) | |
type Request struct { | |
// TODO: Custom dimensions and metrics | |
Version string `url:"v"` | |
TrackingID string `url:"tid"` | |
ClientID string `url:"cid"` | |
HitType HitType `url:"t"` | |
AnonymizeIP bool `url:"aip"` | |
QueueTimeMilliseconds time.Duration `url:"qt"` | |
CacheBuster string `url:"z"` | |
SessionControl SessionEvent `url:"sc"` | |
DocumentReferrer string `url:"dr"` | |
CampaignName string `url:"cn"` | |
CampaignSource string `url:"cs"` | |
CampaignMedium string `url:"cm"` | |
CampaignKeyword string `url:"ck"` | |
CampaignContent string `url:"cc"` | |
CampaignID string `url:"ci"` | |
GoogleAdwordsID string `url:"gclid"` | |
GoogleDisplayAdsID string `url:"gclid"` | |
ScreenResolution string `url:"sc"` | |
ViewportSize string `url:"vp"` | |
DocumentEncoding string `url:"de"` | |
ScreenColors string `url:"sd"` | |
UserLanguage string `url:"ul"` | |
JavaEnabled bool `url:"je"` | |
FlashVersion string `url:"fl"` | |
NonInteractionHit bool `url:"ni"` | |
DocumentLocation string `url:"dl"` | |
DocumentHost string `url:"dh"` | |
DocumentPath string `url:"dp"` | |
DocumentTitle string `url:"dt"` | |
ContentDescription string `url:"cd"` | |
LinkID string `url:"linkid"` | |
ApplicationName string `url:"an"` | |
ApplicationVersion string `url:"av"` | |
EventCategory string `url:"ec"` | |
EventAction string `url:"ea"` | |
EventLabel string `url:"el"` | |
EventValue int `url:"ev"` | |
TransactionID string `url:"ti"` | |
TransactionAffiliation string `url:"ta"` | |
TransactionRevenue float64 `url:"tr"` | |
TransactionShipping float64 `url:"ts"` | |
TransactionTax float64 `url:"tt"` | |
ItemName string `url:"in"` | |
ItemPrice float64 `url:"ip"` | |
ItemQuantity int `url:"iq"` | |
ItemCode string `url:"ic"` | |
ItemCategory string `url:"iv"` | |
CurrencyCode string `url:"cc"` | |
SocialNetwork string `url:"sn"` | |
SocialAction string `url:"sa"` | |
SocialActionTarget string `url:"st"` | |
UserTimingCategory string `url:"utc"` | |
UserTimingVariable string `url:"utv"` | |
UserTimingTimeMilliseconds time.Duration `url:"utt"` | |
UserTimingLabel string `url:"utl"` | |
PageLoadTimeMilliseconds time.Duration `url:"ptl"` | |
DNSTimeMilliseconds time.Duration `url:"dns"` | |
PageDownloadTimeMilliseconds time.Duration `url:"pdt"` | |
RedirectResponseTimeMilliseconds time.Duration `url:"rrt"` | |
TCPConnectTimeMilliseconds time.Duration `url:"tcp"` | |
ServerResponseTimeMilliseconds time.Duration `url:"srt"` | |
ExceptionDescription string `url:"exd"` | |
ExceptionFatal bool `url:"exf"` | |
ExperimentID string `url:"xid"` | |
ExperimentVariant string `url:"xvar"` | |
} | |
func NewRequest(tid, cid string, t HitType) Request { | |
return Request{ | |
TrackingID: tid, | |
ClientID: cid, | |
HitType: t, | |
} | |
} | |
func (r Request) encode() url.Values { | |
vals := url.Values{} | |
rt := reflect.TypeOf(r) | |
rv := reflect.ValueOf(r) | |
for i := 0; i < rt.NumField(); i++ { | |
f := rt.Field(i) | |
fv := rv.FieldByName(f.Name) | |
sv := "" | |
switch f.Type.Kind() { | |
case reflect.Bool: | |
if fv.Bool() { | |
sv = "1" | |
} | |
case reflect.Int: | |
fi := fv.Int() | |
if fi == 0 { | |
continue | |
} | |
sv = string(fi) | |
case reflect.Float64: | |
ff := fv.Float() | |
if ff == 0 { | |
continue | |
} | |
sv = fmt.Sprintf("%d", ff) | |
case reflect.Int64: | |
// Fields of type int64 are probably time.Durations, and we want the millisecond value | |
fi := fv.Int() | |
if fi == 0 { | |
continue | |
} | |
sv = string(fv.Int() / 1000) | |
default: | |
sv = fv.String() | |
} | |
if sv != "" { | |
vals.Set(f.Tag.Get("url"), sv) | |
} | |
} | |
return vals | |
} | |
func (r Request) Validate() error { | |
v := r.encode() | |
if v.Get("v") == "" { | |
v.Set("v", "1") | |
} | |
for _, req := range required { | |
if v.Get(req) == "" { | |
return fmt.Errorf("%s is required\n", req) | |
} | |
} | |
return nil | |
} | |
func (r Request) Request() (*http.Request, error) { | |
u := url.URL{ | |
Scheme: "https", | |
Host: "www.google-analytics.com", | |
Path: "collect", | |
RawQuery: r.encode().Encode(), | |
} | |
req, err := http.NewRequest("POST", u.String(), nil) | |
if err != nil { | |
return nil, err | |
} | |
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | |
return req, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment