Added zoom option to line charts & bar charts in prometheus dashboard (#1104)

This commit is contained in:
Alex
2021-10-11 21:17:18 -05:00
committed by GitHub
parent ebaa1947de
commit d6944ccd3b
12 changed files with 438 additions and 124 deletions

View File

@@ -42,7 +42,6 @@ import { setErrorSnackMessage } from "../../../../../actions";
import { snackBarMessage } from "../../../../../types";
import {
setModalErrorSnackMessage,
setModalSnackMessage,
} from "../../../../../actions";
interface ImodalErrorProps {

View File

@@ -431,6 +431,8 @@ export const widgetCommon = {
borderBottom: "#eef1f4 1px solid",
paddingBottom: 14,
marginBottom: 5,
display: "flex" as const,
justifyContent: "space-between" as const,
},
contentContainer: {
justifyContent: "center" as const,
@@ -470,6 +472,26 @@ export const widgetCommon = {
overflow: "hidden" as const,
textOverflow: "ellipsis" as const,
},
zoomChartCont: {
position: "relative" as const,
height: 340,
width: "100%",
},
zoomChartIcon: {
backgroundColor: "transparent",
border: 0,
padding: 0,
cursor: "pointer",
"& svg": {
color: "#D0D0D0",
height: 16,
},
"&:hover": {
"& svg": {
color: "#404143",
},
},
},
};
export const widgetContainerCommon = {

View File

@@ -25,27 +25,26 @@ import {
actionsTray,
widgetContainerCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import { IDashboardPanel, widgetType } from "./types";
import { IDashboardPanel } from "./types";
import { getWidgetsWithValue, panelsConfiguration } from "./utils";
import { TabPanel } from "../../../shared/tabs";
import { ErrorResponseHandler } from "../../../../common/types";
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";
import SyncIcon from "../../../../icons/SyncIcon";
import TabSelector from "../../Common/TabSelector/TabSelector";
import SimpleWidget from "./Widgets/SimpleWidget";
import MergedWidgets from "./MergedWidgets";
import { componentToUse } from "./widgetUtils";
import ZoomWidget from "./ZoomWidget";
import { AppState } from "../../../../store";
interface IPrDashboard {
classes: any;
displayErrorMessage: typeof setErrorSnackMessage;
apiPrefix?: string;
zoomOpen: boolean;
zoomWidget: null | IDashboardPanel;
}
const styles = (theme: Theme) =>
@@ -82,6 +81,8 @@ const PrDashboard = ({
classes,
displayErrorMessage,
apiPrefix = "admin",
zoomOpen,
zoomWidget,
}: IPrDashboard) => {
const [timeStart, setTimeStart] = useState<any>(null);
const [timeEnd, setTimeEnd] = useState<any>(null);
@@ -92,88 +93,6 @@ const PrDashboard = ({
const panels = useCallback(
(tabName: string, filterPanels?: number[][] | null) => {
const componentToUse = (value: IDashboardPanel, index: number) => {
switch (value.type) {
case widgetType.singleValue:
return (
<SingleValueWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.simpleWidget:
return (
<SimpleWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
iconWidget={value.widgetIcon}
/>
);
case widgetType.pieChart:
return (
<PieChartWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.linearGraph:
case widgetType.areaGraph:
return (
<LinearGraphWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
hideYAxis={value.disableYAxis}
xAxisFormatter={value.xAxisFormatter}
yAxisFormatter={value.yAxisFormatter}
apiPrefix={apiPrefix}
areaWidget={value.type === widgetType.areaGraph}
/>
);
case widgetType.barChart:
return (
<BarChartWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.singleRep:
const fillColor = value.fillColor ? value.fillColor : value.color;
return (
<SingleRepWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
color={value.color as string}
fillColor={fillColor as string}
apiPrefix={apiPrefix}
/>
);
default:
return null;
}
};
return filterPanels?.map((panelLine, indexLine) => {
const totalPanelsContained = panelLine.length;
@@ -216,16 +135,28 @@ const PrDashboard = ({
title={panelInfo.title}
leftComponent={componentToUse(
panelInfo.mergedPanels[0],
0
timeStart,
timeEnd,
loading,
apiPrefix
)}
rightComponent={componentToUse(
panelInfo.mergedPanels[1],
1
timeStart,
timeEnd,
loading,
apiPrefix
)}
/>
</Fragment>
) : (
componentToUse(panelInfo, indexPanel)
componentToUse(
panelInfo,
timeStart,
timeEnd,
loading,
apiPrefix
)
)}
</Fragment>
) : null}
@@ -313,6 +244,16 @@ const PrDashboard = ({
return (
<Fragment>
{zoomOpen && (
<ZoomWidget
modalOpen={zoomOpen}
timeStart={timeStart}
timeEnd={timeEnd}
widgetRender={0}
value={zoomWidget}
apiPrefix={apiPrefix}
/>
)}
<Grid
item
xs={12}
@@ -379,11 +320,13 @@ const PrDashboard = ({
</Fragment>
);
};
/*
<
*/
const connector = connect(null, {
const mapState = (state: AppState) => ({
zoomOpen: state.dashboard.zoom.openZoom,
zoomWidget: state.dashboard.zoom.widgetRender,
});
const connector = connect(mapState, {
displayErrorMessage: setErrorSnackMessage,
});

View File

@@ -28,6 +28,7 @@ import {
import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date";
import { CircularProgress } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import ZoomOutMapIcon from "@material-ui/icons/ZoomOutMap";
import { IBarChartConfiguration } from "./types";
import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary";
import BarChartTooltip from "./tooltips/BarChartTooltip";
@@ -36,6 +37,7 @@ import { IDashboardPanel } from "../types";
import { widgetDetailsToPanel } from "../utils";
import { ErrorResponseHandler } from "../../../../../common/types";
import api from "../../../../../common/api";
import { openZoomPage } from "../../actions";
interface IBarChartWidget {
classes: any;
@@ -46,6 +48,8 @@ interface IBarChartWidget {
propLoading: boolean;
displayErrorMessage: any;
apiPrefix: string;
zoomActivated?: boolean;
openZoomPage: typeof openZoomPage;
}
const styles = (theme: Theme) =>
@@ -84,6 +88,8 @@ const BarChartWidget = ({
propLoading,
displayErrorMessage,
apiPrefix,
zoomActivated = false,
openZoomPage,
}: IBarChartWidget) => {
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<any>([]);
@@ -147,15 +153,31 @@ const BarChartWidget = ({
}
return (
<div className={classes.singleValueContainer}>
<div className={classes.titleContainer}>{title}</div>
<div className={zoomActivated ? "" : classes.singleValueContainer}>
{!zoomActivated && (
<div className={classes.titleContainer}>
{title}{" "}
<button
onClick={() => {
openZoomPage(panelItem);
}}
className={classes.zoomChartIcon}
>
<ZoomOutMapIcon />
</button>
</div>
)}
{loading && (
<div className={classes.loadingAlign}>
<CircularProgress />
</div>
)}
{!loading && (
<div className={classes.contentContainer}>
<div
className={
zoomActivated ? classes.zoomChartCont : classes.contentContainer
}
>
<ResponsiveContainer width="99%">
<BarChart
data={data as object[]}
@@ -178,7 +200,7 @@ const BarChartWidget = ({
dataKey={bar.dataKey}
fill={bar.color}
background={bar.background}
barSize={12}
barSize={zoomActivated ? 25 : 12}
>
{barChartConfiguration.length === 1 ? (
<Fragment>
@@ -214,6 +236,7 @@ const BarChartWidget = ({
const connector = connect(null, {
displayErrorMessage: setErrorSnackMessage,
openZoomPage: openZoomPage,
});
export default withStyles(styles)(connector(BarChartWidget));

View File

@@ -14,7 +14,7 @@
// 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, useState } from "react";
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import {
Area,
@@ -28,6 +28,7 @@ import {
import { CircularProgress } from "@material-ui/core";
import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import ZoomOutMapIcon from "@material-ui/icons/ZoomOutMap";
import { ILinearGraphConfiguration } from "./types";
import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary";
import { IDashboardPanel } from "../types";
@@ -36,6 +37,7 @@ import { widgetDetailsToPanel } from "../utils";
import { ErrorResponseHandler } from "../../../../../common/types";
import api from "../../../../../common/api";
import LineChartTooltip from "./tooltips/LineChartTooltip";
import { openZoomPage } from "../../actions";
interface ILinearGraphWidget {
classes: any;
@@ -50,6 +52,8 @@ interface ILinearGraphWidget {
yAxisFormatter?: (item: string) => string;
xAxisFormatter?: (item: string) => string;
areaWidget?: boolean;
zoomActivated?: boolean;
openZoomPage: typeof openZoomPage;
}
const styles = (theme: Theme) =>
@@ -61,6 +65,9 @@ const styles = (theme: Theme) =>
height: "100%",
flexGrow: 1,
},
verticalAlignment: {
flexDirection: "column",
},
chartCont: {
position: "relative",
height: 140,
@@ -70,7 +77,7 @@ const styles = (theme: Theme) =>
display: "flex",
flexDirection: "column",
flex: "0 1 auto",
height: 130,
maxHeight: 130,
margin: 0,
overflowY: "auto",
position: "relative",
@@ -99,6 +106,8 @@ const LinearGraphWidget = ({
areaWidget = false,
yAxisFormatter = (item: string) => item,
xAxisFormatter = (item: string) => item,
zoomActivated = false,
openZoomPage,
}: ILinearGraphWidget) => {
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<object[]>([]);
@@ -174,13 +183,33 @@ const LinearGraphWidget = ({
};
return (
<div className={classes.singleValueContainer}>
<div className={classes.titleContainer}>{title}</div>
<div className={classes.containerElements}>
<div className={zoomActivated ? "" : classes.singleValueContainer}>
{!zoomActivated && (
<div className={classes.titleContainer}>
{title}{" "}
<button
onClick={() => {
openZoomPage(panelItem);
}}
className={classes.zoomChartIcon}
>
<ZoomOutMapIcon />
</button>
</div>
)}
<div
className={
zoomActivated ? classes.verticalAlignment : classes.containerElements
}
>
{loading && <CircularProgress className={classes.loadingAlign} />}
{!loading && (
<React.Fragment>
<div className={classes.chartCont}>
<div
className={
zoomActivated ? classes.zoomChartCont : classes.chartCont
}
>
<ResponsiveContainer width="99%">
<AreaChart
data={data}
@@ -267,24 +296,33 @@ const LinearGraphWidget = ({
</ResponsiveContainer>
</div>
{!areaWidget && (
<div className={classes.legendChart}>
{linearConfiguration.map((section, index) => {
return (
<div
className={classes.singleLegendContainer}
key={`legend-${section.keyLabel}-${index.toString()}`}
>
<Fragment>
{zoomActivated && (
<Fragment>
<strong>Series</strong>
<br />
<br />
</Fragment>
)}
<div className={classes.legendChart}>
{linearConfiguration.map((section, index) => {
return (
<div
className={classes.colorContainer}
style={{ backgroundColor: section.lineColor }}
/>
<div className={classes.legendLabel}>
{section.keyLabel}
className={classes.singleLegendContainer}
key={`legend-${section.keyLabel}-${index.toString()}`}
>
<div
className={classes.colorContainer}
style={{ backgroundColor: section.lineColor }}
/>
<div className={classes.legendLabel}>
{section.keyLabel}
</div>
</div>
</div>
);
})}
</div>
);
})}
</div>
</Fragment>
)}
</React.Fragment>
)}
@@ -295,6 +333,7 @@ const LinearGraphWidget = ({
const connector = connect(null, {
displayErrorMessage: setErrorSnackMessage,
openZoomPage: openZoomPage,
});
export default withStyles(styles)(connector(LinearGraphWidget));

View File

@@ -0,0 +1,66 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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, { Fragment } from "react";
import { connect } from "react-redux";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { IDashboardPanel } from "./types";
import { componentToUse } from "./widgetUtils";
import { closeZoomPage } from "../actions";
interface IZoomWidget {
widgetRender: number;
value: IDashboardPanel | null;
modalOpen: boolean;
timeStart: any;
timeEnd: any;
apiPrefix: string;
onCloseAction: typeof closeZoomPage;
}
const ZoomWidget = ({
value,
modalOpen,
timeStart,
timeEnd,
apiPrefix,
onCloseAction,
}: IZoomWidget) => {
if (!value) {
return null;
}
return (
<ModalWrapper
title={value.title}
onClose={() => {
onCloseAction();
}}
modalOpen={modalOpen}
wideLimit={false}
noContentPadding
>
<Fragment>
{componentToUse(value, timeStart, timeEnd, true, apiPrefix, true)}
</Fragment>
</ModalWrapper>
);
};
const connector = connect(null, {
onCloseAction: closeZoomPage,
});
export default connector(ZoomWidget);

View File

@@ -27,7 +27,6 @@ import {
} from "../../../../common/utils";
import HealIcon from "../../../../icons/HealIcon";
import DiagnosticsIcon from "../../../../icons/DiagnosticsIcon";
import HistoryIcon from "../../../../icons/HistoryIcon";
import { UptimeIcon } from "../../../../icons";
const colorsMain = [

View File

@@ -0,0 +1,115 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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 from "react";
import { IDashboardPanel, widgetType } from "./types";
import BarChartWidget from "./Widgets/BarChartWidget";
import LinearGraphWidget from "./Widgets/LinearGraphWidget";
import PieChartWidget from "./Widgets/PieChartWidget";
import SimpleWidget from "./Widgets/SimpleWidget";
import SingleRepWidget from "./Widgets/SingleRepWidget";
import SingleValueWidget from "./Widgets/SingleValueWidget";
export const componentToUse = (
value: IDashboardPanel,
timeStart: any,
timeEnd: any,
loading: boolean,
apiPrefix: string,
zoomActivated: boolean = false
) => {
switch (value.type) {
case widgetType.singleValue:
return (
<SingleValueWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.simpleWidget:
return (
<SimpleWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
iconWidget={value.widgetIcon}
/>
);
case widgetType.pieChart:
return (
<PieChartWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.linearGraph:
case widgetType.areaGraph:
return (
<LinearGraphWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
hideYAxis={value.disableYAxis}
xAxisFormatter={value.xAxisFormatter}
yAxisFormatter={value.yAxisFormatter}
apiPrefix={apiPrefix}
areaWidget={value.type === widgetType.areaGraph}
zoomActivated={zoomActivated}
/>
);
case widgetType.barChart:
return (
<BarChartWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
zoomActivated={zoomActivated}
/>
);
case widgetType.singleRep:
const fillColor = value.fillColor ? value.fillColor : value.color;
return (
<SingleRepWidget
title={value.title}
panelItem={value}
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
color={value.color as string}
fillColor={fillColor as string}
apiPrefix={apiPrefix}
/>
);
default:
return null;
}
};

View File

@@ -0,0 +1,44 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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 { IDashboardPanel } from "./Prometheus/types";
export const DASHBOARD_OPEN_ZOOM = "DASHBOARD/OPEN_ZOOM";
export const DASHBOARD_CLOSE_ZOOM = "DASHBOARD/CLOSE_ZOOM";
interface OpenChartZoom {
type: typeof DASHBOARD_OPEN_ZOOM;
widget: IDashboardPanel;
}
interface CloseChartZoom {
type: typeof DASHBOARD_CLOSE_ZOOM;
}
export type ZoomActionTypes = OpenChartZoom | CloseChartZoom;
export function openZoomPage(widget: IDashboardPanel) {
return {
type: DASHBOARD_OPEN_ZOOM,
widget,
};
}
export function closeZoomPage() {
return {
type: DASHBOARD_CLOSE_ZOOM,
};
}

View File

@@ -0,0 +1,55 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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 { zoomState } from "./types";
import { ZoomActionTypes, DASHBOARD_OPEN_ZOOM, DASHBOARD_CLOSE_ZOOM } from "./actions";
export interface DashboardState {
zoom: zoomState;
}
const initialState: DashboardState = {
zoom: {
openZoom: false,
widgetRender: null,
},
};
export function dashboardReducer(
state = initialState,
action: ZoomActionTypes
): DashboardState {
switch (action.type) {
case DASHBOARD_OPEN_ZOOM:
return {
...state,
zoom: {
openZoom: true,
widgetRender: { ...action.widget },
},
};
case DASHBOARD_CLOSE_ZOOM:
return {
...state,
zoom: {
openZoom: false,
widgetRender: null,
},
};
default:
return state;
}
}

View File

@@ -14,6 +14,8 @@
// 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 { IDashboardPanel } from "./Prometheus/types";
export interface Usage {
usage: number;
buckets: number;
@@ -45,3 +47,8 @@ export interface IDriveInfo {
usedSpace: number;
availableSpace: number;
}
export interface zoomState {
openZoom: boolean,
widgetRender: null | IDashboardPanel,
}

View File

@@ -26,6 +26,7 @@ import { bucketsReducer } from "./screens/Console/Buckets/reducers";
import { objectBrowserReducer } from "./screens/Console/ObjectBrowser/reducers";
import { tenantsReducer } from "./screens/Console/Tenants/reducer";
import { directCSIReducer } from "./screens/Console/DirectCSI/reducer";
import { dashboardReducer } from "./screens/Console/Dashboard/reducer";
const globalReducer = combineReducers({
system: systemReducer,
@@ -38,6 +39,7 @@ const globalReducer = combineReducers({
healthInfo: healthInfoReducer,
tenants: tenantsReducer,
directCSI: directCSIReducer,
dashboard: dashboardReducer,
});
declare global {