mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: port metrics list to react-table (#1035)
* refactor: port metrics list to react-table * refactor: hide columns on small screens * refactor: use disableSortBy instead of canSort * refactor: fix text contrast * refactor: fix metrics section ids
This commit is contained in:
		
							parent
							
								
									570e9f88be
								
							
						
					
					
						commit
						36196ad6d5
					
				| @ -39,7 +39,7 @@ export const SortableTableHeader: VFC<ISortableTableHeaderProps> = ({ | ||||
|                                         ? content | ||||
|                                         : undefined | ||||
|                                 } | ||||
|                                 isSortable={column.canSort} | ||||
|                                 isSortable={Boolean(column.canSort)} | ||||
|                                 isSorted={column.isSorted} | ||||
|                                 isDescending={column.isSortedDesc} | ||||
|                                 maxWidth={column.maxWidth} | ||||
|  | ||||
| @ -61,7 +61,7 @@ export const EnvironmentTable = () => { | ||||
|         { | ||||
|             columns: COLUMNS as any, | ||||
|             data: environments, | ||||
|             autoResetGlobalFilter: false, | ||||
|             disableSortBy: true, | ||||
|         }, | ||||
|         useGlobalFilter | ||||
|     ); | ||||
| @ -127,15 +127,13 @@ export const EnvironmentTable = () => { | ||||
| const COLUMNS = [ | ||||
|     { | ||||
|         id: 'Icon', | ||||
|         canSort: false, | ||||
|         width: '1%', | ||||
|         Cell: () => <IconCell icon={<CloudCircle color="disabled" />} />, | ||||
|         disableGlobalFilter: true, | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Name', | ||||
|         accessor: 'name', | ||||
|         width: '100%', | ||||
|         canSort: false, | ||||
|         Cell: ({ row: { original } }: any) => ( | ||||
|             <EnvironmentNameCell environment={original} /> | ||||
|         ), | ||||
| @ -144,7 +142,7 @@ const COLUMNS = [ | ||||
|         Header: 'Actions', | ||||
|         id: 'Actions', | ||||
|         align: 'center', | ||||
|         canSort: false, | ||||
|         width: '1%', | ||||
|         Cell: ({ row: { original } }: any) => ( | ||||
|             <EnvironmentActionCell environment={original} /> | ||||
|         ), | ||||
|  | ||||
| @ -16,16 +16,17 @@ import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { createChartData } from './createChartData'; | ||||
| import { createChartOptions } from './createChartOptions'; | ||||
| import { FEATURE_METRICS_STATS_ID } from '../FeatureMetricsStats/FeatureMetricsStats'; | ||||
| 
 | ||||
| interface IFeatureMetricsChartProps { | ||||
|     metrics: IFeatureMetricsRaw[]; | ||||
|     hoursBack: number; | ||||
|     statsSectionId: string; | ||||
| } | ||||
| 
 | ||||
| export const FeatureMetricsChart = ({ | ||||
|     metrics, | ||||
|     hoursBack, | ||||
|     statsSectionId, | ||||
| }: IFeatureMetricsChartProps) => { | ||||
|     const { locationSettings } = useLocationSettings(); | ||||
| 
 | ||||
| @ -49,7 +50,7 @@ export const FeatureMetricsChart = ({ | ||||
|                 options={options} | ||||
|                 data={data} | ||||
|                 aria-label="A feature metrics line chart, with three lines: all requests, positive requests, and negative requests." | ||||
|                 aria-describedby={FEATURE_METRICS_STATS_ID} | ||||
|                 aria-describedby={statsSectionId} | ||||
|             /> | ||||
|         </div> | ||||
|     ); | ||||
|  | ||||
| @ -6,7 +6,7 @@ export const useStyles = makeStyles()(theme => ({ | ||||
|         marginBottom: '.5rem', | ||||
|         fontSize: theme.fontSizes.smallerBody, | ||||
|         fontWeight: theme.fontWeight.thin, | ||||
|         color: theme.palette.grey[600], | ||||
|         color: theme.palette.grey[800], | ||||
|     }, | ||||
|     list: { | ||||
|         display: 'flex', | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { FeatureMetricsChart } from '../FeatureMetricsChart/FeatureMetricsChart' | ||||
| import { FeatureMetricsEmpty } from '../FeatureMetricsEmpty/FeatureMetricsEmpty'; | ||||
| import { Box } from '@mui/material'; | ||||
| import theme from 'themes/theme'; | ||||
| import { useId } from 'hooks/useId'; | ||||
| 
 | ||||
| interface IFeatureMetricsContentProps { | ||||
|     metrics: IFeatureMetricsRaw[]; | ||||
| @ -15,6 +16,9 @@ export const FeatureMetricsContent = ({ | ||||
|     metrics, | ||||
|     hoursBack, | ||||
| }: IFeatureMetricsContentProps) => { | ||||
|     const statsSectionId = useId(); | ||||
|     const tableSectionId = useId(); | ||||
| 
 | ||||
|     if (metrics.length === 0) { | ||||
|         return ( | ||||
|             <Box mt={6}> | ||||
| @ -31,16 +35,25 @@ export const FeatureMetricsContent = ({ | ||||
|                 mt={3} | ||||
|                 borderColor={theme.palette.grey[200]} | ||||
|             > | ||||
|                 <FeatureMetricsChart metrics={metrics} hoursBack={hoursBack} /> | ||||
|                 <FeatureMetricsChart | ||||
|                     metrics={metrics} | ||||
|                     hoursBack={hoursBack} | ||||
|                     statsSectionId={statsSectionId} | ||||
|                 /> | ||||
|             </Box> | ||||
|             <Box mt={4}> | ||||
|                 <FeatureMetricsStatsRaw | ||||
|                     metrics={metrics} | ||||
|                     hoursBack={hoursBack} | ||||
|                     statsSectionId={statsSectionId} | ||||
|                     tableSectionId={tableSectionId} | ||||
|                 /> | ||||
|             </Box> | ||||
|             <Box mt={4}> | ||||
|                 <FeatureMetricsTable metrics={metrics} /> | ||||
|                 <FeatureMetricsTable | ||||
|                     metrics={metrics} | ||||
|                     tableSectionId={tableSectionId} | ||||
|                 /> | ||||
|             </Box> | ||||
|         </> | ||||
|     ); | ||||
|  | ||||
| @ -27,6 +27,6 @@ export const useStyles = makeStyles()(theme => ({ | ||||
|         borderTopStyle: 'solid', | ||||
|         borderTopColor: theme.palette.grey[300], | ||||
|         fontSize: theme.fontSizes.smallerBody, | ||||
|         color: theme.palette.grey[700], | ||||
|         color: theme.palette.grey[800], | ||||
|     }, | ||||
| })); | ||||
|  | ||||
| @ -2,18 +2,20 @@ import { calculatePercentage } from 'utils/calculatePercentage'; | ||||
| import { useStyles } from './FeatureMetricsStats.styles'; | ||||
| import { Grid } from '@mui/material'; | ||||
| 
 | ||||
| interface IFeatureMetricsStatsProps { | ||||
| export interface IFeatureMetricsStatsProps { | ||||
|     totalYes: number; | ||||
|     totalNo: number; | ||||
|     hoursBack: number; | ||||
|     statsSectionId?: string; | ||||
|     tableSectionId?: string; | ||||
| } | ||||
| 
 | ||||
| export const FEATURE_METRICS_STATS_ID = 'feature-metrics-stats-id'; | ||||
| 
 | ||||
| export const FeatureMetricsStats = ({ | ||||
|     totalYes, | ||||
|     totalNo, | ||||
|     hoursBack, | ||||
|     statsSectionId, | ||||
|     tableSectionId, | ||||
| }: IFeatureMetricsStatsProps) => { | ||||
|     const { classes: styles } = useStyles(); | ||||
| 
 | ||||
| @ -24,7 +26,8 @@ export const FeatureMetricsStats = ({ | ||||
|         <Grid | ||||
|             container | ||||
|             spacing={2} | ||||
|             id={FEATURE_METRICS_STATS_ID} | ||||
|             id={statsSectionId} | ||||
|             aria-describedby={tableSectionId} | ||||
|             aria-label="Feature metrics summary" | ||||
|             component="section" | ||||
|         > | ||||
|  | ||||
| @ -1,15 +1,18 @@ | ||||
| import { IFeatureMetricsRaw } from 'interfaces/featureToggle'; | ||||
| import { useMemo } from 'react'; | ||||
| import { FeatureMetricsStats } from './FeatureMetricsStats'; | ||||
| import { | ||||
|     FeatureMetricsStats, | ||||
|     IFeatureMetricsStatsProps, | ||||
| } from './FeatureMetricsStats'; | ||||
| 
 | ||||
| interface IFeatureMetricsStatsRawProps { | ||||
| interface IFeatureMetricsStatsRawProps | ||||
|     extends Omit<IFeatureMetricsStatsProps, 'totalYes' | 'totalNo'> { | ||||
|     metrics: IFeatureMetricsRaw[]; | ||||
|     hoursBack: number; | ||||
| } | ||||
| 
 | ||||
| export const FeatureMetricsStatsRaw = ({ | ||||
|     metrics, | ||||
|     hoursBack, | ||||
|     ...rest | ||||
| }: IFeatureMetricsStatsRawProps) => { | ||||
|     const totalYes = useMemo(() => { | ||||
|         return metrics.reduce((acc, m) => acc + m.yes, 0); | ||||
| @ -20,10 +23,6 @@ export const FeatureMetricsStatsRaw = ({ | ||||
|     }, [metrics]); | ||||
| 
 | ||||
|     return ( | ||||
|         <FeatureMetricsStats | ||||
|             totalYes={totalYes} | ||||
|             totalNo={totalNo} | ||||
|             hoursBack={hoursBack} | ||||
|         /> | ||||
|         <FeatureMetricsStats {...rest} totalYes={totalYes} totalNo={totalNo} /> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,71 +1,108 @@ | ||||
| import { IFeatureMetricsRaw } from 'interfaces/featureToggle'; | ||||
| import { | ||||
|     Table, | ||||
|     TableBody, | ||||
|     TableCell, | ||||
|     TableHead, | ||||
|     TableRow, | ||||
|     useMediaQuery, | ||||
|     useTheme, | ||||
| } from '@mui/material'; | ||||
| import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||
| import { useMemo } from 'react'; | ||||
| import { formatDateYMDHMS } from 'utils/formatDate'; | ||||
| 
 | ||||
| export const FEATURE_METRICS_TABLE_ID = 'feature-metrics-table-id'; | ||||
| import { TableBody, TableRow, useMediaQuery } from '@mui/material'; | ||||
| import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; | ||||
| import { useTable, useGlobalFilter, useSortBy } from 'react-table'; | ||||
| import { SortableTableHeader, TableCell, Table } from 'component/common/Table'; | ||||
| import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; | ||||
| import { Assessment } from '@mui/icons-material'; | ||||
| import { useMemo, useEffect } from 'react'; | ||||
| import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; | ||||
| import theme from 'themes/theme'; | ||||
| 
 | ||||
| interface IFeatureMetricsTableProps { | ||||
|     metrics: IFeatureMetricsRaw[]; | ||||
|     tableSectionId?: string; | ||||
| } | ||||
| 
 | ||||
| export const FeatureMetricsTable = ({ metrics }: IFeatureMetricsTableProps) => { | ||||
|     const theme = useTheme(); | ||||
|     const smallScreen = useMediaQuery(theme.breakpoints.down('md')); | ||||
|     const { locationSettings } = useLocationSettings(); | ||||
| export const FeatureMetricsTable = ({ | ||||
|     metrics, | ||||
|     tableSectionId, | ||||
| }: IFeatureMetricsTableProps) => { | ||||
|     const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); | ||||
| 
 | ||||
|     const sortedMetrics = useMemo(() => { | ||||
|         return [...metrics].sort((metricA, metricB) => { | ||||
|             return metricB.timestamp.localeCompare(metricA.timestamp); | ||||
|         }); | ||||
|     }, [metrics]); | ||||
|     const initialState = useMemo( | ||||
|         () => ({ sortBy: [{ id: 'timestamp', desc: true }] }), | ||||
|         [] | ||||
|     ); | ||||
| 
 | ||||
|     if (sortedMetrics.length === 0) { | ||||
|     const { | ||||
|         getTableProps, | ||||
|         getTableBodyProps, | ||||
|         headerGroups, | ||||
|         rows, | ||||
|         prepareRow, | ||||
|         setHiddenColumns, | ||||
|     } = useTable( | ||||
|         { | ||||
|             initialState, | ||||
|             columns: COLUMNS as any, | ||||
|             data: metrics as any, | ||||
|             disableSortRemove: true, | ||||
|             defaultColumn: { Cell: TextCell }, | ||||
|         }, | ||||
|         useGlobalFilter, | ||||
|         useSortBy | ||||
|     ); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (isMediumScreen) { | ||||
|             setHiddenColumns(['appName', 'environment']); | ||||
|         } else { | ||||
|             setHiddenColumns([]); | ||||
|         } | ||||
|     }, [setHiddenColumns, isMediumScreen]); | ||||
| 
 | ||||
|     if (metrics.length === 0) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <Table id={FEATURE_METRICS_TABLE_ID} aria-label="Feature metrics table"> | ||||
|             <TableHead> | ||||
|                 <TableRow> | ||||
|                     <TableCell>Time</TableCell> | ||||
|                     <TableCell hidden={smallScreen}>Application</TableCell> | ||||
|                     <TableCell hidden={smallScreen}>Environment</TableCell> | ||||
|                     <TableCell align="right">Requested</TableCell> | ||||
|                     <TableCell align="right">Exposed</TableCell> | ||||
|                 </TableRow> | ||||
|             </TableHead> | ||||
|             <TableBody> | ||||
|                 {sortedMetrics.map(metric => ( | ||||
|                     <TableRow key={metric.timestamp}> | ||||
|                         <TableCell> | ||||
|                             {formatDateYMDHMS( | ||||
|                                 metric.timestamp, | ||||
|                                 locationSettings.locale | ||||
|                             )} | ||||
|                         </TableCell> | ||||
|                         <TableCell hidden={smallScreen}> | ||||
|                             {metric.appName} | ||||
|                         </TableCell> | ||||
|                         <TableCell hidden={smallScreen}> | ||||
|                             {metric.environment} | ||||
|                         </TableCell> | ||||
|                         <TableCell align="right"> | ||||
|                             {metric.yes + metric.no} | ||||
|                         </TableCell> | ||||
|                         <TableCell align="right">{metric.yes}</TableCell> | ||||
|                     </TableRow> | ||||
|                 ))} | ||||
|         <Table {...getTableProps()} rowHeight="standard" id={tableSectionId}> | ||||
|             <SortableTableHeader headerGroups={headerGroups} /> | ||||
|             <TableBody {...getTableBodyProps()}> | ||||
|                 {rows.map(row => { | ||||
|                     prepareRow(row); | ||||
|                     return ( | ||||
|                         <TableRow hover {...row.getRowProps()}> | ||||
|                             {row.cells.map(cell => ( | ||||
|                                 <TableCell {...cell.getCellProps()}> | ||||
|                                     {cell.render('Cell')} | ||||
|                                 </TableCell> | ||||
|                             ))} | ||||
|                         </TableRow> | ||||
|                     ); | ||||
|                 })} | ||||
|             </TableBody> | ||||
|         </Table> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const COLUMNS = [ | ||||
|     { | ||||
|         id: 'Icon', | ||||
|         width: '1%', | ||||
|         disableSortBy: true, | ||||
|         Cell: () => <IconCell icon={<Assessment color="disabled" />} />, | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Time', | ||||
|         accessor: 'timestamp', | ||||
|         Cell: (props: any) => <DateCell value={props.row.original.timestamp} />, | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Application', | ||||
|         accessor: 'appName', | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Environment', | ||||
|         accessor: 'environment', | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Requested', | ||||
|         accessor: (original: any) => original.yes + original.no, | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Exposed', | ||||
|         accessor: 'yes', | ||||
|     }, | ||||
| ]; | ||||
|  | ||||
| @ -159,9 +159,9 @@ const COLUMNS = [ | ||||
|     { | ||||
|         id: 'Icon', | ||||
|         width: '1%', | ||||
|         canSort: false, | ||||
|         Cell: () => <IconCell icon={<DonutLarge color="disabled" />} />, | ||||
|         disableGlobalFilter: true, | ||||
|         disableSortBy: true, | ||||
|         Cell: () => <IconCell icon={<DonutLarge color="disabled" />} />, | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Name', | ||||
| @ -187,7 +187,7 @@ const COLUMNS = [ | ||||
|         id: 'Actions', | ||||
|         align: 'center', | ||||
|         width: '1%', | ||||
|         canSort: false, | ||||
|         disableSortBy: true, | ||||
|         disableGlobalFilter: true, | ||||
|         Cell: ({ row: { original } }: any) => ( | ||||
|             <SegmentActionCell segment={original} /> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user