1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-27 13:49:10 +02:00

chore(AI): lifecycleMetrics flag cleanup (#10511)

This PR cleans up the lifecycleMetrics flag. These changes were
automatically generated by AI and should be reviewed carefully.

Fixes #10505

---------

Co-authored-by: unleash-bot <194219037+unleash-bot[bot]@users.noreply.github.com>
Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
unleash-bot[bot] 2025-08-21 14:35:11 +02:00 committed by GitHub
parent a9b1d7c11b
commit 170ed87fcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 101 additions and 331 deletions

View File

@ -1,63 +0,0 @@
import { render } from 'utils/testRenderer';
import { fireEvent, screen } from '@testing-library/react';
import { Insights } from './Insights.tsx';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { vi } from 'vitest';
const server = testServerSetup();
const setupApi = () => {
testServerRoute(server, '/api/admin/insights', {
users: { total: 0, active: 0, inactive: 0 },
userTrends: [],
projectFlagTrends: [],
metricsSummaryTrends: [],
flags: { total: 0 },
flagTrends: [],
environmentTypeTrends: [],
lifecycleTrends: [],
creationArchiveTrends: [],
});
testServerRoute(server, '/api/admin/projects', {
projects: [
{ name: 'Project A Name', id: 'projectA' },
{ name: 'Project B Name', id: 'projectB' },
],
});
};
const currentTime = '2024-04-25T08:05:00.000Z';
// todo(lifecycleMetrics): this test won't be relevant anymore because the
// filters are on each section instead of the top-level component. Consider
// rewriting this for the individual section components instead.
test('Filter insights by project and date', async () => {
vi.setSystemTime(currentTime);
setupApi();
render(<Insights withCharts={false} />);
const addFilter = await screen.findByText('Filter');
fireEvent.click(addFilter);
const projectFilter = await screen.findByText('Project');
// filter by project
fireEvent.click(projectFilter);
await screen.findByText('Project A Name');
const projectName = await screen.findByText('Project B Name');
await fireEvent.click(projectName);
expect(window.location.href).toContain('project=IS%3AprojectB');
// last month moving window by default
const fromDate = await screen.findByText('03/25/2024');
await screen.findByText('04/25/2024');
// change dates by preset range
fireEvent.click(fromDate);
const previousMonth = await screen.findByText('Previous month');
fireEvent.click(previousMonth);
await screen.findByText('03/01/2024');
await screen.findByText('03/31/2024');
expect(window.location.href).toContain(
'?project=IS%3AprojectB&from=IS%3A2024-03-01&to=IS%3A2024-03-31',
);
});

View File

@ -1,8 +1,6 @@
import type { FC } from 'react';
import { styled } from '@mui/material';
import { InsightsHeader } from './components/InsightsHeader/InsightsHeader.tsx';
import { useUiFlag } from 'hooks/useUiFlag.ts';
import { LegacyInsights } from './LegacyInsights.tsx';
import { StyledContainer } from './InsightsCharts.styles.ts';
import { LifecycleInsights } from './sections/LifecycleInsights.tsx';
import { PerformanceInsights } from './sections/PerformanceInsights.tsx';
@ -12,21 +10,13 @@ const StyledWrapper = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(2),
}));
const NewInsights: FC = () => {
return (
<StyledWrapper>
<InsightsHeader />
<StyledContainer>
<LifecycleInsights />
<PerformanceInsights />
<UserInsights />
</StyledContainer>
</StyledWrapper>
);
};
export const Insights: FC<{ withCharts?: boolean }> = (props) => {
const useNewInsights = useUiFlag('lifecycleMetrics');
return useNewInsights ? <NewInsights /> : <LegacyInsights {...props} />;
};
export const Insights: FC = () => (
<StyledWrapper>
<InsightsHeader />
<StyledContainer>
<LifecycleInsights />
<PerformanceInsights />
<UserInsights />
</StyledContainer>
</StyledWrapper>
);

View File

