mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +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