From 10dffcd232cb4e1c03161a5572591a1ad061668a Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Thu, 3 Oct 2024 10:21:27 +0200 Subject: [PATCH] feat: health score components in personal dashboard (#8348) --- .../PersonalDashboard.test.tsx | 10 ++- .../ProjectSetupComplete.tsx | 28 ++++++- .../ProjectHealth/FlagCounts.tsx | 80 +++++++++++++++++++ .../ProjectHealth/ProjectHealth.tsx | 72 +++-------------- ...alDashboardProjectDetailsSchemaInsights.ts | 5 ++ .../personal-dashboard-controller.e2e.test.ts | 5 ++ .../personal-dashboard-service.ts | 15 ++++ ...rsonal-dashboard-project-details-schema.ts | 37 ++++++++- 8 files changed, 186 insertions(+), 66 deletions(-) create mode 100644 frontend/src/component/project/Project/ProjectInsights/ProjectHealth/FlagCounts.tsx diff --git a/frontend/src/component/personalDashboard/PersonalDashboard.test.tsx b/frontend/src/component/personalDashboard/PersonalDashboard.test.tsx index 2983ff29e3..6af93d7585 100644 --- a/frontend/src/component/personalDashboard/PersonalDashboard.test.tsx +++ b/frontend/src/component/personalDashboard/PersonalDashboard.test.tsx @@ -36,6 +36,11 @@ const setupLongRunningProject = () => { insights: { avgHealthCurrentWindow: 80, avgHealthPastWindow: 70, + totalFlags: 39, + potentiallyStaleFlags: 14, + staleFlags: 13, + activeFlags: 12, + health: 81, }, latestEvents: [{ summary: 'someone created a flag', id: 0 }], roles: [{ name: 'Member' }], @@ -135,7 +140,10 @@ test('Render personal dashboard for a long running project', async () => { await screen.findByText('70%'); // avg health past window await screen.findByText('someone created a flag'); await screen.findByText('Member'); - + await screen.findByText('81%'); // current health score + await screen.findByText('12 feature flags'); // active flags + await screen.findByText('13 feature flags'); // stale flags + await screen.findByText('14 feature flags'); // potentially stale flags await screen.findByText('myFlag'); await screen.findByText('No feature flag metrics data'); await screen.findByText('production'); diff --git a/frontend/src/component/personalDashboard/ProjectSetupComplete.tsx b/frontend/src/component/personalDashboard/ProjectSetupComplete.tsx index 1a3dcc8522..b46cc9cfcd 100644 --- a/frontend/src/component/personalDashboard/ProjectSetupComplete.tsx +++ b/frontend/src/component/personalDashboard/ProjectSetupComplete.tsx @@ -3,6 +3,8 @@ import type { FC } from 'react'; import { Link } from 'react-router-dom'; import Lightbulb from '@mui/icons-material/LightbulbOutlined'; import type { PersonalDashboardProjectDetailsSchemaInsights } from '../../openapi'; +import { ProjectHealthChart } from 'component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart'; +import { FlagCounts } from '../project/Project/ProjectInsights/ProjectHealth/FlagCounts'; const TitleContainer = styled('div')(({ theme }) => ({ display: 'flex', @@ -12,8 +14,16 @@ const TitleContainer = styled('div')(({ theme }) => ({ justifyContent: 'center', })); +const Health = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: theme.spacing(3), +})); + const ActionBox = styled('article')(({ theme }) => ({ - padding: theme.spacing(4, 2), + padding: theme.spacing(0, 2), display: 'flex', gap: theme.spacing(3), flexDirection: 'column', @@ -128,6 +138,22 @@ export const ProjectSetupComplete: FC<{

Project Insight

+ + + + + prop !== 'color', +})<{ color?: string }>(({ theme, color }) => ({ + height: '15px', + width: '15px', + borderRadius: '50%', + display: 'inline-block', + backgroundColor: color, +})); + +const FlagCountsWrapper = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), +})); + +const FlagsCount = styled('p')(({ theme }) => ({ + color: theme.palette.text.secondary, + marginLeft: theme.spacing(3), +})); + +const StatusWithDot = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), +})); + +export const FlagCounts: FC<{ + projectId: string; + activeCount: number; + potentiallyStaleCount: number; + staleCount: number; + hideLinks?: boolean; +}> = ({ + projectId, + activeCount, + potentiallyStaleCount, + staleCount, + hideLinks = false, +}) => { + const theme = useTheme(); + + return ( + + + + + Active + + {activeCount} feature flags + + + + + Potentially stale + {hideLinks ? null : ( + (configure) + )} + + {potentiallyStaleCount} feature flags + + + + + Stale + {hideLinks ? null : ( + + (view flags) + + )} + + {staleCount} feature flags + + + ); +}; diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx index 6b15b4ea96..a026a4b229 100644 --- a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx @@ -1,31 +1,10 @@ import { ProjectHealthChart } from './ProjectHealthChart'; -import { Alert, Box, styled, Typography, useTheme } from '@mui/material'; -import { Link } from 'react-router-dom'; +import { Alert, Box, styled, Typography } from '@mui/material'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import type { ProjectInsightsSchemaHealth } from '../../../../../openapi'; import type { FC } from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; - -const Dot = styled('span', { - shouldForwardProp: (prop) => prop !== 'color', -})<{ color?: string }>(({ theme, color }) => ({ - height: '15px', - width: '15px', - borderRadius: '50%', - display: 'inline-block', - backgroundColor: color, -})); - -const FlagsCount = styled('p')(({ theme }) => ({ - color: theme.palette.text.secondary, - marginLeft: theme.spacing(3), -})); - -const FlagCounts = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), -})); +import { FlagCounts } from './FlagCounts'; const Container = styled(Box)(({ theme }) => ({ display: 'flex', @@ -33,16 +12,9 @@ const Container = styled(Box)(({ theme }) => ({ gap: theme.spacing(2), })); -const StatusWithDot = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), -})); - export const ProjectHealth: FC<{ health: ProjectInsightsSchemaHealth }> = ({ health, }) => { - const theme = useTheme(); const projectId = useRequiredPathParam('projectId'); const { staleCount, potentiallyStaleCount, activeCount, rating } = health; @@ -73,39 +45,13 @@ export const ProjectHealth: FC<{ health: ProjectInsightsSchemaHealth }> = ({ potentiallyStale={potentiallyStaleCount} health={rating} /> - - - - - Active - - {activeCount} feature flags - - - - - - Potentially stale - - (configure) - - - {potentiallyStaleCount} feature flags - - - - - - Stale - - (view flags) - - - {staleCount} feature flags - - + + ); diff --git a/frontend/src/openapi/models/personalDashboardProjectDetailsSchemaInsights.ts b/frontend/src/openapi/models/personalDashboardProjectDetailsSchemaInsights.ts index d081685ecf..284f9bb620 100644 --- a/frontend/src/openapi/models/personalDashboardProjectDetailsSchemaInsights.ts +++ b/frontend/src/openapi/models/personalDashboardProjectDetailsSchemaInsights.ts @@ -18,4 +18,9 @@ export type PersonalDashboardProjectDetailsSchemaInsights = { * @nullable */ avgHealthPastWindow: number | null; + totalFlags: number; + activeFlags: number; + staleFlags: number; + potentiallyStaleFlags: number; + health: number; }; diff --git a/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts b/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts index a89579031c..f4267d7db1 100644 --- a/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts +++ b/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts @@ -326,6 +326,11 @@ test('should return personal dashboard project details', async () => { insights: { avgHealthPastWindow: 80, avgHealthCurrentWindow: 91, + totalFlags: 3, + potentiallyStaleFlags: 0, + staleFlags: 0, + activeFlags: 3, + health: 100, }, }); }); diff --git a/src/lib/features/personal-dashboard/personal-dashboard-service.ts b/src/lib/features/personal-dashboard/personal-dashboard-service.ts index 3ea0b672e7..316d070364 100644 --- a/src/lib/features/personal-dashboard/personal-dashboard-service.ts +++ b/src/lib/features/personal-dashboard/personal-dashboard-service.ts @@ -165,6 +165,16 @@ export class PersonalDashboardService { ); } + const [projectInsights] = + await this.projectReadModel.getProjectsForInsights({ + id: projectId, + }); + const totalFlags = projectInsights?.featureCount || 0; + const potentiallyStaleFlags = + projectInsights?.potentiallyStaleFeatureCount || 0; + const staleFlags = projectInsights?.staleFeatureCount || 0; + const currentHealth = projectInsights?.health || 0; + return { latestEvents, onboardingStatus, @@ -173,6 +183,11 @@ export class PersonalDashboardService { insights: { avgHealthCurrentWindow, avgHealthPastWindow, + totalFlags, + potentiallyStaleFlags, + staleFlags, + activeFlags: totalFlags - staleFlags - potentiallyStaleFlags, + health: currentHealth, }, }; } diff --git a/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts b/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts index b2cba57d5d..74703fbc1c 100644 --- a/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts +++ b/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts @@ -19,7 +19,15 @@ export const personalDashboardProjectDetailsSchema = { type: 'object', description: 'Insights for the project', additionalProperties: false, - required: ['avgHealthCurrentWindow', 'avgHealthPastWindow'], + required: [ + 'avgHealthCurrentWindow', + 'avgHealthPastWindow', + 'totalFlags', + 'activeFlags', + 'staleFlags', + 'potentiallyStaleFlags', + 'health', + ], properties: { avgHealthCurrentWindow: { type: 'number', @@ -35,6 +43,33 @@ export const personalDashboardProjectDetailsSchema = { example: 70, nullable: true, }, + totalFlags: { + type: 'number', + example: 100, + description: 'The current number of all flags', + }, + activeFlags: { + type: 'number', + example: 98, + description: 'The current number of active flags', + }, + staleFlags: { + type: 'number', + example: 0, + description: + 'The current number of user marked stale flags', + }, + potentiallyStaleFlags: { + type: 'number', + example: 2, + description: + 'The current number of time calculated potentially stale flags', + }, + health: { + type: 'number', + description: 'The current health score of the project', + example: 80, + }, }, }, onboardingStatus: projectOverviewSchema.properties.onboardingStatus,