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