Skip to content

Commit

Permalink
Merge pull request #217 from K-Phoen/more-timeseries-overrides
Browse files Browse the repository at this point in the history
Support more timeseries overrides
  • Loading branch information
K-Phoen authored Mar 20, 2023
2 parents 0316293 + 2f42ea5 commit 5dcfa2c
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 12 deletions.
119 changes: 110 additions & 9 deletions decoder/timeseries.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/K-Phoen/grabana/row"
"github.com/K-Phoen/grabana/timeseries"
"github.com/K-Phoen/grabana/timeseries/axis"
"github.com/K-Phoen/grabana/timeseries/fields"
)

var ErrInvalidGradientMode = fmt.Errorf("invalid gradient mode")
Expand All @@ -14,6 +15,7 @@ var ErrInvalidTooltipMode = fmt.Errorf("invalid tooltip mode")
var ErrInvalidStackMode = fmt.Errorf("invalid stack mode")
var ErrInvalidAxisDisplay = fmt.Errorf("invalid axis display")
var ErrInvalidAxisScale = fmt.Errorf("invalid axis scale")
var ErrInvalidOverrideMatcher = fmt.Errorf("invalid override matcher")

type DashboardTimeSeries struct {
Title string
Expand All @@ -29,6 +31,7 @@ type DashboardTimeSeries struct {
Alert *Alert `yaml:",omitempty"`
Visualization *TimeSeriesVisualization `yaml:",omitempty"`
Axis *TimeSeriesAxis `yaml:",omitempty"`
Overrides []TimeSeriesOverride `yaml:",omitempty"`
}

func (timeseriesPanel DashboardTimeSeries) toOption() (row.Option, error) {
Expand Down Expand Up @@ -88,6 +91,15 @@ func (timeseriesPanel DashboardTimeSeries) toOption() (row.Option, error) {
opts = append(opts, timeseries.Axis(axisOpts...))
}

for _, override := range timeseriesPanel.Overrides {
opt, err := override.toOption()
if err != nil {
return nil, err
}

opts = append(opts, opt)
}

for _, t := range timeseriesPanel.Targets {
opt, err := timeseriesPanel.target(t)
if err != nil {
Expand Down Expand Up @@ -367,22 +379,29 @@ func (tsAxis *TimeSeriesAxis) toOptions() ([]axis.Option, error) {
}

func (tsAxis *TimeSeriesAxis) placementOption() (axis.Option, error) {
var placementMode axis.PlacementMode
placementMode, err := axisPlacementFromString(tsAxis.Display)
if err != nil {
return nil, err
}

return axis.Placement(placementMode), nil
}

switch tsAxis.Display {
func axisPlacementFromString(input string) (axis.PlacementMode, error) {
switch input {
case "none":
placementMode = axis.Hidden
return axis.Hidden, nil
case "hidden":
return axis.Hidden, nil
case "auto":
placementMode = axis.Auto
return axis.Auto, nil
case "left":
placementMode = axis.Left
return axis.Left, nil
case "right":
placementMode = axis.Right
return axis.Right, nil
default:
return nil, ErrInvalidAxisDisplay
return axis.Auto, ErrInvalidAxisDisplay
}

return axis.Placement(placementMode), nil
}

func (tsAxis *TimeSeriesAxis) scaleOption() (axis.Option, error) {
Expand All @@ -401,3 +420,85 @@ func (tsAxis *TimeSeriesAxis) scaleOption() (axis.Option, error) {

return axis.Scale(scaleMode), nil
}

type TimeSeriesOverride struct {
Matcher TimeSeriesOverrideMatcher `yaml:"match,flow"`
Properties TimeSeriesOverrideProperties
}

func (override TimeSeriesOverride) toOption() (timeseries.Option, error) {
matcher, err := override.Matcher.toOption()
if err != nil {
return nil, err
}

overrideOpts, err := override.Properties.toOptions()
if err != nil {
return nil, err
}

return timeseries.FieldOverride(matcher, overrideOpts...), nil
}

type TimeSeriesOverrideMatcher struct {
FieldName *string `yaml:"field_name,omitempty"`
QueryRef *string `yaml:"query_ref,omitempty"`
Regex *string `yaml:"regex,omitempty"`
Type *string `yaml:"field_type,omitempty"`
}

func (matcher TimeSeriesOverrideMatcher) toOption() (fields.Matcher, error) {
if matcher.FieldName != nil {
return fields.ByName(*matcher.FieldName), nil
}
if matcher.QueryRef != nil {
return fields.ByQuery(*matcher.QueryRef), nil
}
if matcher.Regex != nil {
return fields.ByRegex(*matcher.Regex), nil
}
if matcher.Type != nil {
return fields.ByType(fields.FieldType(*matcher.Type)), nil
}

return nil, ErrInvalidOverrideMatcher
}

type TimeSeriesOverrideProperties struct {
Unit *string `yaml:",omitempty"`
Color *string `yaml:"color,omitempty"`
FillOpacity *int `yaml:"fill_opacity,omitempty"`
NegativeY *bool `yaml:"negative_Y,omitempty"`
AxisDisplay *string `yaml:"axis_display,omitempty"`
Stack *string `yaml:",omitempty"`
}

func (properties TimeSeriesOverrideProperties) toOptions() ([]fields.OverrideOption, error) {
var opts []fields.OverrideOption

if properties.Unit != nil {
opts = append(opts, fields.Unit(*properties.Unit))
}
if properties.Color != nil {
opts = append(opts, fields.FixedColorScheme(*properties.Color))
}
if properties.FillOpacity != nil {
opts = append(opts, fields.FillOpacity(*properties.FillOpacity))
}
if properties.NegativeY != nil && *properties.NegativeY {
opts = append(opts, fields.NegativeY())
}
if properties.AxisDisplay != nil {
axisPlacement, err := axisPlacementFromString(*properties.AxisDisplay)
if err != nil {
return nil, err
}

opts = append(opts, fields.AxisPlacement(axisPlacement))
}
if properties.Stack != nil {
opts = append(opts, fields.Stack(fields.StackMode(*properties.Stack)))
}

return opts, nil
}
4 changes: 4 additions & 0 deletions decoder/timeseries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ func TestTimeSeriesAxisSupportsDisplay(t *testing.T) {
value: "none",
expected: axis.Hidden,
},
{
value: "hidden",
expected: axis.Hidden,
},
{
value: "auto",
expected: axis.Auto,
Expand Down
2 changes: 1 addition & 1 deletion timeseries/axis/axis.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func Min(value float64) Option {
}
}

// SoftMax defines a hard maximum value for the axis.
// Max defines a hard maximum value for the axis.
func Max(value float64) Option {
return func(axis *Axis) error {
axis.fieldConfig.Defaults.Max = &value
Expand Down
22 changes: 22 additions & 0 deletions timeseries/fields/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import (

type Matcher func(field *sdk.FieldConfigOverride)

type FieldType string

const (
FieldTypeTime FieldType = "time"
)

// ByName matches a specific field name.
func ByName(name string) Matcher {
return func(field *sdk.FieldConfigOverride) {
Expand All @@ -21,3 +27,19 @@ func ByQuery(ref string) Matcher {
field.Matcher.Options = ref
}
}

// ByRegex matches fields names using a regex.
func ByRegex(regex string) Matcher {
return func(field *sdk.FieldConfigOverride) {
field.Matcher.ID = "byRegexp"
field.Matcher.Options = regex
}
}

// ByType matches fields with a specific type.
func ByType(fieldType FieldType) Matcher {
return func(field *sdk.FieldConfigOverride) {
field.Matcher.ID = "byType"
field.Matcher.Options = string(fieldType)
}
}
20 changes: 20 additions & 0 deletions timeseries/fields/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,23 @@ func TestByQuery(t *testing.T) {
req.Equal("byFrameRefID", overrideCfg.Matcher.ID)
req.Equal("A", overrideCfg.Matcher.Options)
}

func TestByRegex(t *testing.T) {
req := require.New(t)

overrideCfg := &sdk.FieldConfigOverride{}
ByRegex("/.*trans.*/")(overrideCfg)

req.Equal("byRegexp", overrideCfg.Matcher.ID)
req.Equal("/.*trans.*/", overrideCfg.Matcher.Options)
}

func TestByType(t *testing.T) {
req := require.New(t)

overrideCfg := &sdk.FieldConfigOverride{}
ByType(FieldTypeTime)(overrideCfg)

req.Equal("byType", overrideCfg.Matcher.ID)
req.Equal("time", overrideCfg.Matcher.Options)
}
54 changes: 53 additions & 1 deletion timeseries/fields/override.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
package fields

import "github.com/K-Phoen/sdk"
import (
"github.com/K-Phoen/grabana/timeseries/axis"
"github.com/K-Phoen/sdk"
)

// StackMode configures mode of series stacking.
// FIXME: copied here to avoid circular imports with parent package
type StackMode string

const (
// Unstacked will not stack series
Unstacked StackMode = "none"
// NormalStack will stack series as absolute numbers
NormalStack StackMode = "normal"
// PercentStack will stack series as percents
PercentStack StackMode = "percent"
)

type OverrideOption func(field *sdk.FieldConfigOverride)

Expand Down Expand Up @@ -39,3 +55,39 @@ func FixedColorScheme(color string) OverrideOption {
})
}
}

// NegativeY flips the results to negative values on the Y axis.
func NegativeY() OverrideOption {
return func(field *sdk.FieldConfigOverride) {
field.Properties = append(field.Properties,
sdk.FieldConfigOverrideProperty{
ID: "custom.transform",
Value: "negative-Y",
})
}
}

// AxisPlacement overrides how the axis should be placed in the panel.
func AxisPlacement(placement axis.PlacementMode) OverrideOption {
return func(field *sdk.FieldConfigOverride) {
field.Properties = append(field.Properties,
sdk.FieldConfigOverrideProperty{
ID: "custom.axisPlacement",
Value: string(placement),
})
}
}

// Stack overrides if the series should be stacked and using which mode (default not stacked).
func Stack(mode StackMode) OverrideOption {
return func(field *sdk.FieldConfigOverride) {
field.Properties = append(field.Properties,
sdk.FieldConfigOverrideProperty{
ID: "custom.stacking",
Value: map[string]interface{}{
"group": false,
"mode": string(mode),
},
})
}
}
37 changes: 37 additions & 0 deletions timeseries/fields/override_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fields
import (
"testing"

"github.com/K-Phoen/grabana/timeseries/axis"
"github.com/K-Phoen/sdk"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -42,3 +43,39 @@ func TestFixedColorScheme(t *testing.T) {
req.Equal("fixed", values["mode"])
req.Equal("dark-blue", values["fixedColor"])
}

func TestNegativeY(t *testing.T) {
req := require.New(t)

overrideCfg := &sdk.FieldConfigOverride{}
NegativeY()(overrideCfg)

req.Len(overrideCfg.Properties, 1)
req.Equal("custom.transform", overrideCfg.Properties[0].ID)
req.Equal("negative-Y", overrideCfg.Properties[0].Value)
}

func TestAxisPlacement(t *testing.T) {
req := require.New(t)

overrideCfg := &sdk.FieldConfigOverride{}
AxisPlacement(axis.Hidden)(overrideCfg)

req.Len(overrideCfg.Properties, 1)
req.Equal("custom.axisPlacement", overrideCfg.Properties[0].ID)
req.Equal("hidden", overrideCfg.Properties[0].Value)
}

func TestStack(t *testing.T) {
req := require.New(t)

overrideCfg := &sdk.FieldConfigOverride{}
Stack(PercentStack)(overrideCfg)

req.Len(overrideCfg.Properties, 1)
req.Equal("custom.stacking", overrideCfg.Properties[0].ID)

values := overrideCfg.Properties[0].Value.(map[string]interface{})
req.Equal("percent", values["mode"])
req.Equal(false, values["group"])
}
2 changes: 1 addition & 1 deletion timeseries/timeseries.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func LineWidth(value int) Option {
}
}

// Stack defines if the series should be stacked and using which mode (default not stacked). the opacity level of the series. The lower the value, the more transparent.
// Stack defines if the series should be stacked and using which mode (default not stacked).
func Stack(value StackMode) Option {
return func(timeseries *TimeSeries) error {
timeseries.Builder.TimeseriesPanel.FieldConfig.Defaults.Custom.Stacking.Mode = string(value)
Expand Down

0 comments on commit 5dcfa2c

Please sign in to comment.