Skip to content

Instantly share code, notes, and snippets.

@nightlyone
Created October 19, 2013 00:04
Show Gist options
  • Save nightlyone/7050070 to your computer and use it in GitHub Desktop.
Save nightlyone/7050070 to your computer and use it in GitHub Desktop.
package main
// overview of crqs.go:
// this example app and supporting documentation enables you to query analytics data via the Core Reporting API in a completely headless manner, with
// no "popup" or user intervention required. it doesn't "handle" the returned data very well, as that will depend on what metrics/dimensions your
// query has. better handling of this is left as an exercise for the reader. :) follow the pre-reqs, change the ga* consts specific to your own app,
// and you should be good to go. uses google-api-go-client, analytics/v3, oauth, and oauth/jwt which you can download and install from
// https://code.google.com/p/goauth2/ and http://code.google.com/p/google-api-go-client/
// docs/pre-reqs:
// 1. create a Project within the Google Cloud Console (https://cloud.google.com/console#/project)
// 2. within that Project, ensure you've turned on the Analytics API (APIs & Auth --> Analytics --> On)
// 3. within that Project, create an application of type "Web Application" (APIs & Auth --> Registered Apps)
// 4. click on the application name. expand the "Certificate" section. you will see that the email/public keys
// have not been generated. click "Generate". download the private key (.p12 file). also, note the private key password, and
// the email address, you will need these later. you will set the string const gaServiceAcctEmail to this email address.
// 5. download the JSON file, too. you will set the const gaServiceAcctSecretsFile to the location of this file.
// 6. now we need to convert the .p12 key to a PEM key. in a unix shell:
// $ openssl pkcs12 -in privatekeyfilename.p12 -nodes -nocerts > privatekeyfilename.pem
// it will ask you for the Import password. enter the private key password you were given in step 4. you should see
// "MAC verified OK", and you will have the PEM key. you will set the const gaServiceAcctPEMKey to the location of this file.
// 7. goto Google Analytics. in Admin section specific to the GA account/property you wish to query, give Read & Analyze permissions to the
// email address you were given in step 4.
// 8. now we need the Table ID of the GA account/property mentioned in step 7. login to the Google Analytics Query Explorer 2 at
// http://ga-dev-tools.appspot.com/explorer/ then select the appropriate account/property/profile in the dropdown menus. you will see
// the Table ID in the "*ids" box. it looks like "ga:1234556". you will set the string const gaTableID to this value.
// 9. that should be all you need to do! compile and "go".
// example runs:
// 1. shows total pageview count for all URLs (starting at default date october 1st through today).
// $ ./crqs
// gaStartDate=2013-10-01, gaEndDate=2013-10-18
// gaMetrics=ga:pageviews
// gaFilter=ga:pagePath=~^/
// len(gaData.Rows)=1, TotalResults=1
// row=0 [25020179]
//
// 2. shows unique pageview count per URL for top 5 URLs starting in "/movies" (starting at default date october 1st through today),
// in descending order.
// $ ./crqs -m=ga:uniquePageviews -d=ga:pagePath -s=-ga:uniquePageviews -f=ga:pagePath=~^/movies -x=5
// gaStartDate=2013-10-01, gaEndDate=2013-10-18
// gaMetrics=ga:uniquePageviews
// gaDimensions=ga:dimensions=ga:pagePath
// gaSortOrder=ga:sort=-ga:uniquePageviews
// gaFilter=ga:pagePath=~^/movie
// len(gaData.Rows)=5, TotalResults=10553
// row=0 [/movies/foo 1005697]
// row=1 [/movies/bar 372121]
// row=2 [/movies/baz 312901]
// row=3 [/movies/qux 248761]
// row=4 [/movies/xyzzy 227286]
//
// 3. shows unique pageview count per URL for top 5 URLs from Aug 1 --> Sep 1, in descending order.
// $ ./crqs -sd=2013-08-01 -ed=2013-09-01 -m=ga:uniquePageviews -d=ga:pagePath -s=-ga:uniquePageviews -x=5
// gaStartDate=2013-08-01, gaEndDate=2013-09-01
// gaMetrics=ga:uniquePageviews
// gaDimensions=ga:dimensions=ga:pagePath
// gaSortOrder=ga:sort=-ga:uniquePageviews
// len(gaData.Rows)=5, TotalResults=159023
// row=0 [/ 4280168]
// row=1 [/foo 2504679]
// row=2 [/bar 1177822]
// row=3 [/baz 755705]
// row=4 [/xyzzy 739513]
import (
"code.google.com/p/goauth2/oauth"
"code.google.com/p/goauth2/oauth/jwt"
"code.google.com/p/google-api-go-client/analytics/v3"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
)
// constants
const (
// don't change these
dateLayout string = "2006-01-02" // date format that Core Reporting API requires
// change these! add in your own app and analytics-property-specific values
gaServiceAcctEmail string = "your-applications-email-address@developer.gserviceaccount.com" // email address of registered application
gaServiceAcctSecretsFile string = "/path/to/client_secret_your-application.apps.googleusercontent.com.json" // registered application JSON file downloaded from Google Cloud Console
gaServiceAcctPEMKey string = "/path/to/your-applications-converted-privatekey.pem" // private key (PEM format) of registered application
gaScope string = "https://www.googleapis.com/auth/analytics.readonly" // oauth 2 scope information for Core Reporting API
gaTableID string = "ga:12345678" // namespaced profile ID of the analytics account/property/profile from which to request data
)
// globals
var (
// vars for the runtime flags
gaDimensions string
gaEndDate string
gaFilter string
gaMetrics string
gaMaxResults int64
gaSortOrder string
help bool
gaStartDate string
)
// types
// struct to read the registered application's JSON secretsfile into
type GAServiceAcctSecretsConfig struct {
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURIs []string `json:"redirect_uris"`
Scope string
AuthURI string `json:"auth_uri"`
TokenURI string `json:"token_uri"`
}
// func: init()
// initialisation function for the command line flags/options.
func init() {
flag.BoolVar(&help, "h", false, "Show all command line arguments.")
flag.StringVar(&gaDimensions, "d", "", "GA query dimensions")
flag.StringVar(&gaEndDate, "ed", "", "GA query end date")
flag.StringVar(&gaFilter, "f", "", "GA filter to apply")
flag.Int64Var(&gaMaxResults, "x", 10000, "GA maximum # of results to return (default: 10000)")
flag.StringVar(&gaMetrics, "m", "ga:pageviews", "GA metrics you want to query (default: ga:pageviews)")
flag.StringVar(&gaSortOrder, "s", "", "GA query reply sort order")
flag.StringVar(&gaStartDate, "sd", "2013-10-01", "GA query start date (default: 2013-10-01)")
}
// func: readConfig()
// reads in the registered application's JSON secretsfile and crafts an oauth.Config which
// it returns to the calling func. exits hard if either the JSON secretsfile load fails,
// or if the unmarshal fails.
func readGAServiceAcctSecretsConfig() *oauth.Config {
configfile := new(GAServiceAcctSecretsConfig)
data, err := ioutil.ReadFile(gaServiceAcctSecretsFile)
if err != nil {
log.Fatal("error reading GA Service Account secret JSON file -", err)
}
err = json.Unmarshal(data, &configfile)
if err != nil {
log.Fatal("error unmarshalling GA Service Account secret JSON file -", err)
}
return &oauth.Config{
ClientId: configfile.ClientId,
ClientSecret: configfile.ClientSecret,
Scope: gaScope,
AuthURL: configfile.AuthURI,
TokenURL: configfile.TokenURI,
}
} //
// func: main()
// the main function. duh.
func main() {
// grok the command line args
flag.Usage = usage
flag.Parse()
if help {
flag.Usage()
}
// read in the registered application's JSON secretsfile and get an oauth.Config in return.
oauthConfig := readGAServiceAcctSecretsConfig()
// read in the registered applications private PEM key.
pemKey, err := ioutil.ReadFile(gaServiceAcctPEMKey)
if err != nil {
log.Fatal("error reading GA Service Account PEM key -", err)
}
// create a JWT (JSON Web Token).
jsonWebToken := jwt.NewToken(gaServiceAcctEmail, oauthConfig.Scope, pemKey)
// form the JWT claim set.
jsonWebToken.ClaimSet.Aud = oauthConfig.TokenURL
// create a basic httpclient. we will use this to send the JWT.
httpClient := &http.Client{}
// build the request, encode it, and then send the JWT. we get a nifty oauth.Token in return.
oauthToken, err := jsonWebToken.Assert(httpClient)
if err != nil {
log.Fatal("error asserting JSON Web Token -", err)
}
// build the oauth transport
oauthTransport := oauth.Transport{Config: oauthConfig}
// set the oauthTransport's token to be the oauthToken
oauthTransport.Token = oauthToken
// create a new analytics service by passing in the httpclient returned from Client() that can now make oauth-authenticated requests
analyticsService, err := analytics.New(oauthTransport.Client())
if err != nil {
log.Fatal("error creating Analytics Service -", err)
}
// create a new analytics data service by passing in the analytics service we just created
dataGaService := analytics.NewDataGaService(analyticsService)
// w00t! now we're talking to the core reporting API. hard stuff over, let's do a simple query.
// if no gaEndDate specified via command line args, set it to today's date.
if gaEndDate == "" {
t := time.Now()
gaEndDate = t.Format(dateLayout)
}
// print the query start & end dates.
fmt.Printf("gaStartDate=%s, gaEndDate=%s\n", gaStartDate, gaEndDate)
fmt.Printf("gaMetrics=%s\n", gaMetrics)
// setup the query
dataGaGetCall := dataGaService.Get(gaTableID, gaStartDate, gaEndDate, gaMetrics)
// setup the dimensions (if any).
if gaDimensions != "" {
dimensions := fmt.Sprintf("ga:dimensions=%s", gaDimensions)
fmt.Printf("gaDimensions=%s\n", dimensions)
dataGaGetCall.Dimensions(gaDimensions)
}
// setup the sort order (if any).
if gaSortOrder != "" {
sortorder := fmt.Sprintf("ga:sort=%s", gaSortOrder)
fmt.Printf("gaSortOrder=%s\n", sortorder)
dataGaGetCall.Sort(gaSortOrder)
}
// setup the filter (if any).
if gaFilter != "" {
filter := fmt.Sprintf("%s", gaFilter)
fmt.Printf("gaFilter=%s\n", filter)
dataGaGetCall.Filters(filter)
}
// setup the max results we want returned.
dataGaGetCall.MaxResults(gaMaxResults)
// send the query to the API, get gaData back.
gaData, err := dataGaGetCall.Do()
if err != nil {
log.Fatal("API error -", err)
}
// how much data did we get back?
fmt.Printf("len(gaData.Rows)=%d, TotalResults=%d\n", len(gaData.Rows), gaData.TotalResults)
// a lazy loop through the returned rows
for row := 0; row <= len(gaData.Rows)-1; row++ {
fmt.Printf("row=%d %v\n", row, gaData.Rows[row])
}
}
// func: usage()
// prints out all possible flags/options when "-h" or an unknown option is used at runtime.
// exits back to shell when complete.
func usage() {
fmt.Printf("usage: %s [OPTION] \n", os.Args[0])
flag.PrintDefaults()
os.Exit(2)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment