Fixed issues found in testing of react-table upgrade changes. #7419

UI fixes and improvement in System Stats Dashboard.
This commit is contained in:
Aditya Toshniwal 2024-05-20 10:41:55 +05:30
parent 9647482791
commit d6a9f8a06c
7 changed files with 137 additions and 158 deletions

View File

@ -21,6 +21,7 @@ import {useInterval, usePrevious} from 'sources/custom_hooks';
import axios from 'axios'; import axios from 'axios';
import { getStatsUrl, transformData, statsReducer, X_AXIS_LENGTH } from './utility.js'; import { getStatsUrl, transformData, statsReducer, X_AXIS_LENGTH } from './utility.js';
import { toPrettySize } from '../../../../static/js/utils'; import { toPrettySize } from '../../../../static/js/utils';
import SectionContainer from '../components/SectionContainer.jsx';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
autoResizer: { autoResizer: {
@ -252,7 +253,7 @@ export function CPUWrapper(props) {
lineBorderWidth: props.lineBorderWidth, lineBorderWidth: props.lineBorderWidth,
}), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]); }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
return ( return (
<> <Box display="flex" flexDirection="column" height="100%">
<Grid container spacing={0.5} className={classes.container}> <Grid container spacing={0.5} className={classes.container}>
<Grid item md={6}> <Grid item md={6}>
<ChartContainer id='cu-graph' title={gettext('CPU usage')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}> <ChartContainer id='cu-graph' title={gettext('CPU usage')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
@ -265,22 +266,20 @@ export function CPUWrapper(props) {
</ChartContainer> </ChartContainer>
</Grid> </Grid>
</Grid> </Grid>
<Grid container spacing={0.5} className={classes.fixedContainer}> <Box flexGrow={1} minHeight={0}>
<div className={classes.tableContainer}> <SectionContainer title={gettext('Process CPU usage')}>
<PgTable <PgTable
className={classes.autoResizer} className={classes.autoResizer}
CustomHeader={() => {
return <div className={classes.containerHeader}>{gettext('Process CPU usage')}</div>;
}}
columns={props.tableHeader} columns={props.tableHeader}
data={props.processCpuUsageStats} data={props.processCpuUsageStats}
msg={props.errorMsg} msg={props.errorMsg}
type={'panel'} type={'panel'}
caveTable={false} caveTable={false}
tableNoBorder={false}
></PgTable> ></PgTable>
</div> </SectionContainer>
</Grid> </Box>
</> </Box>
); );
} }

View File

@ -20,6 +20,7 @@ import {useInterval, usePrevious} from 'sources/custom_hooks';
import axios from 'axios'; import axios from 'axios';
import { getStatsUrl, transformData, statsReducer, X_AXIS_LENGTH } from './utility.js'; import { getStatsUrl, transformData, statsReducer, X_AXIS_LENGTH } from './utility.js';
import { toPrettySize } from '../../../../static/js/utils'; import { toPrettySize } from '../../../../static/js/utils';
import SectionContainer from '../components/SectionContainer.jsx';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
autoResizer: { autoResizer: {
@ -255,7 +256,7 @@ export function MemoryWrapper(props) {
}), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]); }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
return ( return (
<> <Box display="flex" flexDirection="column" height="100%">
<Grid container spacing={0.5} className={classes.container}> <Grid container spacing={0.5} className={classes.container}>
<Grid item md={6}> <Grid item md={6}>
<ChartContainer id='m-graph' title={gettext('Memory')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}> <ChartContainer id='m-graph' title={gettext('Memory')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
@ -270,22 +271,20 @@ export function MemoryWrapper(props) {
</ChartContainer> </ChartContainer>
</Grid> </Grid>
</Grid> </Grid>
<Grid container spacing={0.5} className={classes.fixedContainer}> <Box flexGrow={1} minHeight={0}>
<div className={classes.tableContainer}> <SectionContainer title={gettext('Process memory usage')}>
<PgTable <PgTable
className={classes.autoResizer} className={classes.autoResizer}
CustomHeader={() => {
return <div className={classes.containerHeader}>{gettext('Process memory usage')}</div>;
}}
columns={props.tableHeader} columns={props.tableHeader}
data={props.processMemoryUsageStats} data={props.processMemoryUsageStats}
msg={props.errorMsg} msg={props.errorMsg}
type={'panel'} type={'panel'}
caveTable={false} caveTable={false}
tableNoBorder={false}
></PgTable> ></PgTable>
</div> </SectionContainer>
</Grid> </Box>
</> </Box>
); );
} }

View File

@ -23,6 +23,7 @@ import { getStatsUrl, transformData, X_AXIS_LENGTH } from './utility.js';
import { toPrettySize } from '../../../../static/js/utils'; import { toPrettySize } from '../../../../static/js/utils';
import clsx from 'clsx'; import clsx from 'clsx';
import { commonTableStyles } from '../../../../static/js/Theme'; import { commonTableStyles } from '../../../../static/js/Theme';
import SectionContainer from '../components/SectionContainer.jsx';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
container: { container: {
@ -33,20 +34,8 @@ const useStyles = makeStyles((theme) => ({
driveContainer: { driveContainer: {
width: '100%', width: '100%',
}, },
diskInfoContainer: {
height: 'auto',
padding: '8px 8px 0px 8px',
marginBottom: '0px',
},
diskInfoSummary: {
height: 'auto',
padding: '0px 0px 4px 0px',
marginBottom: '0px',
},
diskInfoCharts: { diskInfoCharts: {
height: 'auto', marginBottom: '4px',
padding: '0px 0px 2px 0px',
marginBottom: '0px',
}, },
containerHeaderText: { containerHeaderText: {
fontWeight: 'bold', fontWeight: 'bold',
@ -54,13 +43,12 @@ const useStyles = makeStyles((theme) => ({
}, },
tableContainer: { tableContainer: {
background: theme.otherVars.tableBg, background: theme.otherVars.tableBg,
padding: '0px',
border: '1px solid '+theme.otherVars.borderColor, border: '1px solid '+theme.otherVars.borderColor,
borderCollapse: 'collapse', borderCollapse: 'collapse',
borderRadius: '4px', borderRadius: '4px',
overflow: 'auto', overflow: 'auto',
width: '100%', width: '100%',
margin: '4px 4px 4px 4px', marginBottom: '4px',
}, },
tableWhiteSpace: { tableWhiteSpace: {
'& td, & th': { '& td, & th': {
@ -143,7 +131,7 @@ const DiskStatsTable = (props) => {
<thead> <thead>
<tr> <tr>
{tableHeader.map((item, index) => ( {tableHeader.map((item, index) => (
<th key={index}>{item.Header}</th> <th key={index}>{item.header}</th>
))} ))}
</tr> </tr>
</thead> </thead>
@ -151,7 +139,7 @@ const DiskStatsTable = (props) => {
{data.map((item, index) => ( {data.map((item, index) => (
<tr key={index}> <tr key={index}>
{tableHeader.map((header, id) => ( {tableHeader.map((header, id) => (
<td key={header.accessor+'-'+id}>{item[header.accessor]}</td> <td key={header.accessorKey+'-'+id}>{item[header.accessorKey]}</td>
))} ))}
</tr> </tr>
))} ))}
@ -428,112 +416,101 @@ export function StorageWrapper(props) {
return ( return (
<> <>
<Grid container spacing={1} className={classes.diskInfoContainer}> <div className={classes.tableContainer}>
<Grid container spacing={1} className={classes.diskInfoSummary}> <div className={classes.containerHeaderText}>{gettext('Disk information')}</div>
<div className={classes.tableContainer}> <DiskStatsTable tableHeader={props.tableHeader} data={props.diskStats} />
<div className={classes.containerHeaderText}>{gettext('Disk information')}</div> </div>
<DiskStatsTable tableHeader={props.tableHeader} data={props.diskStats} /> <Grid container spacing={0.5} sx={{marginBottom: '4px'}}>
</div> <Grid item md={6} sm={12}>
</Grid> <ChartContainer
<Grid container spacing={1} className={classes.diskInfoCharts}> id='t-space-graph'
<Grid item md={6} sm={12}> title={''}
<ChartContainer datasets={props.diskStats.map((item, index) => ({
id='t-space-graph' borderColor: colors[(index + 2) % colors.length],
title={''} label: item.mount_point !== '' ? item.mount_point : item.drive_letter !== '' ? item.drive_letter : 'disk' + index,
datasets={props.diskStats.map((item, index) => ({ }))}
borderColor: colors[(index + 2) % colors.length], errorMsg={props.errorMsg}
label: item.mount_point !== '' ? item.mount_point : item.drive_letter !== '' ? item.drive_letter : 'disk' + index, isTest={props.isTest}>
}))} <PieChart data={{
errorMsg={props.errorMsg} labels: props.diskStats.map((item, index) => item.mount_point!=''?item.mount_point:item.drive_letter!=''?item.drive_letter:'disk'+index),
isTest={props.isTest}> datasets: [
<PieChart data={{
labels: props.diskStats.map((item, index) => item.mount_point!=''?item.mount_point:item.drive_letter!=''?item.drive_letter:'disk'+index),
datasets: [
{
data: props.diskStats.map((item) => item.total_space_actual?item.total_space_actual:0),
backgroundColor: props.diskStats.map((item, index) => colors[(index + 2) % colors.length]),
},
],
}}
options={{
animation: false,
...chartJsExtraOptions,
}}
/>
</ChartContainer>
</Grid>
<Grid item md={6} sm={12}>
<ChartContainer id='ua-space-graph' title={''} datasets={[{borderColor: '#FF6384', label: 'Used space'}, {borderColor: '#36a2eb', label: 'Available space'}]} errorMsg={props.errorMsg} isTest={props.isTest}>
<BarChart data={{
labels: props.diskStats.map((item, index) => item.mount_point!=''?item.mount_point:item.drive_letter!=''?item.drive_letter:'disk'+index),
datasets: [
{
label: 'Used space',
data: props.diskStats.map((item) => item.used_space_actual?item.used_space_actual:0),
backgroundColor: '#FF6384',
borderColor: '#FF6384',
borderWidth: 1,
},
{
label: 'Available space',
data: props.diskStats.map((item) => item.free_space_actual?item.free_space_actual:0),
backgroundColor: '#36a2eb',
borderColor: '#36a2eb',
borderWidth: 1,
},
],
}}
options={
{ {
scales: { data: props.diskStats.map((item) => item.total_space_actual?item.total_space_actual:0),
x: { backgroundColor: props.diskStats.map((item, index) => colors[(index + 2) % colors.length]),
},
],
}}
options={{
animation: false,
...chartJsExtraOptions,
}}
/>
</ChartContainer>
</Grid>
<Grid item md={6} sm={12}>
<ChartContainer id='ua-space-graph' title={''} datasets={[{borderColor: '#FF6384', label: 'Used space'}, {borderColor: '#36a2eb', label: 'Available space'}]} errorMsg={props.errorMsg} isTest={props.isTest}>
<BarChart data={{
labels: props.diskStats.map((item, index) => item.mount_point!=''?item.mount_point:item.drive_letter!=''?item.drive_letter:'disk'+index),
datasets: [
{
label: 'Used space',
data: props.diskStats.map((item) => item.used_space_actual?item.used_space_actual:0),
backgroundColor: '#FF6384',
borderColor: '#FF6384',
borderWidth: 1,
},
{
label: 'Available space',
data: props.diskStats.map((item) => item.free_space_actual?item.free_space_actual:0),
backgroundColor: '#36a2eb',
borderColor: '#36a2eb',
borderWidth: 1,
},
],
}}
options={
{
scales: {
x: {
display: true,
stacked: true,
ticks: {
display: true, display: true,
stacked: true,
ticks: {
display: true,
},
}, },
y: { },
beginAtZero: true, y: {
stacked: true, beginAtZero: true,
ticks: { stacked: true,
callback: function (value) { ticks: {
return toPrettySize(value); callback: function (value) {
}, return toPrettySize(value);
}, },
}, },
}, },
...chartJsExtraOptions, },
} ...chartJsExtraOptions,
} }
/> }
</ChartContainer> />
</Grid> </ChartContainer>
</Grid> </Grid>
</Grid> </Grid>
<Grid container spacing={0.5} className={classes.container}> {Object.keys(props.ioInfo).map((drive) => (
{Object.keys(props.ioInfo).map((drive, index) => ( <SectionContainer key={drive} title={drive} style={{minHeight: 'unset', height: 'auto', marginBottom: '0.5px'}}>
<Grid key={`disk-${index}`} container spacing={1} className={classes.container}> <Grid container spacing={0.5} p={0.5}>
<div className={classes.driveContainer}> {Object.keys(props.ioInfo[drive]).map((type, innerKeyIndex) => (
<Grid container spacing={1} className={classes.driveContainerHeader}> <Grid key={`${type}-${innerKeyIndex}`} item md={4} sm={6}>
<div className={classes.containerHeaderText}>{gettext(drive)}</div> <ChartContainer id={`io-graph-${type}`} title={type.endsWith('_bytes_rw') ? gettext('Data transfer'): type.endsWith('_total_rw') ? gettext('I/O operations count'): type.endsWith('_time_rw') ? gettext('Time spent in I/O operations'):''} datasets={transformData(props.ioInfo[drive][type], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={transformData(props.ioInfo[drive][type], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options}
valueFormatter={(v)=>{
return type.endsWith('_time_rw') ? toPrettySize(v, 'ms') : type.endsWith('_total_rw') ? toPrettySize(v, ''): toPrettySize(v);
}} />
</ChartContainer>
</Grid> </Grid>
<Grid container spacing={0.5} className={classes.driveContainerBody}> ))}
{Object.keys(props.ioInfo[drive]).map((type, innerKeyIndex) => (
<Grid key={`${type}-${innerKeyIndex}`} item md={4} sm={6}>
<ChartContainer id={`io-graph-${type}`} title={type.endsWith('_bytes_rw') ? gettext('Data transfer'): type.endsWith('_total_rw') ? gettext('I/O operations count'): type.endsWith('_time_rw') ? gettext('Time spent in I/O operations'):''} datasets={transformData(props.ioInfo[drive][type], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={transformData(props.ioInfo[drive][type], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options}
valueFormatter={(v)=>{
return type.endsWith('_time_rw') ? toPrettySize(v, 'ms') : type.endsWith('_total_rw') ? toPrettySize(v, ''): toPrettySize(v);
}} />
</ChartContainer>
</Grid>
))}
</Grid>
</div>
</Grid> </Grid>
))} </SectionContainer>
</Grid> ))}
</> </>
); );
} }

View File

@ -317,7 +317,7 @@ DataGridHeader.propTypes = {
onSearchTextChange: PropTypes.func, onSearchTextChange: PropTypes.func,
}; };
function DataGridView({ export default function DataGridView({
value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName, value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName,
fixedRows, ...props}) { fixedRows, ...props}) {
const classes = useStyles(); const classes = useStyles();
@ -639,14 +639,6 @@ function DataGridView({
); );
} }
export default function DataGridViewMoized({memoDeps, ...props}) {
return useMemo(()=><DataGridView {...props} />, memoDeps??[]);
}
DataGridViewMoized.propTypes = {
memoDeps: PropTypes.array,
};
DataGridView.propTypes = { DataGridView.propTypes = {
label: PropTypes.string, label: PropTypes.string,
value: PropTypes.array, value: PropTypes.array,

View File

@ -305,12 +305,7 @@ export default function FormView({
if(CustomControl) { if(CustomControl) {
tabs[group].push(<CustomControl {...ctrlProps}/>); tabs[group].push(<CustomControl {...ctrlProps}/>);
} else { } else {
tabs[group].push(<DataGridView {...ctrlProps} memoDeps={[ tabs[group].push(<DataGridView {...ctrlProps} />);
JSON.stringify(ctrlProps.value),
ctrlProps.containerClassName,
ctrlProps.visible,
...(evalFunc(null, ctrlProps.deps) || []).map((dep)=>value[dep]),
]} />);
} }
} else { } else {
/* Its a form control */ /* Its a form control */

View File

@ -7,7 +7,7 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React, { forwardRef } from 'react'; import React, { forwardRef, useEffect } from 'react';
import { flexRender } from '@tanstack/react-table'; import { flexRender } from '@tanstack/react-table';
import { styled } from '@mui/styles'; import { styled } from '@mui/styles';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -58,6 +58,12 @@ const StyledDiv = styled('div')(({theme})=>({
...theme.mixins.panelBorder.bottom, ...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right, ...theme.mixins.panelBorder.right,
'& > div': {
overflow: 'hidden',
textOverflow: 'ellipsis',
textWrap: 'nowrap'
},
'& .pgrt-header-resizer': { '& .pgrt-header-resizer': {
display: 'inline-block', display: 'inline-block',
width: '5px', width: '5px',
@ -160,8 +166,8 @@ export const PgReactTableCell = forwardRef(({row, cell, children, className}, re
return ( return (
<div ref={ref} key={cell.id} style={{ <div ref={ref} key={cell.id} style={{
flex: `var(--col-${cell.column.id}-size) 0 auto`, flex: `var(--col-${cell.column.id.replace(/\W/g, '_')}-size) 0 auto`,
width: `calc(var(--col-${cell.column.id}-size)*1px)`, width: `calc(var(--col-${cell.column.id.replace(/\W/g, '_')}-size)*1px)`,
...(cell.column.columnDef.maxSize ? { maxWidth: `${cell.column.columnDef.maxSize}px` } : {}) ...(cell.column.columnDef.maxSize ? { maxWidth: `${cell.column.columnDef.maxSize}px` } : {})
}} role='cell' }} role='cell'
className={clsx(...classNames)} className={clsx(...classNames)}
@ -231,14 +237,14 @@ export function PgReactTableHeader({table}) {
key={header.id} key={header.id}
className='pgrt-header-cell' className='pgrt-header-cell'
style={{ style={{
flex: `var(--header-${header?.id}-size) 0 auto`, flex: `var(--header-${header?.id.replace(/\W/g, '_')}-size) 0 auto`,
width: `calc(var(--header-${header?.id}-size)*1px)`, width: `calc(var(--header-${header?.id.replace(/\W/g, '_')}-size)*1px)`,
...(header.column.columnDef.maxSize ? { maxWidth: `${header.column.columnDef.maxSize}px` } : {}), ...(header.column.columnDef.maxSize ? { maxWidth: `${header.column.columnDef.maxSize}px` } : {}),
cursor: header.column.getCanSort() ? 'pointer' : 'initial', cursor: header.column.getCanSort() ? 'pointer' : 'initial',
}} }}
onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined} onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined}
> >
<div> <div title={flexRender(header.column.columnDef.header, header.getContext())}>
{flexRender(header.column.columnDef.header, header.getContext())} {flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getCanSort() && header.column.getIsSorted() && {header.column.getCanSort() && header.column.getIsSorted() &&
<span> <span>
@ -280,22 +286,32 @@ PgReactTableBody.propTypes = {
export const PgReactTable = forwardRef(({children, table, rootClassName, tableClassName, ...props}, ref)=>{ export const PgReactTable = forwardRef(({children, table, rootClassName, tableClassName, ...props}, ref)=>{
const columns = table.getAllColumns(); const columns = table.getAllColumns();
// Render the UI for your table
const maxExpandWidth = (ref.current?.getBoundingClientRect().width ?? 430) - 30; //margin,scrollbar,etc. useEffect(()=>{
const setMaxExpandWidth = ()=>{
if(ref.current) {
ref.current.style['--expand-width'] = (ref.current.getBoundingClientRect().width ?? 430) - 30; //margin,scrollbar,etc.
}
};
const tableResizeObserver = new ResizeObserver(()=>{
setMaxExpandWidth();
});
tableResizeObserver.observe(ref.current);
}, []);
const columnSizeVars = React.useMemo(() => { const columnSizeVars = React.useMemo(() => {
const headers = table.getFlatHeaders(); const headers = table.getFlatHeaders();
const colSizes = {}; const colSizes = {};
for (let i = 0; i < headers.length; i++) { for (let i = 0; i < headers.length; i++) {
const header = headers[i]; const header = headers[i];
colSizes[`--header-${header.id}-size`] = header.getSize(); colSizes[`--header-${header.id.replace(/\W/g, '_')}-size`] = header.getSize();
colSizes[`--col-${header.column.id}-size`] = header.column.getSize(); colSizes[`--col-${header.column.id.replace(/\W/g, '_')}-size`] = header.column.getSize();
} }
return colSizes; return colSizes;
}, [columns, table.getState().columnSizingInfo]); }, [columns, table.getState().columnSizingInfo]);
return ( return (
<StyledDiv className={clsx('pgrt', rootClassName)} style={{'--expand-width': maxExpandWidth }} ref={ref} > <StyledDiv className={clsx('pgrt', rootClassName)} ref={ref} >
<div className={clsx('pgrt-table', tableClassName)} style={{ ...columnSizeVars }} {...props}> <div className={clsx('pgrt-table', tableClassName)} style={{ ...columnSizeVars }} {...props}>
{children} {children}
</div> </div>

View File

@ -22,13 +22,14 @@ global.matchMedia = (query)=>({
dispatchEvent: jest.fn(), dispatchEvent: jest.fn(),
}); });
class IntersectionObserver { class GeneralObserver {
observe() {return null;} observe() {return null;}
unobserve() {return null;} unobserve() {return null;}
disconnect() {return null;} disconnect() {return null;}
} }
global.IntersectionObserver = IntersectionObserver; global.IntersectionObserver = GeneralObserver;
global.ResizeObserver = GeneralObserver;
import lodash from 'lodash'; import lodash from 'lodash';
global._ = lodash; global._ = lodash;