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 f5cd431b0118..18c7b6d9860d 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 1ce06f75cf11..8e2253674ce6 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 000000000000..98e621e7133a --- /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 000000000000..03474e4a982d --- /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 9b86853ad1ca..b56192828bda 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 8c64b94050e1..5e258ed98469 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 {