mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
feat: last usage metrics in project table (#6692)

This commit is contained in:
parent
d065905e73
commit
dc64a81bb9
@ -1,10 +1,10 @@
|
||||
import React, { type VFC } from 'react';
|
||||
import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
||||
import type { FeatureEnvironmentSchema } from 'openapi';
|
||||
import type { FeatureSearchEnvironmentSchema } from 'openapi';
|
||||
|
||||
interface IFeatureSeenCellProps {
|
||||
feature: {
|
||||
environments?: FeatureEnvironmentSchema[];
|
||||
environments?: FeatureSearchEnvironmentSchema[];
|
||||
lastSeenAt?: string | null;
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { LastSeenProgress } from './LastSeenProgress';
|
||||
|
||||
test('Show last seen progress bar', async () => {
|
||||
render(<LastSeenProgress yes={5} no={5} />);
|
||||
|
||||
await screen.findByText('50%');
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
import { styled, CircularProgress, Box } from '@mui/material';
|
||||
|
||||
const ProgressContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(1),
|
||||
width: '20%',
|
||||
justifyContent: 'flex-end',
|
||||
}));
|
||||
|
||||
const BackgroundCircularProgress = styled(CircularProgress)(({ theme }) => ({
|
||||
color: theme.palette.divider,
|
||||
}));
|
||||
|
||||
const MainCircularProgress = styled(CircularProgress)(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
}));
|
||||
|
||||
interface ILastSeenProgressProps {
|
||||
yes: number | undefined;
|
||||
no: number | undefined;
|
||||
}
|
||||
|
||||
export const LastSeenProgress = ({ yes, no }: ILastSeenProgressProps) => {
|
||||
const noData = yes === undefined || no === undefined || yes + no === 0;
|
||||
if (noData) {
|
||||
return <Box />;
|
||||
}
|
||||
|
||||
const progress = (yes / (yes + no)) * 100;
|
||||
return (
|
||||
<ProgressContainer>
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<BackgroundCircularProgress
|
||||
variant='determinate'
|
||||
size={18}
|
||||
thickness={11}
|
||||
value={100}
|
||||
/>
|
||||
<MainCircularProgress
|
||||
variant='determinate'
|
||||
size={18}
|
||||
thickness={11}
|
||||
value={progress}
|
||||
/>
|
||||
</Box>
|
||||
<Box>{progress}%</Box>
|
||||
</ProgressContainer>
|
||||
);
|
||||
};
|
@ -3,6 +3,7 @@ import TimeAgo from 'react-timeago';
|
||||
import type { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
||||
import { LastSeenProgress } from './LastSeenProgress/LastSeenProgress';
|
||||
|
||||
const StyledDescription = styled(
|
||||
'div',
|
||||
@ -17,15 +18,14 @@ const StyledDescription = styled(
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
}));
|
||||
|
||||
const StyledDescriptionBlock = styled('div')({
|
||||
const StyledDescriptionBlock = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
});
|
||||
flexWrap: 'wrap',
|
||||
}));
|
||||
|
||||
const StyledDescriptionHeader = styled('p')(({ theme }) => ({
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
@ -34,18 +34,18 @@ const StyledDescriptionBlockHeader = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
marginBottom: theme.spacing(1),
|
||||
width: '50%',
|
||||
width: '40%',
|
||||
justifyContent: 'flex-start',
|
||||
}));
|
||||
const StyledValueContainer = styled('div')({
|
||||
width: '40%',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
const StyledDescriptionSubHeader = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
margin: theme.spacing(2, 0),
|
||||
}));
|
||||
|
||||
const StyledValueContainer = styled('div')({
|
||||
width: '50%',
|
||||
});
|
||||
|
||||
const StyledValue = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'color',
|
||||
})(({ color }) => ({
|
||||
@ -54,6 +54,12 @@ const StyledValue = styled('div', {
|
||||
color: color,
|
||||
}));
|
||||
|
||||
const StyledListContainer = styled('div')(({ theme }) => ({
|
||||
maxHeight: theme.spacing(24.5),
|
||||
overflowY: 'auto',
|
||||
paddingRight: theme.spacing(2),
|
||||
}));
|
||||
|
||||
interface ILastSeenTooltipProps {
|
||||
featureLastSeen: string;
|
||||
environments?: ILastSeenEnvironments[];
|
||||
@ -73,19 +79,16 @@ export const LastSeenTooltip = ({
|
||||
);
|
||||
return (
|
||||
<StyledDescription {...rest} data-loading>
|
||||
<StyledDescriptionHeader sx={{ mb: 0 }}>
|
||||
<StyledDescriptionHeader>
|
||||
Last usage reported
|
||||
</StyledDescriptionHeader>
|
||||
<StyledDescriptionSubHeader>
|
||||
Usage is reported from connected applications through metrics
|
||||
</StyledDescriptionSubHeader>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
Boolean(environments) && Boolean(environmentsHaveLastSeen)
|
||||
}
|
||||
show={
|
||||
<>
|
||||
{environments?.map(({ name, lastSeenAt }) => (
|
||||
<StyledListContainer>
|
||||
{environments?.map(({ name, lastSeenAt, yes, no }) => (
|
||||
<StyledDescriptionBlock key={name}>
|
||||
<StyledDescriptionBlockHeader>
|
||||
{name}
|
||||
@ -128,9 +131,10 @@ export const LastSeenTooltip = ({
|
||||
}
|
||||
/>
|
||||
</StyledValueContainer>
|
||||
<LastSeenProgress yes={yes} no={no} />
|
||||
</StyledDescriptionBlock>
|
||||
))}
|
||||
</>
|
||||
</StyledListContainer>
|
||||
}
|
||||
elseShow={
|
||||
<TimeAgo
|
||||
@ -156,6 +160,9 @@ export const LastSeenTooltip = ({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<StyledDescriptionSubHeader>
|
||||
Usage is reported from connected applications through metrics
|
||||
</StyledDescriptionSubHeader>
|
||||
</StyledDescription>
|
||||
);
|
||||
};
|
||||
|
@ -46,6 +46,10 @@ const StyledIconWrapper = styled('div')(({ theme }) => ({
|
||||
margin: '0 auto',
|
||||
}));
|
||||
|
||||
const StyledTooltipResolver = styled(TooltipResolver)(({ theme }) => ({
|
||||
maxWidth: theme.spacing(47.5),
|
||||
}));
|
||||
|
||||
const TooltipContainer: FC<{
|
||||
color?: string;
|
||||
tooltip: ReactElement | string;
|
||||
@ -53,7 +57,7 @@ const TooltipContainer: FC<{
|
||||
}> = ({ sx, tooltip, color, children }) => {
|
||||
return (
|
||||
<StyledContainer sx={sx}>
|
||||
<TooltipResolver
|
||||
<StyledTooltipResolver
|
||||
variant='custom'
|
||||
titleComponent={tooltip}
|
||||
arrow
|
||||
@ -64,7 +68,7 @@ const TooltipContainer: FC<{
|
||||
{children}
|
||||
</StyledIconWrapper>
|
||||
</StyledBox>
|
||||
</TooltipResolver>
|
||||
</StyledTooltipResolver>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -23,11 +23,13 @@ export interface IEnvironments {
|
||||
type?: string;
|
||||
hasStrategies?: boolean;
|
||||
hasEnabledStrategies?: boolean;
|
||||
yes?: number;
|
||||
no?: number;
|
||||
}
|
||||
|
||||
export type ILastSeenEnvironments = Pick<
|
||||
IEnvironments,
|
||||
'name' | 'enabled' | 'lastSeenAt'
|
||||
'name' | 'enabled' | 'lastSeenAt' | 'yes' | 'no'
|
||||
>;
|
||||
|
||||
export interface IFeatureToggle {
|
||||
|
Loading…
Reference in New Issue
Block a user