diff --git a/modules/googleanalytics/client.go b/modules/googleanalytics/client.go new file mode 100644 index 00000000..f84b047b --- /dev/null +++ b/modules/googleanalytics/client.go @@ -0,0 +1,93 @@ +package googleanalytics + +import ( + "net/http" + "io/ioutil" + "log" + "fmt" + "time" + + "github.com/wtfutil/wtf/utils" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + ga "google.golang.org/api/analyticsreporting/v4" +) + +type websiteReport struct { + Name string + Report *ga.GetReportsResponse +} + +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) + if err != nil { + log.Fatalf("Unable to create Google Analytics Reporting Service") + } + + visitorsDataArray := getReports(service, widget.settings.viewIds, widget.settings.months) + return visitorsDataArray +} + +func makeReportService(secretPath string) (*ga.Service, error) { + clientSecret, err := ioutil.ReadFile(secretPath) + if err != nil { + log.Fatalf("Unable to read secretPath. %v", err) + } + + jwtConfig, err := google.JWTConfigFromJSON(clientSecret, ga.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) + if err != nil { + log.Fatalf("Failed to create 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 + + for website, viewId := range viewIds { + // For custom queries: https://ga-dev-tools.appspot.com/dimensions-metrics-explorer/ + + req := &ga.GetReportsRequest{ + ReportRequests: []*ga.ReportRequest{ + { + ViewId: viewId.(string), + DateRanges: []*ga.DateRange{ + {StartDate: startDate, EndDate: "today"}, + }, + Metrics: []*ga.Metric{ + {Expression: "ga:sessions"}, + }, + Dimensions: []*ga.Dimension{ + {Name: "ga:month"}, + }, + }, + }, + } + response, err := service.Reports.BatchGet(req).Do() + + if err != nil { + 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,} + websiteReports = append(websiteReports, report) + } + return websiteReports +} diff --git a/modules/googleanalytics/display.go b/modules/googleanalytics/display.go new file mode 100644 index 00000000..43e109ed --- /dev/null +++ b/modules/googleanalytics/display.go @@ -0,0 +1,54 @@ +package googleanalytics + +import ( + "fmt" + "time" + "strings" +) + +func (widget *Widget) createTable(websiteReports []websiteReport) (string) { + content := widget.createHeader() + + for _, websiteReport := range websiteReports { + websiteRow := "" + + for _, report := range websiteReport.Report.Reports { + websiteRow += fmt.Sprintf(" %-20s", websiteReport.Name) + reportRows := report.Data.Rows + noDataMonth := widget.settings.months - len(reportRows) + + // Fill in requested months with no data from query + if noDataMonth > 0 { + websiteRow += strings.Repeat("- ", noDataMonth) + } + + if reportRows == nil { + websiteRow += fmt.Sprintf("No data found for given ViewId.") + } else { + for _, row := range reportRows { + metrics := row.Metrics + + for _, metric := range metrics { + websiteRow += fmt.Sprintf("%-10s", metric.Values[0]) + } + } + } + content += websiteRow + "\n" + } + } + return content +} + +func (widget *Widget) createHeader() (string) { + // Creates the table header of consisting of Months + currentMonth := int(time.Now().Month()) + widgetStartMonth := currentMonth-widget.settings.months+1 + header := " " + + for i := widgetStartMonth; i < currentMonth+1; i++ { + header += fmt.Sprintf("%-10s", time.Month(i)) + } + header += "\n" + + return header +} diff --git a/modules/googleanalytics/settings.go b/modules/googleanalytics/settings.go new file mode 100644 index 00000000..f785800c --- /dev/null +++ b/modules/googleanalytics/settings.go @@ -0,0 +1,29 @@ +package googleanalytics + +import ( + "github.com/olebedev/config" + "github.com/wtfutil/wtf/cfg" +) + +const defaultTitle = "Google Analytics" + +type Settings struct { + common *cfg.Common + + 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{} +} + +func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { + + settings := Settings{ + common: cfg.NewCommonSettingsFromModule(name, defaultTitle, ymlConfig, globalConfig), + + months: ymlConfig.UInt("months"), + secretFile: ymlConfig.UString("secretFile"), + viewIds: ymlConfig.UMap("viewIds"), + } + + return &settings +} diff --git a/modules/googleanalytics/widget.go b/modules/googleanalytics/widget.go new file mode 100644 index 00000000..af808d47 --- /dev/null +++ b/modules/googleanalytics/widget.go @@ -0,0 +1,29 @@ +package googleanalytics + +import ( + "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" +) + +type Widget struct { + wtf.TextWidget + + settings *Settings +} + +func NewWidget(app *tview.Application, settings *Settings) *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(app, settings.common, false), + + settings: settings, + } + + return &widget +} + +func (widget *Widget) Refresh() { + websiteReports := widget.Fetch() + contentTable := widget.createTable(websiteReports) + + widget.Redraw(widget.CommonSettings().Title, contentTable, false) +}