1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

feat: align insights charts (#7984)

Now the left widget will use the same that as the last data point on the
right chart.
With this change, flags/users object never needs to be passed in.

**Next step is to remove the flags/users from the endpoint.**

Previous


![image](https://github.com/user-attachments/assets/b938e568-e32e-4ae8-beb9-d8de8aa2c5ec)

Now


![image](https://github.com/user-attachments/assets/182dfbbb-bf2e-42f9-a024-0c545c604f74)
This commit is contained in:
Jaanus Sellin 2024-08-27 11:06:08 +03:00 committed by GitHub
parent 064744b18f
commit fc86f5b2fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 163 additions and 206 deletions

View File

@ -12,11 +12,7 @@ import { TimeToProduction } from './componentsStat/TimeToProduction/TimeToProduc
import { TimeToProductionChart } from './componentsChart/TimeToProductionChart/TimeToProductionChart'; import { TimeToProductionChart } from './componentsChart/TimeToProductionChart/TimeToProductionChart';
import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart'; import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart';
import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart'; import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart';
import type { import type { InstanceInsightsSchema } from 'openapi';
InstanceInsightsSchema,
InstanceInsightsSchemaFlags,
InstanceInsightsSchemaUsers,
} from 'openapi';
import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends'; import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends';
import { allOption } from 'component/common/ProjectSelect/ProjectSelect'; import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@ -24,7 +20,6 @@ import { WidgetTitle } from './components/WidgetTitle/WidgetTitle';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
export interface IChartsProps { export interface IChartsProps {
flags: InstanceInsightsSchema['flags'];
flagTrends: InstanceInsightsSchema['flagTrends']; flagTrends: InstanceInsightsSchema['flagTrends'];
projectsData: InstanceInsightsSchema['projectFlagTrends']; projectsData: InstanceInsightsSchema['projectFlagTrends'];
groupedProjectsData: GroupedDataByProject< groupedProjectsData: GroupedDataByProject<
@ -34,7 +29,6 @@ export interface IChartsProps {
groupedMetricsData: GroupedDataByProject< groupedMetricsData: GroupedDataByProject<
InstanceInsightsSchema['metricsSummaryTrends'] InstanceInsightsSchema['metricsSummaryTrends']
>; >;
users: InstanceInsightsSchema['users'];
userTrends: InstanceInsightsSchema['userTrends']; userTrends: InstanceInsightsSchema['userTrends'];
environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends']; environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends'];
summary: { summary: {
@ -98,8 +92,6 @@ const StyledChartContainer = styled(Box)(({ theme }) => ({
export const InsightsCharts: FC<IChartsProps> = ({ export const InsightsCharts: FC<IChartsProps> = ({
projects, projects,
flags,
users,
summary, summary,
userTrends, userTrends,
groupedProjectsData, groupedProjectsData,
@ -113,11 +105,16 @@ export const InsightsCharts: FC<IChartsProps> = ({
const isOneProjectSelected = projects.length === 1; const isOneProjectSelected = projects.length === 1;
const { isEnterprise } = useUiConfig(); const { isEnterprise } = useUiConfig();
function getFlagsPerUser( const lastUserTrend = userTrends[userTrends.length - 1];
flags: InstanceInsightsSchemaFlags, const lastFlagTrend = flagTrends[flagTrends.length - 1];
users: InstanceInsightsSchemaUsers,
) { const usersTotal = lastUserTrend?.total ?? 0;
const flagsPerUserCalculation = flags.total / users.total; const usersActive = lastUserTrend?.active ?? 0;
const usersInactive = lastUserTrend?.inactive ?? 0;
const flagsTotal = lastFlagTrend?.total ?? 0;
function getFlagsPerUser(flagsTotal: number, usersTotal: number) {
const flagsPerUserCalculation = flagsTotal / usersTotal;
return Number.isNaN(flagsPerUserCalculation) return Number.isNaN(flagsPerUserCalculation)
? 'N/A' ? 'N/A'
: flagsPerUserCalculation.toFixed(2); : flagsPerUserCalculation.toFixed(2);
@ -133,9 +130,9 @@ export const InsightsCharts: FC<IChartsProps> = ({
<StyledWidgetStats> <StyledWidgetStats>
<WidgetTitle title='Total users' /> <WidgetTitle title='Total users' />
<UserStats <UserStats
count={users.total} count={usersTotal}
active={users.active} active={usersActive}
inactive={users.inactive} inactive={usersInactive}
isLoading={loading} isLoading={loading}
/> />
</StyledWidgetStats> </StyledWidgetStats>
@ -239,8 +236,11 @@ export const InsightsCharts: FC<IChartsProps> = ({
<StyledWidgetStats width={275}> <StyledWidgetStats width={275}>
<WidgetTitle title='Flags' /> <WidgetTitle title='Flags' />
<FlagStats <FlagStats
count={flags.total} count={flagsTotal}
flagsPerUser={getFlagsPerUser(flags, users)} flagsPerUser={getFlagsPerUser(
flagsTotal,
usersTotal,
)}
isLoading={loading} isLoading={loading}
/> />
</StyledWidgetStats> </StyledWidgetStats>

View File

@ -1,63 +1,59 @@
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { useFilteredFlagsSummary } from './useFilteredFlagsSummary'; import { useFilteredFlagsSummary } from './useFilteredFlagsSummary';
import type { InstanceInsightsSchemaUsers } from 'openapi';
describe('useFilteredFlagTrends', () => { describe('useFilteredFlagTrends', () => {
it('should summarize only last week of project flag trends', () => { it('should summarize only last week of project flag trends', () => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useFilteredFlagsSummary( useFilteredFlagsSummary([
[ {
{ week: '2024-01',
week: '2024-01', project: 'project1',
project: 'project1', total: 1,
total: 1, active: 1,
active: 1, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 1,
users: 1, date: '',
date: '', timeToProduction: 4,
timeToProduction: 4, health: 100,
health: 100, },
}, {
{ week: '2024-01',
week: '2024-01', project: 'project2',
project: 'project2', total: 2,
total: 2, active: 2,
active: 2, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 2,
users: 2, date: '',
date: '', timeToProduction: 5,
timeToProduction: 5, health: 100,
health: 100, },
}, {
{ week: '2024-02',
week: '2024-02', project: 'project1',
project: 'project1', total: 4,
total: 4, active: 3,
active: 3, stale: 0,
stale: 0, potentiallyStale: 1,
potentiallyStale: 1, users: 1,
users: 1, date: '',
date: '', timeToProduction: 4,
timeToProduction: 4, health: 75,
health: 75, },
}, {
{ week: '2024-02',
week: '2024-02', project: 'project3',
project: 'project3', total: 10,
total: 10, active: 8,
active: 8, stale: 2,
stale: 2, potentiallyStale: 0,
potentiallyStale: 0, users: 3,
users: 3, date: '',
date: '', timeToProduction: 2,
timeToProduction: 2, health: 80,
health: 80, },
}, ]),
],
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
),
); );
expect(result.current).toEqual({ expect(result.current).toEqual({
@ -67,30 +63,26 @@ describe('useFilteredFlagTrends', () => {
potentiallyStale: 1, potentiallyStale: 1,
averageUsers: 2, averageUsers: 2,
averageHealth: '79', averageHealth: '79',
flagsPerUser: '14.00',
medianTimeToProduction: 3, medianTimeToProduction: 3,
}); });
}); });
it('should work with project with zero users', () => { it('should work with project with zero users', () => {
const { result, rerender } = renderHook(() => const { result, rerender } = renderHook(() =>
useFilteredFlagsSummary( useFilteredFlagsSummary([
[ {
{ week: '2024-01',
week: '2024-01', project: 'project1',
project: 'project1', total: 5,
total: 5, active: 5,
active: 5, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 0,
users: 0, date: '',
date: '', timeToProduction: 4,
timeToProduction: 4, health: 100,
health: 100, },
}, ]),
],
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
),
); );
expect(result.current).toEqual({ expect(result.current).toEqual({
@ -100,42 +92,38 @@ describe('useFilteredFlagTrends', () => {
potentiallyStale: 0, potentiallyStale: 0,
averageUsers: 0, averageUsers: 0,
averageHealth: '100', averageHealth: '100',
flagsPerUser: '5.00',
medianTimeToProduction: 4, medianTimeToProduction: 4,
}); });
}); });
it('should work with projects where some have with zero users', () => { it('should work with projects where some have with zero users', () => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useFilteredFlagsSummary( useFilteredFlagsSummary([
[ {
{ week: '2024-01',
week: '2024-01', project: 'project1',
project: 'project1', total: 5,
total: 5, active: 5,
active: 5, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 0,
users: 0, date: '',
date: '', timeToProduction: 2,
timeToProduction: 2, health: 100,
health: 100, },
}, {
{ week: '2024-01',
week: '2024-01', project: 'project2',
project: 'project2', total: 5,
total: 5, active: 5,
active: 5, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 3,
users: 3, date: '',
date: '', timeToProduction: 5,
timeToProduction: 5, health: 100,
health: 100, },
}, ]),
],
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
),
); );
expect(result.current).toEqual({ expect(result.current).toEqual({
@ -145,30 +133,26 @@ describe('useFilteredFlagTrends', () => {
potentiallyStale: 0, potentiallyStale: 0,
averageUsers: 1.5, averageUsers: 1.5,
averageHealth: '100', averageHealth: '100',
flagsPerUser: '10.00',
medianTimeToProduction: 3.5, medianTimeToProduction: 3.5,
}); });
}); });
it('should set health of a project without feature flags to undefined', () => { it('should set health of a project without feature flags to undefined', () => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useFilteredFlagsSummary( useFilteredFlagsSummary([
[ {
{ week: '2024-01',
week: '2024-01', project: 'project1',
project: 'project1', total: 0,
total: 0, active: 0,
active: 0, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 0,
users: 0, date: '',
date: '', timeToProduction: 0,
timeToProduction: 0, health: 100,
health: 100, },
}, ]),
],
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
),
); );
expect(result.current).toEqual({ expect(result.current).toEqual({
@ -178,54 +162,50 @@ describe('useFilteredFlagTrends', () => {
potentiallyStale: 0, potentiallyStale: 0,
averageUsers: 0, averageUsers: 0,
averageHealth: undefined, averageHealth: undefined,
flagsPerUser: '0.00',
medianTimeToProduction: undefined, medianTimeToProduction: undefined,
}); });
}); });
it('should not use 0 timeToProduction projects for median calculation', () => { it('should not use 0 timeToProduction projects for median calculation', () => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useFilteredFlagsSummary( useFilteredFlagsSummary([
[ {
{ week: '2024-01',
week: '2024-01', project: 'project1',
project: 'project1', total: 0,
total: 0, active: 0,
active: 0, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 0,
users: 0, date: '',
date: '', timeToProduction: 0,
timeToProduction: 0, health: 100,
health: 100, },
}, {
{ week: '2024-01',
week: '2024-01', project: 'project2',
project: 'project2', total: 0,
total: 0, active: 0,
active: 0, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 0,
users: 0, date: '',
date: '', timeToProduction: 0,
timeToProduction: 0, health: 100,
health: 100, },
}, {
{ week: '2024-01',
week: '2024-01', project: 'project3',
project: 'project3', total: 0,
total: 0, active: 0,
active: 0, stale: 0,
stale: 0, potentiallyStale: 0,
potentiallyStale: 0, users: 0,
users: 0, date: '',
date: '', timeToProduction: 5,
timeToProduction: 5, health: 100,
health: 100, },
}, ]),
],
{ total: 1 } as unknown as InstanceInsightsSchemaUsers,
),
); );
expect(result.current).toEqual({ expect(result.current).toEqual({
@ -235,7 +215,6 @@ describe('useFilteredFlagTrends', () => {
potentiallyStale: 0, potentiallyStale: 0,
averageUsers: 0, averageUsers: 0,
averageHealth: undefined, averageHealth: undefined,
flagsPerUser: '0.00',
medianTimeToProduction: 5, medianTimeToProduction: 5,
}); });
}); });

View File

@ -1,8 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { import type { InstanceInsightsSchemaProjectFlagTrendsItem } from 'openapi';
InstanceInsightsSchemaProjectFlagTrendsItem,
InstanceInsightsSchemaUsers,
} from 'openapi';
const validTimeToProduction = ( const validTimeToProduction = (
item: InstanceInsightsSchemaProjectFlagTrendsItem, item: InstanceInsightsSchemaProjectFlagTrendsItem,
@ -14,7 +11,6 @@ const validTimeToProduction = (
// NOTE: should we move project filtering to the backend? // NOTE: should we move project filtering to the backend?
export const useFilteredFlagsSummary = ( export const useFilteredFlagsSummary = (
filteredProjectFlagTrends: InstanceInsightsSchemaProjectFlagTrendsItem[], filteredProjectFlagTrends: InstanceInsightsSchemaProjectFlagTrendsItem[],
users: InstanceInsightsSchemaUsers,
) => ) =>
useMemo(() => { useMemo(() => {
const lastWeekId = filteredProjectFlagTrends.reduce((prev, current) => { const lastWeekId = filteredProjectFlagTrends.reduce((prev, current) => {
@ -69,14 +65,8 @@ export const useFilteredFlagsSummary = (
? undefined ? undefined
: medianTimeToProductionCalculation; : medianTimeToProductionCalculation;
const flagsPerUserCalculation = sum.total / users.total;
const flagsPerUser = Number.isNaN(flagsPerUserCalculation)
? 'N/A'
: flagsPerUserCalculation.toFixed(2);
return { return {
...sum, ...sum,
flagsPerUser,
averageUsers, averageUsers,
averageHealth: sum.total averageHealth: sum.total
? ((sum.active / (sum.total || 1)) * 100).toFixed(0) ? ((sum.active / (sum.total || 1)) * 100).toFixed(0)

View File

@ -25,10 +25,7 @@ export const useInsightsData = (
); );
const groupedMetricsData = useGroupedProjectTrends(metricsData); const groupedMetricsData = useGroupedProjectTrends(metricsData);
const summary = useFilteredFlagsSummary( const summary = useFilteredFlagsSummary(projectsData);
projectsData,
instanceInsights.users,
);
return useMemo( return useMemo(
() => ({ () => ({
@ -37,7 +34,6 @@ export const useInsightsData = (
groupedProjectsData, groupedProjectsData,
metricsData, metricsData,
groupedMetricsData, groupedMetricsData,
users: instanceInsights.users,
environmentTypeTrends: instanceInsights.environmentTypeTrends, environmentTypeTrends: instanceInsights.environmentTypeTrends,
summary, summary,
allMetricsDatapoints, allMetricsDatapoints,

View File

@ -30,8 +30,6 @@ export const useInsights = (
return { return {
insights: data || { insights: data || {
users: { total: 0, inactive: 0, active: 0 },
flags: { total: 0 },
userTrends: [], userTrends: [],
flagTrends: [], flagTrends: [],
projectFlagTrends: [], projectFlagTrends: [],

View File

@ -4,11 +4,9 @@
* See `gen:api` script in package.json * See `gen:api` script in package.json
*/ */
import type { InstanceInsightsSchemaEnvironmentTypeTrendsItem } from './instanceInsightsSchemaEnvironmentTypeTrendsItem'; import type { InstanceInsightsSchemaEnvironmentTypeTrendsItem } from './instanceInsightsSchemaEnvironmentTypeTrendsItem';
import type { InstanceInsightsSchemaFlags } from './instanceInsightsSchemaFlags';
import type { InstanceInsightsSchemaFlagTrendsItem } from './instanceInsightsSchemaFlagTrendsItem'; import type { InstanceInsightsSchemaFlagTrendsItem } from './instanceInsightsSchemaFlagTrendsItem';
import type { InstanceInsightsSchemaMetricsSummaryTrendsItem } from './instanceInsightsSchemaMetricsSummaryTrendsItem'; import type { InstanceInsightsSchemaMetricsSummaryTrendsItem } from './instanceInsightsSchemaMetricsSummaryTrendsItem';
import type { InstanceInsightsSchemaProjectFlagTrendsItem } from './instanceInsightsSchemaProjectFlagTrendsItem'; import type { InstanceInsightsSchemaProjectFlagTrendsItem } from './instanceInsightsSchemaProjectFlagTrendsItem';
import type { InstanceInsightsSchemaUsers } from './instanceInsightsSchemaUsers';
import type { InstanceInsightsSchemaUserTrendsItem } from './instanceInsightsSchemaUserTrendsItem'; import type { InstanceInsightsSchemaUserTrendsItem } from './instanceInsightsSchemaUserTrendsItem';
/** /**
@ -17,16 +15,12 @@ import type { InstanceInsightsSchemaUserTrendsItem } from './instanceInsightsSch
export interface InstanceInsightsSchema { export interface InstanceInsightsSchema {
/** How updates per environment type changed over time */ /** How updates per environment type changed over time */
environmentTypeTrends: InstanceInsightsSchemaEnvironmentTypeTrendsItem[]; environmentTypeTrends: InstanceInsightsSchemaEnvironmentTypeTrendsItem[];
/** High level flag count statistics */
flags: InstanceInsightsSchemaFlags;
/** How number of flags changed over time */ /** How number of flags changed over time */
flagTrends: InstanceInsightsSchemaFlagTrendsItem[]; flagTrends: InstanceInsightsSchemaFlagTrendsItem[];
/** How metrics data per project changed over time */ /** How metrics data per project changed over time */
metricsSummaryTrends: InstanceInsightsSchemaMetricsSummaryTrendsItem[]; metricsSummaryTrends: InstanceInsightsSchemaMetricsSummaryTrendsItem[];
/** How number of flags per project changed over time */ /** How number of flags per project changed over time */
projectFlagTrends: InstanceInsightsSchemaProjectFlagTrendsItem[]; projectFlagTrends: InstanceInsightsSchemaProjectFlagTrendsItem[];
/** High level user count statistics */
users: InstanceInsightsSchemaUsers;
/** How number of users changed over time */ /** How number of users changed over time */
userTrends: InstanceInsightsSchemaUserTrendsItem[]; userTrends: InstanceInsightsSchemaUserTrendsItem[];
} }