1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-01 13:47:27 +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 type { FC } from 'react';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import { InsightsHeader } from './components/InsightsHeader/InsightsHeader.tsx'; 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 { StyledContainer } from './InsightsCharts.styles.ts';
import { LifecycleInsights } from './sections/LifecycleInsights.tsx'; import { LifecycleInsights } from './sections/LifecycleInsights.tsx';
import { PerformanceInsights } from './sections/PerformanceInsights.tsx'; import { PerformanceInsights } from './sections/PerformanceInsights.tsx';
@ -12,21 +10,13 @@ const StyledWrapper = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
})); }));
const NewInsights: FC = () => { export const Insights: FC = () => (
return ( <StyledWrapper>
<StyledWrapper> <InsightsHeader />
<InsightsHeader /> <StyledContainer>
<StyledContainer> <LifecycleInsights />
<LifecycleInsights /> <PerformanceInsights />
<PerformanceInsights /> <UserInsights />
<UserInsights /> </StyledContainer>
</StyledContainer> </StyledWrapper>
</StyledWrapper> );
);
};
export const Insights: FC<{ withCharts?: boolean }> = (props) => {
const useNewInsights = useUiFlag('lifecycleMetrics');
return useNewInsights ? <NewInsights /> : <LegacyInsights {...props} />;
};

View File

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

View File

@ -7,7 +7,6 @@ import {
type IFilterItem, type IFilterItem,
} from 'component/filter/Filters/Filters'; } from 'component/filter/Filters/Filters';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import { useUiFlag } from 'hooks/useUiFlag';
interface IFeatureToggleFiltersProps { interface IFeatureToggleFiltersProps {
state: FilterItemParamHolder; state: FilterItemParamHolder;
@ -26,9 +25,6 @@ export const InsightsFilters: FC<IFeatureToggleFiltersProps> = ({
...filterProps ...filterProps
}) => { }) => {
const { projects } = useProjects(); const { projects } = useProjects();
const FilterComponent = useUiFlag('lifecycleMetrics')
? FiltersNoPadding
: Filters;
const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]); const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]);
@ -81,7 +77,7 @@ export const InsightsFilters: FC<IFeatureToggleFiltersProps> = ({
}, [JSON.stringify(projects)]); }, [JSON.stringify(projects)]);
return ( return (
<FilterComponent <FiltersNoPadding
{...filterProps} {...filterProps}
state={state} state={state}
availableFilters={availableFilters} 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 Icon from '@mui/material/Icon';
import { Box, Typography, styled } from '@mui/material'; 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 { useId } from 'hooks/useId';
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly'; import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
@ -79,27 +77,19 @@ const LabelContainer = styled('div')({
interface IFlagStatsProps { interface IFlagStatsProps {
count: number; count: number;
flagsPerUser?: string; // todo: remove this prop with the lifecycleMetrics flag
isLoading?: boolean; isLoading?: boolean;
} }
export const FlagStats: React.FC<IFlagStatsProps> = ({ export const FlagStats: React.FC<IFlagStatsProps> = ({ count, isLoading }) => {
count, const labelId = useId();
flagsPerUser, const descriptionId = useId();
isLoading,
}) => {
const hideFlagsPerUser = useUiFlag('lifecycleMetrics');
const labelId = hideFlagsPerUser ? useId() : '';
const descriptionId = hideFlagsPerUser ? useId() : '';
return ( return (
<> <>
<StyledRingContainer> <StyledRingContainer>
<StyledRing> <StyledRing>
<StyledRingContent <StyledRingContent
{...(hideFlagsPerUser && { aria-labelledby={labelId}
'aria-labelledby': labelId, aria-describedby={descriptionId}
'aria-describedby': descriptionId,
})}
> >
{isLoading ? ( {isLoading ? (
<ScreenReaderOnly> <ScreenReaderOnly>
@ -112,50 +102,20 @@ export const FlagStats: React.FC<IFlagStatsProps> = ({
</StyledRing> </StyledRing>
</StyledRingContainer> </StyledRingContainer>
{hideFlagsPerUser ? ( <LabelContainer>
<LabelContainer> <Typography id={labelId} variant='body2'>
<Typography id={labelId} variant='body2'> Total number of flags
Total number of flags </Typography>
</Typography> <HelpIcon
<HelpIcon htmlTooltip
htmlTooltip tooltipId={descriptionId}
tooltipId={descriptionId} tooltip={
tooltip={ 'This includes the four lifecycle stages define, develop, production, and cleanup'
'This includes the four lifecycle stages define, develop, production, and cleanup'
}
>
<InfoOutlined />
</HelpIcon>
</LabelContainer>
) : (
<ConditionallyRender
condition={
flagsPerUser !== undefined && flagsPerUser !== ''
} }
show={ >
<StyledInsightsContainer> <InfoOutlined />
<StyledTextContainer> </HelpIcon>
<StyledHeaderContainer> </LabelContainer>
<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>
}
/>
)}
</> </>
); );
}; };

View File

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

View File

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

View File

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

View File

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