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;