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 }