mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
feat: compact graphs
This commit is contained in:
parent
b655291e85
commit
175f7b801e
@ -3,46 +3,60 @@ import { Box, Paper, styled, Typography } from '@mui/material';
|
|||||||
export const StyledContainer = styled(Box)(({ theme }) => ({
|
export const StyledContainer = styled(Box)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(4),
|
gap: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StyledCompactCard = styled(Paper)(({ theme }) => ({
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
'&:hover': {
|
||||||
|
boxShadow: theme.shadows[2],
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledWidget = styled(Paper)(({ theme }) => ({
|
export const StyledWidget = styled(Paper)(({ theme }) => ({
|
||||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
flexDirection: 'column',
|
||||||
[theme.breakpoints.up('md')]: {
|
overflow: 'hidden',
|
||||||
flexDirection: 'row',
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
flexWrap: 'nowrap',
|
transition: 'box-shadow 0.2s ease',
|
||||||
|
'&:hover': {
|
||||||
|
boxShadow: theme.shadows[2],
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledWidgetContent = styled(Box)(({ theme }) => ({
|
export const StyledWidgetContent = styled(Box)(({ theme }) => ({
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(2),
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledWidgetStats = styled(Box)<{
|
export const StyledWidgetStats = styled(Box)<{
|
||||||
width?: number;
|
width?: number;
|
||||||
padding?: number;
|
padding?: number;
|
||||||
}>(({ theme, width = 300, padding = 3 }) => ({
|
}>(({ theme, width = 300, padding = 2 }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(1.5),
|
||||||
padding: theme.spacing(padding),
|
padding: theme.spacing(padding),
|
||||||
minWidth: '100%',
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
[theme.breakpoints.up('md')]: {
|
backgroundColor: theme.palette.background.elevation1,
|
||||||
minWidth: `${width}px`,
|
|
||||||
borderRight: `1px solid ${theme.palette.background.application}`,
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledChartContainer = styled(Box)(({ theme }) => ({
|
export const StyledChartContainer = styled(Box)(({ theme }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128
|
minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128
|
||||||
flexGrow: 1,
|
width: '100%',
|
||||||
margin: 'auto 0',
|
padding: theme.spacing(2),
|
||||||
padding: theme.spacing(3),
|
height: '300px',
|
||||||
|
'& canvas': {
|
||||||
|
maxHeight: '280px',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StatsExplanation = styled(Typography)(({ theme }) => ({
|
export const StatsExplanation = styled(Typography)(({ theme }) => ({
|
||||||
|
@ -6,29 +6,18 @@ import { usePersistentTableState } from 'hooks/usePersistentTableState';
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { withDefault } from 'use-query-params';
|
import { withDefault } from 'use-query-params';
|
||||||
import { FilterItemParam } from 'utils/serializeQueryParams';
|
import { FilterItemParam } from 'utils/serializeQueryParams';
|
||||||
import { WidgetTitle } from 'component/insights/components/WidgetTitle/WidgetTitle';
|
|
||||||
import { FlagsChart } from 'component/insights/componentsChart/FlagsChart/FlagsChart';
|
import { FlagsChart } from 'component/insights/componentsChart/FlagsChart/FlagsChart';
|
||||||
import { FlagsProjectChart } from 'component/insights/componentsChart/FlagsProjectChart/FlagsProjectChart';
|
import { FlagsProjectChart } from 'component/insights/componentsChart/FlagsProjectChart/FlagsProjectChart';
|
||||||
import { MetricsSummaryChart } from 'component/insights/componentsChart/MetricsSummaryChart/MetricsSummaryChart';
|
import { MetricsSummaryChart } from 'component/insights/componentsChart/MetricsSummaryChart/MetricsSummaryChart';
|
||||||
import { ProjectHealthChart } from 'component/insights/componentsChart/ProjectHealthChart/ProjectHealthChart';
|
import { ProjectHealthChart } from 'component/insights/componentsChart/ProjectHealthChart/ProjectHealthChart';
|
||||||
import { UpdatesPerEnvironmentTypeChart } from 'component/insights/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart';
|
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 { useInsightsData } from 'component/insights/hooks/useInsightsData';
|
||||||
import { InsightsSection } from 'component/insights/sections/InsightsSection';
|
import { InsightsSection } from 'component/insights/sections/InsightsSection';
|
||||||
import { InsightsFilters } from 'component/insights/InsightsFilters';
|
import { InsightsFilters } from 'component/insights/InsightsFilters';
|
||||||
import {
|
import { Box, Typography, GlobalStyles, Tooltip, Link } from '@mui/material';
|
||||||
StyledChartContainer,
|
|
||||||
StyledWidget,
|
|
||||||
StyledWidgetContent,
|
|
||||||
StyledWidgetStats,
|
|
||||||
StatsExplanation,
|
|
||||||
} from '../InsightsCharts.styles';
|
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { NewProductionFlagsChart } from '../componentsChart/NewProductionFlagsChart/NewProductionFlagsChart.tsx';
|
import { NewProductionFlagsChart } from '../componentsChart/NewProductionFlagsChart/NewProductionFlagsChart.tsx';
|
||||||
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
|
|
||||||
import { CreationArchiveChart } from '../componentsChart/CreationArchiveChart/CreationArchiveChart.tsx';
|
import { CreationArchiveChart } from '../componentsChart/CreationArchiveChart/CreationArchiveChart.tsx';
|
||||||
import { CreationArchiveStats } from '../componentsStat/CreationArchiveStats/CreationArchiveStats.tsx';
|
|
||||||
|
|
||||||
export const PerformanceInsights: FC = () => {
|
export const PerformanceInsights: FC = () => {
|
||||||
const statePrefix = 'performance-';
|
const statePrefix = 'performance-';
|
||||||
@ -83,7 +72,50 @@ export const PerformanceInsights: FC = () => {
|
|||||||
|
|
||||||
const isLifecycleGraphsEnabled = useUiFlag('lifecycleGraphs');
|
const isLifecycleGraphsEnabled = useUiFlag('lifecycleGraphs');
|
||||||
|
|
||||||
|
function getCurrentArchiveRatio() {
|
||||||
|
if (!groupedCreationArchiveData || Object.keys(groupedCreationArchiveData).length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalArchived = 0;
|
||||||
|
let totalCreated = 0;
|
||||||
|
|
||||||
|
Object.values(groupedCreationArchiveData).forEach((projectData) => {
|
||||||
|
const latestData = projectData[projectData.length - 1];
|
||||||
|
if (latestData) {
|
||||||
|
totalArchived += latestData.archivedFlags || 0;
|
||||||
|
const createdSum = latestData.createdFlags
|
||||||
|
? Object.values(latestData.createdFlags).reduce(
|
||||||
|
(sum: number, count: number) => sum + count,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
: 0;
|
||||||
|
totalCreated += createdSum;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return totalCreated > 0
|
||||||
|
? Math.round((totalArchived / totalCreated) * 100)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentRatio = getCurrentArchiveRatio();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<GlobalStyles styles={{
|
||||||
|
'@keyframes pulse': {
|
||||||
|
'0%': {
|
||||||
|
boxShadow: '0 0 0 0 rgba(16, 185, 129, 0.7)',
|
||||||
|
},
|
||||||
|
'70%': {
|
||||||
|
boxShadow: '0 0 0 6px rgba(16, 185, 129, 0)',
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
boxShadow: '0 0 0 0 rgba(16, 185, 129, 0)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}} />
|
||||||
<InsightsSection
|
<InsightsSection
|
||||||
title='Performance insights'
|
title='Performance insights'
|
||||||
filters={
|
filters={
|
||||||
@ -94,143 +126,435 @@ export const PerformanceInsights: FC = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: {
|
||||||
|
xs: '1fr',
|
||||||
|
md: 'repeat(2, 1fr)'
|
||||||
|
},
|
||||||
|
gap: 2,
|
||||||
|
'& > *': {
|
||||||
|
minWidth: 0,
|
||||||
|
}
|
||||||
|
}}>
|
||||||
{isLifecycleGraphsEnabled && isEnterprise() ? (
|
{isLifecycleGraphsEnabled && isEnterprise() ? (
|
||||||
<StyledWidget>
|
<Box sx={{
|
||||||
<StyledWidgetStats width={275}>
|
borderRadius: 3,
|
||||||
<WidgetTitle title='New flags in production' />
|
overflow: 'hidden',
|
||||||
<StatsExplanation>
|
backgroundColor: 'background.paper',
|
||||||
<Lightbulb color='primary' />
|
border: '1px solid',
|
||||||
How often do flags go live in production?
|
borderColor: 'divider',
|
||||||
</StatsExplanation>
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
</StyledWidgetStats>
|
'&:hover': {
|
||||||
<StyledChartContainer>
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
|
transform: 'translateY(-4px)',
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
|
: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#10b981',
|
||||||
|
animation: 'pulse 2s infinite'
|
||||||
|
}} />
|
||||||
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
|
Production Deployments
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
<NewProductionFlagsChart
|
<NewProductionFlagsChart
|
||||||
lifecycleTrends={groupedLifecycleData}
|
lifecycleTrends={groupedLifecycleData}
|
||||||
isAggregate={showAllProjects}
|
isAggregate={showAllProjects}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
</StyledChartContainer>
|
</Box>
|
||||||
</StyledWidget>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{isLifecycleGraphsEnabled && isEnterprise() ? (
|
{isLifecycleGraphsEnabled && isEnterprise() ? (
|
||||||
<StyledWidget>
|
<Box sx={{
|
||||||
<StyledWidgetStats width={275}>
|
borderRadius: 3,
|
||||||
<WidgetTitle title='Flags created vs archived' />
|
overflow: 'hidden',
|
||||||
<CreationArchiveStats
|
backgroundColor: 'background.paper',
|
||||||
groupedCreationArchiveData={
|
border: '1px solid',
|
||||||
groupedCreationArchiveData
|
borderColor: 'divider',
|
||||||
}
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
isLoading={loading}
|
'&:hover': {
|
||||||
/>
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
</StyledWidgetStats>
|
transform: 'translateY(-4px)',
|
||||||
<StyledChartContainer>
|
}
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
|
: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#8b5cf6',
|
||||||
|
}}/>
|
||||||
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
|
Lifecycle Balance
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Tooltip title="Current ratio of archived flags to created flags" placement="top">
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'help'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '...' : `${currentRatio}%`}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
<Link
|
||||||
|
href='/search?lifecycle=IS:completed'
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
'&:hover': {
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View cleanup
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
<CreationArchiveChart
|
<CreationArchiveChart
|
||||||
creationArchiveTrends={groupedCreationArchiveData}
|
creationArchiveTrends={groupedCreationArchiveData}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
</StyledChartContainer>
|
</Box>
|
||||||
</StyledWidget>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showAllProjects ? (
|
{showAllProjects ? (
|
||||||
<StyledWidget>
|
<Box sx={{
|
||||||
<StyledWidgetStats width={275}>
|
borderRadius: 3,
|
||||||
<WidgetTitle title='Flags' />
|
overflow: 'hidden',
|
||||||
<FlagStats
|
backgroundColor: 'background.paper',
|
||||||
count={flagsTotal}
|
border: '1px solid',
|
||||||
flagsPerUser={getFlagsPerUser(
|
borderColor: 'divider',
|
||||||
flagsTotal,
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
usersTotal,
|
'&:hover': {
|
||||||
)}
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
isLoading={loading}
|
transform: 'translateY(-4px)',
|
||||||
/>
|
}
|
||||||
</StyledWidgetStats>
|
}}>
|
||||||
<StyledChartContainer>
|
<Box sx={{
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
|
: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#3b82f6',
|
||||||
|
}}/>
|
||||||
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
|
Flags
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Tooltip title={`Total: ${flagsTotal} flags, ${getFlagsPerUser(flagsTotal, usersTotal)} per user`} placement="top">
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'help'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '...' : flagsTotal}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
<FlagsChart
|
<FlagsChart
|
||||||
flagTrends={flagTrends}
|
flagTrends={flagTrends}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
</StyledChartContainer>
|
</Box>
|
||||||
</StyledWidget>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<StyledWidget>
|
<Box sx={{
|
||||||
<StyledWidgetStats width={275}>
|
borderRadius: 3,
|
||||||
<WidgetTitle title='Flags' />
|
overflow: 'hidden',
|
||||||
<FlagStats
|
backgroundColor: 'background.paper',
|
||||||
count={summary.total}
|
border: '1px solid',
|
||||||
flagsPerUser={''}
|
borderColor: 'divider',
|
||||||
isLoading={loading}
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
/>
|
'&:hover': {
|
||||||
</StyledWidgetStats>
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
<StyledChartContainer>
|
transform: 'translateY(-4px)',
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
|
: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#3b82f6',
|
||||||
|
}}/>
|
||||||
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
|
Flags
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Tooltip title={`Total: ${summary.total} flags in selected projects`} placement="top">
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'help'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '...' : summary.total}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
<FlagsProjectChart
|
<FlagsProjectChart
|
||||||
projectFlagTrends={groupedProjectsData}
|
projectFlagTrends={groupedProjectsData}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
</StyledChartContainer>
|
</Box>
|
||||||
</StyledWidget>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{isEnterprise() ? (
|
{isEnterprise() ? (
|
||||||
<StyledWidget>
|
<Box sx={{
|
||||||
<StyledWidgetStats width={350} padding={0}>
|
borderRadius: 3,
|
||||||
<HealthStats
|
overflow: 'hidden',
|
||||||
value={summary.averageHealth}
|
backgroundColor: 'background.paper',
|
||||||
technicalDebt={summary.technicalDebt}
|
border: '1px solid',
|
||||||
healthy={summary.active}
|
borderColor: 'divider',
|
||||||
stale={summary.stale}
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
potentiallyStale={summary.potentiallyStale}
|
'&:hover': {
|
||||||
title={
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
<WidgetTitle
|
transform: 'translateY(-4px)',
|
||||||
title='Technical debt'
|
}
|
||||||
tooltip={
|
}}>
|
||||||
'Percentage of flags that are stale or potentially stale.'
|
<Box sx={{
|
||||||
}
|
p: 2,
|
||||||
/>
|
borderBottom: '1px solid',
|
||||||
}
|
borderColor: 'divider',
|
||||||
/>
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
</StyledWidgetStats>
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
<StyledChartContainer>
|
: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#ef4444',
|
||||||
|
}}/>
|
||||||
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
|
Technical Debt
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Tooltip title={`${summary.technicalDebt}% of flags are stale or potentially stale. Healthy: ${summary.active}, Stale: ${summary.stale}, Potentially stale: ${summary.potentiallyStale}`} placement="top">
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{
|
||||||
|
color: summary.technicalDebt > 50 ? 'error.main' : 'primary.main',
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'help'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '...' : `${summary.technicalDebt}%`}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
<Link
|
||||||
|
href='https://docs.getunleash.io/reference/insights#health'
|
||||||
|
target="_blank"
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
'&:hover': {
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
<ProjectHealthChart
|
<ProjectHealthChart
|
||||||
projectFlagTrends={groupedProjectsData}
|
projectFlagTrends={groupedProjectsData}
|
||||||
isAggregate={showAllProjects}
|
isAggregate={showAllProjects}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
</StyledChartContainer>
|
</Box>
|
||||||
</StyledWidget>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
{isEnterprise() ? (
|
{isEnterprise() ? (
|
||||||
<>
|
<Box sx={{
|
||||||
<StyledWidget>
|
borderRadius: 3,
|
||||||
<StyledWidgetContent>
|
overflow: 'hidden',
|
||||||
<WidgetTitle
|
backgroundColor: 'background.paper',
|
||||||
title='Flag evaluation metrics'
|
border: '1px solid',
|
||||||
tooltip='Summary of all flag evaluations reported by SDKs.'
|
borderColor: 'divider',
|
||||||
/>
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
<StyledChartContainer>
|
'&:hover': {
|
||||||
<MetricsSummaryChart
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
metricsSummaryTrends={groupedMetricsData}
|
transform: 'translateY(-4px)',
|
||||||
allDatapointsSorted={allMetricsDatapoints}
|
}
|
||||||
isAggregate={showAllProjects}
|
}}>
|
||||||
isLoading={loading}
|
<Box sx={{
|
||||||
/>
|
p: 2,
|
||||||
</StyledChartContainer>
|
borderBottom: '1px solid',
|
||||||
</StyledWidgetContent>
|
borderColor: 'divider',
|
||||||
</StyledWidget>
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
<StyledWidget>
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
<StyledWidgetContent>
|
: theme.palette.background.default
|
||||||
<WidgetTitle
|
}}>
|
||||||
title='Updates per environment type'
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
tooltip='Summary of all configuration updates per environment type.'
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
/>
|
<Box sx={{
|
||||||
<UpdatesPerEnvironmentTypeChart
|
width: 8,
|
||||||
environmentTypeTrends={environmentTypeTrends}
|
height: 8,
|
||||||
isLoading={loading}
|
borderRadius: '50%',
|
||||||
/>
|
backgroundColor: '#f59e0b',
|
||||||
</StyledWidgetContent>
|
}}/>
|
||||||
</StyledWidget>
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
</>
|
Flag Evaluation Metrics
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Tooltip title="Summary of all flag evaluations reported by SDKs" placement="top">
|
||||||
|
<Box sx={{
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'help'
|
||||||
|
}}>
|
||||||
|
<Typography variant='caption' sx={{ fontWeight: 600 }}>?</Typography>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<MetricsSummaryChart
|
||||||
|
metricsSummaryTrends={groupedMetricsData}
|
||||||
|
allDatapointsSorted={allMetricsDatapoints}
|
||||||
|
isAggregate={showAllProjects}
|
||||||
|
isLoading={loading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
{isEnterprise() ? (
|
||||||
|
<Box sx={{
|
||||||
|
borderRadius: 3,
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
'&:hover': {
|
||||||
|
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
|
||||||
|
transform: 'translateY(-4px)',
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
p: 2,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
background: (theme) => theme.palette.mode === 'light'
|
||||||
|
? 'linear-gradient(to right, #f8f9fa, #ffffff)'
|
||||||
|
: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#06b6d4',
|
||||||
|
}}/>
|
||||||
|
<Typography variant='body1' sx={{ fontWeight: 600, letterSpacing: '-0.01em' }}>
|
||||||
|
Updates per Environment Type
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Tooltip title="Summary of all configuration updates per environment type" placement="top">
|
||||||
|
<Box sx={{
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'help'
|
||||||
|
}}>
|
||||||
|
<Typography variant='caption' sx={{ fontWeight: 600 }}>?</Typography>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<UpdatesPerEnvironmentTypeChart
|
||||||
|
environmentTypeTrends={environmentTypeTrends}
|
||||||
|
isLoading={loading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
</InsightsSection>
|
</InsightsSection>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user