From 6c5ce52470a16e9f4d5ffdfb782de21b14f52866 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:03:03 +0200 Subject: [PATCH] Refactor: Remove `react-timeago` (#7943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove dependency 🫡 --- .../application/ApplicationChart.tsx | 16 +- .../application/ApplicationOverview.test.tsx | 8 +- .../FeatureArchivedCell.tsx | 13 +- .../ChangeRequestComment.tsx | 9 +- .../ChangeRequestHeader.tsx | 10 +- .../common/Notifications/Notification.tsx | 8 +- .../cells/FeatureSeenCell/FeatureSeenCell.tsx | 131 ----------------- .../cells/FeatureSeenCell/LastSeenTooltip.tsx | 81 +++------- .../common/Table/cells/TextCell/TextCell.tsx | 9 +- .../Table/cells/TimeAgoCell/TimeAgoCell.tsx | 13 +- .../component/common/TimeAgo/TimeAgo.test.tsx | 139 ++++++++++++++++++ .../src/component/common/TimeAgo/TimeAgo.tsx | 73 +++++++++ .../FeatureEnvironmentSeen.tsx | 70 ++++----- .../useLastSeenColors.ts | 58 +++++--- .../FeatureLifecycleTooltip.tsx | 26 +--- .../NewProjectCard/ProjectArchiveCard.tsx | 10 +- frontend/src/interfaces/uiConfig.ts | 1 + .../__snapshots__/create-config.test.ts.snap | 1 + src/lib/types/experimental.ts | 7 +- src/server-dev.ts | 1 + 20 files changed, 353 insertions(+), 331 deletions(-) delete mode 100644 frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureSeenCell.tsx create mode 100644 frontend/src/component/common/TimeAgo/TimeAgo.test.tsx create mode 100644 frontend/src/component/common/TimeAgo/TimeAgo.tsx diff --git a/frontend/src/component/application/ApplicationChart.tsx b/frontend/src/component/application/ApplicationChart.tsx index e8d4089e0b..c76a7ea64b 100644 --- a/frontend/src/component/application/ApplicationChart.tsx +++ b/frontend/src/component/application/ApplicationChart.tsx @@ -13,7 +13,7 @@ import CheckCircle from '@mui/icons-material/CheckCircle'; import CloudCircle from '@mui/icons-material/CloudCircle'; import Flag from '@mui/icons-material/Flag'; import WarningAmberRounded from '@mui/icons-material/WarningAmberRounded'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { getApplicationIssues } from './ApplicationIssues/ApplicationIssues'; @@ -305,17 +305,9 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => { Last seen: - {environment.lastSeen && ( - - )} + diff --git a/frontend/src/component/application/ApplicationOverview.test.tsx b/frontend/src/component/application/ApplicationOverview.test.tsx index c1db878abb..7d029130b4 100644 --- a/frontend/src/component/application/ApplicationOverview.test.tsx +++ b/frontend/src/component/application/ApplicationOverview.test.tsx @@ -13,7 +13,11 @@ const setupApi = (application: ApplicationOverviewSchema) => { '/api/admin/metrics/applications/my-app/overview', application, ); - testServerRoute(server, '/api/admin/ui-config', {}); + testServerRoute(server, '/api/admin/ui-config', { + flags: { + timeAgoRefactor: true, + }, + }); }; test('Display application overview with environments', async () => { @@ -51,7 +55,7 @@ test('Display application overview with environments', async () => { await screen.findByText('development environment'); await screen.findByText('999'); await screen.findByText('unleash-client-node:5.5.0-beta.0'); - await screen.findByText('0 seconds ago'); + await screen.findByText('< 1 minute ago'); }); test('Display application overview without environments', async () => { diff --git a/frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx b/frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx index 3f2864f46b..16f97c3e27 100644 --- a/frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx @@ -1,5 +1,5 @@ -import type { VFC } from 'react'; -import TimeAgo from 'react-timeago'; +import type { FC } from 'react'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import { Tooltip, Typography, useTheme } from '@mui/material'; import { formatDateYMD } from 'utils/formatDate'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; @@ -9,7 +9,7 @@ interface IFeatureArchivedCellProps { value?: string | Date | null; } -export const FeatureArchivedCell: VFC = ({ +export const FeatureArchivedCell: FC = ({ value: archivedAt, }) => { const { locationSettings } = useLocationSettings(); @@ -37,12 +37,7 @@ export const FeatureArchivedCell: VFC = ({ arrow > - + diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx index 3f63465fff..c822321736 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react'; import { Markdown } from 'component/common/Markdown/Markdown'; import Paper from '@mui/material/Paper'; import { Box, styled, Typography } from '@mui/material'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import { StyledAvatar } from './StyledAvatar'; import type { IChangeRequestComment } from '../../changeRequest.types'; @@ -35,12 +35,7 @@ export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({ {comment.createdBy.username}{' '} - commented{' '} - + commented diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestHeader/ChangeRequestHeader.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestHeader/ChangeRequestHeader.tsx index e5c3d4aac5..32038ea6af 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestHeader/ChangeRequestHeader.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestHeader/ChangeRequestHeader.tsx @@ -1,7 +1,7 @@ import { Box } from '@mui/material'; import { type FC, useState } from 'react'; import { Typography, Tooltip } from '@mui/material'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import type { ChangeRequestType } from 'component/changeRequest/changeRequest.types'; import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge'; import { @@ -38,13 +38,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: ChangeRequestType }> = ({ margin: theme.spacing('auto', 0, 'auto', 2), })} > - Created{' '} - {' '} - by + Created by ({ diff --git a/frontend/src/component/common/Notifications/Notification.tsx b/frontend/src/component/common/Notifications/Notification.tsx index 6b584507d1..6797105cad 100644 --- a/frontend/src/component/common/Notifications/Notification.tsx +++ b/frontend/src/component/common/Notifications/Notification.tsx @@ -11,7 +11,7 @@ import type { NotificationsSchemaItemNotificationType, } from 'openapi'; import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import ToggleOffOutlined from '@mui/icons-material/ToggleOffOutlined'; import { flexRow } from 'themes/themeStyles'; @@ -157,11 +157,7 @@ export const Notification = ({ - + diff --git a/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureSeenCell.tsx b/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureSeenCell.tsx deleted file mode 100644 index 98fba47e81..0000000000 --- a/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureSeenCell.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import type React from 'react'; -import type { FC, VFC } from 'react'; -import TimeAgo from 'react-timeago'; -import { styled, Tooltip, useTheme } from '@mui/material'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; - -function shortenUnitName(unit?: string): string { - switch (unit) { - case 'second': - return 's'; - case 'minute': - return 'm'; - case 'hour': - return 'h'; - case 'day': - return 'D'; - case 'week': - return 'W'; - case 'month': - return 'M'; - case 'year': - return 'Y'; - default: - return ''; - } -} - -const useFeatureColor = () => { - const theme = useTheme(); - - return (unit?: string): string => { - switch (unit) { - case 'second': - return theme.palette.seen.recent; - case 'minute': - return theme.palette.seen.recent; - case 'hour': - return theme.palette.seen.recent; - case 'day': - return theme.palette.seen.recent; - case 'week': - return theme.palette.seen.inactive; - case 'month': - return theme.palette.seen.abandoned; - case 'year': - return theme.palette.seen.abandoned; - default: - return theme.palette.seen.unknown; - } - }; -}; - -const StyledContainer = styled('div')(({ theme }) => ({ - display: 'flex', - padding: theme.spacing(1.5), -})); - -const StyledBox = styled('div')(({ theme }) => ({ - width: '38px', - height: '38px', - background: theme.palette.background.paper, - borderRadius: `${theme.shape.borderRadius}px`, - textAlign: 'center', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - fontSize: theme.typography.body2.fontSize, - margin: '0 auto', -})); - -interface IFeatureSeenCellProps { - value?: string | Date | null; -} - -const Wrapper: FC<{ - unit?: string; - tooltip: string; - children?: React.ReactNode; -}> = ({ unit, tooltip, children }) => { - const getColor = useFeatureColor(); - - return ( - - - - {children} - - - - ); -}; - -export const FeatureSeenCell: VFC = ({ - value: lastSeenAt, -}) => { - return ( - { - return ( - - {value} - {shortenUnitName(unit)} - - ); - }} - /> - } - elseShow={ - - – - - } - /> - ); -}; diff --git a/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx b/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx index db09b5470d..cc40260e52 100644 --- a/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx +++ b/frontend/src/component/common/Table/cells/FeatureSeenCell/LastSeenTooltip.tsx @@ -1,5 +1,5 @@ import { styled, type SxProps, type Theme, Typography } from '@mui/material'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import type { ILastSeenEnvironments } from 'interfaces/featureToggle'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors'; @@ -74,10 +74,10 @@ export const LastSeenTooltip = ({ ...rest }: ILastSeenTooltipProps) => { const getColor = useLastSeenColors(); - const [, defaultTextColor] = getColor(); const environmentsHaveLastSeen = environments?.some((environment) => Boolean(environment.lastSeenAt), ); + return ( @@ -85,7 +85,9 @@ export const LastSeenTooltip = ({ @@ -95,43 +97,15 @@ export const LastSeenTooltip = ({ {name} - { - const [, textColor] = - getColor(unit); - return ( - - {`${value} ${unit}${ - value !== 1 - ? 's' - : '' - } ${suffix}`} - - ); - }} - /> - } - elseShow={ - - no usage - - } - /> + + + @@ -139,27 +113,12 @@ export const LastSeenTooltip = ({ } elseShow={ - { - return ( - - {`Reported ${value} ${unit}${ - value !== 1 ? 's' : '' - } ${suffix}`} - - ); - }} - /> + + Reported + } /> diff --git a/frontend/src/component/common/Table/cells/TextCell/TextCell.tsx b/frontend/src/component/common/Table/cells/TextCell/TextCell.tsx index b97389264b..98fd97fe2a 100644 --- a/frontend/src/component/common/Table/cells/TextCell/TextCell.tsx +++ b/frontend/src/component/common/Table/cells/TextCell/TextCell.tsx @@ -24,6 +24,11 @@ const StyledWrapper = styled(Box, { }, })); +const StyledSpan = styled('span')(() => ({ + display: 'inline-block', + maxWidth: '100%', +})); + export const TextCell: FC = ({ value, children, @@ -32,8 +37,8 @@ export const TextCell: FC = ({ 'data-testid': testid, }) => ( - + {children ?? value} - + ); diff --git a/frontend/src/component/common/Table/cells/TimeAgoCell/TimeAgoCell.tsx b/frontend/src/component/common/Table/cells/TimeAgoCell/TimeAgoCell.tsx index 0cee4dd955..02aa222878 100644 --- a/frontend/src/component/common/Table/cells/TimeAgoCell/TimeAgoCell.tsx +++ b/frontend/src/component/common/Table/cells/TimeAgoCell/TimeAgoCell.tsx @@ -3,7 +3,7 @@ import { useLocationSettings } from 'hooks/useLocationSettings'; import type { FC } from 'react'; import { formatDateYMD } from 'utils/formatDate'; import { TextCell } from '../TextCell/TextCell'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; interface ITimeAgoCellProps { value?: string | number | Date; @@ -31,16 +31,15 @@ export const TimeAgoCell: FC = ({ - + diff --git a/frontend/src/component/common/TimeAgo/TimeAgo.test.tsx b/frontend/src/component/common/TimeAgo/TimeAgo.test.tsx new file mode 100644 index 0000000000..1ec64f48c9 --- /dev/null +++ b/frontend/src/component/common/TimeAgo/TimeAgo.test.tsx @@ -0,0 +1,139 @@ +import { vi } from 'vitest'; +import { act, render, screen } from '@testing-library/react'; +import { NewTimeAgo as TimeAgo } from './TimeAgo'; + +const h = 3_600_000 as const; +const min = 60_000 as const; +const s = 1_000 as const; + +beforeAll(() => { + vi.useFakeTimers(); +}); + +afterAll(() => { + vi.useRealTimers(); +}); + +test('renders fallback when date is null or undefined', () => { + render(); + expect(screen.getByText('N/A')).toBeInTheDocument(); + + render(); + expect(screen.getByText('unknown')).toBeInTheDocument(); +}); + +test('formats date correctly', () => { + const date = new Date(); + render(); + expect(screen.getByText('< 1 minute ago')).toBeInTheDocument(); +}); + +test('updates time periodically', () => { + const date = new Date(); + render(); + + expect(screen.getByText('< 1 minute ago')).toBeInTheDocument(); + + act(() => vi.advanceTimersByTime(61 * s)); + + expect(screen.getByText('1 minute ago')).toBeInTheDocument(); +}); + +test('stops updating when live is false', () => { + const date = new Date(); + const setIntervalSpy = vi.spyOn(global, 'setInterval'); + render(); + + expect(screen.getByText('< 1 minute ago')).toBeInTheDocument(); + + act(() => vi.advanceTimersByTime(61 * s)); + + expect(screen.getByText('< 1 minute ago')).toBeInTheDocument(); + + expect(setIntervalSpy).not.toHaveBeenCalled(); +}); + +test('handles string dates', () => { + const dateString = '2024-01-01T00:00:00Z'; + vi.setSystemTime(new Date('2024-01-01T01:01:00Z')); + + render(); + expect(screen.getByText('1 hour ago')).toBeInTheDocument(); +}); + +test('cleans up interval on unmount', () => { + const date = new Date(); + const { unmount } = render(); + + const clearIntervalSpy = vi.spyOn(global, 'clearInterval'); + unmount(); + expect(clearIntervalSpy).toHaveBeenCalled(); +}); + +test('renders fallback for invalid date', () => { + render(); + expect(screen.getByText('Invalid date')).toBeInTheDocument(); +}); + +test('on date change, current time should be updated', () => { + const start = new Date().getTime(); + + vi.advanceTimersByTime(60_000); + + const Component = ({ date }: { date: number }) => ( + <> + + + ); + + const { rerender } = render(); + expect(screen.getByText('1 minute ago')).toBeInTheDocument(); + + act(() => vi.advanceTimersByTime(2 * min)); + rerender(); + + expect(screen.getByText('3 minutes ago')).toBeInTheDocument(); + + rerender(); + + expect(screen.getByText('2 minutes ago')).toBeInTheDocument(); +}); + +test('should refresh on fallback change', () => { + const date = null; + const { rerender } = render( + , + ); + expect(screen.getByText('Initial fallback')).toBeInTheDocument(); + + rerender(); + expect(screen.getByText('Updated fallback')).toBeInTheDocument(); +}); + +test('should create `time` element', () => { + const now = 1724222592978; + vi.setSystemTime(now); + const { container } = render(); + expect(container).toMatchInlineSnapshot(` +
+ +
+ `); +}); + +test('should not create `time` element if `timeElement` is false', () => { + const now = 1724222592978; + vi.setSystemTime(now); + const { container } = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+ 5 hours ago +
+ `); +}); diff --git a/frontend/src/component/common/TimeAgo/TimeAgo.tsx b/frontend/src/component/common/TimeAgo/TimeAgo.tsx new file mode 100644 index 0000000000..0c04e74981 --- /dev/null +++ b/frontend/src/component/common/TimeAgo/TimeAgo.tsx @@ -0,0 +1,73 @@ +import { useEffect, useState, type FC } from 'react'; +import { formatDistanceToNow, secondsToMilliseconds } from 'date-fns'; +import { default as LegacyTimeAgo } from 'react-timeago'; +import { useUiFlag } from 'hooks/useUiFlag'; + +type TimeAgoProps = { + date: Date | number | string | null | undefined; + fallback?: string; + refresh?: boolean; + timeElement?: boolean; +}; + +const formatTimeAgo = (date: string | number | Date) => + formatDistanceToNow(new Date(date), { + addSuffix: true, + }) + .replace('about ', '') + .replace('less than a minute ago', '< 1 minute ago'); + +export const TimeAgo: FC = ({ ...props }) => { + const { date, fallback, refresh } = props; + const timeAgoRefactorEnabled = useUiFlag('timeAgoRefactor'); + + if (timeAgoRefactorEnabled) return ; + if (!date) return fallback; + return ( + + ); +}; + +export const NewTimeAgo: FC = ({ + date, + fallback = '', + refresh = true, + timeElement = true, +}) => { + const getValue = (): { description: string; dateTime?: Date } => { + try { + if (!date) return { description: fallback }; + return { + description: formatTimeAgo(date), + dateTime: timeElement ? new Date(date) : undefined, + }; + } catch { + return { description: fallback }; + } + }; + const [state, setState] = useState(getValue); + + useEffect(() => { + setState(getValue); + }, [date, fallback]); + + useEffect(() => { + if (!date || !refresh) return; + + const intervalId = setInterval(() => { + setState(getValue); + }, secondsToMilliseconds(12)); + + return () => clearInterval(intervalId); + }, [refresh]); + + if (!state.dateTime) { + return state.description; + } + + return ( + + ); +}; + +export default TimeAgo; diff --git a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx index 28583bba89..5ebdafc6d5 100644 --- a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen.tsx @@ -1,4 +1,3 @@ -import TimeAgo from 'react-timeago'; import { LastSeenTooltip } from 'component/common/Table/cells/FeatureSeenCell/LastSeenTooltip'; import type React from 'react'; import type { FC, ReactElement } from 'react'; @@ -85,45 +84,36 @@ export const FeatureEnvironmentSeen = ({ const lastSeen = getLatestLastSeenAt(environments) || featureLastSeen; - return ( - <> - {lastSeen ? ( - { - const [color, textColor] = getColor(unit); - return ( - - } - color={color} - > - - - ); - }} - /> - ) : ( - - - - - + if (!lastSeen) { + return ( + + + + - - )} - + + + ); + } + + const { background, text } = getColor(lastSeen); + + return ( + + } + color={background} + > + + ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors.ts b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors.ts index a069f0bee3..2bcba0221f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors.ts +++ b/frontend/src/component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors.ts @@ -1,25 +1,47 @@ import { useTheme } from '@mui/material'; +import { differenceInDays } from 'date-fns'; -export const useLastSeenColors = () => { +type Color = { + background: string; + text: string; +}; + +export const useLastSeenColors = (): (( + date?: Date | number | string | null, +) => Color) => { const theme = useTheme(); + const colorsForUnknown = { + background: theme.palette.seen.unknown, + text: theme.palette.grey.A400, + }; - return (unit?: string): [string, string] => { - switch (unit) { - case 'second': - case 'minute': - case 'hour': - case 'day': - return [theme.palette.seen.recent, theme.palette.success.main]; - case 'week': - return [ - theme.palette.seen.inactive, - theme.palette.warning.main, - ]; - case 'month': - case 'year': - return [theme.palette.seen.abandoned, theme.palette.error.main]; - default: - return [theme.palette.seen.unknown, theme.palette.grey.A400]; + return (date?: Date | number | string | null): Color => { + if (!date) { + return colorsForUnknown; } + + try { + const days = differenceInDays(Date.now(), new Date(date)); + + if (days < 1) { + return { + background: theme.palette.seen.recent, + text: theme.palette.success.main, + }; + } + if (days <= 7) { + return { + background: theme.palette.seen.inactive, + text: theme.palette.warning.main, + }; + } + + return { + background: theme.palette.seen.abandoned, + text: theme.palette.error.main, + }; + } catch {} + + return colorsForUnknown; }; }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx index eabaa59966..9f262a51cf 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx @@ -11,7 +11,7 @@ import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived import CloudCircle from '@mui/icons-material/CloudCircle'; import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg'; import { FeatureLifecycleStageIcon } from './FeatureLifecycleStageIcon'; -import TimeAgo from 'react-timeago'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors'; import type { LifecycleStage } from './LifecycleStage'; @@ -114,22 +114,12 @@ const LastSeenIcon: FC<{ lastSeen: string; }> = ({ lastSeen }) => { const getColor = useLastSeenColors(); + const { text, background } = getColor(lastSeen); return ( - { - const [color, textColor] = getColor(unit); - return ( - - - - ); - }} - /> + + + ); }; @@ -230,11 +220,7 @@ const Environments: FC<{ {environment.name} - + diff --git a/frontend/src/component/project/NewProjectCard/ProjectArchiveCard.tsx b/frontend/src/component/project/NewProjectCard/ProjectArchiveCard.tsx index 0acd011b68..b0123a7a15 100644 --- a/frontend/src/component/project/NewProjectCard/ProjectArchiveCard.tsx +++ b/frontend/src/component/project/NewProjectCard/ProjectArchiveCard.tsx @@ -17,7 +17,6 @@ import { formatDateYMDHM } from 'utils/formatDate'; import { useLocationSettings } from 'hooks/useLocationSettings'; import { parseISO } from 'date-fns'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import TimeAgo from 'react-timeago'; import { Box, Link, Tooltip } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; import { @@ -29,6 +28,7 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi import Delete from '@mui/icons-material/Delete'; import { Highlighter } from 'component/common/Highlighter/Highlighter'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; +import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; export type ProjectArchiveCardProps = { id: string; @@ -91,12 +91,8 @@ export const ProjectArchiveCard: FC = ({

Archived:{' '}

diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index ad82100c9d..f8e114dc1a 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -93,6 +93,7 @@ export type UiFlags = { newEventSearch?: boolean; archiveProjects?: boolean; projectListImprovements?: boolean; + timeAgoRefactor?: boolean; }; export interface IVersionInfo { diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 1ef0c66592..b09129ae10 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -149,6 +149,7 @@ exports[`should create default config 1`] = ` "showInactiveUsers": false, "signals": false, "strictSchemaValidation": false, + "timeAgoRefactor": false, "useMemoizedActiveTokens": false, "useProjectReadModel": false, "userAccessUIEnabled": false, diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index b8a9252180..6312a2b566 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -66,7 +66,8 @@ export type IFlagKey = | 'projectListImprovements' | 'useProjectReadModel' | 'webhookServiceNameLogging' - | 'addonUsageMetrics'; + | 'addonUsageMetrics' + | 'timeAgoRefactor'; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; @@ -323,6 +324,10 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS, false, ), + timeAgoRefactor: parseEnvVarBoolean( + process.env.UNLEASH_TIMEAGO_REFACTOR, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = { diff --git a/src/server-dev.ts b/src/server-dev.ts index 31ad0c5ecd..1d8860a96e 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -59,6 +59,7 @@ process.nextTick(async () => { useProjectReadModel: true, webhookServiceNameLogging: true, addonUsageMetrics: true, + timeAgoRefactor: true, }, }, authentication: {