github.com/minio/console@v1.4.1/web-app/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 import React, { Fragment, useEffect, useRef, useState } from "react"; 18 import styled from "styled-components"; 19 import { Box, breakPoints, Grid, Loader } from "mds"; 20 import { useSelector } from "react-redux"; 21 import { 22 Bar, 23 BarChart, 24 Cell, 25 ResponsiveContainer, 26 Tooltip, 27 XAxis, 28 YAxis, 29 } from "recharts"; 30 import { IBarChartConfiguration } from "./types"; 31 import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary"; 32 import { IDashboardPanel } from "../types"; 33 import { widgetDetailsToPanel } from "../utils"; 34 import { ErrorResponseHandler } from "../../../../../common/types"; 35 import { setErrorSnackMessage } from "../../../../../systemSlice"; 36 import { AppState, useAppDispatch } from "../../../../../store"; 37 import ExpandGraphLink from "./ExpandGraphLink"; 38 import DownloadWidgetDataButton from "../../DownloadWidgetDataButton"; 39 import BarChartTooltip from "./tooltips/BarChartTooltip"; 40 import api from "../../../../../common/api"; 41 42 interface IBarChartWidget { 43 title: string; 44 panelItem: IDashboardPanel; 45 timeStart: any; 46 timeEnd: any; 47 apiPrefix: string; 48 zoomActivated?: boolean; 49 } 50 51 const BarChartMain = styled.div(({ theme }) => ({ 52 ...widgetCommon(theme), 53 loadingAlign: { 54 width: "100%", 55 paddingTop: "15px", 56 textAlign: "center", 57 margin: "auto", 58 }, 59 })); 60 61 const CustomizedAxisTick = ({ y, payload }: any) => { 62 return ( 63 <text 64 width={50} 65 fontSize={"69.7%"} 66 textAnchor="start" 67 fill="#333" 68 transform={`translate(5,${y})`} 69 fontWeight={400} 70 dy={3} 71 > 72 {payload.value} 73 </text> 74 ); 75 }; 76 77 const BarChartWidget = ({ 78 title, 79 panelItem, 80 timeStart, 81 timeEnd, 82 apiPrefix, 83 zoomActivated = false, 84 }: IBarChartWidget) => { 85 const dispatch = useAppDispatch(); 86 const [loading, setLoading] = useState<boolean>(false); 87 const [data, setData] = useState<any>([]); 88 const [result, setResult] = useState<IDashboardPanel | null>(null); 89 const [hover, setHover] = useState<boolean>(false); 90 const [biggerThanMd, setBiggerThanMd] = useState<boolean>( 91 window.innerWidth >= breakPoints.md, 92 ); 93 94 const componentRef = useRef<HTMLElement>(); 95 const widgetVersion = useSelector( 96 (state: AppState) => state.dashboard.widgetLoadVersion, 97 ); 98 99 const onHover = () => { 100 setHover(true); 101 }; 102 const onStopHover = () => { 103 setHover(false); 104 }; 105 106 useEffect(() => { 107 setLoading(true); 108 }, [widgetVersion]); 109 110 useEffect(() => { 111 const handleWindowResize = () => { 112 let extMD = false; 113 if (window.innerWidth >= breakPoints.md) { 114 extMD = true; 115 } 116 setBiggerThanMd(extMD); 117 }; 118 119 window.addEventListener("resize", handleWindowResize); 120 121 return () => { 122 window.removeEventListener("resize", handleWindowResize); 123 }; 124 }, []); 125 126 useEffect(() => { 127 if (loading) { 128 let stepCalc = 0; 129 if (timeStart !== null && timeEnd !== null) { 130 const secondsInPeriod = 131 timeEnd.toUnixInteger() - timeStart.toUnixInteger(); 132 const periods = Math.floor(secondsInPeriod / 60); 133 134 stepCalc = periods < 1 ? 15 : periods; 135 } 136 137 api 138 .invoke( 139 "GET", 140 `/api/v1/${apiPrefix}/info/widgets/${ 141 panelItem.id 142 }/?step=${stepCalc}&${ 143 timeStart !== null ? `&start=${timeStart.toUnixInteger()}` : "" 144 }${timeStart !== null && timeEnd !== null ? "&" : ""}${ 145 timeEnd !== null ? `end=${timeEnd.toUnixInteger()}` : "" 146 }`, 147 ) 148 .then((res: any) => { 149 const widgetsWithValue = widgetDetailsToPanel(res, panelItem); 150 setData(widgetsWithValue.data); 151 setResult(widgetsWithValue); 152 setLoading(false); 153 }) 154 .catch((err: ErrorResponseHandler) => { 155 dispatch(setErrorSnackMessage(err)); 156 setLoading(false); 157 }); 158 } 159 }, [loading, panelItem, timeEnd, timeStart, dispatch, apiPrefix]); 160 161 const barChartConfiguration = result 162 ? (result.widgetConfiguration as IBarChartConfiguration[]) 163 : []; 164 165 let greatestIndex = 0; 166 let currentValue = 0; 167 168 if (barChartConfiguration.length === 1) { 169 const dataGraph = barChartConfiguration[0]; 170 data.forEach((item: any, index: number) => { 171 if (item[dataGraph.dataKey] > currentValue) { 172 currentValue = item[dataGraph.dataKey]; 173 greatestIndex = index; 174 } 175 }); 176 } 177 178 return ( 179 <BarChartMain> 180 <Box 181 className={zoomActivated ? "" : "singleValueContainer"} 182 onMouseOver={onHover} 183 onMouseLeave={onStopHover} 184 > 185 {!zoomActivated && ( 186 <Grid container> 187 <Grid 188 item 189 xs={10} 190 sx={{ alignItems: "start", justifyItems: "start" }} 191 > 192 <div className={"titleContainer"}>{title}</div> 193 </Grid> 194 <Grid 195 item 196 xs={1} 197 sx={{ display: "flex", justifyContent: "flex-end" }} 198 > 199 {hover && <ExpandGraphLink panelItem={panelItem} />} 200 </Grid> 201 <Grid 202 item 203 xs={1} 204 sx={{ display: "flex", justifyContent: "flex-end" }} 205 > 206 <DownloadWidgetDataButton 207 title={title} 208 componentRef={componentRef} 209 data={data} 210 /> 211 </Grid> 212 </Grid> 213 )} 214 {loading && ( 215 <Box className={"loadingAlign"}> 216 <Loader /> 217 </Box> 218 )} 219 {!loading && ( 220 <div 221 ref={componentRef as React.RefObject<HTMLDivElement>} 222 className={zoomActivated ? "zoomChartCont" : "contentContainer"} 223 > 224 <ResponsiveContainer width="99%"> 225 <BarChart 226 data={data as object[]} 227 layout={"vertical"} 228 barCategoryGap={1} 229 > 230 <XAxis type="number" hide /> 231 <YAxis 232 dataKey="name" 233 type="category" 234 interval={0} 235 tick={<CustomizedAxisTick />} 236 tickLine={false} 237 axisLine={false} 238 width={150} 239 hide={!biggerThanMd} 240 style={{ 241 fontSize: "12px", 242 fontWeight: 100, 243 }} 244 /> 245 {barChartConfiguration.map((bar) => ( 246 <Bar 247 key={`bar-${bar.dataKey}`} 248 dataKey={bar.dataKey} 249 fill={bar.color} 250 background={bar.background} 251 barSize={zoomActivated ? 25 : 12} 252 > 253 {barChartConfiguration.length === 1 ? ( 254 <Fragment> 255 {data.map((_: any, index: number) => ( 256 <Cell 257 key={`chart-bar-${index.toString()}`} 258 fill={ 259 index === greatestIndex 260 ? bar.greatestColor 261 : bar.color 262 } 263 /> 264 ))} 265 </Fragment> 266 ) : null} 267 </Bar> 268 ))} 269 <Tooltip 270 cursor={{ fill: "rgba(255, 255, 255, 0.3)" }} 271 content={ 272 <BarChartTooltip 273 barChartConfiguration={barChartConfiguration} 274 /> 275 } 276 /> 277 </BarChart> 278 </ResponsiveContainer> 279 </div> 280 )} 281 </Box> 282 </BarChartMain> 283 ); 284 }; 285 286 export default BarChartWidget;