mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-31 01:16:01 +02:00
fix: last seen at rendering logic (#5136)
This PR fixes a bug where the rendering in the frontend would only render the last seen component if feature.lastSeenAt was set, the new changes considers whether or not environments last seen at is present and takes precedent over the legacy last seen at field.
This commit is contained in:
parent
da2c46d8c3
commit
5acf691845
@ -1,6 +1,6 @@
|
||||
import { styled, SxProps, Theme, Typography } from '@mui/material';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { IEnvironments, IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
||||
|
||||
@ -56,7 +56,7 @@ const StyledValue = styled('div', {
|
||||
|
||||
interface ILastSeenTooltipProps {
|
||||
featureLastSeen: string;
|
||||
environments?: IEnvironments[] | IFeatureEnvironment[];
|
||||
environments?: ILastSeenEnvironments[];
|
||||
className?: string;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { LastSeenTooltip } from 'component/common/Table/cells/FeatureSeenCell/LastSeenTooltip';
|
||||
import React, { FC, ReactElement } from 'react';
|
||||
import { IEnvironments, IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
|
||||
import { Box, styled, SxProps } from '@mui/material';
|
||||
import { ReactComponent as UsageLine } from 'assets/icons/usage-line.svg';
|
||||
import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg';
|
||||
import { useLastSeenColors } from './useLastSeenColors';
|
||||
import { getLatestLastSeenAt } from './getLatestLastSeenAt';
|
||||
|
||||
interface IFeatureEnvironmentSeenProps {
|
||||
featureLastSeen: string | undefined;
|
||||
environments: IEnvironments[] | IFeatureEnvironment[];
|
||||
environments: ILastSeenEnvironments[];
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
@ -75,43 +76,42 @@ export const FeatureEnvironmentSeen = ({
|
||||
sx,
|
||||
}: IFeatureEnvironmentSeenProps) => {
|
||||
const getColor = useLastSeenColors();
|
||||
|
||||
const lastSeen = getLatestLastSeenAt(environments) || featureLastSeen;
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={Boolean(featureLastSeen)}
|
||||
show={
|
||||
featureLastSeen && (
|
||||
<TimeAgo
|
||||
date={featureLastSeen}
|
||||
title=''
|
||||
live={false}
|
||||
formatter={(value: number, unit: string) => {
|
||||
const [color, textColor] = getColor(unit);
|
||||
return (
|
||||
<TooltipContainer
|
||||
sx={sx}
|
||||
tooltip={
|
||||
<LastSeenTooltip
|
||||
featureLastSeen={featureLastSeen}
|
||||
environments={environments}
|
||||
/>
|
||||
}
|
||||
color={color}
|
||||
>
|
||||
<UsageRate stroke={textColor} />
|
||||
</TooltipContainer>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
elseShow={
|
||||
<>
|
||||
{lastSeen ? (
|
||||
<TimeAgo
|
||||
date={lastSeen}
|
||||
title=''
|
||||
live={false}
|
||||
formatter={(value: number, unit: string) => {
|
||||
const [color, textColor] = getColor(unit);
|
||||
return (
|
||||
<TooltipContainer
|
||||
sx={sx}
|
||||
tooltip={
|
||||
<LastSeenTooltip
|
||||
featureLastSeen={lastSeen}
|
||||
environments={environments}
|
||||
/>
|
||||
}
|
||||
color={color}
|
||||
>
|
||||
<UsageRate stroke={textColor} />
|
||||
</TooltipContainer>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TooltipContainer
|
||||
sx={sx}
|
||||
tooltip='No usage reported from connected applications'
|
||||
>
|
||||
<UsageLine />
|
||||
</TooltipContainer>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,48 @@
|
||||
import { IEnvironments } from 'interfaces/featureToggle';
|
||||
|
||||
import { getLatestLastSeenAt } from './getLatestLastSeenAt';
|
||||
|
||||
describe('getLatestLastSeenAt', () => {
|
||||
test('should return the most recent lastSeenAt date', () => {
|
||||
const input: IEnvironments[] = [
|
||||
{
|
||||
name: 'test1',
|
||||
lastSeenAt: '2023-10-22T08:48:11.869Z',
|
||||
enabled: false,
|
||||
variantCount: 0,
|
||||
},
|
||||
{
|
||||
name: 'test2',
|
||||
lastSeenAt: '2023-10-23T08:48:11.869Z',
|
||||
enabled: true,
|
||||
variantCount: 0,
|
||||
},
|
||||
{
|
||||
name: 'test3',
|
||||
lastSeenAt: '2023-10-24T08:48:11.869Z',
|
||||
enabled: true,
|
||||
variantCount: 0,
|
||||
},
|
||||
];
|
||||
const expected = '2023-10-24T08:48:11.869Z';
|
||||
expect(getLatestLastSeenAt(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should handle an empty array', () => {
|
||||
const input: IEnvironments[] = [];
|
||||
const expected = null;
|
||||
expect(getLatestLastSeenAt(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should not fail with non-standard date formats', () => {
|
||||
const input: IEnvironments[] = [
|
||||
{
|
||||
name: 'test',
|
||||
lastSeenAt: 'Some Invalid Date',
|
||||
enabled: true,
|
||||
variantCount: 0,
|
||||
},
|
||||
];
|
||||
expect(() => getLatestLastSeenAt(input)).not.toThrow();
|
||||
});
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
import { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||
|
||||
export const getLatestLastSeenAt = (
|
||||
environments: ILastSeenEnvironments[],
|
||||
): string | null => {
|
||||
try {
|
||||
if (!Array.isArray(environments) || environments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return environments
|
||||
.filter((item) => Boolean(item.lastSeenAt))
|
||||
.map((item) => new Date(item.lastSeenAt!))
|
||||
.reduce((latest, current) => (current > latest ? current : latest))
|
||||
.toISOString();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
@ -1,4 +1,7 @@
|
||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||
import {
|
||||
IFeatureToggle,
|
||||
ILastSeenEnvironments,
|
||||
} from 'interfaces/featureToggle';
|
||||
import { styled } from '@mui/material';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { formatDateYMD } from 'utils/formatDate';
|
||||
@ -34,6 +37,13 @@ export const FeatureOverviewSidePanelDetails = ({
|
||||
uiConfig.flags.lastSeenByEnvironment,
|
||||
);
|
||||
|
||||
const lastSeenEnvironments: ILastSeenEnvironments[] =
|
||||
feature.environments?.map((env) => ({
|
||||
name: env.name,
|
||||
lastSeenAt: env.lastSeenAt,
|
||||
enabled: env.enabled,
|
||||
}));
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{header}
|
||||
@ -50,7 +60,7 @@ export const FeatureOverviewSidePanelDetails = ({
|
||||
{showLastSeenByEnvironment && (
|
||||
<FeatureEnvironmentSeen
|
||||
featureLastSeen={feature.lastSeenAt}
|
||||
environments={feature.environments}
|
||||
environments={lastSeenEnvironments}
|
||||
sx={{ p: 0 }}
|
||||
/>
|
||||
)}
|
||||
|
@ -22,6 +22,11 @@ export interface IEnvironments {
|
||||
hasEnabledStrategies?: boolean;
|
||||
}
|
||||
|
||||
export type ILastSeenEnvironments = Pick<
|
||||
IEnvironments,
|
||||
'name' | 'enabled' | 'lastSeenAt'
|
||||
>;
|
||||
|
||||
export interface IFeatureToggle {
|
||||
stale: boolean;
|
||||
archived: boolean;
|
||||
@ -52,7 +57,7 @@ export interface IFeatureEnvironment {
|
||||
enabled: boolean;
|
||||
strategies: IFeatureStrategy[];
|
||||
variants?: IFeatureVariant[];
|
||||
lastSeenAt?: Date;
|
||||
lastSeenAt?: string;
|
||||
}
|
||||
|
||||
export interface IFeatureEnvironmentWithCrEnabled extends IFeatureEnvironment {
|
||||
|
Loading…
Reference in New Issue
Block a user