1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

Refactor: Remove react-timeago (#7943)

Remove dependency 🫡
This commit is contained in:
Tymoteusz Czech 2024-08-21 12:03:03 +02:00 committed by GitHub
parent 43100f9561
commit 6c5ce52470
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 353 additions and 331 deletions

View File

@ -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) => {
<tr>
<StyledCell>Last seen:</StyledCell>
<StyledCell>
{environment.lastSeen && (
<TimeAgo
key={`${environment.lastSeen}`}
minPeriod={60}
date={
new Date(
environment.lastSeen,
)
}
/>
)}
<TimeAgo
date={environment.lastSeen}
/>
</StyledCell>
</tr>
</tbody>

View File

@ -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 () => {

View File

@ -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<IFeatureArchivedCellProps> = ({
export const FeatureArchivedCell: FC<IFeatureArchivedCellProps> = ({
value: archivedAt,
}) => {
const { locationSettings } = useLocationSettings();
@ -37,12 +37,7 @@ export const FeatureArchivedCell: VFC<IFeatureArchivedCellProps> = ({
arrow
>
<Typography noWrap variant='body2' data-loading>
<TimeAgo
key={`${archivedAt}`}
date={new Date(archivedAt)}
title=''
live={false}
/>
<TimeAgo date={new Date(archivedAt)} refresh={false} />
</Typography>
</Tooltip>
</TextCell>

View File

@ -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 }> = ({
<Box>
<strong>{comment.createdBy.username}</strong>{' '}
<Typography color='text.secondary' component='span'>
commented{' '}
<TimeAgo
key={`${comment.createdAt}`}
minPeriod={60}
date={new Date(comment.createdAt)}
/>
commented <TimeAgo date={comment.createdAt} />
</Typography>
</Box>
</CommentHeader>

View File

@ -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{' '}
<TimeAgo
key={`${changeRequest.createdAt}`}
minPeriod={60}
date={new Date(changeRequest.createdAt)}
/>{' '}
by
Created <TimeAgo date={changeRequest.createdAt} /> by
</Typography>
<Box
sx={(theme) => ({

View File

@ -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 = ({
</StyledUserContainer>
<StyledTimeAgoTypography>
<TimeAgo
key={`${notification.createdAt}`}
date={new Date(notification.createdAt)}
minPeriod={60}
/>
<TimeAgo date={notification.createdAt} />
</StyledTimeAgoTypography>
</StyledSecondaryInfoBox>
</StyledNotificationMessageBox>

View File

@ -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 (
<StyledContainer>
<Tooltip title={tooltip} arrow describeChild>
<StyledBox style={{ background: getColor(unit) }} data-loading>
{children}
</StyledBox>
</Tooltip>
</StyledContainer>
);
};
export const FeatureSeenCell: VFC<IFeatureSeenCellProps> = ({
value: lastSeenAt,
}) => {
return (
<ConditionallyRender
condition={Boolean(lastSeenAt)}
show={
<TimeAgo
key={`${lastSeenAt}`}
date={lastSeenAt!}
title=''
live={false}
formatter={(
value: number,
unit: string,
suffix: string,
) => {
return (
<Wrapper
tooltip={`Last usage reported ${value} ${unit}${
value !== 1 ? 's' : ''
} ${suffix}`}
unit={unit}
>
{value}
{shortenUnitName(unit)}
</Wrapper>
);
}}
/>
}
elseShow={
<Wrapper tooltip='No usage reported from connected applications'>
&ndash;
</Wrapper>
}
/>
);
};

View File

@ -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 (
<StyledDescription {...rest} data-loading>
<StyledDescriptionHeader>
@ -85,7 +85,9 @@ export const LastSeenTooltip = ({
</StyledDescriptionHeader>
<ConditionallyRender
condition={
Boolean(environments) && Boolean(environmentsHaveLastSeen)
Boolean(environments) &&
Boolean(environmentsHaveLastSeen) &&
false
}
show={
<StyledListContainer>
@ -95,43 +97,15 @@ export const LastSeenTooltip = ({
{name}
</StyledDescriptionBlockHeader>
<StyledValueContainer>
<ConditionallyRender
condition={Boolean(lastSeenAt)}
show={
<TimeAgo
key={`${lastSeenAt}`}
date={lastSeenAt!}
title=''
live={false}
formatter={(
value: number,
unit: string,
suffix: string,
) => {
const [, textColor] =
getColor(unit);
return (
<StyledValue
color={textColor}
>
{`${value} ${unit}${
value !== 1
? 's'
: ''
} ${suffix}`}
</StyledValue>
);
}}
/>
}
elseShow={
<StyledValue
color={defaultTextColor}
>
no usage
</StyledValue>
}
/>
<StyledValue
color={getColor(lastSeenAt).text}
>
<TimeAgo
date={lastSeenAt}
refresh={false}
fallback='no usage'
/>
</StyledValue>
</StyledValueContainer>
<LastSeenProgress yes={yes} no={no} />
</StyledDescriptionBlock>
@ -139,27 +113,12 @@ export const LastSeenTooltip = ({
</StyledListContainer>
}
elseShow={
<TimeAgo
date={featureLastSeen}
title=''
live={false}
formatter={(
value: number,
unit: string,
suffix: string,
) => {
return (
<Typography
fontWeight={'bold'}
color={'text.primary'}
>
{`Reported ${value} ${unit}${
value !== 1 ? 's' : ''
} ${suffix}`}
</Typography>
);
}}
/>
<Typography
fontWeight={'bold'}
color={getColor(featureLastSeen).text}
>
Reported <TimeAgo date={featureLastSeen} />
</Typography>
}
/>
<StyledDescriptionSubHeader>

View File

@ -24,6 +24,11 @@ const StyledWrapper = styled(Box, {
},
}));
const StyledSpan = styled('span')(() => ({
display: 'inline-block',
maxWidth: '100%',
}));
export const TextCell: FC<ITextCellProps> = ({
value,
children,
@ -32,8 +37,8 @@ export const TextCell: FC<ITextCellProps> = ({
'data-testid': testid,
}) => (
<StyledWrapper lineClamp={lineClamp} sx={sx}>
<span data-loading='true' data-testid={testid}>
<StyledSpan data-loading='true' data-testid={testid}>
{children ?? value}
</span>
</StyledSpan>
</StyledWrapper>
);

View File

@ -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<ITimeAgoCellProps> = ({
<Tooltip title={title?.(date) ?? date} arrow>
<Typography
noWrap
sx={{
display: 'inline-block',
maxWidth: '100%',
}}
component='span'
variant='body2'
data-loading
>
<TimeAgo
key={`${value}`}
date={new Date(value)}
live={live}
title={''}
/>
<TimeAgo date={value} refresh={live} />
</Typography>
</Tooltip>
</TextCell>

View File

@ -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(<TimeAgo date={null} fallback='N/A' />);
expect(screen.getByText('N/A')).toBeInTheDocument();
render(<TimeAgo date={undefined} fallback='unknown' />);
expect(screen.getByText('unknown')).toBeInTheDocument();
});
test('formats date correctly', () => {
const date = new Date();
render(<TimeAgo date={date} />);
expect(screen.getByText('< 1 minute ago')).toBeInTheDocument();
});
test('updates time periodically', () => {
const date = new Date();
render(<TimeAgo date={date} />);
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(<TimeAgo date={date} refresh={false} />);
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(<TimeAgo date={dateString} />);
expect(screen.getByText('1 hour ago')).toBeInTheDocument();
});
test('cleans up interval on unmount', () => {
const date = new Date();
const { unmount } = render(<TimeAgo date={date} refresh />);
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
unmount();
expect(clearIntervalSpy).toHaveBeenCalled();
});
test('renders fallback for invalid date', () => {
render(<TimeAgo date='invalid-date' fallback='Invalid date' />);
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 }) => (
<>
<TimeAgo date={date} />
</>
);
const { rerender } = render(<Component date={start} />);
expect(screen.getByText('1 minute ago')).toBeInTheDocument();
act(() => vi.advanceTimersByTime(2 * min));
rerender(<Component date={start} />);
expect(screen.getByText('3 minutes ago')).toBeInTheDocument();
rerender(<Component date={start + min} />);
expect(screen.getByText('2 minutes ago')).toBeInTheDocument();
});
test('should refresh on fallback change', () => {
const date = null;
const { rerender } = render(
<TimeAgo date={date} fallback='Initial fallback' />,
);
expect(screen.getByText('Initial fallback')).toBeInTheDocument();
rerender(<TimeAgo date={date} fallback='Updated fallback' />);
expect(screen.getByText('Updated fallback')).toBeInTheDocument();
});
test('should create `time` element', () => {
const now = 1724222592978;
vi.setSystemTime(now);
const { container } = render(<TimeAgo date={now - 3 * min} />);
expect(container).toMatchInlineSnapshot(`
<div>
<time
datetime="2024-08-21T06:40:12.978Z"
>
3 minutes ago
</time>
</div>
`);
});
test('should not create `time` element if `timeElement` is false', () => {
const now = 1724222592978;
vi.setSystemTime(now);
const { container } = render(
<TimeAgo date={now - 5 * h} timeElement={false} />,
);
expect(container).toMatchInlineSnapshot(`
<div>
5 hours ago
</div>
`);
});

View File

@ -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<TimeAgoProps> = ({ ...props }) => {
const { date, fallback, refresh } = props;
const timeAgoRefactorEnabled = useUiFlag('timeAgoRefactor');
if (timeAgoRefactorEnabled) return <NewTimeAgo {...props} />;
if (!date) return fallback;
return (
<LegacyTimeAgo key={`${date}`} date={new Date(date)} live={refresh} />
);
};
export const NewTimeAgo: FC<TimeAgoProps> = ({
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 (
<time dateTime={state.dateTime.toISOString()}>{state.description}</time>
);
};
export default TimeAgo;

View File

@ -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 ? (
<TimeAgo
key={`${lastSeen}`}
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}
{...rest}
/>
}
color={color}
>
<UsageRate stroke={textColor} />
</TooltipContainer>
);
}}
/>
) : (
<TooltipContainer
sx={sx}
tooltip='No usage reported from connected applications'
>
<Box data-loading>
<Box>
<UsageLine />
</Box>
if (!lastSeen) {
return (
<TooltipContainer
sx={sx}
tooltip='No usage reported from connected applications'
>
<Box data-loading>
<Box>
<UsageLine />
</Box>
</TooltipContainer>
)}
</>
</Box>
</TooltipContainer>
);
}
const { background, text } = getColor(lastSeen);
return (
<TooltipContainer
sx={sx}
tooltip={
<LastSeenTooltip
featureLastSeen={lastSeen}
environments={environments}
{...rest}
/>
}
color={background}
>
<UsageRate stroke={text} />
</TooltipContainer>
);
};

View File

@ -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;
};
};

View File

@ -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 (
<TimeAgo
key={`${lastSeen}`}
date={lastSeen}
title=''
live={false}
formatter={(value: number, unit: string) => {
const [color, textColor] = getColor(unit);
return (
<StyledIconWrapper style={{ background: color }}>
<UsageRate stroke={textColor} />
</StyledIconWrapper>
);
}}
/>
<StyledIconWrapper style={{ background }}>
<UsageRate stroke={text} />
</StyledIconWrapper>
);
};
@ -230,11 +220,7 @@ const Environments: FC<{
<Box>{environment.name}</Box>
</CenteredBox>
<CenteredBox>
<TimeAgo
key={`${environment.lastSeenAt}`}
minPeriod={60}
date={environment.lastSeenAt}
/>
<TimeAgo date={environment.lastSeenAt} />
<LastSeenIcon lastSeen={environment.lastSeenAt} />
</CenteredBox>
</EnvironmentLine>

View File

@ -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<ProjectArchiveCardProps> = ({
<p data-loading>
Archived:{' '}
<TimeAgo
key={`${archivedAt}`}
minPeriod={60}
date={
new Date(archivedAt as string)
}
live={false}
date={archivedAt}
refresh={false}
/>
</p>
</Box>

View File

@ -93,6 +93,7 @@ export type UiFlags = {
newEventSearch?: boolean;
archiveProjects?: boolean;
projectListImprovements?: boolean;
timeAgoRefactor?: boolean;
};
export interface IVersionInfo {

View File

@ -149,6 +149,7 @@ exports[`should create default config 1`] = `
"showInactiveUsers": false,
"signals": false,
"strictSchemaValidation": false,
"timeAgoRefactor": false,
"useMemoizedActiveTokens": false,
"useProjectReadModel": false,
"userAccessUIEnabled": false,

View File

@ -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 = {

View File

@ -59,6 +59,7 @@ process.nextTick(async () => {
useProjectReadModel: true,
webhookServiceNameLogging: true,
addonUsageMetrics: true,
timeAgoRefactor: true,
},
},
authentication: {