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

Merge pull request #582 from Ameobea/google-analytics-realtime

Add support for pulling realtime Google Analytics data
This commit is contained in:
Chris Cummer 2019-09-02 16:18:08 -07:00 committed by GitHub
commit 2bd7c43006
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 47 deletions

View File

@ -1,93 +1,132 @@
package googleanalytics package googleanalytics
import ( import (
"net/http" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"fmt" "net/http"
"time" "time"
"github.com/wtfutil/wtf/utils" "github.com/wtfutil/wtf/utils"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" "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 { type websiteReport struct {
Name string 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) secretPath, err := utils.ExpandHomeDir(widget.settings.secretFile)
if err != nil { if err != nil {
log.Fatalf("Unable to parse secretFile path") log.Fatalf("Unable to parse secretFile path")
} }
service, err := makeReportService(secretPath) serviceV4, err := makeReportServiceV4(secretPath)
if err != nil { 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 return visitorsDataArray
} }
func makeReportService(secretPath string) (*ga.Service, error) { func buildNetClient(secretPath string) *http.Client {
clientSecret, err := ioutil.ReadFile(secretPath) clientSecret, err := ioutil.ReadFile(secretPath)
if err != nil { if err != nil {
log.Fatalf("Unable to read secretPath. %v", err) 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 { if err != nil {
log.Fatalf("Unable to get config from JSON. %v", err) log.Fatalf("Unable to get config from JSON. %v", err)
} }
var netClient *http.Client return jwtConfig.Client(oauth2.NoContext)
netClient = jwtConfig.Client(oauth2.NoContext) }
svc, err := ga.New(netClient)
func makeReportServiceV3(secretPath string) (*gaV3.Service, error) {
var netClient = buildNetClient(secretPath)
svc, err := gaV3.New(netClient)
if err != nil { 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 return svc, err
} }
func getReports(service *ga.Service, viewIds map[string]interface{}, displayedMonths int) ([]websiteReport) { func makeReportServiceV4(secretPath string) (*gaV4.Service, error) {
startDate := fmt.Sprintf("%s-01", time.Now().AddDate(0, -displayedMonths+1, 0).Format("2006-01")) var netClient = buildNetClient(secretPath)
var websiteReports []websiteReport = nil 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/ // For custom queries: https://ga-dev-tools.appspot.com/dimensions-metrics-explorer/
req := &ga.GetReportsRequest{ req := &gaV4.GetReportsRequest{
ReportRequests: []*ga.ReportRequest{ ReportRequests: []*gaV4.ReportRequest{
{ {
ViewId: viewId.(string), ViewId: viewID.(string),
DateRanges: []*ga.DateRange{ DateRanges: []*gaV4.DateRange{
{StartDate: startDate, EndDate: "today"}, {StartDate: startDate, EndDate: "today"},
}, },
Metrics: []*ga.Metric{ Metrics: []*gaV4.Metric{
{Expression: "ga:sessions"}, {Expression: "ga:sessions"},
}, },
Dimensions: []*ga.Dimension{ Dimensions: []*gaV4.Dimension{
{Name: "ga:month"}, {Name: "ga:month"},
}, },
}, },
}, },
} }
response, err := service.Reports.BatchGet(req).Do() response, err := serviceV4.Reports.BatchGet(req).Do()
if err != nil { 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 { if response.HTTPStatusCode != 200 {
log.Fatalf("Did not get expected HTTP response code") 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) websiteReports = append(websiteReports, report)
} }
return websiteReports 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 real time data for view ID %s: %v. Have you enrolled in the real time beta? If not, do so here: https://docs.google.com/forms/d/1qfRFysCikpgCMGqgF3yXdUyQW4xAlLyjKuOoOEFN2Uw/viewform", viewID, err)
}
return res
}

View File

@ -2,12 +2,40 @@ package googleanalytics
import ( import (
"fmt" "fmt"
"time"
"strings" "strings"
"time"
) )
func (widget *Widget) createTable(websiteReports []websiteReport) (string) { func (widget *Widget) createTable(websiteReports []websiteReport) string {
content := widget.createHeader() 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 { for _, websiteReport := range websiteReports {
websiteRow := "" websiteRow := ""
@ -33,16 +61,18 @@ func (widget *Widget) createTable(websiteReports []websiteReport) (string) {
} }
} }
} }
content += websiteRow + "\n" content += websiteRow + "\n"
} }
} }
return content return content
} }
func (widget *Widget) createHeader() (string) { func (widget *Widget) createHeader() string {
// Creates the table header of consisting of Months // Creates the table header of consisting of Months
currentMonth := int(time.Now().Month()) currentMonth := int(time.Now().Month())
widgetStartMonth := currentMonth-widget.settings.months+1 widgetStartMonth := currentMonth - widget.settings.months + 1
header := " " header := " "
for i := widgetStartMonth; i < currentMonth+1; i++ { for i := widgetStartMonth; i < currentMonth+1; i++ {

View File

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