diff --git a/portal-ui/package.json b/portal-ui/package.json index 2a1d9001a..3c33f1915 100644 --- a/portal-ui/package.json +++ b/portal-ui/package.json @@ -33,6 +33,7 @@ "moment": "^2.29.4", "react": "^18.1.0", "react-chartjs-2": "^2.9.0", + "react-component-export-image": "^1.0.6", "react-copy-to-clipboard": "^5.0.2", "react-dom": "^18.1.0", "react-dropzone": "^11.4.2", diff --git a/portal-ui/src/screens/Console/Dashboard/DownloadWidgetDataButton.tsx b/portal-ui/src/screens/Console/Dashboard/DownloadWidgetDataButton.tsx new file mode 100644 index 000000000..6e71e5ade --- /dev/null +++ b/portal-ui/src/screens/Console/Dashboard/DownloadWidgetDataButton.tsx @@ -0,0 +1,160 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 . + +import React, { Fragment } from "react"; +import { Menu, MenuItem, Box } from "@mui/material"; +import ListItemText from "@mui/material/ListItemText"; +import { DownloadIcon } from "../../../icons"; +import { exportComponentAsPNG } from "react-component-export-image"; +import { ErrorResponseHandler } from "../../../common/types"; +import { useAppDispatch } from "../../../../src/store"; +import { setErrorSnackMessage } from "../../../../src/systemSlice"; +interface IDownloadWidgetDataButton { + title: any; + componentRef: any; + data: any; +} + +const DownloadWidgetDataButton = ({ + title, + componentRef, + data, +}: IDownloadWidgetDataButton) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const openDownloadMenu = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleCloseDownload = () => { + setAnchorEl(null); + }; + const download = (filename: string, text: string) => { + let element = document.createElement("a"); + element.setAttribute("href", "data:text/plain;charset=utf-8," + text); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + document.body.removeChild(element); + }; + + const dispatch = useAppDispatch(); + const onDownloadError = (err: ErrorResponseHandler) => + dispatch(setErrorSnackMessage(err)); + + const convertToCSV = (objectToConvert: any) => { + const array = [Object.keys(objectToConvert[0])].concat(objectToConvert); + return array + .map((it) => { + return Object.values(it).toString(); + }) + .join("\n"); + }; + + const widgetDataCSVFileName = () => { + if (title !== null) { + return (title + "_" + Date.now().toString() + ".csv") + .replace(/\s+/g, "") + .trim() + .toLowerCase(); + } else { + return "widgetData_" + Date.now().toString() + ".csv"; + } + }; + + const downloadAsCSV = () => { + if (data !== null && data.length > 0) { + download(widgetDataCSVFileName(), convertToCSV(data)); + } else { + let err: ErrorResponseHandler; + err = { + errorMessage: "Unable to download widget data", + detailedError: "Unable to download widget data - data not available", + }; + onDownloadError(err); + } + }; + + const downloadAsPNG = () => { + if (title !== null) { + const pngFileName = (title + "_" + Date.now().toString() + ".png") + .replace(/\s+/g, "") + .trim() + .toLowerCase(); + exportComponentAsPNG(componentRef, { fileName: pngFileName }); + } else { + const pngFileName = "widgetData_" + Date.now().toString() + ".png"; + exportComponentAsPNG(componentRef, { fileName: pngFileName }); + } + }; + + return ( + + + + { + handleCloseDownload(); + }} + > + { + downloadAsCSV(); + }} + > + Download as CSV + + { + downloadAsPNG(); + }} + > + Download as PNG + + + + + ); +}; + +export default DownloadWidgetDataButton; diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx index aa82f315d..f90897034 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx @@ -17,7 +17,6 @@ import React, { Fragment, useCallback, useEffect, useState } from "react"; import { useSelector } from "react-redux"; import Grid from "@mui/material/Grid"; - import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; @@ -146,17 +145,26 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => { {panelInfo ? ( - {panelInfo.mergedPanels ? ( - - ) : ( - componentToUse(panelInfo, timeStart, timeEnd, loading, apiPrefix) - )} + + {panelInfo.mergedPanels ? ( + + ) : ( + componentToUse( + panelInfo, + timeStart, + timeEnd, + loading, + apiPrefix, + zoomOpen + ) + )} + ) : null} diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx index e4083d9b6..8b4549306 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { Fragment, useEffect, useState } from "react"; +import React, { Fragment, useEffect, useState, useRef } from "react"; import { Bar, @@ -25,14 +25,13 @@ import { XAxis, YAxis, } from "recharts"; -import { useMediaQuery } from "@mui/material"; +import { useMediaQuery, Grid } from "@mui/material"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; import { IBarChartConfiguration } from "./types"; import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary"; import BarChartTooltip from "./tooltips/BarChartTooltip"; - import { IDashboardPanel } from "../types"; import { widgetDetailsToPanel } from "../utils"; import { ErrorResponseHandler } from "../../../../../common/types"; @@ -42,6 +41,7 @@ import Loader from "../../../Common/Loader/Loader"; import ExpandGraphLink from "./ExpandGraphLink"; import { setErrorSnackMessage } from "../../../../../systemSlice"; import { useAppDispatch } from "../../../../../store"; +import DownloadWidgetDataButton from "../../DownloadWidgetDataButton"; interface IBarChartWidget { classes: any; @@ -95,6 +95,15 @@ const BarChartWidget = ({ const [loading, setLoading] = useState(true); const [data, setData] = useState([]); const [result, setResult] = useState(null); + const [hover, setHover] = useState(false); + const componentRef = useRef(); + + const onHover = () => { + setHover(true); + }; + const onStopHover = () => { + setHover(false); + }; useEffect(() => { if (propLoading) { @@ -157,11 +166,27 @@ const BarChartWidget = ({ const biggerThanMd = useMediaQuery(theme.breakpoints.up("md")); return ( -
+
{!zoomActivated && ( -
- {title} -
+ + +
{title}
+
+ + {hover && } + + + + +
)} {loading && (
@@ -170,6 +195,7 @@ const BarChartWidget = ({ )} {!loading && (
} className={ zoomActivated ? classes.zoomChartCont : classes.contentContainer } diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/ExpandGraphLink.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/ExpandGraphLink.tsx index 3b90e2238..3d0f62555 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/ExpandGraphLink.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/ExpandGraphLink.tsx @@ -27,8 +27,7 @@ const ExpandGraphLink = ({ panelItem }: { panelItem: IDashboardPanel }) => { return ( { }, }} > - { - e.preventDefault(); - dispatch(openZoomPage(panelItem)); - }} - > - Expand Graph -