mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-12 13:48:35 +02: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 React, { type VFC } from 'react';
|
||||||
import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
||||||
import type { FeatureEnvironmentSchema } from 'openapi';
|
import type { FeatureSearchEnvironmentSchema } from 'openapi';
|
||||||
|
|
||||||
interface IFeatureSeenCellProps {
|
interface IFeatureSeenCellProps {
|
||||||
feature: {
|
feature: {
|
||||||
environments?: FeatureEnvironmentSchema[];
|
environments?: FeatureSearchEnvironmentSchema[];
|
||||||
lastSeenAt?: string | null;
|
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 type { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
||||||
|
import { LastSeenProgress } from './LastSeenProgress/LastSeenProgress';
|
||||||
|
|
||||||
const StyledDescription = styled(
|
const StyledDescription = styled(
|
||||||
'div',
|
'div',
|
||||||
@ -17,15 +18,14 @@ const StyledDescription = styled(
|
|||||||
borderRadius: theme.shape.borderRadiusMedium,
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledDescriptionBlock = styled('div')({
|
const StyledDescriptionBlock = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexWrap: 'wrap',
|
||||||
});
|
}));
|
||||||
|
|
||||||
const StyledDescriptionHeader = styled('p')(({ theme }) => ({
|
const StyledDescriptionHeader = styled('p')(({ theme }) => ({
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
fontWeight: theme.fontWeight.bold,
|
|
||||||
marginBottom: theme.spacing(1),
|
marginBottom: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -34,18 +34,18 @@ const StyledDescriptionBlockHeader = styled('p')(({ theme }) => ({
|
|||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
fontWeight: theme.fontWeight.bold,
|
fontWeight: theme.fontWeight.bold,
|
||||||
marginBottom: theme.spacing(1),
|
marginBottom: theme.spacing(1),
|
||||||
width: '50%',
|
width: '40%',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
}));
|
}));
|
||||||
|
const StyledValueContainer = styled('div')({
|
||||||
|
width: '40%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
const StyledDescriptionSubHeader = styled('p')(({ theme }) => ({
|
const StyledDescriptionSubHeader = styled('p')(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
margin: theme.spacing(2, 0),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledValueContainer = styled('div')({
|
|
||||||
width: '50%',
|
|
||||||
});
|
|
||||||
|
|
||||||
const StyledValue = styled('div', {
|
const StyledValue = styled('div', {
|
||||||
shouldForwardProp: (prop) => prop !== 'color',
|
shouldForwardProp: (prop) => prop !== 'color',
|
||||||
})(({ color }) => ({
|
})(({ color }) => ({
|
||||||
@ -54,6 +54,12 @@ const StyledValue = styled('div', {
|
|||||||
color: color,
|
color: color,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledListContainer = styled('div')(({ theme }) => ({
|
||||||
|
maxHeight: theme.spacing(24.5),
|
||||||
|
overflowY: 'auto',
|
||||||
|
paddingRight: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
interface ILastSeenTooltipProps {
|
interface ILastSeenTooltipProps {
|
||||||
featureLastSeen: string;
|
featureLastSeen: string;
|
||||||
environments?: ILastSeenEnvironments[];
|
environments?: ILastSeenEnvironments[];
|
||||||
@ -73,19 +79,16 @@ export const LastSeenTooltip = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<StyledDescription {...rest} data-loading>
|
<StyledDescription {...rest} data-loading>
|
||||||
<StyledDescriptionHeader sx={{ mb: 0 }}>
|
<StyledDescriptionHeader>
|
||||||
Last usage reported
|
Last usage reported
|
||||||
</StyledDescriptionHeader>
|
</StyledDescriptionHeader>
|
||||||
<StyledDescriptionSubHeader>
|
|
||||||
Usage is reported from connected applications through metrics
|
|
||||||
</StyledDescriptionSubHeader>
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={
|
condition={
|
||||||
Boolean(environments) && Boolean(environmentsHaveLastSeen)
|
Boolean(environments) && Boolean(environmentsHaveLastSeen)
|
||||||
}
|
}
|
||||||
show={
|
show={
|
||||||
<>
|
<StyledListContainer>
|
||||||
{environments?.map(({ name, lastSeenAt }) => (
|
{environments?.map(({ name, lastSeenAt, yes, no }) => (
|
||||||
<StyledDescriptionBlock key={name}>
|
<StyledDescriptionBlock key={name}>
|
||||||
<StyledDescriptionBlockHeader>
|
<StyledDescriptionBlockHeader>
|
||||||
{name}
|
{name}
|
||||||
@ -128,9 +131,10 @@ export const LastSeenTooltip = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledValueContainer>
|
</StyledValueContainer>
|
||||||
|
<LastSeenProgress yes={yes} no={no} />
|
||||||
</StyledDescriptionBlock>
|
</StyledDescriptionBlock>
|
||||||
))}
|
))}
|
||||||
</>
|
</StyledListContainer>
|
||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
<TimeAgo
|
<TimeAgo
|
||||||
@ -156,6 +160,9 @@ export const LastSeenTooltip = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<StyledDescriptionSubHeader>
|
||||||
|
Usage is reported from connected applications through metrics
|
||||||
|
</StyledDescriptionSubHeader>
|
||||||
</StyledDescription>
|
</StyledDescription>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,10 @@ const StyledIconWrapper = styled('div')(({ theme }) => ({
|
|||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledTooltipResolver = styled(TooltipResolver)(({ theme }) => ({
|
||||||
|
maxWidth: theme.spacing(47.5),
|
||||||
|
}));
|
||||||
|
|
||||||
const TooltipContainer: FC<{
|
const TooltipContainer: FC<{
|
||||||
color?: string;
|
color?: string;
|
||||||
tooltip: ReactElement | string;
|
tooltip: ReactElement | string;
|
||||||
@ -53,7 +57,7 @@ const TooltipContainer: FC<{
|
|||||||
}> = ({ sx, tooltip, color, children }) => {
|
}> = ({ sx, tooltip, color, children }) => {
|
||||||
return (
|
return (
|
||||||
<StyledContainer sx={sx}>
|
<StyledContainer sx={sx}>
|
||||||
<TooltipResolver
|
<StyledTooltipResolver
|
||||||
variant='custom'
|
variant='custom'
|
||||||
titleComponent={tooltip}
|
titleComponent={tooltip}
|
||||||
arrow
|
arrow
|
||||||
@ -64,7 +68,7 @@ const TooltipContainer: FC<{
|
|||||||
{children}
|
{children}
|
||||||
</StyledIconWrapper>
|
</StyledIconWrapper>
|
||||||
</StyledBox>
|
</StyledBox>
|
||||||
</TooltipResolver>
|
</StyledTooltipResolver>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -23,11 +23,13 @@ export interface IEnvironments {
|
|||||||
type?: string;
|
type?: string;
|
||||||
hasStrategies?: boolean;
|
hasStrategies?: boolean;
|
||||||
hasEnabledStrategies?: boolean;
|
hasEnabledStrategies?: boolean;
|
||||||
|
yes?: number;
|
||||||
|
no?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ILastSeenEnvironments = Pick<
|
export type ILastSeenEnvironments = Pick<
|
||||||
IEnvironments,
|
IEnvironments,
|
||||||
'name' | 'enabled' | 'lastSeenAt'
|
'name' | 'enabled' | 'lastSeenAt' | 'yes' | 'no'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface IFeatureToggle {
|
export interface IFeatureToggle {
|
||||||
|
Loading…
Reference in New Issue
Block a user