mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
parent
43100f9561
commit
6c5ce52470
@ -13,7 +13,7 @@ import CheckCircle from '@mui/icons-material/CheckCircle';
|
|||||||
import CloudCircle from '@mui/icons-material/CloudCircle';
|
import CloudCircle from '@mui/icons-material/CloudCircle';
|
||||||
import Flag from '@mui/icons-material/Flag';
|
import Flag from '@mui/icons-material/Flag';
|
||||||
import WarningAmberRounded from '@mui/icons-material/WarningAmberRounded';
|
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 { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { getApplicationIssues } from './ApplicationIssues/ApplicationIssues';
|
import { getApplicationIssues } from './ApplicationIssues/ApplicationIssues';
|
||||||
|
|
||||||
@ -305,17 +305,9 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => {
|
|||||||
<tr>
|
<tr>
|
||||||
<StyledCell>Last seen:</StyledCell>
|
<StyledCell>Last seen:</StyledCell>
|
||||||
<StyledCell>
|
<StyledCell>
|
||||||
{environment.lastSeen && (
|
<TimeAgo
|
||||||
<TimeAgo
|
date={environment.lastSeen}
|
||||||
key={`${environment.lastSeen}`}
|
/>
|
||||||
minPeriod={60}
|
|
||||||
date={
|
|
||||||
new Date(
|
|
||||||
environment.lastSeen,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledCell>
|
</StyledCell>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -13,7 +13,11 @@ const setupApi = (application: ApplicationOverviewSchema) => {
|
|||||||
'/api/admin/metrics/applications/my-app/overview',
|
'/api/admin/metrics/applications/my-app/overview',
|
||||||
application,
|
application,
|
||||||
);
|
);
|
||||||
testServerRoute(server, '/api/admin/ui-config', {});
|
testServerRoute(server, '/api/admin/ui-config', {
|
||||||
|
flags: {
|
||||||
|
timeAgoRefactor: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
test('Display application overview with environments', async () => {
|
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('development environment');
|
||||||
await screen.findByText('999');
|
await screen.findByText('999');
|
||||||
await screen.findByText('unleash-client-node:5.5.0-beta.0');
|
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 () => {
|
test('Display application overview without environments', async () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { VFC } from 'react';
|
import type { FC } from 'react';
|
||||||
import TimeAgo from 'react-timeago';
|
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||||
import { Tooltip, Typography, useTheme } from '@mui/material';
|
import { Tooltip, Typography, useTheme } from '@mui/material';
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
@ -9,7 +9,7 @@ interface IFeatureArchivedCellProps {
|
|||||||
value?: string | Date | null;
|
value?: string | Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureArchivedCell: VFC<IFeatureArchivedCellProps> = ({
|
export const FeatureArchivedCell: FC<IFeatureArchivedCellProps> = ({
|
||||||
value: archivedAt,
|
value: archivedAt,
|
||||||
}) => {
|
}) => {
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
@ -37,12 +37,7 @@ export const FeatureArchivedCell: VFC<IFeatureArchivedCellProps> = ({
|
|||||||
arrow
|
arrow
|
||||||
>
|
>
|
||||||
<Typography noWrap variant='body2' data-loading>
|
<Typography noWrap variant='body2' data-loading>
|
||||||
<TimeAgo
|
<TimeAgo date={new Date(archivedAt)} refresh={false} />
|
||||||
key={`${archivedAt}`}
|
|
||||||
date={new Date(archivedAt)}
|
|
||||||
title=''
|
|
||||||
live={false}
|
|
||||||
/>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TextCell>
|
</TextCell>
|
||||||
|
@ -2,7 +2,7 @@ import type { FC } from 'react';
|
|||||||
import { Markdown } from 'component/common/Markdown/Markdown';
|
import { Markdown } from 'component/common/Markdown/Markdown';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import { Box, styled, Typography } from '@mui/material';
|
import { Box, styled, Typography } from '@mui/material';
|
||||||
import TimeAgo from 'react-timeago';
|
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||||
import { StyledAvatar } from './StyledAvatar';
|
import { StyledAvatar } from './StyledAvatar';
|
||||||
import type { IChangeRequestComment } from '../../changeRequest.types';
|
import type { IChangeRequestComment } from '../../changeRequest.types';
|
||||||
|
|
||||||
@ -35,12 +35,7 @@ export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<strong>{comment.createdBy.username}</strong>{' '}
|
<strong>{comment.createdBy.username}</strong>{' '}
|
||||||
<Typography color='text.secondary' component='span'>
|
<Typography color='text.secondary' component='span'>
|
||||||
commented{' '}
|
commented <TimeAgo date={comment.createdAt} />
|
||||||
<TimeAgo
|
|
||||||
key={`${comment.createdAt}`}
|
|
||||||
minPeriod={60}
|
|
||||||
date={new Date(comment.createdAt)}
|
|
||||||
/>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</CommentHeader>
|
</CommentHeader>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { type FC, useState } from 'react';
|
import { type FC, useState } from 'react';
|
||||||
import { Typography, Tooltip } from '@mui/material';
|
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 type { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
|
||||||
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
import { ChangeRequestStatusBadge } from 'component/changeRequest/ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||||
import {
|
import {
|
||||||
@ -38,13 +38,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: ChangeRequestType }> = ({
|
|||||||
margin: theme.spacing('auto', 0, 'auto', 2),
|
margin: theme.spacing('auto', 0, 'auto', 2),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Created{' '}
|
Created <TimeAgo date={changeRequest.createdAt} /> by
|
||||||
<TimeAgo
|
|
||||||
key={`${changeRequest.createdAt}`}
|
|
||||||
minPeriod={60}
|
|
||||||
date={new Date(changeRequest.createdAt)}
|
|
||||||
/>{' '}
|
|
||||||
by
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
|
@ -11,7 +11,7 @@ import type {
|
|||||||
NotificationsSchemaItemNotificationType,
|
NotificationsSchemaItemNotificationType,
|
||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
|
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 ToggleOffOutlined from '@mui/icons-material/ToggleOffOutlined';
|
||||||
import { flexRow } from 'themes/themeStyles';
|
import { flexRow } from 'themes/themeStyles';
|
||||||
|
|
||||||
@ -157,11 +157,7 @@ export const Notification = ({
|
|||||||
</StyledUserContainer>
|
</StyledUserContainer>
|
||||||
|
|
||||||
<StyledTimeAgoTypography>
|
<StyledTimeAgoTypography>
|
||||||
<TimeAgo
|
<TimeAgo date={notification.createdAt} />
|
||||||
key={`${notification.createdAt}`}
|
|
||||||
date={new Date(notification.createdAt)}
|
|
||||||
minPeriod={60}
|
|
||||||
/>
|
|
||||||
</StyledTimeAgoTypography>
|
</StyledTimeAgoTypography>
|
||||||
</StyledSecondaryInfoBox>
|
</StyledSecondaryInfoBox>
|
||||||
</StyledNotificationMessageBox>
|
</StyledNotificationMessageBox>
|
||||||
|
@ -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'>
|
|
||||||
–
|
|
||||||
</Wrapper>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,5 +1,5 @@
|
|||||||
import { styled, type SxProps, type Theme, Typography } from '@mui/material';
|
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 type { ILastSeenEnvironments } from 'interfaces/featureToggle';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
||||||
@ -74,10 +74,10 @@ export const LastSeenTooltip = ({
|
|||||||
...rest
|
...rest
|
||||||
}: ILastSeenTooltipProps) => {
|
}: ILastSeenTooltipProps) => {
|
||||||
const getColor = useLastSeenColors();
|
const getColor = useLastSeenColors();
|
||||||
const [, defaultTextColor] = getColor();
|
|
||||||
const environmentsHaveLastSeen = environments?.some((environment) =>
|
const environmentsHaveLastSeen = environments?.some((environment) =>
|
||||||
Boolean(environment.lastSeenAt),
|
Boolean(environment.lastSeenAt),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDescription {...rest} data-loading>
|
<StyledDescription {...rest} data-loading>
|
||||||
<StyledDescriptionHeader>
|
<StyledDescriptionHeader>
|
||||||
@ -85,7 +85,9 @@ export const LastSeenTooltip = ({
|
|||||||
</StyledDescriptionHeader>
|
</StyledDescriptionHeader>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={
|
condition={
|
||||||
Boolean(environments) && Boolean(environmentsHaveLastSeen)
|
Boolean(environments) &&
|
||||||
|
Boolean(environmentsHaveLastSeen) &&
|
||||||
|
false
|
||||||
}
|
}
|
||||||
show={
|
show={
|
||||||
<StyledListContainer>
|
<StyledListContainer>
|
||||||
@ -95,43 +97,15 @@ export const LastSeenTooltip = ({
|
|||||||
{name}
|
{name}
|
||||||
</StyledDescriptionBlockHeader>
|
</StyledDescriptionBlockHeader>
|
||||||
<StyledValueContainer>
|
<StyledValueContainer>
|
||||||
<ConditionallyRender
|
<StyledValue
|
||||||
condition={Boolean(lastSeenAt)}
|
color={getColor(lastSeenAt).text}
|
||||||
show={
|
>
|
||||||
<TimeAgo
|
<TimeAgo
|
||||||
key={`${lastSeenAt}`}
|
date={lastSeenAt}
|
||||||
date={lastSeenAt!}
|
refresh={false}
|
||||||
title=''
|
fallback='no usage'
|
||||||
live={false}
|
/>
|
||||||
formatter={(
|
</StyledValue>
|
||||||
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>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StyledValueContainer>
|
</StyledValueContainer>
|
||||||
<LastSeenProgress yes={yes} no={no} />
|
<LastSeenProgress yes={yes} no={no} />
|
||||||
</StyledDescriptionBlock>
|
</StyledDescriptionBlock>
|
||||||
@ -139,27 +113,12 @@ export const LastSeenTooltip = ({
|
|||||||
</StyledListContainer>
|
</StyledListContainer>
|
||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
<TimeAgo
|
<Typography
|
||||||
date={featureLastSeen}
|
fontWeight={'bold'}
|
||||||
title=''
|
color={getColor(featureLastSeen).text}
|
||||||
live={false}
|
>
|
||||||
formatter={(
|
Reported <TimeAgo date={featureLastSeen} />
|
||||||
value: number,
|
</Typography>
|
||||||
unit: string,
|
|
||||||
suffix: string,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
fontWeight={'bold'}
|
|
||||||
color={'text.primary'}
|
|
||||||
>
|
|
||||||
{`Reported ${value} ${unit}${
|
|
||||||
value !== 1 ? 's' : ''
|
|
||||||
} ${suffix}`}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledDescriptionSubHeader>
|
<StyledDescriptionSubHeader>
|
||||||
|
@ -24,6 +24,11 @@ const StyledWrapper = styled(Box, {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledSpan = styled('span')(() => ({
|
||||||
|
display: 'inline-block',
|
||||||
|
maxWidth: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
export const TextCell: FC<ITextCellProps> = ({
|
export const TextCell: FC<ITextCellProps> = ({
|
||||||
value,
|
value,
|
||||||
children,
|
children,
|
||||||
@ -32,8 +37,8 @@ export const TextCell: FC<ITextCellProps> = ({
|
|||||||
'data-testid': testid,
|
'data-testid': testid,
|
||||||
}) => (
|
}) => (
|
||||||
<StyledWrapper lineClamp={lineClamp} sx={sx}>
|
<StyledWrapper lineClamp={lineClamp} sx={sx}>
|
||||||
<span data-loading='true' data-testid={testid}>
|
<StyledSpan data-loading='true' data-testid={testid}>
|
||||||
{children ?? value}
|
{children ?? value}
|
||||||
</span>
|
</StyledSpan>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import { useLocationSettings } from 'hooks/useLocationSettings';
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
import { TextCell } from '../TextCell/TextCell';
|
import { TextCell } from '../TextCell/TextCell';
|
||||||
import TimeAgo from 'react-timeago';
|
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||||
|
|
||||||
interface ITimeAgoCellProps {
|
interface ITimeAgoCellProps {
|
||||||
value?: string | number | Date;
|
value?: string | number | Date;
|
||||||
@ -31,16 +31,15 @@ export const TimeAgoCell: FC<ITimeAgoCellProps> = ({
|
|||||||
<Tooltip title={title?.(date) ?? date} arrow>
|
<Tooltip title={title?.(date) ?? date} arrow>
|
||||||
<Typography
|
<Typography
|
||||||
noWrap
|
noWrap
|
||||||
|
sx={{
|
||||||
|
display: 'inline-block',
|
||||||
|
maxWidth: '100%',
|
||||||
|
}}
|
||||||
component='span'
|
component='span'
|
||||||
variant='body2'
|
variant='body2'
|
||||||
data-loading
|
data-loading
|
||||||
>
|
>
|
||||||
<TimeAgo
|
<TimeAgo date={value} refresh={live} />
|
||||||
key={`${value}`}
|
|
||||||
date={new Date(value)}
|
|
||||||
live={live}
|
|
||||||
title={''}
|
|
||||||
/>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TextCell>
|
</TextCell>
|
||||||
|
139
frontend/src/component/common/TimeAgo/TimeAgo.test.tsx
Normal file
139
frontend/src/component/common/TimeAgo/TimeAgo.test.tsx
Normal 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>
|
||||||
|
`);
|
||||||
|
});
|
73
frontend/src/component/common/TimeAgo/TimeAgo.tsx
Normal file
73
frontend/src/component/common/TimeAgo/TimeAgo.tsx
Normal 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;
|
@ -1,4 +1,3 @@
|
|||||||
import TimeAgo from 'react-timeago';
|
|
||||||
import { LastSeenTooltip } from 'component/common/Table/cells/FeatureSeenCell/LastSeenTooltip';
|
import { LastSeenTooltip } from 'component/common/Table/cells/FeatureSeenCell/LastSeenTooltip';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import type { FC, ReactElement } from 'react';
|
import type { FC, ReactElement } from 'react';
|
||||||
@ -85,45 +84,36 @@ export const FeatureEnvironmentSeen = ({
|
|||||||
|
|
||||||
const lastSeen = getLatestLastSeenAt(environments) || featureLastSeen;
|
const lastSeen = getLatestLastSeenAt(environments) || featureLastSeen;
|
||||||
|
|
||||||
return (
|
if (!lastSeen) {
|
||||||
<>
|
return (
|
||||||
{lastSeen ? (
|
<TooltipContainer
|
||||||
<TimeAgo
|
sx={sx}
|
||||||
key={`${lastSeen}`}
|
tooltip='No usage reported from connected applications'
|
||||||
date={lastSeen}
|
>
|
||||||
title=''
|
<Box data-loading>
|
||||||
live={false}
|
<Box>
|
||||||
formatter={(value: number, unit: string) => {
|
<UsageLine />
|
||||||
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>
|
|
||||||
</Box>
|
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,47 @@
|
|||||||
import { useTheme } from '@mui/material';
|
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 theme = useTheme();
|
||||||
|
const colorsForUnknown = {
|
||||||
|
background: theme.palette.seen.unknown,
|
||||||
|
text: theme.palette.grey.A400,
|
||||||
|
};
|
||||||
|
|
||||||
return (unit?: string): [string, string] => {
|
return (date?: Date | number | string | null): Color => {
|
||||||
switch (unit) {
|
if (!date) {
|
||||||
case 'second':
|
return colorsForUnknown;
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived
|
|||||||
import CloudCircle from '@mui/icons-material/CloudCircle';
|
import CloudCircle from '@mui/icons-material/CloudCircle';
|
||||||
import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg';
|
import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg';
|
||||||
import { FeatureLifecycleStageIcon } from './FeatureLifecycleStageIcon';
|
import { FeatureLifecycleStageIcon } from './FeatureLifecycleStageIcon';
|
||||||
import TimeAgo from 'react-timeago';
|
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||||
import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
||||||
import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors';
|
import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors';
|
||||||
import type { LifecycleStage } from './LifecycleStage';
|
import type { LifecycleStage } from './LifecycleStage';
|
||||||
@ -114,22 +114,12 @@ const LastSeenIcon: FC<{
|
|||||||
lastSeen: string;
|
lastSeen: string;
|
||||||
}> = ({ lastSeen }) => {
|
}> = ({ lastSeen }) => {
|
||||||
const getColor = useLastSeenColors();
|
const getColor = useLastSeenColors();
|
||||||
|
const { text, background } = getColor(lastSeen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimeAgo
|
<StyledIconWrapper style={{ background }}>
|
||||||
key={`${lastSeen}`}
|
<UsageRate stroke={text} />
|
||||||
date={lastSeen}
|
</StyledIconWrapper>
|
||||||
title=''
|
|
||||||
live={false}
|
|
||||||
formatter={(value: number, unit: string) => {
|
|
||||||
const [color, textColor] = getColor(unit);
|
|
||||||
return (
|
|
||||||
<StyledIconWrapper style={{ background: color }}>
|
|
||||||
<UsageRate stroke={textColor} />
|
|
||||||
</StyledIconWrapper>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -230,11 +220,7 @@ const Environments: FC<{
|
|||||||
<Box>{environment.name}</Box>
|
<Box>{environment.name}</Box>
|
||||||
</CenteredBox>
|
</CenteredBox>
|
||||||
<CenteredBox>
|
<CenteredBox>
|
||||||
<TimeAgo
|
<TimeAgo date={environment.lastSeenAt} />
|
||||||
key={`${environment.lastSeenAt}`}
|
|
||||||
minPeriod={60}
|
|
||||||
date={environment.lastSeenAt}
|
|
||||||
/>
|
|
||||||
<LastSeenIcon lastSeen={environment.lastSeenAt} />
|
<LastSeenIcon lastSeen={environment.lastSeenAt} />
|
||||||
</CenteredBox>
|
</CenteredBox>
|
||||||
</EnvironmentLine>
|
</EnvironmentLine>
|
||||||
|
@ -17,7 +17,6 @@ import { formatDateYMDHM } from 'utils/formatDate';
|
|||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
import { parseISO } from 'date-fns';
|
import { parseISO } from 'date-fns';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import TimeAgo from 'react-timeago';
|
|
||||||
import { Box, Link, Tooltip } from '@mui/material';
|
import { Box, Link, Tooltip } from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
@ -29,6 +28,7 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi
|
|||||||
import Delete from '@mui/icons-material/Delete';
|
import Delete from '@mui/icons-material/Delete';
|
||||||
import { Highlighter } from 'component/common/Highlighter/Highlighter';
|
import { Highlighter } from 'component/common/Highlighter/Highlighter';
|
||||||
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
|
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||||
|
|
||||||
export type ProjectArchiveCardProps = {
|
export type ProjectArchiveCardProps = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -91,12 +91,8 @@ export const ProjectArchiveCard: FC<ProjectArchiveCardProps> = ({
|
|||||||
<p data-loading>
|
<p data-loading>
|
||||||
Archived:{' '}
|
Archived:{' '}
|
||||||
<TimeAgo
|
<TimeAgo
|
||||||
key={`${archivedAt}`}
|
date={archivedAt}
|
||||||
minPeriod={60}
|
refresh={false}
|
||||||
date={
|
|
||||||
new Date(archivedAt as string)
|
|
||||||
}
|
|
||||||
live={false}
|
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -93,6 +93,7 @@ export type UiFlags = {
|
|||||||
newEventSearch?: boolean;
|
newEventSearch?: boolean;
|
||||||
archiveProjects?: boolean;
|
archiveProjects?: boolean;
|
||||||
projectListImprovements?: boolean;
|
projectListImprovements?: boolean;
|
||||||
|
timeAgoRefactor?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -149,6 +149,7 @@ exports[`should create default config 1`] = `
|
|||||||
"showInactiveUsers": false,
|
"showInactiveUsers": false,
|
||||||
"signals": false,
|
"signals": false,
|
||||||
"strictSchemaValidation": false,
|
"strictSchemaValidation": false,
|
||||||
|
"timeAgoRefactor": false,
|
||||||
"useMemoizedActiveTokens": false,
|
"useMemoizedActiveTokens": false,
|
||||||
"useProjectReadModel": false,
|
"useProjectReadModel": false,
|
||||||
"userAccessUIEnabled": false,
|
"userAccessUIEnabled": false,
|
||||||
|
@ -66,7 +66,8 @@ export type IFlagKey =
|
|||||||
| 'projectListImprovements'
|
| 'projectListImprovements'
|
||||||
| 'useProjectReadModel'
|
| 'useProjectReadModel'
|
||||||
| 'webhookServiceNameLogging'
|
| 'webhookServiceNameLogging'
|
||||||
| 'addonUsageMetrics';
|
| 'addonUsageMetrics'
|
||||||
|
| 'timeAgoRefactor';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -323,6 +324,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS,
|
process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
timeAgoRefactor: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_TIMEAGO_REFACTOR,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
@ -59,6 +59,7 @@ process.nextTick(async () => {
|
|||||||
useProjectReadModel: true,
|
useProjectReadModel: true,
|
||||||
webhookServiceNameLogging: true,
|
webhookServiceNameLogging: true,
|
||||||
addonUsageMetrics: true,
|
addonUsageMetrics: true,
|
||||||
|
timeAgoRefactor: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user