From 9cd324bd7cb7760febcb867a6145c156453eace9 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Tue, 27 Feb 2024 12:24:44 +0200 Subject: [PATCH] feat: projects using this application (#6355) ![image](https://github.com/Unleash/unleash/assets/964450/bcb5a612-3202-46ae-be20-bd23cbd8d6e4) --- .../src/component/application/Application.tsx | 5 +- .../application/ApplicationChart.tsx | 227 ++++++++++++++ .../ApplicationIssues/ApplicationIssues.tsx | 2 +- .../application/ApplicationOverview.tsx | 285 +++--------------- 4 files changed, 268 insertions(+), 251 deletions(-) create mode 100644 frontend/src/component/application/ApplicationChart.tsx diff --git a/frontend/src/component/application/Application.tsx b/frontend/src/component/application/Application.tsx index 8f0461b26c..3ee87dd9e4 100644 --- a/frontend/src/component/application/Application.tsx +++ b/frontend/src/component/application/Application.tsx @@ -1,5 +1,5 @@ /* eslint react/no-multi-comp:off */ -import React, { lazy, useContext, useState } from 'react'; +import React, { useContext, useState } from 'react'; import { Box, Avatar, @@ -31,6 +31,7 @@ import { formatUnknownError } from 'utils/formatUnknownError'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useUiFlag } from 'hooks/useUiFlag'; import { ApplicationEdit } from './ApplicationEdit/ApplicationEdit'; +import ApplicationOverview from './ApplicationOverview'; type Tab = { title: string; @@ -68,8 +69,6 @@ const StyledTab = styled(Tab)(({ theme }) => ({ }, })); -const ApplicationOverview = lazy(() => import('./ApplicationOverview')); - export const Application = () => { const useOldApplicationScreen = !useUiFlag('sdkReporting'); const navigate = useNavigate(); diff --git a/frontend/src/component/application/ApplicationChart.tsx b/frontend/src/component/application/ApplicationChart.tsx new file mode 100644 index 0000000000..d28437521a --- /dev/null +++ b/frontend/src/component/application/ApplicationChart.tsx @@ -0,0 +1,227 @@ +import { Box, Divider, styled, Typography, useTheme } from '@mui/material'; +import { ArcherContainer, ArcherElement } from 'react-archer'; +import { ConditionallyRender } from '../common/ConditionallyRender/ConditionallyRender'; +import { useNavigate } from 'react-router-dom'; +import { FC, useLayoutEffect, useRef, useState } from 'react'; +import { ApplicationOverviewSchema } from '../../openapi'; +import { useRequiredPathParam } from '../../hooks/useRequiredPathParam'; +import { WarningAmberRounded } from '@mui/icons-material'; + +const StyledTable = styled('table')(({ theme }) => ({ + fontSize: theme.fontSizes.smallerBody, + marginTop: theme.spacing(2), +})); + +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), +})); + +const StyledDivider = styled(Divider)(({ theme }) => ({ + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + width: '100%', +})); + +const StyledEnvironmentsContainer = styled(Box)({ + display: 'flex', + justifyContent: 'start', + gap: '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 useElementWidth = () => { + const elementRef = useRef(null); + const [width, setWidth] = useState('100%'); + + useLayoutEffect(() => { + setWidth(`${elementRef.current?.scrollWidth}px`); + }, [elementRef, setWidth]); + + return { + elementRef, + width, + }; +}; +const SuccessStatus = () => ( + + ({ + color: theme.palette.success.main, + })} + />{' '} + Everything looks good! + +); + +const WarningStatus: FC = ({ children }) => ( + + ({ + color: theme.palette.warning.main, + })} + />{' '} + {children} + +); + +interface IApplicationChartProps { + data: ApplicationOverviewSchema; +} + +export const ApplicationChart = ({ data }: IApplicationChartProps) => { + const applicationName = useRequiredPathParam('name'); + const { elementRef, width } = useElementWidth(); + const navigate = useNavigate(); + const theme = useTheme(); + + const mode: 'success' | 'warning' = + data.issues.length === 0 ? 'success' : 'warning'; + + return ( + + + + ({ + targetId: environment.name, + targetAnchor: 'top', + sourceAnchor: 'bottom', + style: { + strokeColor: + mode === 'success' + ? theme.palette.secondary.border + : theme.palette.warning.border, + }, + }))} + > + + ({ + fontSize: theme.fontSizes.smallerBody, + })} + color='text.secondary' + > + Application + + ({ + fontSize: theme.fontSizes.bodySize, + fontWeight: theme.fontWeight.bold, + })} + > + {applicationName} + + + + + } + elseShow={ + + {data.issues.length} issues detected + + } + /> + + + + + + {data.environments.map((environment) => ( + + + + {environment.name} environment + + + + + + Instances: + + {environment.instanceCount} + + + + SDK: + + {environment.sdks.map((sdk) => ( +
{sdk}
+ ))} +
+ + + Last seen: + + {environment.lastSeen} + + + +
+
+
+ ))} +
+
+
+ ); +}; diff --git a/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx b/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx index 6deae0987c..1340cbf0a3 100644 --- a/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx +++ b/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx @@ -6,7 +6,7 @@ import { ApplicationOverviewIssuesSchema } from 'openapi'; const WarningContainer = styled(Box)(({ theme }) => ({ display: 'flex', flexDirection: 'column', - paddingBottom: theme.spacing(8), + alignSelf: 'stretch', })); const WarningHeader = styled(Box)(({ theme }) => ({ diff --git a/frontend/src/component/application/ApplicationOverview.tsx b/frontend/src/component/application/ApplicationOverview.tsx index c1c74f2e68..85fed61fc0 100644 --- a/frontend/src/component/application/ApplicationOverview.tsx +++ b/frontend/src/component/application/ApplicationOverview.tsx @@ -1,73 +1,13 @@ import { usePageTitle } from 'hooks/usePageTitle'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { - Alert, - Box, - Divider, - styled, - Typography, - useTheme, -} from '@mui/material'; +import { Alert, Box, Divider, styled } from '@mui/material'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { useNavigate } from 'react-router-dom'; -import { ArcherContainer, ArcherElement } from 'react-archer'; -import { FC, useLayoutEffect, useRef, useState } from 'react'; import { useApplicationOverview } from 'hooks/api/getters/useApplicationOverview/useApplicationOverview'; -import { WarningAmberRounded } from '@mui/icons-material'; import { ApplicationIssues } from './ApplicationIssues/ApplicationIssues'; - -const StyledTable = styled('table')(({ theme }) => ({ - fontSize: theme.fontSizes.smallerBody, - marginTop: theme.spacing(2), -})); - -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 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 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), -})); +import { ApplicationChart } from './ApplicationChart'; +import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; +import { Badge } from '../common/Badge/Badge'; +import { useNavigate } from 'react-router-dom'; const StyledDivider = styled(Divider)(({ theme }) => ({ marginTop: theme.spacing(2), @@ -75,203 +15,54 @@ const StyledDivider = styled(Divider)(({ theme }) => ({ width: '100%', })); -const StyledEnvironmentsContainer = styled(Box)({ +const ApplicationContainer = styled(Box)(({ theme }) => ({ display: 'flex', - justifyContent: 'start', - gap: '20px', -}); - -const EnvironmentHeader = styled(Typography)(({ theme }) => ({ - fontSize: theme.fontSizes.smallerBody, - fontWeight: theme.fontWeight.bold, + padding: theme.spacing(1), + flexDirection: 'column', + alignItems: 'center', + gap: theme.spacing(2), + alignSelf: 'stretch', })); -const SuccessStatus = () => ( - - ({ - color: theme.palette.success.main, - })} - />{' '} - Everything looks good! - -); +const ProjectContainer = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(2), + alignSelf: 'stretch', +})); -const WarningStatus: FC = ({ children }) => ( - - ({ - color: theme.palette.warning.main, - })} - />{' '} - {children} - -); - -const useElementWidth = () => { - const elementRef = useRef(null); - const [width, setWidth] = useState('100%'); - - useLayoutEffect(() => { - setWidth(`${elementRef.current?.scrollWidth}px`); - }, [elementRef, setWidth]); - - return { - elementRef, - width, - }; -}; - -export const ApplicationOverview = () => { +const ApplicationOverview = () => { usePageTitle('Applications - Overview'); const applicationName = useRequiredPathParam('name'); const navigate = useNavigate(); - const theme = useTheme(); const { data, loading } = useApplicationOverview(applicationName); - // @ts-ignore - window.navigateToInstances = (environment: string) => { - navigate( - `/applications/${applicationName}/instances?environment=${environment}`, - ); - }; - - const { elementRef, width } = useElementWidth(); - - const mode: 'success' | 'warning' = - data.issues.length === 0 ? 'success' : 'warning'; - return ( No data available.} elseShow={ - <> + + + Projects using this application + {data.projects.map((project) => ( + { + e.preventDefault(); + navigate(`/projects/${project}`); + }} + color='secondary' + icon={} + > + {project} + + ))} + + - - - - ({ - targetId: environment.name, - targetAnchor: 'top', - sourceAnchor: 'bottom', - style: { - strokeColor: - mode === 'success' - ? theme.palette - .secondary.border - : theme.palette.warning - .border, - }, - }), - )} - > - - ({ - fontSize: - theme.fontSizes.smallerBody, - })} - color='text.secondary' - > - Application - - ({ - fontSize: - theme.fontSizes.bodySize, - fontWeight: - theme.fontWeight.bold, - })} - > - {applicationName} - - - - - } - elseShow={ - - {data.issues.length} issues - detected - - } - /> - - - - - - {data.environments.map((environment) => ( - - - - {environment.name} environment - - - - - - - Instances: - - - { - environment.instanceCount - } - - - - - SDK: - - - {environment.sdks.map( - (sdk) => ( -
- {sdk} -
- ), - )} -
- - - - Last seen: - - - { - environment.lastSeen - } - - - -
-
-
- ))} -
-
-
- + +
} /> );