mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-22 01:16:07 +02:00
feat: add UI to variant metrics (#3697)
This commit is contained in:
parent
6053963750
commit
bacb73667a
@ -7,7 +7,6 @@ import { Badge } from 'component/common/Badge/Badge';
|
|||||||
import { GroupCardActions } from './GroupCardActions/GroupCardActions';
|
import { GroupCardActions } from './GroupCardActions/GroupCardActions';
|
||||||
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
|
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
|
||||||
import { IProjectRole } from 'interfaces/role';
|
import { IProjectRole } from 'interfaces/role';
|
||||||
import { IProject } from 'interfaces/project';
|
|
||||||
|
|
||||||
const StyledLink = styled(Link)(({ theme }) => ({
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
|
@ -34,7 +34,6 @@ import { ArchivedFeatureDeleteConfirm } from './ArchivedFeatureActionCell/Archiv
|
|||||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||||
import { RowSelectCell } from '../../project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell';
|
import { RowSelectCell } from '../../project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
||||||
import { ArchiveBatchActions } from './ArchiveBatchActions';
|
import { ArchiveBatchActions } from './ArchiveBatchActions';
|
||||||
|
|
||||||
@ -64,7 +63,6 @@ export const ArchiveTable = ({
|
|||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||||
const [deletedFeature, setDeletedFeature] = useState<IFeatureToggle>();
|
const [deletedFeature, setDeletedFeature] = useState<IFeatureToggle>();
|
||||||
|
@ -5,7 +5,6 @@ import Input from 'component/common/Input/Input';
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
|
|
||||||
interface IArchivedFeatureDeleteConfirmProps {
|
interface IArchivedFeatureDeleteConfirmProps {
|
||||||
deletedFeatures: string[];
|
deletedFeatures: string[];
|
||||||
@ -35,8 +34,7 @@ export const ArchivedFeatureDeleteConfirm = ({
|
|||||||
}: IArchivedFeatureDeleteConfirmProps) => {
|
}: IArchivedFeatureDeleteConfirmProps) => {
|
||||||
const [confirmName, setConfirmName] = useState('');
|
const [confirmName, setConfirmName] = useState('');
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { deleteFeature, deleteFeatures } = useProjectApi();
|
const { deleteFeatures } = useProjectApi();
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
const onDeleteFeatureToggle = async () => {
|
const onDeleteFeatureToggle = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -13,9 +13,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
|||||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
import { changesCount } from '../../../changesCount';
|
import { changesCount } from '../../../changesCount';
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Link,
|
|
||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
@ -25,7 +23,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Delete, Edit, GroupRounded, MoreVert } from '@mui/icons-material';
|
import { Delete, Edit, MoreVert } from '@mui/icons-material';
|
||||||
import { EditChange } from './EditChange';
|
import { EditChange } from './EditChange';
|
||||||
|
|
||||||
const useShowActions = (changeRequest: IChangeRequest, change: IChange) => {
|
const useShowActions = (changeRequest: IChangeRequest, change: IChange) => {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
import { Link, styled, Typography } from '@mui/material';
|
import { Link, styled, Typography } from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { useTheme } from '@mui/system';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
|
||||||
interface IChangeRequestTitleCellProps {
|
interface IChangeRequestTitleCellProps {
|
||||||
|
@ -4,9 +4,10 @@ import { ILocationSettings } from 'hooks/useLocationSettings';
|
|||||||
import 'chartjs-adapter-date-fns';
|
import 'chartjs-adapter-date-fns';
|
||||||
import { Theme } from '@mui/material/styles/createTheme';
|
import { Theme } from '@mui/material/styles/createTheme';
|
||||||
|
|
||||||
interface IPoint {
|
export interface IPoint {
|
||||||
x: string;
|
x: string;
|
||||||
y: number;
|
y: number;
|
||||||
|
variants: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createChartData = (
|
export const createChartData = (
|
||||||
@ -15,7 +16,7 @@ export const createChartData = (
|
|||||||
locationSettings: ILocationSettings
|
locationSettings: ILocationSettings
|
||||||
): ChartData<'line', IPoint[], string> => {
|
): ChartData<'line', IPoint[], string> => {
|
||||||
const requestsSeries = {
|
const requestsSeries = {
|
||||||
label: 'total requests',
|
label: 'Total requests',
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
data: createChartPoints(metrics, locationSettings, m => m.yes + m.no),
|
data: createChartPoints(metrics, locationSettings, m => m.yes + m.no),
|
||||||
@ -31,7 +32,7 @@ export const createChartData = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const yesSeries = {
|
const yesSeries = {
|
||||||
label: 'exposed',
|
label: 'Exposed',
|
||||||
borderColor: theme.palette.success.main,
|
borderColor: theme.palette.success.main,
|
||||||
backgroundColor: theme.palette.success.main,
|
backgroundColor: theme.palette.success.main,
|
||||||
data: createChartPoints(metrics, locationSettings, m => m.yes),
|
data: createChartPoints(metrics, locationSettings, m => m.yes),
|
||||||
@ -44,7 +45,7 @@ export const createChartData = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const noSeries = {
|
const noSeries = {
|
||||||
label: 'not exposed',
|
label: 'Not exposed',
|
||||||
borderColor: theme.palette.error.main,
|
borderColor: theme.palette.error.main,
|
||||||
backgroundColor: theme.palette.error.main,
|
backgroundColor: theme.palette.error.main,
|
||||||
data: createChartPoints(metrics, locationSettings, m => m.no),
|
data: createChartPoints(metrics, locationSettings, m => m.no),
|
||||||
@ -57,7 +58,9 @@ export const createChartData = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return { datasets: [yesSeries, noSeries, requestsSeries] };
|
return {
|
||||||
|
datasets: [yesSeries, noSeries, requestsSeries],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createChartPoints = (
|
const createChartPoints = (
|
||||||
@ -68,5 +71,6 @@ const createChartPoints = (
|
|||||||
return metrics.map(metric => ({
|
return metrics.map(metric => ({
|
||||||
x: metric.timestamp,
|
x: metric.timestamp,
|
||||||
y: y(metric),
|
y: y(metric),
|
||||||
|
variants: metric.variants,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,17 @@ import { ChartOptions, defaults } from 'chart.js';
|
|||||||
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
|
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
|
||||||
import { formatDateHM } from 'utils/formatDate';
|
import { formatDateHM } from 'utils/formatDate';
|
||||||
import { Theme } from '@mui/material/styles/createTheme';
|
import { Theme } from '@mui/material/styles/createTheme';
|
||||||
|
import { IPoint } from './createChartData';
|
||||||
|
|
||||||
|
const formatVariantEntry = (
|
||||||
|
variant: [string, number],
|
||||||
|
totalExposure: number
|
||||||
|
) => {
|
||||||
|
if (totalExposure === 0) return '';
|
||||||
|
const [key, value] = variant;
|
||||||
|
const percentage = Math.floor((Number(value) / totalExposure) * 100);
|
||||||
|
return `${value} (${percentage}%) - ${key}`;
|
||||||
|
};
|
||||||
|
|
||||||
export const createChartOptions = (
|
export const createChartOptions = (
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
@ -30,15 +41,38 @@ export const createChartOptions = (
|
|||||||
padding: 10,
|
padding: 10,
|
||||||
boxPadding: 5,
|
boxPadding: 5,
|
||||||
usePointStyle: true,
|
usePointStyle: true,
|
||||||
|
itemSort: (a, b) => {
|
||||||
|
const order = ['Total requests', 'Exposed', 'Not exposed'];
|
||||||
|
const aIndex = order.indexOf(a.dataset.label!);
|
||||||
|
const bIndex = order.indexOf(b.dataset.label!);
|
||||||
|
return aIndex - bIndex;
|
||||||
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
label: item => {
|
||||||
|
return `${item.formattedValue} - ${item.dataset.label}`;
|
||||||
|
},
|
||||||
|
afterLabel: item => {
|
||||||
|
const data = item.dataset.data[
|
||||||
|
item.dataIndex
|
||||||
|
] as unknown as IPoint;
|
||||||
|
|
||||||
|
if (
|
||||||
|
item.dataset.label !== 'Exposed' ||
|
||||||
|
data.variants === undefined
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const { disabled, ...actualVariants } = data.variants;
|
||||||
|
return Object.entries(actualVariants)
|
||||||
|
.map(entry => formatVariantEntry(entry, data.y))
|
||||||
|
.join('\n');
|
||||||
|
},
|
||||||
title: items =>
|
title: items =>
|
||||||
formatDateHM(
|
`Time: ${formatDateHM(
|
||||||
items[0].parsed.x,
|
items[0].parsed.x,
|
||||||
locationSettings.locale
|
locationSettings.locale
|
||||||
),
|
)}`,
|
||||||
},
|
},
|
||||||
// Sort tooltip items in the same order as the lines in the chart.
|
|
||||||
itemSort: (a, b) => b.parsed.y - a.parsed.y,
|
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
position: 'top',
|
position: 'top',
|
@ -88,4 +88,5 @@ export interface IFeatureMetricsRaw {
|
|||||||
timestamp: string;
|
timestamp: string;
|
||||||
yes: number;
|
yes: number;
|
||||||
no: number;
|
no: number;
|
||||||
|
variants: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user