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: {