1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Add support for pulling realtime Google Analytics metrics

* Add config option `enableRealtime` that, if set to true, will cause realtime metrics to be displayed above the historicaly view counts for all view IDs
 * Add in the v3 Google API client and construct a service for it conditionally if realtime metrics are enabled
 * Update google analytics data pulling code to retrieve realtime metrics using the v3 client if realtime metrics are enabled in settings
 * Update table generation code to display fetched realtime metrics if they are available
This commit is contained in:
Casey Primozic 2019-08-30 18:34:31 -07:00
parent c3a54de181
commit d5e06fe0c2
No known key found for this signature in database
GPG Key ID: 2A02222DA3425B99
3 changed files with 118 additions and 47 deletions

View File

@ -1,93 +1,132 @@
package googleanalytics
import (
"net/http"
"fmt"
"io/ioutil"
"log"
"fmt"
"net/http"
"time"
"github.com/wtfutil/wtf/utils"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
ga "google.golang.org/api/analyticsreporting/v4"
gaV3 "google.golang.org/api/analytics/v3"
gaV4 "google.golang.org/api/analyticsreporting/v4"
)
type websiteReport struct {
Name string
Report *ga.GetReportsResponse
Report *gaV4.GetReportsResponse
RealtimeReport *gaV3.RealtimeData
}
func (widget *Widget) Fetch() ([]websiteReport) {
func (widget *Widget) Fetch() []websiteReport {
secretPath, err := utils.ExpandHomeDir(widget.settings.secretFile)
if err != nil {
log.Fatalf("Unable to parse secretFile path")
}
service, err := makeReportService(secretPath)
serviceV4, err := makeReportServiceV4(secretPath)
if err != nil {
log.Fatalf("Unable to create Google Analytics Reporting Service")
log.Fatalf("Unable to create v3 Google Analytics Reporting Service")
}
visitorsDataArray := getReports(service, widget.settings.viewIds, widget.settings.months)
var serviceV3 *gaV3.Service
if widget.settings.enableRealtime {
serviceV3, err = makeReportServiceV3(secretPath)
if err != nil {
log.Fatalf("Unable to create v3 Google Analytics Reporting Service")
}
}
visitorsDataArray := getReports(
serviceV4, widget.settings.viewIds, widget.settings.months, serviceV3,
)
return visitorsDataArray
}
func makeReportService(secretPath string) (*ga.Service, error) {
func buildNetClient(secretPath string) *http.Client {
clientSecret, err := ioutil.ReadFile(secretPath)
if err != nil {
log.Fatalf("Unable to read secretPath. %v", err)
}
jwtConfig, err := google.JWTConfigFromJSON(clientSecret, ga.AnalyticsReadonlyScope)
jwtConfig, err := google.JWTConfigFromJSON(clientSecret, gaV4.AnalyticsReadonlyScope)
if err != nil {
log.Fatalf("Unable to get config from JSON. %v", err)
}
var netClient *http.Client
netClient = jwtConfig.Client(oauth2.NoContext)
svc, err := ga.New(netClient)
return jwtConfig.Client(oauth2.NoContext)
}
func makeReportServiceV3(secretPath string) (*gaV3.Service, error) {
var netClient = buildNetClient(secretPath)
svc, err := gaV3.New(netClient)
if err != nil {
log.Fatalf("Failed to create Google Analytics Reporting Service")
log.Fatalf("Failed to create v3 Google Analytics Reporting Service")
}
return svc, err
}
func getReports(service *ga.Service, viewIds map[string]interface{}, displayedMonths int) ([]websiteReport) {
startDate := fmt.Sprintf("%s-01", time.Now().AddDate(0, -displayedMonths+1, 0).Format("2006-01"))
var websiteReports []websiteReport = nil
func makeReportServiceV4(secretPath string) (*gaV4.Service, error) {
var netClient = buildNetClient(secretPath)
svc, err := gaV4.New(netClient)
if err != nil {
log.Fatalf("Failed to create v4 Google Analytics Reporting Service")
}
for website, viewId := range viewIds {
return svc, err
}
func getReports(
serviceV4 *gaV4.Service, viewIds map[string]interface{}, displayedMonths int, serviceV3 *gaV3.Service,
) []websiteReport {
startDate := fmt.Sprintf("%s-01", time.Now().AddDate(0, -displayedMonths+1, 0).Format("2006-01"))
var websiteReports []websiteReport
for website, viewID := range viewIds {
// For custom queries: https://ga-dev-tools.appspot.com/dimensions-metrics-explorer/
req := &ga.GetReportsRequest{
ReportRequests: []*ga.ReportRequest{
req := &gaV4.GetReportsRequest{
ReportRequests: []*gaV4.ReportRequest{
{
ViewId: viewId.(string),
DateRanges: []*ga.DateRange{
ViewId: viewID.(string),
DateRanges: []*gaV4.DateRange{
{StartDate: startDate, EndDate: "today"},
},
Metrics: []*ga.Metric{
Metrics: []*gaV4.Metric{
{Expression: "ga:sessions"},
},
Dimensions: []*ga.Dimension{
Dimensions: []*gaV4.Dimension{
{Name: "ga:month"},
},
},
},
}
response, err := service.Reports.BatchGet(req).Do()
response, err := serviceV4.Reports.BatchGet(req).Do()
if err != nil {
log.Fatalf("GET request to analyticsreporting/v4 returned error with viewID: %s", viewId)
log.Fatalf("GET request to analyticsreporting/v4 returned error with viewID: %s", viewID)
}
if response.HTTPStatusCode != 200 {
log.Fatalf("Did not get expected HTTP response code")
}
report := websiteReport{Name: website, Report: response,}
report := websiteReport{Name: website, Report: response}
if serviceV3 != nil {
report.RealtimeReport = getLiveCount(serviceV3, viewID.(string))
}
websiteReports = append(websiteReports, report)
}
return websiteReports
}
func getLiveCount(service *gaV3.Service, viewID string) *gaV3.RealtimeData {
res, err := service.Data.Realtime.Get("ga:"+viewID, "rt:activeUsers").Do()
if err != nil {
log.Fatalf("Failed to fetch realtime data for view ID %s: %v", viewID, err)
}
return res
}

View File

@ -2,12 +2,40 @@ package googleanalytics
import (
"fmt"
"time"
"strings"
"time"
)
func (widget *Widget) createTable(websiteReports []websiteReport) (string) {
content := widget.createHeader()
func (widget *Widget) createTable(websiteReports []websiteReport) string {
content := ""
if len(websiteReports) == 0 {
return content
}
if websiteReports[0].RealtimeReport != nil {
content += "Realtime Visitor Counts\n"
for _, websiteReport := range websiteReports {
websiteRow := fmt.Sprintf(" %-20s", websiteReport.Name)
if websiteReport.RealtimeReport == nil {
websiteRow += fmt.Sprintf("No data found for given ViewId.")
} else {
if len(websiteReport.RealtimeReport.Rows) == 0 {
websiteRow += "-"
} else {
websiteRow += fmt.Sprintf("%-10s", websiteReport.RealtimeReport.Rows[0][0])
}
}
content += websiteRow + "\n"
}
content += "\n"
content += "Historical Visitor Counts\n"
}
content += widget.createHeader()
for _, websiteReport := range websiteReports {
websiteRow := ""
@ -33,13 +61,15 @@ func (widget *Widget) createTable(websiteReports []websiteReport) (string) {
}
}
}
content += websiteRow + "\n"
}
}
return content
}
func (widget *Widget) createHeader() (string) {
func (widget *Widget) createHeader() string {
// Creates the table header of consisting of Months
currentMonth := int(time.Now().Month())
widgetStartMonth := currentMonth - widget.settings.months + 1

View File

@ -13,6 +13,7 @@ type Settings struct {
months int
secretFile string `help:"Your Google client secret JSON file." values:"A string representing a file path to the JSON secret file."`
viewIds map[string]interface{}
enableRealtime bool
}
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
@ -23,6 +24,7 @@ func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *co
months: ymlConfig.UInt("months"),
secretFile: ymlConfig.UString("secretFile"),
viewIds: ymlConfig.UMap("viewIds"),
enableRealtime: ymlConfig.UBool("enableRealtime", false),
}
return &settings