From 609fa24a27c0ead5726f18d6f9d00bb929c8003b Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Mon, 20 Feb 2023 22:16:59 +0100 Subject: [PATCH] console: factor graphXXXHandlerInput This is similar to what is done in TypeScript. --- console/graph.go | 23 +++++ console/line.go | 17 +--- console/line_test.go | 214 +++++++++++++++++++++++------------------ console/sankey.go | 18 ++-- console/sankey_test.go | 72 ++++++++------ 5 files changed, 197 insertions(+), 147 deletions(-) create mode 100644 console/graph.go diff --git a/console/graph.go b/console/graph.go new file mode 100644 index 00000000..90dae065 --- /dev/null +++ b/console/graph.go @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Free Mobile +// SPDX-License-Identifier: AGPL-3.0-only + +package console + +import ( + "time" + + "akvorado/common/schema" + "akvorado/console/query" +) + +// graphCommonHandlerInput is for bits common to graphLineHandlerInput and +// graphSankeyHandlerInput. +type graphCommonHandlerInput struct { + schema *schema.Component + Start time.Time `json:"start" binding:"required"` + End time.Time `json:"end" binding:"required,gtfield=Start"` + Dimensions []query.Column `json:"dimensions"` // group by ... + Limit int `json:"limit" binding:"min=1"` // limit product of dimensions + Filter query.Filter `json:"filter"` // where ... + Units string `json:"units" binding:"required,oneof=pps l3bps l2bps inl2% outl2%"` +} diff --git a/console/line.go b/console/line.go index 1d7b5395..d7321dc3 100644 --- a/console/line.go +++ b/console/line.go @@ -14,22 +14,15 @@ import ( "golang.org/x/exp/slices" "akvorado/common/helpers" - "akvorado/common/schema" "akvorado/console/query" ) // graphLineHandlerInput describes the input for the /graph/line endpoint. type graphLineHandlerInput struct { - schema *schema.Component - Start time.Time `json:"start" binding:"required"` - End time.Time `json:"end" binding:"required,gtfield=Start"` - Points uint `json:"points" binding:"required,min=5,max=2000"` // minimum number of points - Dimensions []query.Column `json:"dimensions"` // group by ... - Limit int `json:"limit" binding:"min=1"` // limit product of dimensions - Filter query.Filter `json:"filter"` // where ... - Units string `json:"units" binding:"required,oneof=pps l2bps l3bps inl2% outl2%"` - Bidirectional bool `json:"bidirectional"` - PreviousPeriod bool `json:"previous-period"` + graphCommonHandlerInput + Points uint `json:"points" binding:"required,min=5,max=2000"` // minimum number of points + Bidirectional bool `json:"bidirectional"` + PreviousPeriod bool `json:"previous-period"` } // graphLineHandlerOutput describes the output for the /graph/line endpoint. A @@ -224,7 +217,7 @@ func (input graphLineHandlerInput) toSQL() string { func (c *Component) graphLineHandlerFunc(gc *gin.Context) { ctx := c.t.Context(gc.Request.Context()) - input := graphLineHandlerInput{schema: c.d.Schema} + input := graphLineHandlerInput{graphCommonHandlerInput: graphCommonHandlerInput{schema: c.d.Schema}} if err := gc.ShouldBindJSON(&input); err != nil { gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())}) return diff --git a/console/line_test.go b/console/line_test.go index 171b9117..e6c07239 100644 --- a/console/line_test.go +++ b/console/line_test.go @@ -19,28 +19,32 @@ import ( func TestGraphLineInputReverseDirection(t *testing.T) { input := graphLineHandlerInput{ - schema: schema.NewMock(t), - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: query.Columns{ - query.NewColumn("ExporterName"), - query.NewColumn("InIfProvider"), + graphCommonHandlerInput: graphCommonHandlerInput{ + schema: schema.NewMock(t), + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: query.Columns{ + query.NewColumn("ExporterName"), + query.NewColumn("InIfProvider"), + }, + Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), + Units: "l3bps", }, - Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), - Units: "l3bps", + Points: 100, } original1 := fmt.Sprintf("%+v", input) expected := graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: query.Columns{ - query.NewColumn("ExporterName"), - query.NewColumn("OutIfProvider"), + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: query.Columns{ + query.NewColumn("ExporterName"), + query.NewColumn("OutIfProvider"), + }, + Filter: query.NewFilter("SrcCountry = 'FR' AND DstCountry = 'US'"), + Units: "l3bps", }, - Filter: query.NewFilter("SrcCountry = 'FR' AND DstCountry = 'US'"), - Units: "l3bps", + Points: 100, } query.Columns(input.Dimensions).Validate(input.schema) query.Columns(expected.Dimensions).Validate(input.schema) @@ -116,20 +120,24 @@ func TestGraphPreviousPeriod(t *testing.T) { t.Fatalf("time.Parse(%q) error:\n%+v", tc.ExpectedEnd, err) } input := graphLineHandlerInput{ - schema: schema.NewMock(t), - Start: start, - End: end, - Dimensions: query.Columns{ - query.NewColumn("ExporterAddress"), - query.NewColumn("ExporterName"), + graphCommonHandlerInput: graphCommonHandlerInput{ + schema: schema.NewMock(t), + Start: start, + End: end, + Dimensions: query.Columns{ + query.NewColumn("ExporterAddress"), + query.NewColumn("ExporterName"), + }, }, } query.Columns(input.Dimensions).Validate(input.schema) got := input.previousPeriod() expected := graphLineHandlerInput{ - Start: expectedStart, - End: expectedEnd, - Dimensions: []query.Column{}, + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: expectedStart, + End: expectedEnd, + Dimensions: []query.Column{}, + }, } if diff := helpers.Diff(got, expected); diff != "" { t.Fatalf("previousPeriod() (-got, +want):\n%s", diff) @@ -147,12 +155,14 @@ func TestGraphQuerySQL(t *testing.T) { { Description: "no dimensions, no filters, bps", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: []query.Column{}, - Filter: query.Filter{}, - Units: "l3bps", + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.Filter{}, + Units: "l3bps", + }, + Points: 100, }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} @@ -173,12 +183,14 @@ ORDER BY time WITH FILL }, { Description: "no dimensions, no filters, l2 bps", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: []query.Column{}, - Filter: query.Filter{}, - Units: "l2bps", + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.Filter{}, + Units: "l2bps", + }, + Points: 100, }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l2bps"}@@ }} @@ -200,12 +212,14 @@ ORDER BY time WITH FILL }, { Description: "no dimensions, no filters, pps", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: []query.Column{}, - Filter: query.Filter{}, - Units: "pps", + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.Filter{}, + Units: "pps", + }, + Points: 100, }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"pps"}@@ }} @@ -226,12 +240,14 @@ ORDER BY time WITH FILL }, { Description: "no dimensions", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: []query.Column{}, - Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), - Units: "l3bps", + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), + Units: "l3bps", + }, + Points: 100, }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} @@ -252,12 +268,14 @@ ORDER BY time WITH FILL }, { Description: "no dimensions, escaped filter", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Dimensions: []query.Column{}, - Filter: query.NewFilter("InIfDescription = '{{ hello }}' AND SrcCountry = 'US'"), - Units: "l3bps", + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.NewFilter("InIfDescription = '{{ hello }}' AND SrcCountry = 'US'"), + Units: "l3bps", + }, + Points: 100, }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} @@ -278,12 +296,14 @@ ORDER BY time WITH FILL }, { Description: "no dimensions, reverse direction", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), + Units: "l3bps", + }, Points: 100, - Dimensions: []query.Column{}, - Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), - Units: "l3bps", Bidirectional: true, }, Expected: ` @@ -321,12 +341,14 @@ ORDER BY time WITH FILL }, { Description: "no dimensions, reverse direction, inl2%", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{}, + Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), + Units: "inl2%", + }, Points: 100, - Dimensions: []query.Column{}, - Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"), - Units: "inl2%", Bidirectional: true, }, Expected: ` @@ -364,16 +386,18 @@ ORDER BY time WITH FILL }, { Description: "no filters", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Limit: 20, - Dimensions: []query.Column{ - query.NewColumn("ExporterName"), - query.NewColumn("InIfProvider"), + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Limit: 20, + Dimensions: []query.Column{ + query.NewColumn("ExporterName"), + query.NewColumn("InIfProvider"), + }, + Filter: query.Filter{}, + Units: "l3bps", }, - Filter: query.Filter{}, - Units: "l3bps", + Points: 100, }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} @@ -396,16 +420,18 @@ ORDER BY time WITH FILL }, { Description: "no filters, reverse", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Limit: 20, - Dimensions: []query.Column{ - query.NewColumn("ExporterName"), - query.NewColumn("InIfProvider"), + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Limit: 20, + Dimensions: []query.Column{ + query.NewColumn("ExporterName"), + query.NewColumn("InIfProvider"), + }, + Filter: query.Filter{}, + Units: "l3bps", }, - Filter: query.Filter{}, - Units: "l3bps", + Points: 100, Bidirectional: true, }, Expected: ` @@ -445,16 +471,18 @@ ORDER BY time WITH FILL }, { Description: "no filters, previous period", Input: graphLineHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Points: 100, - Limit: 20, - Dimensions: []query.Column{ - query.NewColumn("ExporterName"), - query.NewColumn("InIfProvider"), + graphCommonHandlerInput: graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Limit: 20, + Dimensions: []query.Column{ + query.NewColumn("ExporterName"), + query.NewColumn("InIfProvider"), + }, + Filter: query.Filter{}, + Units: "l3bps", }, - Filter: query.Filter{}, - Units: "l3bps", + Points: 100, PreviousPeriod: true, }, Expected: ` diff --git a/console/sankey.go b/console/sankey.go index 31b756a2..7c6e9eba 100644 --- a/console/sankey.go +++ b/console/sankey.go @@ -8,24 +8,16 @@ import ( "net/http" "sort" "strings" - "time" "github.com/gin-gonic/gin" "akvorado/common/helpers" - "akvorado/common/schema" "akvorado/console/query" ) // graphSankeyHandlerInput describes the input for the /graph/sankey endpoint. type graphSankeyHandlerInput struct { - schema *schema.Component - Start time.Time `json:"start" binding:"required"` - End time.Time `json:"end" binding:"required,gtfield=Start"` - Dimensions []query.Column `json:"dimensions" binding:"required,min=2"` // group by ... - Limit int `json:"limit" binding:"min=1,max=50"` // limit product of dimensions - Filter query.Filter `json:"filter"` // where ... - Units string `json:"units" binding:"required,oneof=pps l3bps l2bps inl2% outl2%"` + graphCommonHandlerInput } // graphSankeyHandlerOutput describes the output for the /graph/sankey endpoint. @@ -97,7 +89,7 @@ ORDER BY xps DESC func (c *Component) graphSankeyHandlerFunc(gc *gin.Context) { ctx := c.t.Context(gc.Request.Context()) - input := graphSankeyHandlerInput{schema: c.d.Schema} + input := graphSankeyHandlerInput{graphCommonHandlerInput: graphCommonHandlerInput{schema: c.d.Schema}} if err := gc.ShouldBindJSON(&input); err != nil { gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())}) return @@ -110,6 +102,12 @@ func (c *Component) graphSankeyHandlerFunc(gc *gin.Context) { gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())}) return } + if input.Limit > c.config.DimensionsLimit { + gc.JSON(http.StatusBadRequest, + gin.H{"message": fmt.Sprintf("Limit is set beyond maximum value (%d)", + c.config.DimensionsLimit)}) + return + } sqlQuery, err := input.toSQL() if err != nil { diff --git a/console/sankey_test.go b/console/sankey_test.go index 25e14073..b0815594 100644 --- a/console/sankey_test.go +++ b/console/sankey_test.go @@ -25,15 +25,17 @@ func TestSankeyQuerySQL(t *testing.T) { { Description: "two dimensions, no filters, l3 bps", Input: graphSankeyHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Dimensions: []query.Column{ - query.NewColumn("SrcAS"), - query.NewColumn("ExporterName"), + graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{ + query.NewColumn("SrcAS"), + query.NewColumn("ExporterName"), + }, + Limit: 5, + Filter: query.Filter{}, + Units: "l3bps", }, - Limit: 5, - Filter: query.Filter{}, - Units: "l3bps", }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l3bps"}@@ }} @@ -52,15 +54,17 @@ ORDER BY xps DESC }, { Description: "two dimensions, no filters, l2 bps", Input: graphSankeyHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Dimensions: []query.Column{ - query.NewColumn("SrcAS"), - query.NewColumn("ExporterName"), + graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{ + query.NewColumn("SrcAS"), + query.NewColumn("ExporterName"), + }, + Limit: 5, + Filter: query.Filter{}, + Units: "l2bps", }, - Limit: 5, - Filter: query.Filter{}, - Units: "l2bps", }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l2bps"}@@ }} @@ -80,15 +84,17 @@ ORDER BY xps DESC }, { Description: "two dimensions, no filters, pps", Input: graphSankeyHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Dimensions: []query.Column{ - query.NewColumn("SrcAS"), - query.NewColumn("ExporterName"), + graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{ + query.NewColumn("SrcAS"), + query.NewColumn("ExporterName"), + }, + Limit: 5, + Filter: query.Filter{}, + Units: "pps", }, - Limit: 5, - Filter: query.Filter{}, - Units: "pps", }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"pps"}@@ }} @@ -107,15 +113,17 @@ ORDER BY xps DESC }, { Description: "two dimensions, with filter", Input: graphSankeyHandlerInput{ - Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), - End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), - Dimensions: []query.Column{ - query.NewColumn("SrcAS"), - query.NewColumn("ExporterName"), + graphCommonHandlerInput{ + Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), + End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), + Dimensions: []query.Column{ + query.NewColumn("SrcAS"), + query.NewColumn("ExporterName"), + }, + Limit: 10, + Filter: query.NewFilter("DstCountry = 'FR'"), + Units: "l3bps", }, - Limit: 10, - Filter: query.NewFilter("DstCountry = 'FR'"), - Units: "l3bps", }, Expected: ` {{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l3bps"}@@ }}