mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
53354224fc
Upgrades biome to 1.6.1, and updates husky pre-commit hook. Most changes here are making type imports explicit.
329 lines
12 KiB
TypeScript
329 lines
12 KiB
TypeScript
import { Box, Divider, styled, Typography, useTheme } from '@mui/material';
|
|
import { ArcherContainer, ArcherElement } from 'react-archer';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { type FC, useLayoutEffect, useRef, useState } from 'react';
|
|
import type {
|
|
ApplicationOverviewEnvironmentSchema,
|
|
ApplicationOverviewSchema,
|
|
} from 'openapi';
|
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|
import { HelpIcon } from '../common/HelpIcon/HelpIcon';
|
|
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
import CloudCircle from '@mui/icons-material/CloudCircle';
|
|
import Flag from '@mui/icons-material/Flag';
|
|
import WarningAmberRounded from '@mui/icons-material/WarningAmberRounded';
|
|
import TimeAgo from 'react-timeago';
|
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
|
import { getApplicationIssues } from './ApplicationIssues/ApplicationIssues';
|
|
|
|
const StyledTable = styled('table')(({ theme }) => ({
|
|
fontSize: theme.fontSizes.smallerBody,
|
|
marginTop: theme.spacing(1),
|
|
}));
|
|
|
|
const StyledCell = styled('td')(({ theme }) => ({
|
|
verticalAlign: 'top',
|
|
paddingLeft: 0,
|
|
paddingRight: theme.spacing(1),
|
|
}));
|
|
|
|
const StyleApplicationContainer = styled(Box)(({ theme }) => ({
|
|
marginBottom: theme.spacing(18),
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
}));
|
|
|
|
const StyledApplicationBox = styled(Box)<{
|
|
mode: 'success' | 'warning';
|
|
}>(({ theme, mode }) => ({
|
|
borderRadius: theme.shape.borderRadiusMedium,
|
|
border: '1px solid',
|
|
borderColor: theme.palette[mode].border,
|
|
backgroundColor: theme.palette[mode].light,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
padding: theme.spacing(1.5, 3, 2, 3),
|
|
}));
|
|
|
|
const StyledEnvironmentBox = styled(Box)<{
|
|
mode: 'success' | 'warning';
|
|
}>(({ theme, mode }) => ({
|
|
borderRadius: theme.shape.borderRadiusMedium,
|
|
border: '1px solid',
|
|
borderColor:
|
|
theme.palette[mode === 'success' ? 'secondary' : 'warning'].border,
|
|
backgroundColor:
|
|
theme.palette[mode === 'success' ? 'secondary' : 'warning'].light,
|
|
display: 'inline-block',
|
|
padding: theme.spacing(1.5, 1.5, 1.5, 1.5),
|
|
zIndex: 1,
|
|
opacity: 0.9,
|
|
}));
|
|
|
|
const StyledDivider = styled(Divider)(({ theme }) => ({
|
|
marginTop: theme.spacing(2),
|
|
marginBottom: theme.spacing(2),
|
|
width: '100%',
|
|
}));
|
|
|
|
const StyledEnvironmentsContainer = styled(Box)({
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
flexWrap: 'wrap',
|
|
gap: '60px 20px',
|
|
});
|
|
|
|
const EnvironmentHeader = styled(Typography)(({ theme }) => ({
|
|
fontSize: theme.fontSizes.smallerBody,
|
|
fontWeight: theme.fontWeight.bold,
|
|
}));
|
|
|
|
const StyledStatus = styled(Typography)<{
|
|
mode: 'success' | 'warning';
|
|
}>(({ theme, mode }) => ({
|
|
gap: theme.spacing(1),
|
|
fontSize: theme.fontSizes.smallBody,
|
|
color: theme.palette[mode].dark,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
}));
|
|
|
|
const StyledIconRow = styled(Box)(({ theme }) => ({
|
|
display: 'flex',
|
|
gap: theme.spacing(3),
|
|
color: theme.palette.secondary.main,
|
|
paddingTop: theme.spacing(2),
|
|
}));
|
|
|
|
const StyledIconContainer = styled(Box)(({ theme }) => ({
|
|
display: 'flex',
|
|
gap: theme.spacing(0.5),
|
|
}));
|
|
const StyledText = styled(Box)(({ theme }) => ({
|
|
color: theme.palette.text.primary,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
}));
|
|
|
|
interface IApplicationChartProps {
|
|
data: ApplicationOverviewSchema;
|
|
}
|
|
|
|
interface IApplicationCountersProps {
|
|
environmentCount: number;
|
|
featureCount: number;
|
|
}
|
|
|
|
const useElementWidth = () => {
|
|
const elementRef = useRef<HTMLDivElement>(null);
|
|
const [width, setWidth] = useState('100%');
|
|
|
|
useLayoutEffect(() => {
|
|
setWidth(`${elementRef.current?.scrollWidth}px`);
|
|
}, [elementRef, setWidth]);
|
|
|
|
return {
|
|
elementRef,
|
|
width,
|
|
};
|
|
};
|
|
const SuccessStatus = () => (
|
|
<StyledStatus mode='success'>
|
|
<CheckCircle
|
|
sx={(theme) => ({
|
|
color: theme.palette.success.main,
|
|
})}
|
|
/>{' '}
|
|
Everything looks good!
|
|
</StyledStatus>
|
|
);
|
|
|
|
const WarningStatus: FC = ({ children }) => (
|
|
<StyledStatus mode='warning'>
|
|
<WarningAmberRounded
|
|
sx={(theme) => ({
|
|
color: theme.palette.warning.main,
|
|
})}
|
|
/>{' '}
|
|
{children}
|
|
</StyledStatus>
|
|
);
|
|
|
|
const ApplicationCounters = ({
|
|
environmentCount,
|
|
featureCount,
|
|
}: IApplicationCountersProps) => {
|
|
return (
|
|
<StyledIconRow>
|
|
<StyledIconContainer>
|
|
<CloudCircle />
|
|
<StyledText>{environmentCount}</StyledText>
|
|
</StyledIconContainer>
|
|
<StyledIconContainer>
|
|
<Flag />
|
|
<StyledText>{featureCount}</StyledText>
|
|
</StyledIconContainer>
|
|
</StyledIconRow>
|
|
);
|
|
};
|
|
|
|
const useTracking = () => {
|
|
const { trackEvent } = usePlausibleTracker();
|
|
return () => {
|
|
trackEvent('sdk-reporting', {
|
|
props: {
|
|
eventType: 'environment box clicked',
|
|
},
|
|
});
|
|
};
|
|
};
|
|
|
|
const getEnvironmentMode = (
|
|
environment: ApplicationOverviewEnvironmentSchema,
|
|
) => {
|
|
return environment.issues.missingFeatures.length +
|
|
environment.issues.outdatedSdks.length ===
|
|
0
|
|
? 'success'
|
|
: 'warning';
|
|
};
|
|
|
|
export const ApplicationChart = ({ data }: IApplicationChartProps) => {
|
|
const trackClick = useTracking();
|
|
const applicationName = useRequiredPathParam('name');
|
|
const { elementRef, width } = useElementWidth();
|
|
const navigate = useNavigate();
|
|
const theme = useTheme();
|
|
|
|
const mode = getApplicationIssues(data);
|
|
|
|
return (
|
|
<Box sx={{ width }}>
|
|
<ArcherContainer
|
|
strokeColor={theme.palette.secondary.border}
|
|
endMarker={false}
|
|
>
|
|
<StyleApplicationContainer>
|
|
<ArcherElement
|
|
id='application'
|
|
relations={data.environments.map((environment) => ({
|
|
targetId: environment.name,
|
|
targetAnchor: 'top',
|
|
sourceAnchor: 'bottom',
|
|
style: {
|
|
strokeColor:
|
|
getEnvironmentMode(environment) ===
|
|
'success'
|
|
? theme.palette.secondary.border
|
|
: theme.palette.warning.border,
|
|
},
|
|
}))}
|
|
>
|
|
<StyledApplicationBox mode={mode.applicationMode}>
|
|
<Typography
|
|
sx={(theme) => ({
|
|
fontSize: theme.fontSizes.smallerBody,
|
|
})}
|
|
color='text.secondary'
|
|
>
|
|
Application
|
|
</Typography>
|
|
<Typography
|
|
sx={(theme) => ({
|
|
fontSize: theme.fontSizes.bodySize,
|
|
fontWeight: theme.fontWeight.bold,
|
|
})}
|
|
>
|
|
{applicationName}
|
|
</Typography>
|
|
<ApplicationCounters
|
|
environmentCount={data.environments.length}
|
|
featureCount={data.featureCount}
|
|
/>
|
|
<StyledDivider />
|
|
{mode.applicationMode === 'success' ? (
|
|
<SuccessStatus />
|
|
) : (
|
|
<WarningStatus>
|
|
{mode.issueCount} issues detected
|
|
</WarningStatus>
|
|
)}
|
|
</StyledApplicationBox>
|
|
</ArcherElement>
|
|
</StyleApplicationContainer>
|
|
|
|
<StyledEnvironmentsContainer ref={elementRef}>
|
|
{data.environments.map((environment) => (
|
|
<ArcherElement
|
|
id={environment.name}
|
|
key={environment.name}
|
|
>
|
|
<StyledEnvironmentBox
|
|
mode={getEnvironmentMode(environment)}
|
|
key={environment.name}
|
|
sx={{ cursor: 'pointer' }}
|
|
onClick={(e) => {
|
|
trackClick();
|
|
navigate(
|
|
`/applications/${applicationName}/instances?environment=${environment.name}`,
|
|
);
|
|
}}
|
|
>
|
|
<EnvironmentHeader>
|
|
{environment.name} environment
|
|
</EnvironmentHeader>
|
|
|
|
<StyledTable>
|
|
<tbody>
|
|
<tr>
|
|
<StyledCell
|
|
sx={{ display: 'flex' }}
|
|
>
|
|
Instances:{' '}
|
|
<HelpIcon
|
|
size={
|
|
theme.fontSizes
|
|
.smallBody
|
|
}
|
|
tooltip='Active instances in the last 2 days'
|
|
/>
|
|
</StyledCell>
|
|
<StyledCell>
|
|
{environment.instanceCount}
|
|
</StyledCell>
|
|
</tr>
|
|
<tr>
|
|
<StyledCell>SDK:</StyledCell>
|
|
<StyledCell>
|
|
{environment.sdks.map((sdk) => (
|
|
<div key={sdk}>{sdk}</div>
|
|
))}
|
|
</StyledCell>
|
|
</tr>
|
|
<tr>
|
|
<StyledCell>Last seen:</StyledCell>
|
|
<StyledCell>
|
|
{environment.lastSeen && (
|
|
<TimeAgo
|
|
minPeriod={60}
|
|
date={
|
|
new Date(
|
|
environment.lastSeen,
|
|
)
|
|
}
|
|
/>
|
|
)}
|
|
</StyledCell>
|
|
</tr>
|
|
</tbody>
|
|
</StyledTable>
|
|
</StyledEnvironmentBox>
|
|
</ArcherElement>
|
|
))}
|
|
</StyledEnvironmentsContainer>
|
|
</ArcherContainer>
|
|
</Box>
|
|
);
|
|
};
|