Created
October 21, 2013 16:06
-
-
Save reedobrien/7086414 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
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 = "[email protected]" // 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