mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
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:
@@ -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(() => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<{
|
||||||
@@ -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)!;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -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{}
|
||||||
@@ -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),
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user