From 5acf69184552047f08ad5f0849bf742ebf398d31 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 24 Oct 2023 13:58:55 +0200 Subject: [PATCH] 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. --- .../cells/FeatureSeenCell/LastSeenTooltip.tsx | 4 +- .../FeatureEnvironmentSeen.tsx | 68 +++++++++---------- .../getLatestLastSeenAt.test.ts | 48 +++++++++++++ .../getLatestLastSeenAt.ts | 19 ++++++ .../FeatureOverviewSidePanelDetails.tsx | 14 +++- frontend/src/interfaces/featureToggle.ts | 7 +- 6 files changed, 121 insertions(+), 39 deletions(-) create mode 100644 frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.test.ts create mode 100644 frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.ts diff --git a/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx b/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx index f5cd431b01..18c7b6d986 100644 --- a/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx +++ b/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx @@ -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; } diff --git a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx index 1ce06f75cf..8e2253674c 100644 --- a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx @@ -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 ( - { - const [color, textColor] = getColor(unit); - return ( - - } - color={color} - > - - - ); - }} - /> - ) - } - elseShow={ + <> + {lastSeen ? ( + { + const [color, textColor] = getColor(unit); + return ( + + } + color={color} + > + + + ); + }} + /> + ) : ( - } - /> + )} + ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.test.ts b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.test.ts new file mode 100644 index 0000000000..98e621e713 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.test.ts @@ -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(); + }); +}); diff --git a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.ts b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.ts new file mode 100644 index 0000000000..03474e4a98 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/getLatestLastSeenAt.ts @@ -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; + } +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx index 9b86853ad1..b56192828b 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx @@ -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 ( {header} @@ -50,7 +60,7 @@ export const FeatureOverviewSidePanelDetails = ({ {showLastSeenByEnvironment && ( )} diff --git a/frontend/src/interfaces/featureToggle.ts b/frontend/src/interfaces/featureToggle.ts index 8c64b94050..5e258ed984 100644 --- a/frontend/src/interfaces/featureToggle.ts +++ b/frontend/src/interfaces/featureToggle.ts @@ -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 {