AllocationReport.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import React, { useEffect, useState } from 'react'
  2. import { get, round } from 'lodash'
  3. import { makeStyles } from '@material-ui/styles'
  4. import Table from '@material-ui/core/Table'
  5. import TableBody from '@material-ui/core/TableBody'
  6. import TableCell from '@material-ui/core/TableCell'
  7. import TableContainer from '@material-ui/core/TableContainer'
  8. import TableHead from '@material-ui/core/TableHead'
  9. import TablePagination from '@material-ui/core/TablePagination'
  10. import TableRow from '@material-ui/core/TableRow'
  11. import TableSortLabel from '@material-ui/core/TableSortLabel'
  12. import Typography from '@material-ui/core/Typography'
  13. import AllocationChart from './AllocationChart';
  14. import { toCurrency } from '../util';
  15. const useStyles = makeStyles({
  16. noResults: {
  17. padding: 24,
  18. },
  19. })
  20. function descendingComparator(a, b, orderBy) {
  21. if (get(b, orderBy) < get(a, orderBy)) {
  22. return -1
  23. }
  24. if (get(b, orderBy) > get(a, orderBy)) {
  25. return 1
  26. }
  27. return 0
  28. }
  29. function getComparator(order, orderBy) {
  30. return order === 'desc'
  31. ? (a, b) => descendingComparator(a, b, orderBy)
  32. : (a, b) => -descendingComparator(a, b, orderBy)
  33. }
  34. function stableSort(array, comparator) {
  35. const stabilizedThis = array.map((el, index) => [el, index])
  36. stabilizedThis.sort((a, b) => {
  37. const order = comparator(a[0], b[0])
  38. if (order !== 0) return order
  39. return a[1] - b[1]
  40. })
  41. return stabilizedThis.map((el) => el[0])
  42. }
  43. const headCells = [
  44. { id: 'name', numeric: false, label: 'Name', width: 'auto' },
  45. { id: 'cpuCost', numeric: true, label: 'CPU', width: 90 },
  46. { id: 'ramCost', numeric: true, label: "RAM", width: 90 },
  47. { id: 'pvCost', numeric: true, label: 'PV', width: 90 },
  48. { id: 'totalEfficiency', numeric: true, label: 'Efficiency', width: 90 },
  49. { id: 'totalCost', numeric: true, label: 'Total cost', width: 90 },
  50. ]
  51. const AllocationReport = ({ allocationData, cumulativeData, totalData, currency }) => {
  52. const classes = useStyles()
  53. if (allocationData.length === 0) {
  54. return <Typography variant="body2" className={classes.noResults}>No results</Typography>
  55. }
  56. const [order, setOrder] = React.useState('desc')
  57. const [orderBy, setOrderBy] = React.useState('totalCost')
  58. const [page, setPage] = useState(0)
  59. const [rowsPerPage, setRowsPerPage] = useState(25)
  60. const numData = cumulativeData.length
  61. useEffect(() => {
  62. setPage(0)
  63. }, [numData])
  64. const lastPage = Math.floor(numData / rowsPerPage)
  65. const handleChangePage = (event, newPage) => setPage(newPage)
  66. const handleChangeRowsPerPage = event => {
  67. setRowsPerPage(parseInt(event.target.value, 10))
  68. setPage(0)
  69. }
  70. const createSortHandler = (property) => (event) => handleRequestSort(event, property)
  71. const handleRequestSort = (event, property) => {
  72. const isDesc = orderBy === property && order === 'desc'
  73. setOrder(isDesc ? 'asc' : 'desc')
  74. setOrderBy(property)
  75. }
  76. const orderedRows = stableSort(cumulativeData, getComparator(order, orderBy))
  77. const pageRows = orderedRows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
  78. return (
  79. <div id="report">
  80. <AllocationChart allocationRange={allocationData} currency={currency} n={10} height={300} />
  81. <TableContainer>
  82. <Table>
  83. <TableHead>
  84. <TableRow>
  85. {headCells.map((cell) => (
  86. <TableCell
  87. key={cell.id}
  88. colSpan={cell.colspan}
  89. align={cell.numeric ? 'right' : 'left'}
  90. sortDirection={orderBy === cell.id ? order : false}
  91. style={{ width: cell.width }}
  92. >
  93. <TableSortLabel
  94. active={orderBy === cell.id}
  95. direction={orderBy === cell.id ? order : 'asc'}
  96. onClick={createSortHandler(cell.id)}
  97. >
  98. {cell.label}
  99. </TableSortLabel>
  100. </TableCell>
  101. ))}
  102. </TableRow>
  103. </TableHead>
  104. <TableBody>
  105. <TableRow>
  106. {headCells.map((cell) => {
  107. return (
  108. <TableCell
  109. key={cell.id}
  110. colSpan={cell.colspan}
  111. align={cell.numeric ? 'right' : 'left'}
  112. style={{ fontWeight: 500 }}
  113. >
  114. {cell.numeric
  115. ? (cell.label === 'Efficiency'
  116. ? (totalData.totalEfficiency == 1.0 && totalData.cpuReqCoreHrs == 0 && totalData.ramReqByteHrs == 0)
  117. ? "Inf%"
  118. : `${round(totalData.totalEfficiency*100, 1)}%`
  119. : toCurrency(totalData[cell.id], currency))
  120. : totalData[cell.id]}
  121. </TableCell>
  122. )})}
  123. </TableRow>
  124. {pageRows.map((row, key) => {
  125. if (row.name === "__unmounted__") {
  126. row.name = "Unmounted PVs"
  127. }
  128. let isIdle = row.name.indexOf("__idle__") >= 0
  129. let isUnallocated = row.name.indexOf("__unallocated__") >= 0
  130. let isUnmounted = row.name.indexOf("Unmounted PVs") >= 0
  131. // Replace "efficiency" with Inf if there is usage w/o request
  132. let efficiency = round(row.totalEfficiency*100, 1)
  133. if (row.totalEfficiency == 1.0 && row.cpuReqCoreHrs == 0 && row.ramReqByteHrs == 0) {
  134. efficiency = "Inf"
  135. }
  136. // Do not allow drill-down for idle and unallocated rows
  137. if (isIdle || isUnallocated || isUnmounted) {
  138. return (
  139. <TableRow key={key}>
  140. <TableCell align="left">{row.name}</TableCell>
  141. <TableCell align="right">{toCurrency(row.cpuCost, currency)}</TableCell>
  142. <TableCell align="right">{toCurrency(row.ramCost, currency)}</TableCell>
  143. <TableCell align="right">{toCurrency(row.pvCost, currency)}</TableCell>
  144. {isIdle ? (
  145. <TableCell align="right">&mdash;</TableCell>
  146. ) : (
  147. <TableCell align="right">{efficiency}%</TableCell>
  148. )}
  149. <TableCell align="right">{toCurrency(row.totalCost, currency)}</TableCell>
  150. </TableRow>
  151. )
  152. }
  153. return (
  154. <TableRow key={key}>
  155. <TableCell align="left">{row.name}</TableCell>
  156. <TableCell align="right">{toCurrency(row.cpuCost, currency)}</TableCell>
  157. <TableCell align="right">{toCurrency(row.ramCost, currency)}</TableCell>
  158. <TableCell align="right">{toCurrency(row.pvCost, currency)}</TableCell>
  159. <TableCell align="right">{efficiency}%</TableCell>
  160. <TableCell align="right">{toCurrency(row.totalCost, currency)}</TableCell>
  161. </TableRow>
  162. )
  163. })}
  164. </TableBody>
  165. </Table>
  166. </TableContainer>
  167. <TablePagination
  168. component="div"
  169. count={numData}
  170. rowsPerPage={rowsPerPage}
  171. rowsPerPageOptions={[10, 25, 50]}
  172. page={Math.min(page, lastPage)}
  173. onChangePage={handleChangePage}
  174. onChangeRowsPerPage={handleChangeRowsPerPage}
  175. />
  176. </div>
  177. )
  178. }
  179. export default React.memo(AllocationReport)