diff options
author | matthieu <matthieu.cneude@gmail.com> | 2020-04-05 19:16:45 +0200 |
---|---|---|
committer | matthieu <matthieu.cneude@gmail.com> | 2020-04-05 19:16:45 +0200 |
commit | 5c8cd384bed4762c13895f26b23438135e41e369 (patch) | |
tree | 2421f3cf1e1e93e7e2685a56097b2c957a1bc596 | |
parent | 361b27597c49cd557239aacdb4fd47333e6f49af (diff) |
Add ga.bar_devices and improve code of stacked bar
-rw-r--r-- | internal/ga_widget.go | 68 | ||||
-rw-r--r-- | internal/platform/ga.go | 48 | ||||
-rw-r--r-- | internal/platform/ga_test.go | 51 | ||||
-rw-r--r-- | internal/platform/termui.go | 6 | ||||
-rw-r--r-- | internal/platform/testdata/fixtures/ga_bar_devices.json | 173 | ||||
-rw-r--r-- | internal/tui.go | 4 |
6 files changed, 287 insertions, 63 deletions
diff --git a/internal/ga_widget.go b/internal/ga_widget.go index 04dd82c..2c257d8 100644 --- a/internal/ga_widget.go +++ b/internal/ga_widget.go @@ -21,7 +21,8 @@ const ( gaBarReturning = "ga.bar_returning" gaBarNewReturning = "ga.bar_new_returning" gaBarPages = "ga.bar_pages" - gaBarCountry = "ga.bar_country" + gaBarCountries = "ga.bar_countries" + gaBarDevices = "ga.bar_devices" gaTablePages = "ga.table_pages" gaTableTrafficSources = "ga.table_traffic_sources" gaTable = "ga.table" @@ -69,13 +70,15 @@ func (g *gaWidget) CreateWidgets(widget Widget, tui *Tui) (err error) { case gaTableTrafficSources: err = g.trafficSource(widget) case gaBarNewReturning: - err = g.newVsReturning(widget) + err = g.stackedBarNewReturningUsers(widget) + case gaBarDevices: + err = g.stackedBarDevices(widget) case gaBarReturning: err = g.barReturning(widget) case gaBarPages: err = g.barPages(widget) - case gaBarCountry: - err = g.barCountry(widget) + case gaBarCountries: + err = g.barCountries(widget) case gaBarBounces: err = g.barBounces(widget) case gaTable: @@ -209,7 +212,7 @@ func (g *gaWidget) barPages(widget Widget) (err error) { return g.barMetric(widget, platform.XHeaderTime) } -func (g *gaWidget) barCountry(widget Widget) (err error) { +func (g *gaWidget) barCountries(widget Widget) (err error) { if widget.Options == nil { widget.Options = map[string]string{} } @@ -446,7 +449,7 @@ func (g *gaWidget) trafficSource(widget Widget) (err error) { return g.table(widget, "Source") } -func (g *gaWidget) newVsReturning(widget Widget) error { +func (g *gaWidget) stackedBarNewReturningUsers(widget Widget) error { if widget.Options == nil { widget.Options = map[string]string{} } @@ -456,6 +459,16 @@ func (g *gaWidget) newVsReturning(widget Widget) error { return g.stackedBar(widget) } +func (g *gaWidget) stackedBarDevices(widget Widget) error { + if widget.Options == nil { + widget.Options = map[string]string{} + } + + widget.Options[optionDimensions] = "device_category" + + return g.stackedBar(widget) +} + func (g *gaWidget) stackedBar(widget Widget) error { // defaults sd := "7_days_ago" @@ -493,7 +506,7 @@ func (g *gaWidget) stackedBar(widget Widget) error { } // this should return new and ret instead of a unique slice val... - dim, new, ret, err := g.analytics.StackedBar( + dim, val, err := g.analytics.StackedBar( platform.AnalyticValues{ ViewID: g.viewID, StartDate: startDate.Format(gaTimeFormat), @@ -507,30 +520,37 @@ func (g *gaWidget) stackedBar(widget Widget) error { return err } - var data [8][]int - // need to fill data with []int containing 0 - for i := 0; i < 8; i++ { - for j := 0; j < len(ret); j++ { - data[i] = append(data[i], 0) - } - } - data[0] = new - data[1] = ret - - colors := []uint16{blue, green} + // Only support 5 different colors for now + colors := []uint16{blue, green, yellow, red, magenta} if _, ok := widget.Options[optionFirstColor]; ok { colors[0] = colorLookUp[widget.Options[optionFirstColor]] } if _, ok := widget.Options[optionSecondColor]; ok { colors[1] = colorLookUp[widget.Options[optionSecondColor]] } + if _, ok := widget.Options[optionThirdColor]; ok { + colors[2] = colorLookUp[widget.Options[optionThirdColor]] + } + if _, ok := widget.Options[optionFourthColor]; ok { + colors[3] = colorLookUp[widget.Options[optionFourthColor]] + } + if _, ok := widget.Options[optionFifthColor]; ok { + colors[4] = colorLookUp[widget.Options[optionFifthColor]] + } - title := fmt.Sprintf( - " %s: Returning (%s) vs New (%s) ", - strings.Trim(strings.Title(metric), "_"), - colorStr(colors[0]), - colorStr(colors[1]), - ) + var data [8][]int + title := fmt.Sprintf(strings.Trim(strings.Title(metric), "_")) + " - " + count := 0 + for k, v := range val { + // need to fill data with []int containing 0 + for j := 0; j < len(val[k]); j++ { + data[count] = append(data[count], 0) + } + data[count] = v + title += fmt.Sprintf("/ %s (%s) ", k, colorStr(colors[count])) + + count++ + } if _, ok := widget.Options[optionTitle]; ok { title = widget.Options[optionTitle] } diff --git a/internal/platform/ga.go b/internal/platform/ga.go index 20c1ea9..abf01b2 100644 --- a/internal/platform/ga.go +++ b/internal/platform/ga.go @@ -50,10 +50,11 @@ var mappingTimePeriod = map[string][]string{ } var mappingDimensions = map[string]string{ - "page_path": "ga:pagePath", - "traffic_source": "ga:source", - "user_type": "ga:userType", - "country": "ga:country", + "page_path": "ga:pagePath", + "traffic_source": "ga:source", + "user_type": "ga:userType", + "device_category": "ga:deviceCategory", + "country": "ga:country", } var mappingHeader = map[string]string{ @@ -166,7 +167,7 @@ func (c *Analytics) BarMetric(val AnalyticValues) ([]string, []int, error) { formater := formatBar for _, v := range val.Dimensions { - // TODO - looks weird. We should maybe pass the formater in this function? Extract "user_returning" case in new function? + // Special formater - depends on a string returned by API if v == "user_returning" { formater = formatBarReturning } @@ -327,7 +328,7 @@ func (c *Analytics) Table( } // StackedBar returns one dimension set linked with multiple values. -func (c *Analytics) StackedBar(an AnalyticValues) (dim []string, new []int, ret []int, err error) { +func (c *Analytics) StackedBar(an AnalyticValues) (dim []string, values map[string][]int, err error) { d := mapDimensions(an.Dimensions) tm := mapTimePeriod(an.TimePeriod) @@ -359,7 +360,7 @@ func (c *Analytics) StackedBar(an AnalyticValues) (dim []string, new []int, ret resp, err := c.service.Reports.BatchGet(req).Do() if err != nil { - return nil, nil, nil, errors.Wrapf( + return nil, nil, errors.Wrapf( err, "can't get stacked bar data from google analytics with start data %s / end_date %s", an.StartDate, @@ -367,11 +368,12 @@ func (c *Analytics) StackedBar(an AnalyticValues) (dim []string, new []int, ret ) } + // Always date on header x-axis formater := func(dim []string) string { return dim[1] + "-" + dim[2] } - return formatNewReturning(resp.Reports, formater) + return formatStackedBar(resp.Reports, formater) } // formatBar to return one slice of dimension which elements are all linked with the elements of another slice with the values. @@ -466,14 +468,16 @@ func formatTable( return dim, u } -func formatNewReturning( +func formatStackedBar( reps []*ga.Report, dimFormater func(dim []string) string, -) (dim []string, new []int, ret []int, err error) { +) (dim []string, values map[string][]int, err error) { + values = make(map[string][]int) for _, v := range reps { for l := 0; l < len(v.Data.Rows); l++ { - if v.Data.Rows[l].Dimensions[0] == newVisitor { - dim = append(dim, dimFormater(v.Data.Rows[l].Dimensions)) + d := dimFormater(v.Data.Rows[l].Dimensions) + if !inArray(dim, d) { + dim = append(dim, d) } for m := 0; m < len(v.Data.Rows[l].Metrics); m++ { @@ -481,18 +485,24 @@ func formatNewReturning( var vu int64 if vu, err = strconv.ParseInt(value, 0, 0); err != nil { - return nil, nil, nil, err - } - if v.Data.Rows[l].Dimensions[0] == newVisitor { - new = append(new, int(vu)) - } else { - ret = append(ret, int(vu)) + return nil, nil, err } + header := v.Data.Rows[l].Dimensions[0] + values[header] = append(values[header], int(vu)) } } } - return dim, new, ret, nil + return dim, values, nil +} + +func inArray(haystack []string, needle string) bool { + for _, v := range haystack { + if v == needle { + return true + } + } + return false } // The map functions map the properties of the application to the Google Analytics API params. diff --git a/internal/platform/ga_test.go b/internal/platform/ga_test.go index 8b43b4f..0bd5185 100644 --- a/internal/platform/ga_test.go +++ b/internal/platform/ga_test.go @@ -91,11 +91,10 @@ func Test_Format(t *testing.T) { } } -func Test_FormatNewReturning(t *testing.T) { +func Test_FormatStackedBar(t *testing.T) { testCases := []struct { name string - new []int - ret []int + values map[string][]int fixtureFile string expectedDim []string formater func([]string) string @@ -103,15 +102,17 @@ func Test_FormatNewReturning(t *testing.T) { }{ { name: "format new vs returning", - new: []int{ - 11245, - 13966, - 13804, - }, - ret: []int{ - 1386, - 1472, - 1633, + values: map[string][]int{ + newVisitor: { + 11245, + 13966, + 13804, + }, + returningVisitor: { + 1386, + 1472, + 1633, + }, }, expectedDim: []string{ "2019-04", @@ -122,6 +123,22 @@ func Test_FormatNewReturning(t *testing.T) { formater: func(dim []string) string { return dim[1] + "-" + dim[2] }, wantErr: false, }, + { + name: "format bar devices", + values: map[string][]int{ + "desktop": {11309, 10239, 1454}, + "mobile": {6916, 3050, 359}, + "tablet": {210, 92, 16}, + }, + expectedDim: []string{ + "2020-02", + "2020-03", + "2020-04", + }, + fixtureFile: "./testdata/fixtures/ga_bar_devices.json", + formater: func(dim []string) string { return dim[1] + "-" + dim[2] }, + wantErr: false, + }, } for _, tc := range testCases { @@ -133,7 +150,7 @@ func Test_FormatNewReturning(t *testing.T) { t.Error(err) } - dim, new, ret, err := formatNewReturning(r.Reports, tc.formater) + dim, values, err := formatStackedBar(r.Reports, tc.formater) if (err != nil) != tc.wantErr { t.Errorf("Error '%v' even if wantErr is %t", err, tc.wantErr) return @@ -143,12 +160,8 @@ func Test_FormatNewReturning(t *testing.T) { t.Errorf("Expected %v, actual %v", tc.expectedDim, dim) } - if tc.wantErr == false && !reflect.DeepEqual(new, tc.new) { - t.Errorf("Expected %v, actual %v", tc.new, new) - } - - if tc.wantErr == false && !reflect.DeepEqual(ret, tc.ret) { - t.Errorf("Expected %v, actual %v", tc.ret, ret) + if tc.wantErr == false && !reflect.DeepEqual(values, tc.values) { + t.Errorf("Expected %v, actual %v", tc.values, values) } }) } diff --git a/internal/platform/termui.go b/internal/platform/termui.go index c993bd4..bf227ef 100644 --- a/internal/platform/termui.go +++ b/internal/platform/termui.go @@ -153,7 +153,11 @@ func (t *termUI) StackedBarChart( bc.DataLabels = dimensions bc.TextColor = termui.Attribute(fg) bc.BorderFg = termui.Attribute(bd) - bc.BarColor = [8]termui.Attribute{termui.Attribute(colors[0]), termui.Attribute(colors[1])} + bc.BarColor = [8]termui.Attribute{} + + for k, v := range colors { + bc.BarColor[k] = termui.Attribute(v) + } bc.NumColor = [8]termui.Attribute{termui.Attribute(nc), termui.Attribute(nc)} t.widgets = append(t.widgets, bc) diff --git a/internal/platform/testdata/fixtures/ga_bar_devices.json b/internal/platform/testdata/fixtures/ga_bar_devices.json new file mode 100644 index 0000000..cb098ce --- /dev/null +++ b/internal/platform/testdata/fixtures/ga_bar_devices.json @@ -0,0 +1,173 @@ +{ + "reports": [ + { + "columnHeader": { + "dimensions": [ + "ga:deviceCategory", + "ga:year", + "ga:month" + ], + "metricHeader": { + "metricHeaderEntries": [ + { + "name": "ga:sessions", + "type": "INTEGER" + } + ] + } + }, + "data": { + "maximums": [ + { + "values": [ + "11309" + ] + } + ], + "minimums": [ + { + "values": [ + "16" + ] + } + ], + "rowCount": 9, + "rows": [ + { + "dimensions": [ + "desktop", + "2020", + "02" + ], + "metrics": [ + { + "values": [ + "11309" + ] + } + ] + }, + { + "dimensions": [ + "desktop", + "2020", + "03" + ], + "metrics": [ + { + "values": [ + "10239" + ] + } + ] + }, + { + "dimensions": [ + "desktop", + "2020", + "04" + ], + "metrics": [ + { + "values": [ + "1454" + ] + } + ] + }, + { + "dimensions": [ + "mobile", + "2020", + "02" + ], + "metrics": [ + { + "values": [ + "6916" + ] + } + ] + }, + { + "dimensions": [ + "mobile", + "2020", + "03" + ], + "metrics": [ + { + "values": [ + "3050" + ] + } + ] + }, + { + "dimensions": [ + "mobile", + "2020", + "04" + ], + "metrics": [ + { + "values": [ + "359" + ] + } + ] + }, + { + "dimensions": [ + "tablet", + "2020", + "02" + ], + "metrics": [ + { + "values": [ + "210" + ] + } + ] + }, + { + "dimensions": [ + "tablet", + "2020", + "03" + ], + "metrics": [ + { + "values": [ + "92" + ] + } + ] + }, + { + "dimensions": [ + "tablet", + "2020", + "04" + ], + "metrics": [ + { + "values": [ + "16" + ] + } + ] + } + ], + "totals": [ + { + "values": [ + "33645" + ] + } + ] + } + } + ] +} diff --git a/internal/tui.go b/internal/tui.go index db56a6e..a1a3ea4 100644 --- a/internal/tui.go +++ b/internal/tui.go @@ -36,6 +36,10 @@ const ( optionFirstColor = "first_color" optionSecondColor = "second_color" + optionThirdColor = "third_color" + optionFourthColor = "fourth_color" + optionFifthColor = "fifth_color" + optionSixthColor = "sixth_color" optionHeight = "height" |