console: move /sankey and /graph to /graph/sankey and /graph/line

Also rename stuff to GraphSankey and GraphLine for consistency.
This commit is contained in:
Vincent Bernat
2023-02-20 21:58:38 +01:00
parent 841b1a534d
commit e4c3a6b723
11 changed files with 89 additions and 85 deletions

View File

@@ -58,12 +58,12 @@ import {
} from "./VisualizePage/OptionsPanel.vue"; } from "./VisualizePage/OptionsPanel.vue";
import type { GraphType } from "./VisualizePage/graphtypes"; import type { GraphType } from "./VisualizePage/graphtypes";
import type { import type {
SankeyHandlerInput, GraphSankeyHandlerInput,
GraphHandlerInput, GraphLineHandlerInput,
SankeyHandlerOutput, GraphSankeyHandlerOutput,
GraphHandlerOutput, GraphLineHandlerOutput,
SankeyHandlerResult, GraphSankeyHandlerResult,
GraphHandlerResult, GraphLineHandlerResult,
} from "./VisualizePage"; } from "./VisualizePage";
import { isEqual, omit, pick } from "lodash-es"; import { isEqual, omit, pick } from "lodash-es";
@@ -125,7 +125,9 @@ watch(
const encodedState = computed(() => encodeState(state.value)); const encodedState = computed(() => encodeState(state.value));
// Fetch data // Fetch data
const fetchedData = ref<GraphHandlerResult | SankeyHandlerResult | null>(null); const fetchedData = ref<
GraphLineHandlerResult | GraphSankeyHandlerResult | null
>(null);
const orderedJSONPayload = <T extends Record<string, any>>(input: T): T => { const orderedJSONPayload = <T extends Record<string, any>>(input: T): T => {
return Object.keys(input) return Object.keys(input)
.sort() .sort()
@@ -135,10 +137,10 @@ const orderedJSONPayload = <T extends Record<string, any>>(input: T): T => {
) as T; ) as T;
}; };
const jsonPayload = computed( const jsonPayload = computed(
(): SankeyHandlerInput | GraphHandlerInput | null => { (): GraphSankeyHandlerInput | GraphLineHandlerInput | null => {
if (state.value === null) return null; if (state.value === null) return null;
if (state.value.graphType === "sankey") { if (state.value.graphType === "sankey") {
const input: SankeyHandlerInput = { const input: GraphSankeyHandlerInput = {
...omit(state.value, [ ...omit(state.value, [
"graphType", "graphType",
"bidirectional", "bidirectional",
@@ -149,7 +151,7 @@ const jsonPayload = computed(
}; };
return orderedJSONPayload(input); return orderedJSONPayload(input);
} else { } else {
const input: GraphHandlerInput = { const input: GraphLineHandlerInput = {
...omit(state.value, [ ...omit(state.value, [
"graphType", "graphType",
"previousPeriod", "previousPeriod",
@@ -176,20 +178,20 @@ const { data, execute, isFetching, aborted, abort, canAbort, error } = useFetch(
return ctx; return ctx;
} }
const endpoint: Record<GraphType, string> = { const endpoint: Record<GraphType, string> = {
stacked: "graph", stacked: "line",
stacked100: "graph", stacked100: "line",
lines: "graph", lines: "line",
grid: "graph", grid: "line",
sankey: "sankey", sankey: "sankey",
}; };
const url = endpoint[state.value.graphType]; const url = endpoint[state.value.graphType];
return { return {
...ctx, ...ctx,
url: `/api/v0/console/${url}`, url: `/api/v0/console/graph/${url}`,
}; };
}, },
async afterFetch( async afterFetch(
ctx: AfterFetchContext<GraphHandlerOutput | SankeyHandlerOutput> ctx: AfterFetchContext<GraphLineHandlerOutput | GraphSankeyHandlerOutput>
) { ) {
// Update data. Not done in a computed value as we want to keep the // Update data. Not done in a computed value as we want to keep the
// previous data in case of errors. // previous data in case of errors.
@@ -203,13 +205,13 @@ const { data, execute, isFetching, aborted, abort, canAbort, error } = useFetch(
if (state.value.graphType === "sankey") { if (state.value.graphType === "sankey") {
fetchedData.value = { fetchedData.value = {
graphType: "sankey", graphType: "sankey",
...(data as SankeyHandlerOutput), ...(data as GraphSankeyHandlerOutput),
...pick(state.value, ["start", "end", "dimensions", "units"]), ...pick(state.value, ["start", "end", "dimensions", "units"]),
}; };
} else { } else {
fetchedData.value = { fetchedData.value = {
graphType: state.value.graphType, graphType: state.value.graphType,
...(data as GraphHandlerOutput), ...(data as GraphLineHandlerOutput),
...pick(state.value, [ ...pick(state.value, [
"start", "start",
"end", "end",
@@ -240,7 +242,9 @@ const { data, execute, isFetching, aborted, abort, canAbort, error } = useFetch(
} }
) )
.post(jsonPayload, "json") .post(jsonPayload, "json")
.json<GraphHandlerOutput | SankeyHandlerOutput | { message: string }>(); .json<
GraphLineHandlerOutput | GraphSankeyHandlerOutput | { message: string }
>();
watch(jsonPayload, () => execute(), { immediate: true }); watch(jsonPayload, () => execute(), { immediate: true });
const errorMessage = computed(() => { const errorMessage = computed(() => {

View File

@@ -12,14 +12,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject } from "vue"; import { computed, inject } from "vue";
import DataGraphTimeSeries from "./DataGraphTimeSeries.vue"; import DataGraphLine from "./DataGraphLine.vue";
import DataGraphSankey from "./DataGraphSankey.vue"; import DataGraphSankey from "./DataGraphSankey.vue";
import type { GraphHandlerResult, SankeyHandlerResult } from "."; import type { GraphLineHandlerResult, GraphSankeyHandlerResult } from ".";
import { ThemeKey } from "@/components/ThemeProvider.vue"; import { ThemeKey } from "@/components/ThemeProvider.vue";
const { isDark } = inject(ThemeKey)!; const { isDark } = inject(ThemeKey)!;
const props = defineProps<{ const props = defineProps<{
data: GraphHandlerResult | SankeyHandlerResult | null; data: GraphLineHandlerResult | GraphSankeyHandlerResult | null;
}>(); }>();
const component = computed(() => { const component = computed(() => {
@@ -28,7 +28,7 @@ const component = computed(() => {
case "stacked100": case "stacked100":
case "lines": case "lines":
case "grid": case "grid":
return DataGraphTimeSeries; return DataGraphLine;
case "sankey": case "sankey":
return DataGraphSankey; return DataGraphSankey;
} }

View File

@@ -15,7 +15,7 @@ import { ref, watch, inject, computed, onMounted, nextTick } from "vue";
import { useMediaQuery } from "@vueuse/core"; import { useMediaQuery } from "@vueuse/core";
import { formatXps, dataColor, dataColorGrey } from "@/utils"; import { formatXps, dataColor, dataColorGrey } from "@/utils";
import { ThemeKey } from "@/components/ThemeProvider.vue"; import { ThemeKey } from "@/components/ThemeProvider.vue";
import type { GraphHandlerResult } from "."; import type { GraphLineHandlerResult } from ".";
import { uniqWith, isEqual, findIndex } from "lodash-es"; import { uniqWith, isEqual, findIndex } from "lodash-es";
import { use, graphic, type ComposeOption } from "echarts/core"; import { use, graphic, type ComposeOption } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers"; import { CanvasRenderer } from "echarts/renderers";
@@ -58,7 +58,7 @@ type ECOption = ComposeOption<
>; >;
const props = defineProps<{ const props = defineProps<{
data: GraphHandlerResult; data: GraphLineHandlerResult;
highlight: number | null; highlight: number | null;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -9,7 +9,7 @@
import { inject, computed } from "vue"; import { inject, computed } from "vue";
import { formatXps, dataColor, dataColorGrey } from "@/utils"; import { formatXps, dataColor, dataColorGrey } from "@/utils";
import { ThemeKey } from "@/components/ThemeProvider.vue"; import { ThemeKey } from "@/components/ThemeProvider.vue";
import type { SankeyHandlerResult } from "."; import type { GraphSankeyHandlerResult } from ".";
import { use, type ComposeOption } from "echarts/core"; import { use, type ComposeOption } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers"; import { CanvasRenderer } from "echarts/renderers";
import { SankeyChart, type SankeySeriesOption } from "echarts/charts"; import { SankeyChart, type SankeySeriesOption } from "echarts/charts";
@@ -26,7 +26,7 @@ type ECOption = ComposeOption<
>; >;
const props = defineProps<{ const props = defineProps<{
data: SankeyHandlerResult; data: GraphSankeyHandlerResult;
}>(); }>();
const { isDark } = inject(ThemeKey)!; const { isDark } = inject(ThemeKey)!;

View File

@@ -90,11 +90,11 @@ import { computed, inject, ref } from "vue";
import { uniqWith, isEqual, findIndex, takeWhile, toPairs } from "lodash-es"; import { uniqWith, isEqual, findIndex, takeWhile, toPairs } from "lodash-es";
import { formatXps, dataColor, dataColorGrey } from "@/utils"; import { formatXps, dataColor, dataColorGrey } from "@/utils";
import { ThemeKey } from "@/components/ThemeProvider.vue"; import { ThemeKey } from "@/components/ThemeProvider.vue";
import type { GraphHandlerResult, SankeyHandlerResult } from "."; import type { GraphLineHandlerResult, GraphSankeyHandlerResult } from ".";
const { isDark } = inject(ThemeKey)!; const { isDark } = inject(ThemeKey)!;
const props = defineProps<{ const props = defineProps<{
data: GraphHandlerResult | SankeyHandlerResult | null; data: GraphLineHandlerResult | GraphSankeyHandlerResult | null;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: "highlighted", index: number | null): void; (e: "highlighted", index: number | null): void;

View File

@@ -4,7 +4,7 @@
import type { GraphType } from "./graphtypes"; import type { GraphType } from "./graphtypes";
export type Units = "l3bps" | "l2bps" | "pps" | "inl2%" | "outl2%"; export type Units = "l3bps" | "l2bps" | "pps" | "inl2%" | "outl2%";
export type SankeyHandlerInput = { export type GraphSankeyHandlerInput = {
start: string; start: string;
end: string; end: string;
dimensions: string[]; dimensions: string[];
@@ -12,12 +12,12 @@ export type SankeyHandlerInput = {
filter: string; filter: string;
units: Units; units: Units;
}; };
export type GraphHandlerInput = SankeyHandlerInput & { export type GraphLineHandlerInput = GraphSankeyHandlerInput & {
points: number; points: number;
bidirectional: boolean; bidirectional: boolean;
"previous-period": boolean; "previous-period": boolean;
}; };
export type SankeyHandlerOutput = { export type GraphSankeyHandlerOutput = {
rows: string[][]; rows: string[][];
xps: number[]; xps: number[];
nodes: string[]; nodes: string[];
@@ -27,7 +27,7 @@ export type SankeyHandlerOutput = {
xps: number; xps: number;
}[]; }[];
}; };
export type GraphHandlerOutput = { export type GraphLineHandlerOutput = {
t: string[]; t: string[];
rows: string[][]; rows: string[][];
points: number[][]; points: number[][];
@@ -38,12 +38,12 @@ export type GraphHandlerOutput = {
max: number[]; max: number[];
"95th": number[]; "95th": number[];
}; };
export type SankeyHandlerResult = SankeyHandlerOutput & { export type GraphSankeyHandlerResult = GraphSankeyHandlerOutput & {
graphType: Extract<GraphType, "sankey">; graphType: Extract<GraphType, "sankey">;
} & Pick<SankeyHandlerInput, "start" | "end" | "dimensions" | "units">; } & Pick<GraphSankeyHandlerInput, "start" | "end" | "dimensions" | "units">;
export type GraphHandlerResult = GraphHandlerOutput & { export type GraphLineHandlerResult = GraphLineHandlerOutput & {
graphType: Exclude<GraphType, "sankey">; graphType: Exclude<GraphType, "sankey">;
} & Pick< } & Pick<
GraphHandlerInput, GraphLineHandlerInput,
"start" | "end" | "dimensions" | "units" | "bidirectional" "start" | "end" | "dimensions" | "units" | "bidirectional"
>; >;

View File

@@ -18,8 +18,8 @@ import (
"akvorado/console/query" "akvorado/console/query"
) )
// graphHandlerInput describes the input for the /graph endpoint. // graphLineHandlerInput describes the input for the /graph/line endpoint.
type graphHandlerInput struct { type graphLineHandlerInput struct {
schema *schema.Component schema *schema.Component
Start time.Time `json:"start" binding:"required"` Start time.Time `json:"start" binding:"required"`
End time.Time `json:"end" binding:"required,gtfield=Start"` End time.Time `json:"end" binding:"required,gtfield=Start"`
@@ -32,11 +32,11 @@ type graphHandlerInput struct {
PreviousPeriod bool `json:"previous-period"` PreviousPeriod bool `json:"previous-period"`
} }
// graphHandlerOutput describes the output for the /graph endpoint. A // graphLineHandlerOutput describes the output for the /graph/line endpoint. A
// row is a set of values for dimensions. Currently, axis 1 is for the // row is a set of values for dimensions. Currently, axis 1 is for the
// direct direction and axis 2 is for the reverse direction. Rows are // direct direction and axis 2 is for the reverse direction. Rows are
// sorted by axis, then by the sum of traffic. // sorted by axis, then by the sum of traffic.
type graphHandlerOutput struct { type graphLineHandlerOutput struct {
Time []time.Time `json:"t"` Time []time.Time `json:"t"`
Rows [][]string `json:"rows"` // List of rows Rows [][]string `json:"rows"` // List of rows
Points [][]int `json:"points"` // t → row → xps Points [][]int `json:"points"` // t → row → xps
@@ -50,7 +50,7 @@ type graphHandlerOutput struct {
// reverseDirection reverts the direction of a provided input. It does not // reverseDirection reverts the direction of a provided input. It does not
// modify the original. // modify the original.
func (input graphHandlerInput) reverseDirection() graphHandlerInput { func (input graphLineHandlerInput) reverseDirection() graphLineHandlerInput {
input.Filter.Swap() input.Filter.Swap()
input.Dimensions = slices.Clone(input.Dimensions) input.Dimensions = slices.Clone(input.Dimensions)
query.Columns(input.Dimensions).Reverse(input.schema) query.Columns(input.Dimensions).Reverse(input.schema)
@@ -82,7 +82,7 @@ func nearestPeriod(period time.Duration) (time.Duration, string) {
// period, this is the day. For less than 2-weeks, this is the week, // period, this is the day. For less than 2-weeks, this is the week,
// for less than 2-months, this is the month, otherwise, this is the // for less than 2-months, this is the month, otherwise, this is the
// year. Also, dimensions are stripped. // year. Also, dimensions are stripped.
func (input graphHandlerInput) previousPeriod() graphHandlerInput { func (input graphLineHandlerInput) previousPeriod() graphLineHandlerInput {
input.Dimensions = []query.Column{} input.Dimensions = []query.Column{}
diff := input.End.Sub(input.Start) diff := input.End.Sub(input.Start)
period, _ := nearestPeriod(diff) period, _ := nearestPeriod(diff)
@@ -105,7 +105,7 @@ type toSQL1Options struct {
offsetedStart time.Time offsetedStart time.Time
} }
func (input graphHandlerInput) toSQL1(axis int, options toSQL1Options) string { func (input graphLineHandlerInput) toSQL1(axis int, options toSQL1Options) string {
var startForInterval *time.Time var startForInterval *time.Time
var offsetShift string var offsetShift string
if !options.offsetedStart.IsZero() { if !options.offsetedStart.IsZero() {
@@ -195,8 +195,8 @@ ORDER BY time WITH FILL
return strings.TrimSpace(sqlQuery) return strings.TrimSpace(sqlQuery)
} }
// graphHandlerInputToSQL converts a graph input to an SQL request // toSQL converts a graph input to an SQL request
func (input graphHandlerInput) toSQL() string { func (input graphLineHandlerInput) toSQL() string {
parts := []string{input.toSQL1(1, toSQL1Options{})} parts := []string{input.toSQL1(1, toSQL1Options{})}
// Handle specific options. We have to align time periods in // Handle specific options. We have to align time periods in
// case the previous period does not use the same offsets. // case the previous period does not use the same offsets.
@@ -222,9 +222,9 @@ func (input graphHandlerInput) toSQL() string {
return strings.Join(parts, "\nUNION ALL\n") return strings.Join(parts, "\nUNION ALL\n")
} }
func (c *Component) graphHandlerFunc(gc *gin.Context) { func (c *Component) graphLineHandlerFunc(gc *gin.Context) {
ctx := c.t.Context(gc.Request.Context()) ctx := c.t.Context(gc.Request.Context())
input := graphHandlerInput{schema: c.d.Schema} input := graphLineHandlerInput{schema: c.d.Schema}
if err := gc.ShouldBindJSON(&input); err != nil { if err := gc.ShouldBindJSON(&input); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())}) gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return return
@@ -276,7 +276,7 @@ func (c *Component) graphHandlerFunc(gc *gin.Context) {
} }
// Set time axis. We assume the first returned axis has the complete view. // Set time axis. We assume the first returned axis has the complete view.
output := graphHandlerOutput{ output := graphLineHandlerOutput{
Time: []time.Time{}, Time: []time.Time{},
} }
lastTime := time.Time{} lastTime := time.Time{}

View File

@@ -17,8 +17,8 @@ import (
"akvorado/console/query" "akvorado/console/query"
) )
func TestGraphInputReverseDirection(t *testing.T) { func TestGraphLineInputReverseDirection(t *testing.T) {
input := graphHandlerInput{ input := graphLineHandlerInput{
schema: schema.NewMock(t), schema: schema.NewMock(t),
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
@@ -31,7 +31,7 @@ func TestGraphInputReverseDirection(t *testing.T) {
Units: "l3bps", Units: "l3bps",
} }
original1 := fmt.Sprintf("%+v", input) original1 := fmt.Sprintf("%+v", input)
expected := graphHandlerInput{ expected := graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -115,7 +115,7 @@ func TestGraphPreviousPeriod(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("time.Parse(%q) error:\n%+v", tc.ExpectedEnd, err) t.Fatalf("time.Parse(%q) error:\n%+v", tc.ExpectedEnd, err)
} }
input := graphHandlerInput{ input := graphLineHandlerInput{
schema: schema.NewMock(t), schema: schema.NewMock(t),
Start: start, Start: start,
End: end, End: end,
@@ -126,7 +126,7 @@ func TestGraphPreviousPeriod(t *testing.T) {
} }
query.Columns(input.Dimensions).Validate(input.schema) query.Columns(input.Dimensions).Validate(input.schema)
got := input.previousPeriod() got := input.previousPeriod()
expected := graphHandlerInput{ expected := graphLineHandlerInput{
Start: expectedStart, Start: expectedStart,
End: expectedEnd, End: expectedEnd,
Dimensions: []query.Column{}, Dimensions: []query.Column{},
@@ -141,12 +141,12 @@ func TestGraphPreviousPeriod(t *testing.T) {
func TestGraphQuerySQL(t *testing.T) { func TestGraphQuerySQL(t *testing.T) {
cases := []struct { cases := []struct {
Description string Description string
Input graphHandlerInput Input graphLineHandlerInput
Expected string Expected string
}{ }{
{ {
Description: "no dimensions, no filters, bps", Description: "no dimensions, no filters, bps",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -172,7 +172,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no dimensions, no filters, l2 bps", Description: "no dimensions, no filters, l2 bps",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -199,7 +199,7 @@ ORDER BY time WITH FILL
`, `,
}, { }, {
Description: "no dimensions, no filters, pps", Description: "no dimensions, no filters, pps",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -225,7 +225,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no dimensions", Description: "no dimensions",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -251,7 +251,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no dimensions, escaped filter", Description: "no dimensions, escaped filter",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -277,7 +277,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no dimensions, reverse direction", Description: "no dimensions, reverse direction",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -320,7 +320,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no dimensions, reverse direction, inl2%", Description: "no dimensions, reverse direction, inl2%",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -363,7 +363,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no filters", Description: "no filters",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -395,7 +395,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no filters, reverse", Description: "no filters, reverse",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -444,7 +444,7 @@ ORDER BY time WITH FILL
{{ end }}`, {{ end }}`,
}, { }, {
Description: "no filters, previous period", Description: "no filters, previous period",
Input: graphHandlerInput{ Input: graphLineHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100, Points: 100,
@@ -512,7 +512,7 @@ ORDER BY time WITH FILL
} }
} }
func TestGraphHandler(t *testing.T) { func TestGraphLineHandler(t *testing.T) {
_, h, mockConn, _ := NewMock(t, DefaultConfiguration()) _, h, mockConn, _ := NewMock(t, DefaultConfiguration())
base := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) base := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
@@ -622,7 +622,7 @@ func TestGraphHandler(t *testing.T) {
helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{ helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
{ {
Description: "single direction", Description: "single direction",
URL: "/api/v0/console/graph", URL: "/api/v0/console/graph/line",
JSONInput: gin.H{ JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), "start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), "end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
@@ -697,7 +697,7 @@ func TestGraphHandler(t *testing.T) {
}, },
}, { }, {
Description: "bidirectional", Description: "bidirectional",
URL: "/api/v0/console/graph", URL: "/api/v0/console/graph/line",
JSONInput: gin.H{ JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), "start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), "end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
@@ -816,7 +816,7 @@ func TestGraphHandler(t *testing.T) {
}, },
}, { }, {
Description: "previous period", Description: "previous period",
URL: "/api/v0/console/graph", URL: "/api/v0/console/graph/line",
JSONInput: gin.H{ JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), "start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), "end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),

View File

@@ -92,8 +92,8 @@ func (c *Component) Start() error {
endpoint.GET("/widget/exporters", c.d.HTTP.CacheByRequestPath(30*time.Second), c.widgetExportersHandlerFunc) endpoint.GET("/widget/exporters", c.d.HTTP.CacheByRequestPath(30*time.Second), c.widgetExportersHandlerFunc)
endpoint.GET("/widget/top/:name", c.d.HTTP.CacheByRequestPath(30*time.Second), c.widgetTopHandlerFunc) endpoint.GET("/widget/top/:name", c.d.HTTP.CacheByRequestPath(30*time.Second), c.widgetTopHandlerFunc)
endpoint.GET("/widget/graph", c.d.HTTP.CacheByRequestPath(5*time.Minute), c.widgetGraphHandlerFunc) endpoint.GET("/widget/graph", c.d.HTTP.CacheByRequestPath(5*time.Minute), c.widgetGraphHandlerFunc)
endpoint.POST("/graph", c.d.HTTP.CacheByRequestBody(c.config.CacheTTL), c.graphHandlerFunc) endpoint.POST("/graph/line", c.d.HTTP.CacheByRequestBody(c.config.CacheTTL), c.graphLineHandlerFunc)
endpoint.POST("/sankey", c.d.HTTP.CacheByRequestBody(c.config.CacheTTL), c.sankeyHandlerFunc) endpoint.POST("/graph/sankey", c.d.HTTP.CacheByRequestBody(c.config.CacheTTL), c.graphSankeyHandlerFunc)
endpoint.POST("/filter/validate", c.filterValidateHandlerFunc) endpoint.POST("/filter/validate", c.filterValidateHandlerFunc)
endpoint.POST("/filter/complete", c.d.HTTP.CacheByRequestBody(time.Minute), c.filterCompleteHandlerFunc) endpoint.POST("/filter/complete", c.d.HTTP.CacheByRequestBody(time.Minute), c.filterCompleteHandlerFunc)
endpoint.GET("/filter/saved", c.filterSavedListHandlerFunc) endpoint.GET("/filter/saved", c.filterSavedListHandlerFunc)

View File

@@ -17,8 +17,8 @@ import (
"akvorado/console/query" "akvorado/console/query"
) )
// sankeyHandlerInput describes the input for the /sankey endpoint. // graphSankeyHandlerInput describes the input for the /graph/sankey endpoint.
type sankeyHandlerInput struct { type graphSankeyHandlerInput struct {
schema *schema.Component schema *schema.Component
Start time.Time `json:"start" binding:"required"` Start time.Time `json:"start" binding:"required"`
End time.Time `json:"end" binding:"required,gtfield=Start"` End time.Time `json:"end" binding:"required,gtfield=Start"`
@@ -28,8 +28,8 @@ type sankeyHandlerInput struct {
Units string `json:"units" binding:"required,oneof=pps l3bps l2bps inl2% outl2%"` Units string `json:"units" binding:"required,oneof=pps l3bps l2bps inl2% outl2%"`
} }
// sankeyHandlerOutput describes the output for the /sankey endpoint. // graphSankeyHandlerOutput describes the output for the /graph/sankey endpoint.
type sankeyHandlerOutput struct { type graphSankeyHandlerOutput struct {
// Unprocessed data for table view // Unprocessed data for table view
Rows [][]string `json:"rows"` Rows [][]string `json:"rows"`
Xps []int `json:"xps"` // row → xps Xps []int `json:"xps"` // row → xps
@@ -44,7 +44,7 @@ type sankeyLink struct {
} }
// sankeyHandlerInputToSQL converts a sankey query to an SQL request // sankeyHandlerInputToSQL converts a sankey query to an SQL request
func (input sankeyHandlerInput) toSQL() (string, error) { func (input graphSankeyHandlerInput) toSQL() (string, error) {
where := templateWhere(input.Filter) where := templateWhere(input.Filter)
// Select // Select
@@ -95,9 +95,9 @@ ORDER BY xps DESC
return strings.TrimSpace(sqlQuery), nil return strings.TrimSpace(sqlQuery), nil
} }
func (c *Component) sankeyHandlerFunc(gc *gin.Context) { func (c *Component) graphSankeyHandlerFunc(gc *gin.Context) {
ctx := c.t.Context(gc.Request.Context()) ctx := c.t.Context(gc.Request.Context())
input := sankeyHandlerInput{schema: c.d.Schema} input := graphSankeyHandlerInput{schema: c.d.Schema}
if err := gc.ShouldBindJSON(&input); err != nil { if err := gc.ShouldBindJSON(&input); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())}) gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return return
@@ -131,7 +131,7 @@ func (c *Component) sankeyHandlerFunc(gc *gin.Context) {
} }
// Prepare output // Prepare output
output := sankeyHandlerOutput{ output := graphSankeyHandlerOutput{
Rows: make([][]string, 0, len(results)), Rows: make([][]string, 0, len(results)),
Xps: make([]int, 0, len(results)), Xps: make([]int, 0, len(results)),
Nodes: make([]string, 0), Nodes: make([]string, 0),

View File

@@ -19,12 +19,12 @@ import (
func TestSankeyQuerySQL(t *testing.T) { func TestSankeyQuerySQL(t *testing.T) {
cases := []struct { cases := []struct {
Description string Description string
Input sankeyHandlerInput Input graphSankeyHandlerInput
Expected string Expected string
}{ }{
{ {
Description: "two dimensions, no filters, l3 bps", Description: "two dimensions, no filters, l3 bps",
Input: sankeyHandlerInput{ Input: graphSankeyHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Dimensions: []query.Column{ Dimensions: []query.Column{
@@ -51,7 +51,7 @@ ORDER BY xps DESC
{{ end }}`, {{ end }}`,
}, { }, {
Description: "two dimensions, no filters, l2 bps", Description: "two dimensions, no filters, l2 bps",
Input: sankeyHandlerInput{ Input: graphSankeyHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Dimensions: []query.Column{ Dimensions: []query.Column{
@@ -79,7 +79,7 @@ ORDER BY xps DESC
`, `,
}, { }, {
Description: "two dimensions, no filters, pps", Description: "two dimensions, no filters, pps",
Input: sankeyHandlerInput{ Input: graphSankeyHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Dimensions: []query.Column{ Dimensions: []query.Column{
@@ -106,7 +106,7 @@ ORDER BY xps DESC
{{ end }}`, {{ end }}`,
}, { }, {
Description: "two dimensions, with filter", Description: "two dimensions, with filter",
Input: sankeyHandlerInput{ Input: graphSankeyHandlerInput{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Dimensions: []query.Column{ Dimensions: []query.Column{
@@ -192,7 +192,7 @@ func TestSankeyHandler(t *testing.T) {
helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{ helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
{ {
URL: "/api/v0/console/sankey", URL: "/api/v0/console/graph/sankey",
JSONInput: gin.H{ JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), "start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), "end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),