1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-13 11:17:26 +02:00
unleash.unleash/frontend/src/component/insights/sections/PerformanceInsights.tsx
2025-07-24 11:53:25 +02:00

214 lines
9.1 KiB
TypeScript

import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
import { format, subMonths } from 'date-fns';
import { useInsights } from 'hooks/api/getters/useInsights/useInsights';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { usePersistentTableState } from 'hooks/usePersistentTableState';
import type { FC } from 'react';
import { withDefault } from 'use-query-params';
import { FilterItemParam } from 'utils/serializeQueryParams';
import { WidgetTitle } from 'component/insights/components/WidgetTitle/WidgetTitle';
import { FlagsChart } from 'component/insights/componentsChart/FlagsChart/FlagsChart';
import { FlagsProjectChart } from 'component/insights/componentsChart/FlagsProjectChart/FlagsProjectChart';
import { MetricsSummaryChart } from 'component/insights/componentsChart/MetricsSummaryChart/MetricsSummaryChart';
import { ProjectHealthChart } from 'component/insights/componentsChart/ProjectHealthChart/ProjectHealthChart';
import { UpdatesPerEnvironmentTypeChart } from 'component/insights/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart';
import { FlagStats } from 'component/insights/componentsStat/FlagStats/FlagStats';
import { HealthStats } from 'component/insights/componentsStat/HealthStats/HealthStats';
import { useInsightsData } from 'component/insights/hooks/useInsightsData';
import { InsightsSection } from 'component/insights/sections/InsightsSection';
import { InsightsFilters } from 'component/insights/InsightsFilters';
import {
StyledChartContainer,
StyledWidget,
StyledWidgetContent,
StyledWidgetStats,
StatsExplanation,
} from '../InsightsCharts.styles';
import { useUiFlag } from 'hooks/useUiFlag';
import { NewProductionFlagsChart } from '../componentsChart/NewProductionFlagsChart/NewProductionFlagsChart.tsx';
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
export const PerformanceInsights: FC = () => {
const statePrefix = 'performance-';
const stateConfig = {
[`${statePrefix}project`]: FilterItemParam,
[`${statePrefix}from`]: withDefault(FilterItemParam, {
values: [format(subMonths(new Date(), 1), 'yyyy-MM-dd')],
operator: 'IS',
}),
[`${statePrefix}to`]: withDefault(FilterItemParam, {
values: [format(new Date(), 'yyyy-MM-dd')],
operator: 'IS',
}),
};
const [state, setState] = usePersistentTableState('insights', stateConfig, [
'performance-from',
'performance-to',
]);
const { insights, loading } = useInsights(
state[`${statePrefix}from`]?.values[0],
state[`${statePrefix}to`]?.values[0],
);
const projects = state[`${statePrefix}project`]?.values ?? [allOption.id];
const showAllProjects = projects[0] === allOption.id;
const {
flagTrends,
summary,
groupedProjectsData,
groupedLifecycleData,
userTrends,
groupedMetricsData,
allMetricsDatapoints,
environmentTypeTrends,
} = 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;
function getFlagsPerUser(flagsTotal: number, usersTotal: number) {
const flagsPerUserCalculation = flagsTotal / usersTotal;
return Number.isNaN(flagsPerUserCalculation)
? 'N/A'
: flagsPerUserCalculation.toFixed(2);
}
const isLifecycleGraphsEnabled = useUiFlag('lifecycleGraphs');
return (
<InsightsSection
title='Performance insights'
filters={
<InsightsFilters
state={state}
onChange={setState}
filterNamePrefix={statePrefix}
/>
}
>
{isLifecycleGraphsEnabled && isEnterprise() ? (
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='New flags in production' />
<StatsExplanation>
<Lightbulb color='primary' />
How often do flags go live in production?
</StatsExplanation>
</StyledWidgetStats>
<StyledChartContainer>
<NewProductionFlagsChart
lifecycleTrends={groupedLifecycleData}
isAggregate={showAllProjects}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
) : null}
{showAllProjects ? (
<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={summary.total}
flagsPerUser={''}
isLoading={loading}
/>
</StyledWidgetStats>
<StyledChartContainer>
<FlagsProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
)}
{isEnterprise() ? (
<StyledWidget>
<StyledWidgetStats width={350} padding={0}>
<HealthStats
value={summary.averageHealth}
technicalDebt={summary.technicalDebt}
healthy={summary.active}
stale={summary.stale}
potentiallyStale={summary.potentiallyStale}
title={
<WidgetTitle
title='Technical debt'
tooltip={
'Percentage of flags that are stale or potentially stale.'
}
/>
}
/>
</StyledWidgetStats>
<StyledChartContainer>
<ProjectHealthChart
projectFlagTrends={groupedProjectsData}
isAggregate={showAllProjects}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
) : null}
{isEnterprise() ? (
<>
<StyledWidget>
<StyledWidgetContent>
<WidgetTitle
title='Flag evaluation metrics'
tooltip='Summary of all flag evaluations reported by SDKs.'
/>
<StyledChartContainer>
<MetricsSummaryChart
metricsSummaryTrends={groupedMetricsData}
allDatapointsSorted={allMetricsDatapoints}
isAggregate={showAllProjects}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidgetContent>
</StyledWidget>
<StyledWidget>
<StyledWidgetContent>
<WidgetTitle
title='Updates per environment type'
tooltip='Summary of all configuration updates per environment type.'
/>
<UpdatesPerEnvironmentTypeChart
environmentTypeTrends={environmentTypeTrends}
isLoading={loading}
/>
</StyledWidgetContent>
</StyledWidget>
</>
) : null}
</InsightsSection>
);
};