Fixed some issues with dashboard (#570)

-Added padding to the bottom of dashboard

-Added calculations for linear chart tick interval

-Added default min width configurations to panels.

- Fixed crash on clean tenant

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2021-01-25 12:12:50 -06:00
committed by GitHub
parent 5c96eb9e25
commit 52fac7f542
4 changed files with 460 additions and 206 deletions

File diff suppressed because one or more lines are too long

View File

@@ -14,38 +14,40 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useMemo, useState, useCallback } from "react";
import React, { useEffect, useState, useCallback } from "react";
import { connect } from "react-redux";
import ReactGridLayout from "react-grid-layout";
import Grid from "@material-ui/core/Grid";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button } from "@material-ui/core";
import {
actionsTray,
containerForHeader,
} from "../../Common/FormComponents/common/styleLibrary";
import SingleValueWidget from "./Widgets/SingleValueWidget";
import { AutoSizer } from "react-virtualized";
import LinearGraphWidget from "./Widgets/LinearGraphWidget";
import {
IBarChartConfiguration,
IDataSRep,
ILinearGraphConfiguration,
IPieChartConfiguration,
} from "./Widgets/types";
import BarChartWidget from "./Widgets/BarChartWidget";
import PieChartWidget from "./Widgets/PieChartWidget";
import SingleRepWidget from "./Widgets/SingleRepWidget";
import DateTimePickerWrapper from "../../Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper";
import { IDashboardPanel, widgetType } from "./types";
import api from "../../../../common/api";
import {
getDashboardDistribution,
getWidgetsWithValue,
panelsConfiguration,
saveDashboardDistribution,
} from "./utils";
import { Button } from "@material-ui/core";
import { connect } from "react-redux";
import { setErrorSnackMessage } from "../../../../actions";
import SingleValueWidget from "./Widgets/SingleValueWidget";
import LinearGraphWidget from "./Widgets/LinearGraphWidget";
import BarChartWidget from "./Widgets/BarChartWidget";
import PieChartWidget from "./Widgets/PieChartWidget";
import SingleRepWidget from "./Widgets/SingleRepWidget";
import DateTimePickerWrapper from "../../Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper";
import api from "../../../../common/api";
interface IPrDashboard {
classes: any;
@@ -56,6 +58,7 @@ const styles = (theme: Theme) =>
createStyles({
widgetsContainer: {
height: "calc(100vh - 250px)",
paddingBottom: 235,
},
...actionsTray,
...containerForHeader(theme.spacing(4)),
@@ -70,72 +73,91 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
);
const minHeight = 600;
const colsInGrid = 8;
const xSpacing = 10;
const ySpacing = 10;
const panels = useMemo(() => {
const componentToUse = (value: IDashboardPanel) => {
switch (value.type) {
case widgetType.singleValue:
return (
<SingleValueWidget
title={value.title}
data={value.data as string}
/>
);
case widgetType.pieChart:
return (
<PieChartWidget
title={value.title}
dataInner={value.data as object[]}
dataOuter={(value.dataOuter as object[]) || null}
pieChartConfiguration={
value.widgetConfiguration as IPieChartConfiguration
}
middleLabel={value.innerLabel}
/>
);
case widgetType.linearGraph:
return (
<LinearGraphWidget
title={value.title}
data={value.data as object[]}
linearConfiguration={
value.widgetConfiguration as ILinearGraphConfiguration[]
}
hideYAxis={value.disableYAxis}
xAxisFormatter={value.xAxisFormatter}
yAxisFormatter={value.yAxisFormatter}
/>
);
case widgetType.barChart:
return (
<BarChartWidget
title={value.title}
data={value.data as object[]}
barChartConfiguration={
value.widgetConfiguration as IBarChartConfiguration[]
}
/>
);
case widgetType.singleRep:
const fillColor = value.fillColor ? value.fillColor : value.color;
return (
<SingleRepWidget
title={value.title}
data={value.data as IDataSRep[]}
label={value.innerLabel as string}
color={value.color as string}
fillColor={fillColor as string}
/>
);
default:
return null;
}
};
const dashboardDistr = getDashboardDistribution();
return panelInformation.map((val) => {
return <div key={val.layoutIdentifier}>{componentToUse(val)}</div>;
});
}, [panelInformation]);
const autoSizerStyleProp = {
width: "100%",
height: "auto",
paddingBottom: 45,
};
const panels = useCallback(
(width: number) => {
const singlePanelWidth = width / colsInGrid + xSpacing / 2;
const componentToUse = (value: IDashboardPanel, index: number) => {
switch (value.type) {
case widgetType.singleValue:
return (
<SingleValueWidget
title={value.title}
data={value.data as string}
/>
);
case widgetType.pieChart:
return (
<PieChartWidget
title={value.title}
dataInner={value.data as object[]}
dataOuter={(value.dataOuter as object[]) || null}
pieChartConfiguration={
value.widgetConfiguration as IPieChartConfiguration
}
middleLabel={value.innerLabel}
/>
);
case widgetType.linearGraph:
return (
<LinearGraphWidget
title={value.title}
data={value.data as object[]}
linearConfiguration={
value.widgetConfiguration as ILinearGraphConfiguration[]
}
hideYAxis={value.disableYAxis}
xAxisFormatter={value.xAxisFormatter}
yAxisFormatter={value.yAxisFormatter}
panelWidth={singlePanelWidth * dashboardDistr[index].w}
/>
);
case widgetType.barChart:
return (
<BarChartWidget
title={value.title}
data={value.data as object[]}
barChartConfiguration={
value.widgetConfiguration as IBarChartConfiguration[]
}
/>
);
case widgetType.singleRep:
const fillColor = value.fillColor ? value.fillColor : value.color;
return (
<SingleRepWidget
title={value.title}
data={value.data as IDataSRep[]}
label={value.innerLabel as string}
color={value.color as string}
fillColor={fillColor as string}
/>
);
default:
return null;
}
};
return panelInformation.map((val, index) => {
return (
<div key={val.layoutIdentifier}>{componentToUse(val, index)}</div>
);
});
},
[panelInformation, dashboardDistr]
);
const fetchUsage = useCallback(() => {
let stepCalc = 15;
@@ -184,8 +206,6 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
}
}, [loading, fetchUsage]);
const dashboardDistr = getDashboardDistribution();
return (
<Grid container className={classes.container}>
<Grid
@@ -207,19 +227,19 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
</Button>
</Grid>
<Grid item xs={12} className={classes.widgetsContainer}>
<AutoSizer>
<AutoSizer style={autoSizerStyleProp}>
{({ width, height }: any) => {
const hpanel = height < minHeight ? minHeight : height;
return (
<ReactGridLayout
width={width}
cols={8}
containerPadding={[10, 10]}
cols={colsInGrid}
containerPadding={[xSpacing, ySpacing]}
onLayoutChange={saveDashboardDistribution}
layout={dashboardDistr}
rowHeight={hpanel / 6}
>
{panels}
{panels(width)}
</ReactGridLayout>
);
}}

View File

@@ -37,6 +37,7 @@ interface ILinearGraphWidget {
hideYAxis?: boolean;
yAxisFormatter?: (item: string) => string;
xAxisFormatter?: (item: string) => string;
panelWidth?: number;
}
const styles = (theme: Theme) =>
@@ -73,7 +74,23 @@ const LinearGraphWidget = ({
hideYAxis = false,
yAxisFormatter = (item: string) => item,
xAxisFormatter = (item: string) => item,
panelWidth = 0,
}: ILinearGraphWidget) => {
let intervalCount = 5;
if (panelWidth !== 0) {
if (panelWidth > 400) {
intervalCount = 5;
} else if (panelWidth > 350) {
intervalCount = 10;
} else if (panelWidth > 300) {
intervalCount = 15;
} else if (panelWidth > 250) {
intervalCount = 20;
} else {
intervalCount = 30;
}
}
return (
<div className={classes.singleValueContainer}>
<div className={classes.titleContainer}>{title}</div>
@@ -97,7 +114,7 @@ const LinearGraphWidget = ({
<XAxis
dataKey="name"
tickFormatter={(value: any) => xAxisFormatter(value)}
interval={5}
interval={intervalCount}
tick={{ fontSize: "70%" }}
tickCount={10}
/>

View File

@@ -27,27 +27,216 @@ import {
const dLocalStorageV = "dashboardConfig";
export const defaultWidgetsLayout: Layout[] = [
{ w: 1, h: 2, x: 0, y: 0, i: "panel-0", moved: false, static: false },
{ w: 1, h: 1, x: 1, y: 0, i: "panel-1", moved: false, static: false },
{ w: 1, h: 1, x: 1, y: 1, i: "panel-2", moved: false, static: false },
{ w: 1, h: 2, x: 2, y: 0, i: "panel-3", moved: false, static: false },
{ w: 2, h: 2, x: 3, y: 0, i: "panel-4", moved: false, static: false },
{ w: 3, h: 2, x: 5, y: 0, i: "panel-5", moved: false, static: false },
{ w: 1, h: 1, x: 0, y: 2, i: "panel-6", moved: false, static: false },
{ w: 1, h: 1, x: 0, y: 3, i: "panel-7", moved: false, static: false },
{ w: 1, h: 1, x: 1, y: 2, i: "panel-8", moved: false, static: false },
{ w: 1, h: 1, x: 1, y: 3, i: "panel-9", moved: false, static: false },
{ w: 1, h: 1, x: 2, y: 2, i: "panel-10", moved: false, static: false },
{ w: 1, h: 1, x: 2, y: 3, i: "panel-11", moved: false, static: false },
{ w: 4, h: 2, x: 3, y: 2, i: "panel-12", moved: false, static: false },
{ w: 1, h: 1, x: 7, y: 2, i: "panel-13", moved: false, static: false },
{ w: 1, h: 1, x: 7, y: 3, i: "panel-14", moved: false, static: false },
{ w: 8, h: 2, x: 0, y: 4, i: "panel-15", moved: false, static: false },
{ w: 4, h: 2, x: 0, y: 5, i: "panel-16", moved: false, static: false },
{ w: 4, h: 2, x: 5, y: 5, i: "panel-17", moved: false, static: false },
{ w: 8, h: 2, x: 0, y: 7, i: "panel-18", moved: false, static: false },
{ w: 4, h: 2, x: 0, y: 9, i: "panel-19", moved: false, static: false },
{ w: 4, h: 2, x: 5, y: 9, i: "panel-20", moved: false, static: false },
{
w: 1,
h: 2,
x: 0,
y: 0,
minW: 1,
i: "panel-0",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 1,
y: 0,
minW: 1,
i: "panel-1",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 1,
y: 1,
minW: 1,
i: "panel-2",
moved: false,
static: false,
},
{
w: 1,
h: 2,
x: 2,
y: 0,
minW: 1,
i: "panel-3",
moved: false,
static: false,
},
{
w: 2,
h: 2,
x: 3,
y: 0,
minW: 2,
i: "panel-4",
moved: false,
static: false,
},
{
w: 3,
h: 2,
x: 5,
y: 0,
minW: 2,
i: "panel-5",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 0,
y: 2,
minW: 1,
i: "panel-6",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 0,
y: 3,
minW: 1,
i: "panel-7",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 1,
y: 2,
minW: 1,
i: "panel-8",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 1,
y: 3,
minW: 1,
i: "panel-9",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 2,
y: 2,
minW: 1,
i: "panel-10",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 2,
y: 3,
minW: 1,
i: "panel-11",
moved: false,
static: false,
},
{
w: 4,
h: 2,
x: 3,
y: 2,
minW: 2,
i: "panel-12",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 7,
y: 2,
minW: 1,
i: "panel-13",
moved: false,
static: false,
},
{
w: 1,
h: 1,
x: 7,
y: 3,
minW: 1,
i: "panel-14",
moved: false,
static: false,
},
{
w: 8,
h: 2,
x: 0,
y: 4,
minW: 2,
i: "panel-15",
moved: false,
static: false,
},
{
w: 4,
h: 2,
x: 0,
y: 5,
minW: 2,
i: "panel-16",
moved: false,
static: false,
},
{
w: 4,
h: 2,
x: 5,
y: 5,
minW: 2,
i: "panel-17",
moved: false,
static: false,
},
{
w: 8,
h: 2,
x: 0,
y: 7,
minW: 2,
i: "panel-18",
moved: false,
static: false,
},
{
w: 4,
h: 2,
x: 0,
y: 9,
minW: 2,
i: "panel-19",
moved: false,
static: false,
},
{
w: 4,
h: 2,
x: 5,
y: 9,
minW: 2,
i: "panel-20",
moved: false,
static: false,
},
];
const colorsMain = [
@@ -347,6 +536,10 @@ export const panelsConfiguration: IDashboardPanel[] = [
];
const calculateMainValue = (elements: any[], metricCalc: string) => {
if (elements.length === 0) {
return ["", "0"];
}
switch (metricCalc) {
case "mean":
const sumValues = elements.reduce((accumulator, currValue) => {
@@ -394,7 +587,12 @@ export const getWidgetsWithValue = (payload: any[]) => {
case widgetType.singleValue:
if (typeOfPayload === "stat" || typeOfPayload === "singlestat") {
// We sort values & get the last value
const elements = get(payloadData, "targets[0].result[0].values", []);
let elements = get(payloadData, "targets[0].result[0].values", []);
if (elements === null) {
elements = [];
}
const metricCalc = get(
payloadData,
"options.reduceOptions.calcs[0]",
@@ -415,17 +613,22 @@ export const getWidgetsWithValue = (payload: any[]) => {
break;
case widgetType.pieChart:
if (typeOfPayload === "gauge") {
const chartSeries = get(payloadData, "targets[0].result", []);
let chartSeries = get(payloadData, "targets[0].result", []);
if (chartSeries === null) {
chartSeries = [];
}
const metricCalc = get(
payloadData,
"options.reduceOptions.calcs[0]",
"lastNotNull"
);
const totalValues = calculateMainValue(
chartSeries[0].values,
metricCalc
);
const valuesArray =
chartSeries.length > 0 ? chartSeries[0].values : [];
const totalValues = calculateMainValue(valuesArray, metricCalc);
const values = chartSeries.map((elementValue: any) => {
const values = get(elementValue, "values", []);
@@ -549,7 +752,12 @@ export const getWidgetsWithValue = (payload: any[]) => {
break;
case widgetType.barChart:
if (typeOfPayload === "bargauge") {
const chartBars = get(payloadData, "targets[0].result", []);
let chartBars = get(payloadData, "targets[0].result", []);
if (chartBars === null) {
chartBars = [];
}
const sortFunction = (value1: any[], value2: any[]) =>
value1[0] - value2[0];
@@ -567,7 +775,7 @@ export const getWidgetsWithValue = (payload: any[]) => {
const elements = get(metricTake, "values", []);
const sortResult = elements.sort(sortFunction);
const lastValue = sortResult[sortResult.length - 1];
const lastValue = sortResult[sortResult.length - 1] || ["", "0"];
return {
name: structureItem.displayTag,
@@ -584,7 +792,7 @@ export const getWidgetsWithValue = (payload: any[]) => {
const elements = get(elementValue, "values", []);
const sortResult = elements.sort(sortFunction);
const lastValue = sortResult[sortResult.length - 1];
const lastValue = sortResult[sortResult.length - 1] || ["", "0"];
return { name: metricName, a: parseInt(lastValue[1]) };
});
}
@@ -652,5 +860,14 @@ export const getDashboardDistribution = () => {
return defaultWidgetsLayout;
}
return JSON.parse(atob(storedConfiguration));
const parsedConfig = JSON.parse(atob(storedConfiguration));
if (
parsedConfig.length === 0 ||
(parsedConfig.length > 0 && !parsedConfig[0].minW)
) {
return defaultWidgetsLayout;
}
return parsedConfig;
};