1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +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 { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart';
import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart';
import type {
InstanceInsightsSchema,
InstanceInsightsSchemaFlags,
InstanceInsightsSchemaUsers,
} from 'openapi';
import type { InstanceInsightsSchema } from 'openapi';
import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends';
import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
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';
export interface IChartsProps {
flags: InstanceInsightsSchema['flags'];
flagTrends: InstanceInsightsSchema['flagTrends'];
projectsData: InstanceInsightsSchema['projectFlagTrends'];
groupedProjectsData: GroupedDataByProject<
@ -34,7 +29,6 @@ export interface IChartsProps {
groupedMetricsData: GroupedDataByProject<
InstanceInsightsSchema['metricsSummaryTrends']
>;
users: InstanceInsightsSchema['users'];
userTrends: InstanceInsightsSchema['userTrends'];
environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends'];
summary: {
@ -98,8 +92,6 @@ const StyledChartContainer = styled(Box)(({ theme }) => ({
export const InsightsCharts: FC<IChartsProps> = ({
projects,
flags,
users,
summary,
userTrends,
groupedProjectsData,
@ -113,11 +105,16 @@ export const InsightsCharts: FC<IChartsProps> = ({
const isOneProjectSelected = projects.length === 1;
const { isEnterprise } = useUiConfig();
function getFlagsPerUser(
flags: InstanceInsightsSchemaFlags,
users: InstanceInsightsSchemaUsers,
) {
const flagsPerUserCalculation = flags.total / users.total;
const lastUserTrend = userTrends[userTrends.length - 1];
const lastFlagTrend = flagTrends[flagTrends.length - 1];
const usersTotal = lastUserTrend?.total ?? 0;
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)
? 'N/A'
: flagsPerUserCalculation.toFixed(2);
@ -133,9 +130,9 @@ export const InsightsCharts: FC<IChartsProps> = ({
<StyledWidgetStats>
<WidgetTitle title='Total users' />
<UserStats
count={users.total}
active={users.active}
inactive={users.inactive}
count={usersTotal}
active={usersActive}
inactive={usersInactive}
isLoading={loading}
/>
</StyledWidgetStats>
@ -239,8 +236,11 @@ export const InsightsCharts: FC<IChartsProps> = ({
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags' />
<FlagStats
count={flags.total}
flagsPerUser={getFlagsPerUser(flags, users)}
count={flagsTotal}
flagsPerUser={getFlagsPerUser(
flagsTotal,
usersTotal,
)}
isLoading={loading}
/>
</StyledWidgetStats>

View File

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

View File

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

View File

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

View File

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

View File

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