Skip to content

Commit

Permalink
Feat null data to zero (#40)
Browse files Browse the repository at this point in the history
* Add padding frame before, after data frame

* fix typo


.

* Update change log and img

* Update plugin version 0.1.1 -> 0.1.2
  • Loading branch information
lcc3108 authored Aug 9, 2021
1 parent 6af382a commit 478988d
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 39 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog
## 0.1.2
- seperate time dimension and other dimensions
- fix bug multi dimensions breaking graphs

## 0.1.1
- enhancement query editor ui
- add timezone label at query editor
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
module github.com/grafana/simple-datasource-backend

go 1.14
go 1.16

require (
github.com/grafana/grafana-plugin-sdk-go v0.88.0
github.com/jinzhu/copier v0.3.2
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w=
github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "blackcowmoo-googleanalytics-datasource",
"version": "0.1.1",
"version": "0.1.2",
"description": " Google-Analytics to Grafana",
"scripts": {
"build": "rm -rf node_modules/@grafana/data/node_modules; grafana-toolkit plugin:build",
Expand Down
158 changes: 123 additions & 35 deletions pkg/grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package main

import (
"fmt"
"sort"
"strconv"
"strings"
"time"

"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/jinzhu/copier"
reporting "google.golang.org/api/analyticsreporting/v4"
)

func transformReportToDataFrameByDimensions(columns []*ColumnDefinition, report *reporting.Report, refId string, dimensions string) (*data.Frame, error) {
func transformReportToDataFrameByDimensions(columns []*ColumnDefinition, rows []*reporting.ReportRow, refId string, dimensions string) (*data.Frame, error) {
warnings := []string{}
meta := map[string]interface{}{}

Expand All @@ -24,7 +26,7 @@ func transformReportToDataFrameByDimensions(columns []*ColumnDefinition, report
converters[i] = fc
}

inputConverter, err := data.NewFrameInputConverter(converters, len(report.Data.Rows))
inputConverter, err := data.NewFrameInputConverter(converters, len(rows))
if err != nil {
return nil, err
}
Expand All @@ -41,23 +43,26 @@ func transformReportToDataFrameByDimensions(columns []*ColumnDefinition, report
field.Name = column.Header
displayName := dimensions
if len(dimensions) > 0 {
displayName = displayName + ":"
displayName = displayName + "|"
}
field.Config = &data.FieldConfig{
DisplayName: displayName + column.Header,
// Unit: column.GetUnit(),
}
}

for rowIndex, row := range report.Data.Rows {
i := 0
for rowIndex, row := range rows {
if dimensions == strings.Join(row.Dimensions, "|") {
for _, metrics := range row.Metrics {
// d := row.Dimensions[dateIndex]
for valueIndex, value := range metrics.Values {
err := inputConverter.Set(valueIndex, rowIndex, value)
if err != nil {
log.DefaultLogger.Error("frame convert", "err", err.Error())
warnings = append(warnings, err.Error())
continue
}
i++
}
}
}
Expand All @@ -72,46 +77,79 @@ func transformReportToDataFrameByDimensions(columns []*ColumnDefinition, report
var timeDimensions []string = []string{"ga:dateHourMinute", "ga:dateHour", "ga:date"}

func transformReportToDataFrames(report *reporting.Report, refId string, timezone string) ([]*data.Frame, error) {

report.ColumnHeader.MetricHeader.MetricHeaderEntries = append(report.ColumnHeader.MetricHeader.MetricHeaderEntries, &reporting.MetricHeaderEntry{
timeDimension := reporting.MetricHeaderEntry{
Name: report.ColumnHeader.Dimensions[0],
Type: "TIME",
})
}
report.ColumnHeader.MetricHeader.MetricHeaderEntries = append([]*reporting.MetricHeaderEntry{
&timeDimension,
}, report.ColumnHeader.MetricHeader.MetricHeaderEntries...)

timeAddFunction, timeSubFunction := getTimeFunction(timeDimension.Name)

tz, err := time.LoadLocation(timezone)
if err != nil {
log.DefaultLogger.Info("LoadTimezone err", "err", err.Error())
}
tz, err := time.LoadLocation(timezone)
if err != nil {
log.DefaultLogger.Info("LoadTimezone err", "err", err.Error())
}

dimensions := map[string]struct{}{}
var parsedReportMap = make(map[string]map[int64]*reporting.ReportRow)

var dimensions []string = []string{}
for _, row := range report.Data.Rows {
timeDimension := row.Dimensions[0]
parsedTime, err := ParseAndTimezoneTime(timeDimension, tz)
if err != nil {
log.DefaultLogger.Info("parsedTime err", "err", err.Error())
}
sTime := parsedTime.Format(time.RFC3339)
row.Metrics[0].Values = append(row.Metrics[0].Values, sTime)
row.Dimensions = row.Dimensions[1:]
find := false
for _, dimension := range dimensions {
if strings.Join(row.Dimensions, "|") == dimension {
find = true
break
}
parsedRow, parsedTime := parseRow(row, tz)
dimension := strings.Join(parsedRow.Dimensions, "|")
if _, ok := dimensions[dimension]; !ok {
dimensions[dimension] = struct{}{}
}

if !find {
dimensions = append(dimensions, strings.Join(row.Dimensions, "|"))
if inner, ok := parsedReportMap[dimension]; !ok {
inner = make(map[int64]*reporting.ReportRow)
parsedReportMap[dimension] = inner
}
parsedReportMap[dimension][parsedTime.Unix()] = parsedRow

beforeTime := timeSubFunction(*parsedTime)
afterTime := timeAddFunction(*parsedTime)
if _, ok := parsedReportMap[dimension][beforeTime.Unix()]; !ok {
copyRow := copyRowAndInit(row)
copyRow.Metrics[0].Values[0] = beforeTime.Format(time.RFC3339)
parsedReportMap[dimension][beforeTime.Unix()] = copyRow
}
if _, ok := parsedReportMap[dimension][afterTime.Unix()]; !ok {
copyRow := copyRowAndInit(row)
copyRow.Metrics[0].Values[0] = afterTime.Format(time.RFC3339)
parsedReportMap[dimension][afterTime.Unix()] = copyRow
}
}

var frames = make([]*data.Frame, 0)
var dimensionKeys = make([]string, len(dimensions))
i := 0
for value, _ := range dimensions {
dimensionKeys[i] = value
i++
}

var frames = make([]*data.Frame, 0, len(dimensionKeys))
columns := getColumnDefinitions(report.ColumnHeader)

for _, dimension := range dimensions {
frame, err := transformReportToDataFrameByDimensions(columns, report, refId, dimension)
for _, dimension := range dimensionKeys {
keys := make([]int64, len(parsedReportMap[dimension]))
i := 0
for k := range parsedReportMap[dimension] {
keys[i] = k
i++
}

sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
var parsedRows = make([]*reporting.ReportRow, len(parsedReportMap[dimension]))
i = 0
for _, t := range keys {
parsedRows[i] = parsedReportMap[dimension][t]
i++
}

frame, err := transformReportToDataFrameByDimensions(columns, parsedRows, refId, dimension)
if err != nil {
log.DefaultLogger.Error("transformReportToDataFrameByDimensions", "err", err.Error())
return nil, err
}

Expand Down Expand Up @@ -143,11 +181,11 @@ func padRightSide(str string, item string, count int) string {
var timeConverter = data.FieldConverter{
OutputFieldType: data.FieldTypeNullableTime,
Converter: func(i interface{}) (interface{}, error) {
sTime, ok := i.(string)
strTime, ok := i.(string)
if !ok {
return nil, fmt.Errorf("expected type string, but got %T", i)
}
time, err := time.Parse(time.RFC3339, sTime)
time, err := time.Parse(time.RFC3339, strTime)
if err != nil {
log.DefaultLogger.Info("timeConverter", "err", err)
return nil, err
Expand Down Expand Up @@ -206,3 +244,53 @@ func getColumnDefinitions(header *reporting.ColumnHeader) []*ColumnDefinition {

return columns
}

func copyRow(row *reporting.ReportRow) *reporting.ReportRow {
var copyRow reporting.ReportRow
copier.CopyWithOption(&copyRow.Dimensions, row.Dimensions, copier.Option{DeepCopy: true})
copier.CopyWithOption(&copyRow.Metrics, row.Metrics, copier.Option{DeepCopy: true})
return &copyRow
}

func copyRowAndInit(row *reporting.ReportRow) *reporting.ReportRow {
copyRow := copyRow(row)
copyRow.Metrics[0].Values = FillArray(make([]string, len(row.Metrics[0].Values)), "0")
return copyRow
}

func parseRow(row *reporting.ReportRow, timezone *time.Location) (*reporting.ReportRow, *time.Time) {
timeDimension := row.Dimensions[0]
otherDimensions := row.Dimensions[1:]
parsedTime, err := ParseAndTimezoneTime(timeDimension, timezone)
if err != nil {
log.DefaultLogger.Info("parsedTime err", "err", err.Error())
}
strTime := parsedTime.Format(time.RFC3339)

// row.Metrics[0].Values = append(row.Metrics[0].Values, strTime)
row.Metrics[0].Values = append([]string{strTime}, row.Metrics[0].Values...)
row.Dimensions = otherDimensions
return row, parsedTime
}

func getTimeFunction(timeDimension string) (func(time.Time) time.Time, func(time.Time) time.Time) {
var add, sub func(time.Time) time.Time
switch timeDimension {
case timeDimensions[0]:
add = AddOneMinute
sub = SubOneMinute
break
case timeDimensions[1]:
add = AddOneHour
sub = SubOneHour
break
case timeDimensions[2]:
add = AddOneDay
sub = SubOneDay
break
default:
add = AddOneHour
sub = SubOneHour
}
return add, sub
}
40 changes: 40 additions & 0 deletions pkg/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,43 @@ func ParseAndTimezoneTime(sTime string, timezone *time.Location) (*time.Time, er
}
return &time, nil
}

func addTime(t1 time.Time, t2 time.Duration) time.Time {
tmp := time.Time(t1)
return tmp.Add(t2)
}

func subTime(t1 time.Time, t2 time.Duration) time.Time {
return addTime(t1, t2*-1)
}

func SubOneHour(t1 time.Time) time.Time {
return subTime(t1, time.Hour)
}

func SubOneDay(t1 time.Time) time.Time {
return subTime(t1, time.Hour*24)
}

func SubOneMinute(t1 time.Time) time.Time {
return subTime(t1, time.Minute)
}

func AddOneHour(t1 time.Time) time.Time {
return addTime(t1, time.Hour)
}

func AddOneDay(t1 time.Time) time.Time {
return addTime(t1, time.Hour*24)
}

func AddOneMinute(t1 time.Time) time.Time {
return addTime(t1, time.Minute)
}

func FillArray(array []string, value string) []string {
for i := range array {
array[i] = value
}
return array
}
4 changes: 2 additions & 2 deletions src/QueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export class QueryEditor extends PureComponent<Props> {
width={10}
tooltip={
<>
The <code>dimensions</code> At least one ga:date* is required.
The <code>time dimensions</code> At least one ga:date* is required.
</>
}
>
Expand All @@ -235,7 +235,7 @@ export class QueryEditor extends PureComponent<Props> {
width={10}
tooltip={
<>
The <code>dimensions</code> At least one ga:date* is required.
The <code>dimensions</code> exclude time dimensions
</>
}
>
Expand Down
Binary file modified src/img/query.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 478988d

Please sign in to comment.