console: factor graphXXXHandlerInput

This is similar to what is done in TypeScript.
This commit is contained in:
Vincent Bernat
2023-02-20 22:16:59 +01:00
parent e4c3a6b723
commit 609fa24a27
5 changed files with 197 additions and 147 deletions

23
console/graph.go Normal file
View File

@@ -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%"`
}

View File

@@ -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

View File

@@ -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: `

View File

@@ -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 {

View File

@@ -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"}@@ }}