@ -127,55 +127,51 @@ export const InsightsCharts: FC<IChartsProps> = ({
<ConditionallyRender
condition={showAllProjects}
show={
<>
<StyledWidget>
<StyledWidgetStats>
<WidgetTitle title='Total users' />
<UserStats
count={usersTotal}
active={usersActive}
inactive={usersInactive}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<UsersChart
userTrends={userTrends}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
</>
<StyledWidget>
<StyledWidgetStats>
<WidgetTitle title='Total users' />
<UserStats
count={usersTotal}
active={usersActive}
inactive={usersInactive}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<UsersChart
userTrends={userTrends}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
}
elseShow={
<>
<StyledWidget>
<StyledWidgetStats>
<WidgetTitle
title={
isOneProjectSelected
? 'Users in project'
: 'Users per project on average'
}
tooltip={
isOneProjectSelected
? 'Number of users in selected projects.'
: 'Average number of users for selected projects.'
}
/>
<UserStats
count={summary.averageUsers}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<UsersPerProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
</>
<StyledWidget>
<StyledWidgetStats>
<WidgetTitle
title={
isOneProjectSelected
? 'Users in project'
: 'Users per project on average'
}
tooltip={
isOneProjectSelected
? 'Number of users in selected projects.'
: 'Average number of users for selected projects.'
}
/>
<UserStats
count={summary.averageUsers}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<UsersPerProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
}
/>
<ConditionallyRender
@ -234,47 +230,35 @@ export const InsightsCharts: FC<IChartsProps> = ({
<ConditionallyRender
condition={showAllProjects}
show={
<>
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats
count={flagsTotal}
flagsPerUser={getFlagsPerUser(
flagsTotal,
usersTotal,
)}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<FlagsChart
flagTrends={flagTrends}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
</>
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats count={flagsTotal} isLoading={loading} />
</StyledWidgetStats>
<StyledChartContainer>
<FlagsChart
flagTrends={flagTrends}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
}
elseShow={
<>
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats
count={summary.total}
flagsPerUser={''}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<FlagsProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
</>
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats
count={summary.total}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<FlagsProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
}
/>
<ConditionallyRender

View File

@ -7,7 +7,6 @@ import {
type IFilterItem,
} from 'component/filter/Filters/Filters';
import { styled } from '@mui/material';
import { useUiFlag } from 'hooks/useUiFlag';
interface IFeatureToggleFiltersProps {
state: FilterItemParamHolder;
@ -26,9 +25,6 @@ export const InsightsFilters: FC<IFeatureToggleFiltersProps> = ({
...filterProps
}) => {
const { projects } = useProjects();
const FilterComponent = useUiFlag('lifecycleMetrics')
? FiltersNoPadding
: Filters;
const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]);
@ -81,7 +77,7 @@ export const InsightsFilters: FC<IFeatureToggleFiltersProps> = ({
}, [JSON.stringify(projects)]);
return (
<FilterComponent
<FiltersNoPadding
{...filterProps}
state={state}
availableFilters={availableFilters}

View File

@ -1,76 +0,0 @@
import type { FC } from 'react';
import { styled } from '@mui/material';
import { usePersistentTableState } from 'hooks/usePersistentTableState';
import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
import { useInsights } from 'hooks/api/getters/useInsights/useInsights';
import { InsightsHeader } from './components/InsightsHeader/InsightsHeader.tsx';
import { useInsightsData } from './hooks/useInsightsData.ts';
import { Sticky } from 'component/common/Sticky/Sticky';
import { InsightsFilters } from './InsightsFilters.tsx';
import { FilterItemParam } from '../../utils/serializeQueryParams.ts';
import { format, subMonths } from 'date-fns';
import { withDefault } from 'use-query-params';
import { InsightsCharts } from './InsightsCharts.tsx';
const StyledWrapper = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(2),
}));
const StickyContainer = styled(Sticky)(({ theme }) => ({
position: 'sticky',
top: 0,
zIndex: theme.zIndex.sticky,
padding: theme.spacing(2, 0),
background: theme.palette.background.application,
transition: 'padding 0.3s ease',
}));
interface InsightsProps {
withCharts?: boolean;
}
export const LegacyInsights: FC<InsightsProps> = ({ withCharts = true }) => {
const stateConfig = {
project: FilterItemParam,
from: withDefault(FilterItemParam, {
values: [format(subMonths(new Date(), 1), 'yyyy-MM-dd')],
operator: 'IS',
}),
to: withDefault(FilterItemParam, {
values: [format(new Date(), 'yyyy-MM-dd')],
operator: 'IS',
}),
};
const [state, setState] = usePersistentTableState('insights', stateConfig, [
'from',
'to',
]);
const { insights, loading } = useInsights(
state.from?.values[0],
state.to?.values[0],
);
const projects = state.project?.values ?? [allOption.id];
const insightsData = useInsightsData(insights, projects);
return (
<StyledWrapper>
<StickyContainer>
<InsightsHeader
actions={
<InsightsFilters state={state} onChange={setState} />
}
/>
</StickyContainer>
{withCharts && (
<InsightsCharts
loading={loading}
projects={projects}
{...insightsData}
/>
)}
</StyledWrapper>
);
};

View File

@ -1,7 +1,5 @@
import Icon from '@mui/material/Icon';
import { Box, Typography, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useUiFlag } from 'hooks/useUiFlag';
import { useId } from 'hooks/useId';
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
@ -79,27 +77,19 @@ const LabelContainer = styled('div')({
interface IFlagStatsProps {
count: number;
flagsPerUser?: string; // todo: remove this prop with the lifecycleMetrics flag
isLoading?: boolean;
}
export const FlagStats: React.FC<IFlagStatsProps> = ({
count,
flagsPerUser,
isLoading,
}) => {
const hideFlagsPerUser = useUiFlag('lifecycleMetrics');
const labelId = hideFlagsPerUser ? useId() : '';
const descriptionId = hideFlagsPerUser ? useId() : '';
export const FlagStats: React.FC<IFlagStatsProps> = ({ count, isLoading }) => {
const labelId = useId();
const descriptionId = useId();
return (
<>
<StyledRingContainer>
<StyledRing>
<StyledRingContent
{...(hideFlagsPerUser && {
'aria-labelledby': labelId,
'aria-describedby': descriptionId,
})}
aria-labelledby={labelId}
aria-describedby={descriptionId}
>
{isLoading ? (
<ScreenReaderOnly>
@ -112,50 +102,20 @@ export const FlagStats: React.FC<IFlagStatsProps> = ({
</StyledRing>
</StyledRingContainer>
{hideFlagsPerUser ? (
<LabelContainer>
<Typography id={labelId} variant='body2'>
Total number of flags
</Typography>
<HelpIcon
htmlTooltip
tooltipId={descriptionId}
tooltip={
'This includes the four lifecycle stages define, develop, production, and cleanup'
}
>
<InfoOutlined />
</HelpIcon>
</LabelContainer>
) : (
<ConditionallyRender
condition={
flagsPerUser !== undefined && flagsPerUser !== ''
<LabelContainer>
<Typography id={labelId} variant='body2'>
Total number of flags
</Typography>
<HelpIcon
htmlTooltip
tooltipId={descriptionId}
tooltip={
'This includes the four lifecycle stages define, develop, production, and cleanup'
}
show={
<StyledInsightsContainer>
<StyledTextContainer>
<StyledHeaderContainer>
<StyledIcon>award_star</StyledIcon>
<Typography
fontWeight='bold'
variant='body2'
color='primary'
>
Insights
</Typography>
</StyledHeaderContainer>
<Typography variant='body2'>
Flags per user
</Typography>
</StyledTextContainer>
<StyledFlagCountPerUser>
{flagsPerUser}
</StyledFlagCountPerUser>
</StyledInsightsContainer>
}
/>
)}
>
<InfoOutlined />
</HelpIcon>
</LabelContainer>
</>
);
};

View File

@ -61,7 +61,6 @@ export const PerformanceInsights: FC = () => {
summary,
groupedProjectsData,
groupedLifecycleData,
userTrends,
groupedMetricsData,
allMetricsDatapoints,
environmentTypeTrends,
@ -69,8 +68,6 @@ export const PerformanceInsights: FC = () => {
} = useInsightsData(insights, projects);
const { isEnterprise } = useUiConfig();
const lastUserTrend = userTrends[userTrends.length - 1];
const usersTotal = lastUserTrend?.total ?? 0;
const lastFlagTrend = flagTrends[flagTrends.length - 1];
const flagsTotal = lastFlagTrend?.total ?? 0;
@ -137,14 +134,7 @@ export const PerformanceInsights: FC = () => {
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats
count={flagsTotal}
flagsPerUser={getFlagsPerUser(
flagsTotal,
usersTotal,
)}
isLoading={loading}
/>
<FlagStats count={flagsTotal} isLoading={loading} />
</StyledWidgetStats>
<StyledChartContainer>
<FlagsChart
@ -157,11 +147,7 @@ export const PerformanceInsights: FC = () => {
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats
count={summary.total}
flagsPerUser={''}
isLoading={loading}
/>
<FlagStats count={summary.total} isLoading={loading} />
</StyledWidgetStats>
<StyledChartContainer>
<FlagsProjectChart

View File

@ -86,7 +86,6 @@ export type UiFlags = {
consumptionModelUI?: boolean;
edgeObservability?: boolean;
customMetrics?: boolean;
lifecycleMetrics?: boolean;
createFlagDialogCache?: boolean;
impactMetrics?: boolean;
changeRequestApproverEmails?: boolean;

View File

@ -54,7 +54,6 @@ export type IFlagKey =
| 'consumptionModelUI'
| 'edgeObservability'
| 'reportUnknownFlags'
| 'lifecycleMetrics'
| 'customMetrics'
| 'impactMetrics'
| 'createFlagDialogCache'
@ -261,10 +260,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_REPORT_UNKNOWN_FLAGS,
false,
),
lifecycleMetrics: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_LIFECYCLE_METRICS,
false,
),
createFlagDialogCache: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_CREATE_FLAG_DIALOG_CACHE,
false,

View File

@ -53,7 +53,6 @@ process.nextTick(async () => {
strictSchemaValidation: true,
reportUnknownFlags: true,
customMetrics: true,
lifecycleMetrics: true,
impactMetrics: true,
paygTrialEvents: true,
lifecycleGraphs: true,