github.com/pelicanplatform/pelican@v1.0.5/web_ui/frontend/components/graphs/RateGraph.tsx (about)

     1  /***************************************************************
     2   *
     3   * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License"); you
     6   * may not use this file except in compliance with the License.  You may
     7   * obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   ***************************************************************/
    18  
    19  "use client"
    20  
    21  import dynamic from "next/dynamic";
    22  
    23  import {useEffect, useState} from "react";
    24  import {
    25      ChartOptions,
    26      ChartDataset,
    27      ChartData
    28  } from 'chart.js';
    29  
    30  import {DateTime} from "luxon";
    31  
    32  import 'chartjs-adapter-luxon';
    33  
    34  import {BoxProps, Button, FormControl, Grid, IconButton, InputLabel, MenuItem, Paper, Select} from "@mui/material";
    35  
    36  import {Box} from "@mui/material";
    37  
    38  import {query_rate, TimeDuration, DurationType} from "@/components/graphs/prometheus";
    39  import {AutoGraphOutlined, CalendarMonth, QuestionMark, ReplayOutlined} from "@mui/icons-material";
    40  import {DatePicker, LocalizationProvider} from "@mui/x-date-pickers";
    41  import {AdapterLuxon} from "@mui/x-date-pickers/AdapterLuxon";
    42  import TextField from "@mui/material/TextField";
    43  
    44  const Graph = dynamic(
    45      () => import('@/components/graphs/Graph'),
    46      { ssr: false }
    47  )
    48  
    49  function DrawerBox({children, hidden=false}: {children: any, hidden: boolean}) {
    50  
    51      return (
    52          <Box
    53              sx={{
    54                  display: "flex",
    55                  overflow: "hidden",
    56                  maxHeight: hidden ? 0 : "200px",
    57                  m: hidden ? 0 : 2,
    58                  pt: hidden ? 0 : 2,
    59                  flexDirection: "column",
    60                  transition: "all 0.2s ease",
    61              }}>
    62              {children}
    63          </Box>
    64      )
    65  }
    66  
    67  
    68  interface RateGraphDrawerProps {
    69      reset: Function;
    70      rate: TimeDuration;
    71      resolution: TimeDuration;
    72      duration: TimeDuration;
    73      time: DateTime;
    74      setRate: Function
    75      setResolution: Function
    76      setDuration: Function
    77      setTime: Function
    78  }
    79  
    80  function RateGraphDrawer({reset, rate, resolution, duration, time, setRate, setResolution, setDuration, setTime}: RateGraphDrawerProps) {
    81  
    82      const [reportPeriodHidden, setReportPeriodHidden] = useState<boolean>(true)
    83      const [graphSettingsHidden, setGraphSettingsHidden] = useState<boolean>(true)
    84  
    85      const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
    86  
    87      useEffect(() => {
    88          setDrawerOpen(!reportPeriodHidden || !graphSettingsHidden)
    89      }, [reportPeriodHidden, graphSettingsHidden])
    90  
    91      return (
    92          <Paper elevation={drawerOpen ? 1 : 0} sx={{display: "flex", flexGrow: 1, flexDirection: "column", transition: "all 0.5s ease", backgroundColor: drawerOpen ? "#fff" : "#fff0" }}>
    93              <Box display={"flex"} flexGrow={1} m={1} mb={0}>
    94                  <IconButton size={"small"} onClick={() => reset()}><ReplayOutlined /></IconButton>
    95                  <Button
    96                      size={"small"}
    97                      variant="outlined"
    98                      startIcon={<CalendarMonth />}
    99                      onClick={() => {
   100                          setReportPeriodHidden(!reportPeriodHidden);
   101                          setGraphSettingsHidden(true)
   102                      }}
   103                  >
   104                      Report Period
   105                  </Button>
   106                  <Button
   107                      size={"small"}
   108                      variant="outlined"
   109                      startIcon={<AutoGraphOutlined />}
   110                      onClick={() => {
   111                          setGraphSettingsHidden(!graphSettingsHidden);
   112                          setReportPeriodHidden(true)
   113                      }}
   114                      sx={{marginLeft: 1}}
   115                  >
   116                      Graph Settings
   117                  </Button>
   118              </Box>
   119              <Box>
   120                  <DrawerBox hidden={reportPeriodHidden}>
   121                      <Grid container spacing={2}>
   122                          <Grid item xs={12} md={6}>
   123                              <FormControl fullWidth>
   124                                  <InputLabel id="select-report-length-label">Report Length</InputLabel>
   125                                  <Select
   126                                      labelId="select-report-length-label"
   127                                      id="select-report-length"
   128                                      label="Report Length"
   129                                      value={duration.value}
   130                                      onChange={(e) => setDuration(new TimeDuration(e.target.value as number, duration.type))}
   131                                  >
   132                                      <MenuItem value={1}>Day</MenuItem>
   133                                      <MenuItem value={7}>Week</MenuItem>
   134                                      <MenuItem value={31}>Month</MenuItem>
   135                                  </Select>
   136                              </FormControl>
   137                          </Grid>
   138                          <Grid item xs={12} md={6}>
   139                              <LocalizationProvider dateAdapter={AdapterLuxon}>
   140                                  <DatePicker sx={{width: "100%"}} label={"Report End"} value={time} onChange={d => {console.log(d); setTime(d)}} />
   141                              </LocalizationProvider>
   142                          </Grid>
   143                      </Grid>
   144                  </DrawerBox>
   145                  <DrawerBox hidden={graphSettingsHidden}>
   146                      <Grid container spacing={2}>
   147                          <Grid item xs={12}>
   148                              <Grid container spacing={1}>
   149                                  <Grid item xs={6}>
   150                                      <FormControl fullWidth>
   151                                          <TextField
   152                                              label={"Rate Time Range"}
   153                                              type={"number"}
   154                                              inputProps={{min:0}}
   155                                              value={rate.value}
   156                                              onChange={(e) => setRate(new TimeDuration(parseInt(e.target.value), rate.type))}
   157                                          ></TextField>
   158                                      </FormControl>
   159                                  </Grid>
   160                                  <Grid item xs={5}>
   161                                      <FormControl fullWidth>
   162                                          <InputLabel id="select-report-length-label">Unit</InputLabel>
   163                                          <Select
   164                                              labelId="select-report-length-label"
   165                                              id="select-report-length"
   166                                              label="Report Length"
   167                                              value={rate.type}
   168                                              onChange={(e) => setRate(new TimeDuration(rate.value, e.target.value as DurationType))}
   169                                          >
   170                                              <MenuItem value={"m"}>Minute</MenuItem>
   171                                              <MenuItem value={"h"}>Hour</MenuItem>
   172                                              <MenuItem value={"d"}>Day</MenuItem>
   173                                          </Select>
   174                                      </FormControl>
   175                                  </Grid>
   176                                  <Grid item xs={1} m={"auto"}>
   177                                      <IconButton href={"https://prometheus.io/docs/prometheus/latest/querying/functions/#rate"} size={"small"}><QuestionMark/></IconButton>
   178                                  </Grid>
   179                              </Grid>
   180                          </Grid>
   181                          <Grid item xs={12}>
   182                              <Grid container spacing={1}>
   183                                  <Grid item xs={6}>
   184                                      <FormControl fullWidth>
   185                                          <TextField
   186                                              label={"Resolution"}
   187                                              type={"number"}
   188                                              inputProps={{min:0}}
   189                                              value={resolution.value}
   190                                              onChange={(e) => setResolution(new TimeDuration(parseInt(e.target.value), resolution.type))}
   191                                          ></TextField>
   192                                      </FormControl>
   193                                  </Grid>
   194                                  <Grid item xs={5}>
   195                                      <FormControl fullWidth>
   196                                          <InputLabel id="select-report-length-label">Unit</InputLabel>
   197                                          <Select
   198                                              labelId="select-report-length-label"
   199                                              id="select-report-length"
   200                                              label="Report Length"
   201                                              value={resolution.type}
   202                                              onChange={(e) => setResolution(new TimeDuration(resolution.value, e.target.value as DurationType))}
   203                                          >
   204                                              <MenuItem value={"m"}>Minute</MenuItem>
   205                                              <MenuItem value={"h"}>Hour</MenuItem>
   206                                              <MenuItem value={"d"}>Day</MenuItem>
   207                                          </Select>
   208                                      </FormControl>
   209                                  </Grid>
   210                                  <Grid item xs={1} m={"auto"}>
   211                                      <IconButton href={"https://prometheus.io/docs/prometheus/latest/querying/examples/#subquery"} size={"small"}><QuestionMark/></IconButton>
   212                                  </Grid>
   213                              </Grid>
   214                          </Grid>
   215                      </Grid>
   216                  </DrawerBox>
   217              </Box>
   218          </Paper>
   219      )
   220  }
   221  
   222  interface RateGraphProps {
   223      boxProps?: BoxProps;
   224      metric: string[];
   225      rate?: TimeDuration;
   226      duration?: TimeDuration;
   227      resolution?: TimeDuration;
   228      options?: ChartOptions<"line">
   229      datasetOptions?: Partial<ChartDataset<"line">> | Partial<ChartDataset<"line">>[];
   230  }
   231  
   232  export default function RateGraph({boxProps, metric, rate=new TimeDuration(3, "h"), duration=new TimeDuration(7, "d"), resolution=new TimeDuration(3, "h"), options={}, datasetOptions={}}: RateGraphProps) {
   233  
   234      let default_rate = rate
   235      let default_duration = duration
   236      let default_resolution = resolution
   237  
   238      let reset = () => {
   239          setRate(default_rate.copy())
   240          setDuration(default_duration.copy())
   241          setResolution(default_resolution.copy())
   242          setTime(DateTime.now())
   243      }
   244  
   245  
   246      let [_rate, setRate] = useState(rate)
   247      let [_duration, _setDuration] = useState(duration)
   248      let [_resolution, setResolution] = useState(resolution)
   249      let [_time, _setTime] = useState<DateTime>(DateTime.now().plus({ days: 1 }).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }))
   250  
   251      // Create some reasonable defaults for the graph
   252      let setDuration = (duration: TimeDuration) => {
   253          if(duration.value == 1){
   254              setRate(new TimeDuration(30, "m"))
   255              setResolution(new TimeDuration(30, "m"))
   256          } else if(duration.value == 7){
   257              setRate(new TimeDuration(3, "h"))
   258              setResolution(new TimeDuration(3, "h"))
   259          } else if(duration.value == 31){
   260              setRate(new TimeDuration(12, "h"))
   261              setResolution(new TimeDuration(12, "h"))
   262          }
   263  
   264          _setDuration(duration)
   265      }
   266  
   267      let setTime = (time: DateTime) => {
   268          _setTime(time.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }))
   269      }
   270  
   271  
   272      async function getData(){
   273          let chartData: ChartData<"line", any, any> = {
   274              datasets: await Promise.all(metric.map(async (metric, index) => {
   275  
   276                  let datasetOption: Partial<ChartDataset<"line">> = {}
   277                  if(datasetOptions instanceof Array){
   278                      try {
   279                          datasetOption = datasetOptions[index]
   280                      } catch (e) {
   281                          console.error("datasetOptions is an array, but the number of elements < the number of metrics")
   282                      }
   283                  } else {
   284                      datasetOption = datasetOptions
   285                  }
   286  
   287                  return {
   288                      data: (await query_rate({metric: `${metric}`, rate:_rate, duration:_duration, resolution:_resolution, time:_time})),
   289                      ...datasetOption
   290                  }
   291              }))
   292          }
   293  
   294          return chartData
   295      }
   296  
   297      return (
   298          <Graph
   299              getData={getData}
   300              drawer={<RateGraphDrawer
   301                          reset={reset}
   302                          duration={_duration}
   303                          setDuration={setDuration}
   304                          rate={_rate}
   305                          setRate={setRate}
   306                          resolution={_resolution}
   307                          setResolution={setResolution}
   308                          time={_time}
   309                          setTime={setTime}
   310                      />}
   311              options={options} boxProps={boxProps}
   312          />
   313      )
   314  }