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,20 +14,13 @@ 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"`
graphCommonHandlerInput
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"`
}
@@ -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{
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),
Points: 100,
Dimensions: query.Columns{
query.NewColumn("ExporterName"),
query.NewColumn("InIfProvider"),
},
Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"),
Units: "l3bps",
},
Points: 100,
}
original1 := fmt.Sprintf("%+v", input)
expected := graphLineHandlerInput{
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),
Points: 100,
Dimensions: query.Columns{
query.NewColumn("ExporterName"),
query.NewColumn("OutIfProvider"),
},
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,6 +120,7 @@ func TestGraphPreviousPeriod(t *testing.T) {
t.Fatalf("time.Parse(%q) error:\n%+v", tc.ExpectedEnd, err)
}
input := graphLineHandlerInput{
graphCommonHandlerInput: graphCommonHandlerInput{
schema: schema.NewMock(t),
Start: start,
End: end,
@@ -123,13 +128,16 @@ func TestGraphPreviousPeriod(t *testing.T) {
query.NewColumn("ExporterAddress"),
query.NewColumn("ExporterName"),
},
},
}
query.Columns(input.Dimensions).Validate(input.schema)
got := input.previousPeriod()
expected := graphLineHandlerInput{
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,13 +155,15 @@ func TestGraphQuerySQL(t *testing.T) {
{
Description: "no dimensions, no filters, bps",
Input: graphLineHandlerInput{
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),
Points: 100,
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"}@@ }}
SELECT 1 AS axis, * FROM (
@@ -173,13 +183,15 @@ ORDER BY time WITH FILL
}, {
Description: "no dimensions, no filters, l2 bps",
Input: graphLineHandlerInput{
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),
Points: 100,
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"}@@ }}
SELECT 1 AS axis, * FROM (
@@ -200,13 +212,15 @@ ORDER BY time WITH FILL
}, {
Description: "no dimensions, no filters, pps",
Input: graphLineHandlerInput{
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),
Points: 100,
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"}@@ }}
SELECT 1 AS axis, * FROM (
@@ -226,13 +240,15 @@ ORDER BY time WITH FILL
}, {
Description: "no dimensions",
Input: graphLineHandlerInput{
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),
Points: 100,
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"}@@ }}
SELECT 1 AS axis, * FROM (
@@ -252,13 +268,15 @@ ORDER BY time WITH FILL
}, {
Description: "no dimensions, escaped filter",
Input: graphLineHandlerInput{
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),
Points: 100,
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"}@@ }}
SELECT 1 AS axis, * FROM (
@@ -278,12 +296,14 @@ ORDER BY time WITH FILL
}, {
Description: "no dimensions, reverse direction",
Input: graphLineHandlerInput{
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),
Points: 100,
Dimensions: []query.Column{},
Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"),
Units: "l3bps",
},
Points: 100,
Bidirectional: true,
},
Expected: `
@@ -321,12 +341,14 @@ ORDER BY time WITH FILL
}, {
Description: "no dimensions, reverse direction, inl2%",
Input: graphLineHandlerInput{
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),
Points: 100,
Dimensions: []query.Column{},
Filter: query.NewFilter("DstCountry = 'FR' AND SrcCountry = 'US'"),
Units: "inl2%",
},
Points: 100,
Bidirectional: true,
},
Expected: `
@@ -364,9 +386,9 @@ ORDER BY time WITH FILL
}, {
Description: "no filters",
Input: graphLineHandlerInput{
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),
Points: 100,
Limit: 20,
Dimensions: []query.Column{
query.NewColumn("ExporterName"),
@@ -375,6 +397,8 @@ ORDER BY time WITH FILL
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"}@@ }}
WITH
@@ -396,9 +420,9 @@ ORDER BY time WITH FILL
}, {
Description: "no filters, reverse",
Input: graphLineHandlerInput{
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),
Points: 100,
Limit: 20,
Dimensions: []query.Column{
query.NewColumn("ExporterName"),
@@ -406,6 +430,8 @@ ORDER BY time WITH FILL
},
Filter: query.Filter{},
Units: "l3bps",
},
Points: 100,
Bidirectional: true,
},
Expected: `
@@ -445,9 +471,9 @@ ORDER BY time WITH FILL
}, {
Description: "no filters, previous period",
Input: graphLineHandlerInput{
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),
Points: 100,
Limit: 20,
Dimensions: []query.Column{
query.NewColumn("ExporterName"),
@@ -455,6 +481,8 @@ ORDER BY time WITH FILL
},
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,6 +25,7 @@ func TestSankeyQuerySQL(t *testing.T) {
{
Description: "two dimensions, no filters, l3 bps",
Input: graphSankeyHandlerInput{
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{
@@ -35,6 +36,7 @@ func TestSankeyQuerySQL(t *testing.T) {
Filter: query.Filter{},
Units: "l3bps",
},
},
Expected: `
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l3bps"}@@ }}
WITH
@@ -52,6 +54,7 @@ ORDER BY xps DESC
}, {
Description: "two dimensions, no filters, l2 bps",
Input: graphSankeyHandlerInput{
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{
@@ -62,6 +65,7 @@ ORDER BY xps DESC
Filter: query.Filter{},
Units: "l2bps",
},
},
Expected: `
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l2bps"}@@ }}
WITH
@@ -80,6 +84,7 @@ ORDER BY xps DESC
}, {
Description: "two dimensions, no filters, pps",
Input: graphSankeyHandlerInput{
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{
@@ -90,6 +95,7 @@ ORDER BY xps DESC
Filter: query.Filter{},
Units: "pps",
},
},
Expected: `
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"pps"}@@ }}
WITH
@@ -107,6 +113,7 @@ ORDER BY xps DESC
}, {
Description: "two dimensions, with filter",
Input: graphSankeyHandlerInput{
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{
@@ -117,6 +124,7 @@ ORDER BY xps DESC
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"}@@ }}
WITH