Homepage refactor (#2606)
* Added UI screens for new homepages Signed-off-by: Saranya-jena <saranya.jena@mayadata.io> * analytics data integration in homepage cards Signed-off-by: Saranya-jena <saranya.jena@mayadata.io> * style fixes / removed un-used files Signed-off-by: Vansh Bhatia <vansh@chaosnative.com> * Added routes for returning Home, fixed UI for landing home Signed-off-by: Vansh Bhatia <vansh@chaosnative.com> * Fixed linting issues Signed-off-by: Vansh Bhatia <vansh@chaosnative.com> Co-authored-by: Saranya-jena <saranya.jena@mayadata.io>
This commit is contained in:
parent
ef64693841
commit
ca7e52aab3
|
|
@ -3254,9 +3254,9 @@
|
|||
"integrity": "sha512-mHkm6FvepJECMNthFuIgpAEFmPOk71UyXuIxYfjytvFTnSDBIz7jmViO+LfHI/AjrazWije0PnSP3+/NlwzqtA=="
|
||||
},
|
||||
"@visx/annotation": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/annotation/-/annotation-1.7.0.tgz",
|
||||
"integrity": "sha512-ZnLQ1f4QMhaVy71X/j20FU4faOsAc+bsGRJ5tdpK/L8TBrw5TH4silrXQtQ5esriDwRKyMvu8CAXUWNbeRjOhw==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@visx/annotation/-/annotation-1.7.2.tgz",
|
||||
"integrity": "sha512-/XsgzSMF5ls2z6okHy71gu2uWZDTFg5yBimG34gK5cO2G0ZP7oBv7p6k4yyaGroAy7gXbiXAQqqMsAJPmKtq+Q==",
|
||||
"requires": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/react": "*",
|
||||
|
|
@ -3267,7 +3267,7 @@
|
|||
"@visx/text": "1.7.0",
|
||||
"classnames": "^2.2.5",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-use-measure": "^2.0.3"
|
||||
"react-use-measure": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"@visx/axis": {
|
||||
|
|
@ -3616,24 +3616,24 @@
|
|||
}
|
||||
},
|
||||
"@visx/tooltip": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.0.tgz",
|
||||
"integrity": "sha512-GV2cZduCAeuthUYyq2hzD1oDdd+Gkm0LWJcJOB2VkIaW8H7Uf1evN396lWNRSFNPPFYaAONKfyPM6JHsPsL1OA==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz",
|
||||
"integrity": "sha512-QCNLaOGpgZ6RnZjCIbEJtfbu8ZkSwDOXRy5SOUvcC5J9RiEwCi0DXF1JzT7ZPv3hkLU1KiSPaUFf5q/5LNHwLg==",
|
||||
"requires": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/react": "*",
|
||||
"@visx/bounds": "1.7.0",
|
||||
"classnames": "^2.2.5",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-use-measure": "^2.0.3"
|
||||
"react-use-measure": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"@visx/visx": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/visx/-/visx-1.7.0.tgz",
|
||||
"integrity": "sha512-OdQo62gJPuRQyKbUuP+4SC/nrzFYUBgvDUpZ/0IZO2EVEdfkm0uAxs2h/R6OCZSOwudH85oPAfKUzAcbtZlIhA==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@visx/visx/-/visx-1.7.2.tgz",
|
||||
"integrity": "sha512-WmNBbx8UF1reM99yGbUli2INh/ZJjWJXYwg8N/L8n8WxNli9AQRYTbOynkvbE2eSee3XcWgmyErqsE85g1cljw==",
|
||||
"requires": {
|
||||
"@visx/annotation": "1.7.0",
|
||||
"@visx/annotation": "1.7.2",
|
||||
"@visx/axis": "1.7.0",
|
||||
"@visx/bounds": "1.7.0",
|
||||
"@visx/brush": "1.7.0",
|
||||
|
|
@ -3658,9 +3658,9 @@
|
|||
"@visx/scale": "1.7.0",
|
||||
"@visx/shape": "1.7.0",
|
||||
"@visx/text": "1.7.0",
|
||||
"@visx/tooltip": "1.7.0",
|
||||
"@visx/tooltip": "1.7.2",
|
||||
"@visx/voronoi": "1.7.0",
|
||||
"@visx/xychart": "1.7.0",
|
||||
"@visx/xychart": "1.7.2",
|
||||
"@visx/zoom": "1.7.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -3678,14 +3678,14 @@
|
|||
}
|
||||
},
|
||||
"@visx/xychart": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/xychart/-/xychart-1.7.0.tgz",
|
||||
"integrity": "sha512-HW+TrMZNiPnvqpkCmHnSiuZh5XDWruM1ckq5Kwf73ERgp0Xdx/tDbknn0wC7TP1BFCvfCeXHN/W3czrUy7tWIg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@visx/xychart/-/xychart-1.7.2.tgz",
|
||||
"integrity": "sha512-um9P+YPf23mkWxPQtVQ3NHGIdUuW1dzGCm8xK9ZouguzEcZMuskKIxLSIN1Pq3F56h+u+perjAdXutXd80Xmcg==",
|
||||
"requires": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/lodash": "^4.14.146",
|
||||
"@types/react": "*",
|
||||
"@visx/annotation": "1.7.0",
|
||||
"@visx/annotation": "1.7.2",
|
||||
"@visx/axis": "1.7.0",
|
||||
"@visx/event": "1.7.0",
|
||||
"@visx/glyph": "1.7.0",
|
||||
|
|
@ -3695,7 +3695,7 @@
|
|||
"@visx/scale": "1.7.0",
|
||||
"@visx/shape": "1.7.0",
|
||||
"@visx/text": "1.7.0",
|
||||
"@visx/tooltip": "1.7.0",
|
||||
"@visx/tooltip": "1.7.2",
|
||||
"@visx/voronoi": "1.7.0",
|
||||
"classnames": "^2.2.5",
|
||||
"d3-array": "^2.6.0",
|
||||
|
|
@ -14725,9 +14725,9 @@
|
|||
}
|
||||
},
|
||||
"litmus-ui": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/litmus-ui/-/litmus-ui-1.0.4.tgz",
|
||||
"integrity": "sha512-XMWH/pePXhEJrLoUttwp0FQPXjgK2MMH8fmw0q2+nuniojxIcQU2zUX7V0R5EqfOG1LpRGEJrNY+/lPgOVM00w==",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/litmus-ui/-/litmus-ui-1.0.5.tgz",
|
||||
"integrity": "sha512-BIclRoQZhOxMSziGb4A7zKWMgLtALxxzJDqUVPTUXc3m7CgeGKcidOgq3yxX0xzrVQpvMnkLlEapFdOSuO+VLA==",
|
||||
"requires": {
|
||||
"@material-ui/core": "^4.10.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
|
@ -19467,9 +19467,9 @@
|
|||
}
|
||||
},
|
||||
"react-use-measure": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.3.tgz",
|
||||
"integrity": "sha512-57O8Os9MbgFEHuOHOXNdPmBHhXjCBIwtB3YxyrM/MgaX44a1o97Mu9YqiOA6cAF8kXIw4fO3XK0r2Taa4SqaqQ==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.4.tgz",
|
||||
"integrity": "sha512-7K2HIGaPMl3Q9ZQiEVjen3tRXl4UDda8LiTPy/QxP8dP2rl5gPBhf7mMH6MVjjRNv3loU7sNzey/ycPNnHVTxQ==",
|
||||
"requires": {
|
||||
"debounce": "^1.2.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"jsonwebtoken": "^8.5.1",
|
||||
"jspdf": "^2.1.1",
|
||||
"jspdf-autotable": "^3.5.13",
|
||||
"litmus-ui": "^1.0.4",
|
||||
"litmus-ui": "^1.0.5",
|
||||
"localforage": "^1.7.3",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.27.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M55.5564 77.7773H44.4453V66.6662H55.5564V77.7773ZM55.5564 55.5551H44.4453V27.7773H55.5564V55.5551Z" fill="#DBA017"/>
|
||||
<circle cx="50" cy="50" r="49" stroke="#DBA017" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 300 B |
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="11" cy="11" r="10.5" stroke="#5B44BA"/>
|
||||
<path d="M15.8402 10.8172L12.2288 7.16163C12.1257 7.05728 11.9883 7 11.8418 7C11.6952 7 11.5578 7.05736 11.4548 7.16163L11.1269 7.4936C11.0239 7.59779 10.9671 7.73695 10.9671 7.88533C10.9671 8.03362 11.0239 8.17747 11.1269 8.28166L13.2337 10.4189H6.54024C6.23845 10.4189 6 10.6581 6 10.9636V11.433C6 11.7385 6.23845 12.0018 6.54024 12.0018H13.2576L11.1269 14.151C11.0239 14.2553 10.9672 14.3907 10.9672 14.5391C10.9672 14.6873 11.0239 14.8247 11.1269 14.9289L11.4548 15.2598C11.5579 15.3642 11.6952 15.4211 11.8419 15.4211C11.9884 15.4211 12.1258 15.3634 12.2289 15.2591L15.8402 11.6036C15.9436 11.4989 16.0004 11.3592 16 11.2106C16.0003 11.0616 15.9436 10.9218 15.8402 10.8172Z" fill="#5B44BA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 852 B |
|
|
@ -0,0 +1,13 @@
|
|||
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="11.3699" cy="11.4237" rx="11.0788" ry="11.08" fill="#5B44BA"/>
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M9.52323 10.6328H7.94098C7.79574 10.6328 7.6783 10.7503 7.67676 10.8971V14.8531C7.67676 14.9999 7.79574 15.1174 7.94098 15.1174H9.52323C9.67002 15.1174 9.78745 14.9984 9.78745 14.8531V10.8971C9.78745 10.7503 9.66848 10.6328 9.52323 10.6328Z" fill="white"/>
|
||||
<path d="M12.1609 7.73047H10.5787C10.4334 7.73047 10.3145 7.84791 10.3145 7.99317V14.8513C10.3145 14.9981 10.4334 15.1156 10.5787 15.1156H12.1609C12.3077 15.1156 12.4251 14.9966 12.4251 14.8513V7.99472C12.4251 7.84791 12.3062 7.73047 12.1609 7.73047Z" fill="white"/>
|
||||
<path d="M14.7976 9.83984H13.2154C13.0686 9.83984 12.9512 9.95729 12.9512 10.1041V14.8513C12.9512 14.9981 13.0701 15.1156 13.2154 15.1156H14.7976C14.9444 15.1156 15.0619 14.9966 15.0619 14.8513V10.1041C15.0619 9.95729 14.9429 9.83984 14.7976 9.83984Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="7.38589" height="7.38667" fill="white" transform="translate(7.67676 7.73047)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="19.75" cy="19.75" r="19.75" fill="#5B44BA"/>
|
||||
<circle cx="15.1569" cy="15.1559" r="2.29651" fill="white"/>
|
||||
<circle cx="24.3434" cy="24.3434" r="2.29651" fill="white"/>
|
||||
<rect x="12.8604" y="22.0469" width="4.59302" height="4.59302" fill="white"/>
|
||||
<rect x="22.0469" y="15.6172" width="3.67442" height="3.67442" transform="rotate(-45 22.0469 15.6172)" fill="white"/>
|
||||
<rect x="14.6973" y="19.2891" width="0.918605" height="1.83721" fill="white"/>
|
||||
<rect x="18.8311" y="16.0742" width="0.918605" height="1.83721" transform="rotate(-90 18.8311 16.0742)" fill="white"/>
|
||||
<rect x="23.8838" y="19.2891" width="0.918605" height="1.83721" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 755 B |
|
|
@ -109,9 +109,12 @@ home:
|
|||
agentDeployInfo: To configure your workflow you need to deploy an agent. Click on “Deploy your first Agent” to continue.
|
||||
chaosAgent: Deploy LitmusChaos agent on your Kubernetes clusters.
|
||||
agents: Agents
|
||||
agent: Agent
|
||||
chaosAgentInfo: Cloud native chaos engineering can be setup on this cluster using LitmusChaos
|
||||
deployFirst: Deploy your first Agent
|
||||
toProjects: To projects
|
||||
toProjects: To Projects
|
||||
toAnalytics: To Analytics
|
||||
deploy: Deploy another Agent
|
||||
|
||||
login:
|
||||
heading: Welcome to <strong>Litmus!</strong>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { QuickActionCard } from 'litmus-ui';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Role } from '../../models/graphql/user';
|
||||
import useActions from '../../redux/actions';
|
||||
import * as TabActions from '../../redux/actions/tabs';
|
||||
import { history } from '../../redux/configureStore';
|
||||
|
|
@ -11,6 +12,7 @@ type Variant = 'homePage' | 'returningHome' | 'analytics' | 'community';
|
|||
|
||||
interface LocalQuickActionCardProps {
|
||||
variant?: Variant;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface QuickActionCardProps {
|
||||
|
|
@ -22,6 +24,7 @@ interface QuickActionCardProps {
|
|||
|
||||
const LocalQuickActionCard: React.FC<LocalQuickActionCardProps> = ({
|
||||
variant,
|
||||
className,
|
||||
}) => {
|
||||
const tabs = useActions(TabActions);
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -75,7 +78,8 @@ const LocalQuickActionCard: React.FC<LocalQuickActionCardProps> = ({
|
|||
: emptyData,
|
||||
|
||||
// TODO: settings only accessible by Owner
|
||||
homePage || returningHome || community || analytics
|
||||
(homePage || returningHome || community || analytics) &&
|
||||
getProjectRole() === Role.owner
|
||||
? {
|
||||
src: '/icons/teamMember.svg',
|
||||
alt: 'team',
|
||||
|
|
@ -123,6 +127,7 @@ const LocalQuickActionCard: React.FC<LocalQuickActionCardProps> = ({
|
|||
|
||||
return (
|
||||
<QuickActionCard
|
||||
className={className}
|
||||
quickActions={quickActionData}
|
||||
title={t('quickActionCard.quickActions')}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,106 +1,32 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import { ButtonFilled } from 'litmus-ui';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CreateWorkflowCard } from '../../components/CreateWorkflowCard';
|
||||
import InfoFilledWrap from '../../components/InfoFilled';
|
||||
import { LocalQuickActionCard } from '../../components/LocalQuickActionCard';
|
||||
import Scaffold from '../../containers/layouts/Scaffold';
|
||||
import useActions from '../../redux/actions';
|
||||
import * as TabActions from '../../redux/actions/tabs';
|
||||
import { history } from '../../redux/configureStore';
|
||||
import { getUserDetailsFromJwt } from '../../utils/auth';
|
||||
import { getProjectID, getProjectRole } from '../../utils/getSearchParams';
|
||||
import useStyles from './style';
|
||||
import { getUsername } from '../../utils/auth';
|
||||
import LandingHome from '../../views/Home/LandingHome';
|
||||
import ReturningHome from '../../views/Home/ReturningHome';
|
||||
import useStyles from './styles';
|
||||
|
||||
const HomePage: React.FC = () => {
|
||||
const userData = getUserDetailsFromJwt();
|
||||
const [dataPresent, setDataPresent] = useState<boolean>(true);
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const tabs = useActions(TabActions);
|
||||
const projectID = getProjectID();
|
||||
const userRole = getProjectRole();
|
||||
|
||||
return (
|
||||
<Scaffold>
|
||||
<div className={classes.rootContainer}>
|
||||
<div>
|
||||
<Typography variant="h3" className={classes.userName}>
|
||||
{t('home.heading')}
|
||||
<strong>{` ${userData.username}`}</strong>
|
||||
</Typography>
|
||||
<div className={classes.headingDiv}>
|
||||
<div className={classes.mainDiv}>
|
||||
<div>
|
||||
<Typography className={classes.mainHeading}>
|
||||
<strong>{t('home.subHeading1')}</strong>
|
||||
</Typography>
|
||||
<Typography className={classes.mainResult}>
|
||||
<strong>{t('home.subHeading2')}</strong>
|
||||
</Typography>
|
||||
<Typography className={classes.mainDesc}>
|
||||
{t('home.subHeading3')}
|
||||
</Typography>
|
||||
<div className={classes.predefinedBtn}>
|
||||
<ButtonFilled
|
||||
variant="success"
|
||||
onClick={() => {
|
||||
tabs.changeWorkflowsTabs(2);
|
||||
history.push({
|
||||
pathname: '/workflows',
|
||||
search: `?projectID=${projectID}&projectRole=${userRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1">
|
||||
{t('home.button1')}
|
||||
</Typography>
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.imageDiv}>
|
||||
<img src="icons/applause.png" alt="Applause icon" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<CreateWorkflowCard isDisabled data-cy="CreateWorkflowCard" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.contentDiv}>
|
||||
<div className={classes.statDiv}>
|
||||
<div className={classes.btnHeaderDiv}>
|
||||
<Typography className={classes.statsHeading}>
|
||||
<strong>{t('home.analytics.heading')}</strong>
|
||||
</Typography>
|
||||
<IconButton
|
||||
className={classes.seeAllBtn}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
history.push({
|
||||
pathname: '/community',
|
||||
search: `?projectID=${projectID}&projectRole=${userRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={classes.btnSpan}>
|
||||
<Typography className={classes.btnText}>
|
||||
{t('home.analytics.moreInfo')}
|
||||
</Typography>
|
||||
<img src="icons/next.png" alt="next" />
|
||||
</div>
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className={classes.cardDiv}>
|
||||
<InfoFilledWrap />
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.quickActionDiv}>
|
||||
<LocalQuickActionCard variant="homePage" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Typography variant="h3" className={classes.userName}>
|
||||
{t('home.heading')} {getUsername()}
|
||||
</Typography>
|
||||
{dataPresent ? (
|
||||
<ReturningHome
|
||||
callbackToSetDataPresent={(dataPresent: boolean) => {
|
||||
setDataPresent(dataPresent);
|
||||
}}
|
||||
currentStatus={dataPresent}
|
||||
/>
|
||||
) : (
|
||||
<LandingHome />
|
||||
)}
|
||||
</Scaffold>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,171 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
rootContainer: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
|
||||
backdrop: {
|
||||
background: theme.palette.border.main,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
|
||||
mainDiv: {
|
||||
padding: theme.spacing(3.75),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
width: '50rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
marginRight: theme.spacing(6.5),
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '95%',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
workflowCardDiv: {
|
||||
marginRight: theme.spacing(10),
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
marginTop: theme.spacing(3),
|
||||
height: '20rem',
|
||||
width: '95%',
|
||||
},
|
||||
},
|
||||
|
||||
userName: {
|
||||
fontSize: '2.5rem',
|
||||
marginTop: theme.spacing(1.75),
|
||||
marginBottom: theme.spacing(2.75),
|
||||
},
|
||||
|
||||
headingDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
mainHeading: {
|
||||
color: theme.palette.secondary.main,
|
||||
fontSize: '1.5625rem',
|
||||
marginBottom: theme.spacing(0.625),
|
||||
},
|
||||
|
||||
mainResult: {
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '1.5625rem',
|
||||
maxWidth: '27.5rem',
|
||||
marginBottom: theme.spacing(3.125),
|
||||
},
|
||||
|
||||
mainDesc: {
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '1.125rem',
|
||||
maxWidth: '36rem',
|
||||
},
|
||||
|
||||
imageDiv: {
|
||||
marginLeft: theme.spacing(10),
|
||||
marginTop: theme.spacing(10),
|
||||
'& img': {
|
||||
userDrag: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
contentDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginTop: theme.spacing(3.75),
|
||||
marginBottom: theme.spacing(2),
|
||||
[theme.breakpoints.down(1150)]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
statDiv: {
|
||||
width: '70%',
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.cards.background,
|
||||
borderRadius: 3,
|
||||
[theme.breakpoints.down(1150)]: {
|
||||
width: '90%',
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '95%',
|
||||
},
|
||||
},
|
||||
|
||||
statsHeading: {
|
||||
fontSize: '1.5625rem',
|
||||
marginBottom: theme.spacing(3.75),
|
||||
paddingTop: theme.spacing(5),
|
||||
paddingLeft: theme.spacing(5),
|
||||
},
|
||||
|
||||
quickActionDiv: {
|
||||
marginTop: theme.spacing(2),
|
||||
paddingLeft: theme.spacing(2),
|
||||
marginLeft: theme.spacing(3),
|
||||
background: 'inherit',
|
||||
},
|
||||
|
||||
cardDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(4),
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
|
||||
predefinedBtn: {
|
||||
marginTop: theme.spacing(2.5),
|
||||
},
|
||||
|
||||
btnHeaderDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
seeAllBtn: {
|
||||
marginTop: theme.spacing(1.5),
|
||||
marginRight: theme.spacing(6),
|
||||
marginLeft: 'auto',
|
||||
backgroundColor: 'transparent !important',
|
||||
cursor: 'pointer',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
marginTop: theme.spacing(0.2),
|
||||
marginBottom: theme.spacing(1),
|
||||
marginLeft: theme.spacing(3.8),
|
||||
},
|
||||
},
|
||||
|
||||
btnSpan: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
|
||||
btnText: {
|
||||
paddingRight: theme.spacing(1.25),
|
||||
textDecoration: 'none',
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
userName: {
|
||||
fontSize: '2.5rem',
|
||||
margin: theme.spacing(1.75, 0, 2.75, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
import { useQuery } from '@apollo/client';
|
||||
import { IconButton, Paper, Typography } from '@material-ui/core';
|
||||
import { ButtonFilled } from 'litmus-ui';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CreateWorkflowCard } from '../../../components/CreateWorkflowCard';
|
||||
import { LocalQuickActionCard } from '../../../components/LocalQuickActionCard';
|
||||
import { GET_CLUSTER, LIST_PROJECTS } from '../../../graphql';
|
||||
import { Clusters, ClusterVars } from '../../../models/graphql/clusterData';
|
||||
import { Member, Project, Projects } from '../../../models/graphql/user';
|
||||
import useActions from '../../../redux/actions';
|
||||
import * as TabActions from '../../../redux/actions/tabs';
|
||||
import { history } from '../../../redux/configureStore';
|
||||
import { getUserId } from '../../../utils/auth';
|
||||
import { getProjectID, getProjectRole } from '../../../utils/getSearchParams';
|
||||
import useStyles from './styles';
|
||||
|
||||
const LandingHome: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const tabs = useActions(TabActions);
|
||||
|
||||
const userID = getUserId();
|
||||
|
||||
const [projectOwnerCount, setProjectOwnerCount] = useState<number>(0);
|
||||
const [projectOtherCount, setProjectOtherCount] = useState<number>(0);
|
||||
const [invitationsCount, setInvitationCount] = useState<number>(0);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
|
||||
const { data: dataProject } = useQuery<Projects>(LIST_PROJECTS, {
|
||||
onCompleted: () => {
|
||||
if (dataProject?.listProjects) {
|
||||
setProjects(dataProject?.listProjects);
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let projectOwner = 0;
|
||||
let projectInvitation = 0;
|
||||
let projectOther = 0;
|
||||
projects.forEach((project) => {
|
||||
project.members.forEach((member: Member) => {
|
||||
if (member.user_id === userID && member.role === 'Owner') {
|
||||
projectOwner++;
|
||||
} else if (
|
||||
member.user_id === userID &&
|
||||
member.invitation === 'Pending'
|
||||
) {
|
||||
projectInvitation++;
|
||||
} else if (
|
||||
member.user_id === userID &&
|
||||
member.role !== 'Owner' &&
|
||||
member.invitation === 'Accepted'
|
||||
) {
|
||||
projectOther++;
|
||||
}
|
||||
});
|
||||
});
|
||||
setProjectOwnerCount(projectOwner);
|
||||
setInvitationCount(projectInvitation);
|
||||
setProjectOtherCount(projectOther);
|
||||
}, [projects, dataProject]);
|
||||
|
||||
// Apollo query to get the agent data
|
||||
const { data: agentList } = useQuery<Clusters, ClusterVars>(GET_CLUSTER, {
|
||||
variables: { project_id: getProjectID() },
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Row 1 */}
|
||||
<div className={classes.firstRow}>
|
||||
<Paper className={classes.mainDiv}>
|
||||
<div className={classes.paperContent}>
|
||||
<Typography className={classes.mainHeading}>
|
||||
<strong>{t('home.subHeading1')}</strong>
|
||||
</Typography>
|
||||
<Typography className={classes.mainResult}>
|
||||
<strong>{t('home.subHeading2')}</strong>
|
||||
</Typography>
|
||||
{!(agentList && agentList.getCluster.length > 0) && (
|
||||
<Typography className={classes.warningText}>
|
||||
{t('home.NonAdmin.noAgent')}
|
||||
</Typography>
|
||||
)}
|
||||
{!(agentList && agentList.getCluster.length > 0) ? (
|
||||
<Typography className={classes.mainDesc}>
|
||||
{t('home.NonAdmin.agentDeployInfo')}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography className={classes.mainDesc}>
|
||||
{t('home.subHeading3')}
|
||||
</Typography>
|
||||
)}
|
||||
{agentList && agentList.getCluster.length > 0 && (
|
||||
<div className={classes.predefinedBtn}>
|
||||
<ButtonFilled
|
||||
variant="success"
|
||||
onClick={() => {
|
||||
tabs.changeWorkflowsTabs(2);
|
||||
history.push({
|
||||
pathname: '/workflows',
|
||||
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1">
|
||||
{t('home.button1')}
|
||||
</Typography>
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!(agentList && agentList.getCluster.length > 0) ? (
|
||||
<div className={classes.imageDiv}>
|
||||
<img src="./icons/NoAgentAlert.svg" alt="No Agent Found" />
|
||||
</div>
|
||||
) : (
|
||||
<div className={classes.imageDiv}>
|
||||
<img src="icons/applause.png" alt="Applause icon" />
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
<div className={classes.workflowCard}>
|
||||
<CreateWorkflowCard
|
||||
isDisabled={agentList ? agentList.getCluster.length <= 0 : true}
|
||||
data-cy="CreateWorkflowCard"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Row 2 */}
|
||||
<div className={classes.secondRow}>
|
||||
{/* First Card */}
|
||||
<Paper className={classes.rowTwoPaper}>
|
||||
<Typography id="agentText">
|
||||
{t('home.NonAdmin.chaosAgent')}
|
||||
</Typography>
|
||||
<div className={classes.agentFlex}>
|
||||
<Typography className={classes.agentCount}>
|
||||
{agentList?.getCluster.length}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{agentList?.getCluster.length !== 1
|
||||
? t('home.NonAdmin.agents')
|
||||
: t('home.NonAdmin.agent')}
|
||||
</Typography>
|
||||
<div className={classes.agentDesc}>
|
||||
<Typography>{t('home.NonAdmin.chaosAgentInfo')}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonFilled
|
||||
onClick={() => {
|
||||
history.push({
|
||||
pathname: '/targets',
|
||||
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('home.NonAdmin.deployFirst')}
|
||||
</ButtonFilled>
|
||||
</Paper>
|
||||
{/* Second Card */}
|
||||
<Paper id="rowTwoSecondPaper" className={classes.rowTwoPaper}>
|
||||
<div className={classes.flexEnd}>
|
||||
<div className={classes.invitationBoxFlex}>
|
||||
{t('settings.teamingTab.invitations')}
|
||||
<Typography>{invitationsCount}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.projectInfoProjectStats}>
|
||||
<Typography>{projectOtherCount + projectOwnerCount}</Typography>
|
||||
{projectOtherCount + projectOwnerCount > 1 ? (
|
||||
<Typography>{t('settings.teamingTab.projects')}</Typography>
|
||||
) : (
|
||||
<Typography>{t('settings.teamingTab.project')}</Typography>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.flexEnd}>
|
||||
{t('home.NonAdmin.toProjects')}
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
tabs.changeSettingsTabs(1);
|
||||
history.push({
|
||||
pathname: '/settings',
|
||||
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src="./icons/goToIcon.svg" alt="go to" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Paper>
|
||||
<LocalQuickActionCard
|
||||
className={classes.quickActionCard}
|
||||
variant="homePage"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LandingHome;
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
userName: {
|
||||
fontSize: '2.5rem',
|
||||
margin: theme.spacing(1.75, 0, 2.75, 0),
|
||||
},
|
||||
|
||||
firstRow: {
|
||||
display: 'flex',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
mainDiv: {
|
||||
padding: theme.spacing(6.625, 5),
|
||||
borderRadius: 3,
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
},
|
||||
|
||||
paperContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
},
|
||||
|
||||
mainHeading: {
|
||||
color: theme.palette.primary.dark,
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: theme.spacing(1.5),
|
||||
},
|
||||
|
||||
mainResult: {
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '1.5625rem',
|
||||
maxWidth: '25rem',
|
||||
marginBottom: theme.spacing(3.125),
|
||||
},
|
||||
|
||||
warningText: {
|
||||
color: theme.palette.warning.main,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
mainDesc: {
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '0.875rem',
|
||||
maxWidth: '25rem',
|
||||
marginTop: theme.spacing(1.375),
|
||||
},
|
||||
|
||||
imageDiv: {
|
||||
marginRight: theme.spacing(6.25),
|
||||
},
|
||||
|
||||
workflowCard: {
|
||||
[theme.breakpoints.down('md')]: {
|
||||
maxWidth: '19.375rem',
|
||||
marginTop: theme.spacing(3.125),
|
||||
},
|
||||
},
|
||||
|
||||
secondRow: {
|
||||
display: 'flex',
|
||||
marginTop: theme.spacing(3.125),
|
||||
'& #rowTwoSecondPaper': {
|
||||
margin: theme.spacing(0, 5.625, 0, 5.35),
|
||||
minWidth: '15.6875rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
margin: theme.spacing(3.125, 0, 3.125, 0),
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
rowTwoPaper: {
|
||||
padding: theme.spacing(5),
|
||||
flexGrow: 1,
|
||||
maxWidth: '50%',
|
||||
'& #agentText': {
|
||||
fontWeight: 700,
|
||||
fontSize: '1.25rem',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
maxWidth: '100%',
|
||||
},
|
||||
},
|
||||
|
||||
agentFlex: {
|
||||
display: 'flex',
|
||||
marginTop: theme.spacing(2.5),
|
||||
'& p:nth-child(2)': {
|
||||
fontWeight: 500,
|
||||
fontSize: '1.25rem',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
margin: theme.spacing(0, 0, 4, 2),
|
||||
},
|
||||
},
|
||||
|
||||
agentCount: {
|
||||
fontWeight: 500,
|
||||
fontSize: '6.25rem',
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
|
||||
agentDesc: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: theme.spacing(5),
|
||||
maxWidth: '16.25rem',
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
|
||||
flexEnd: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
invitationBoxFlex: {
|
||||
borderRadius: '0.1875rem',
|
||||
border: `1px solid ${theme.palette.warning.main}`,
|
||||
padding: theme.spacing(1),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'& p': {
|
||||
fontSize: '1.25rem',
|
||||
color: theme.palette.warning.main,
|
||||
padding: theme.spacing(0, 0, 0, 1),
|
||||
},
|
||||
},
|
||||
|
||||
projectInfoProjectStats: {
|
||||
display: 'flex',
|
||||
paddingBottom: theme.spacing(1),
|
||||
'& p:first-child': {
|
||||
fontSize: '5.625rem',
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
'& p:nth-child(2)': {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
fontSize: '1.875rem',
|
||||
padding: theme.spacing(0, 0, 2.875, 2.5),
|
||||
},
|
||||
},
|
||||
|
||||
predefinedBtn: {
|
||||
marginTop: theme.spacing(2.5),
|
||||
},
|
||||
|
||||
quickActionCard: {
|
||||
marginRight: theme.spacing(3.125),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { useQuery } from '@apollo/client';
|
||||
import { Paper, Typography } from '@material-ui/core';
|
||||
import { ButtonFilled } from 'litmus-ui';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GET_CLUSTER } from '../../../../graphql';
|
||||
import { Clusters, ClusterVars } from '../../../../models/graphql/clusterData';
|
||||
import { history } from '../../../../redux/configureStore';
|
||||
import {
|
||||
getProjectID,
|
||||
getProjectRole,
|
||||
} from '../../../../utils/getSearchParams';
|
||||
import useStyles from './styles';
|
||||
|
||||
const AgentCard: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Apollo query to get the agent data
|
||||
const { data: agentList } = useQuery<Clusters, ClusterVars>(GET_CLUSTER, {
|
||||
variables: { project_id: getProjectID() },
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
return (
|
||||
<Paper id="totWorkflows" className={classes.totWorkFlow}>
|
||||
<div className={classes.agentFlex}>
|
||||
<div className={classes.agentCountDiv}>
|
||||
<Typography className={classes.agentCount}>
|
||||
{agentList?.getCluster.length}
|
||||
</Typography>
|
||||
<Typography className={classes.agentText}>
|
||||
{agentList?.getCluster.length !== 1
|
||||
? t('home.NonAdmin.agents')
|
||||
: t('home.NonAdmin.agent')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className={classes.agentDesc}>
|
||||
<Typography>{t('home.NonAdmin.chaosAgentInfo')}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonFilled
|
||||
className={classes.deployButton}
|
||||
onClick={() => {
|
||||
history.push({
|
||||
pathname: '/targets',
|
||||
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{agentList?.getCluster.length === 0
|
||||
? t('home.NonAdmin.deployFirst')
|
||||
: t('home.NonAdmin.deploy')}
|
||||
</ButtonFilled>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentCard;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
totWorkFlow: {
|
||||
width: '21.125rem',
|
||||
maxHeight: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
agentFlex: {
|
||||
display: 'flex',
|
||||
},
|
||||
agentCountDiv: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
agentCount: {
|
||||
fontWeight: 500,
|
||||
fontSize: '4.56rem',
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
agentText: {
|
||||
fontSize: '0.91rem',
|
||||
marginBottom: theme.spacing(3),
|
||||
},
|
||||
agentDesc: {
|
||||
marginLeft: theme.spacing(3.75),
|
||||
// maxWidth: '16.25rem',
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
deployButton: {
|
||||
maxWidth: '10.125rem',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 400,
|
||||
height: '2.25rem',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { Paper, Typography } from '@material-ui/core';
|
||||
import { PassFailBar } from 'litmus-ui';
|
||||
import * as React from 'react';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface PassedVsFailedProps {
|
||||
passed: number;
|
||||
}
|
||||
|
||||
const PassFailCard: React.FC<PassedVsFailedProps> = ({ passed }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Paper id="totWorkflows" className={classes.totWorkFlow}>
|
||||
<div className={classes.detailsDiv}>
|
||||
<Typography className={classes.workflowHeader}>
|
||||
Passed vs Failed
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{ height: '4rem', width: '18rem' }}>
|
||||
<PassFailBar passPercentage={passed} />
|
||||
</div>
|
||||
<Typography className={classes.wfText}>
|
||||
Statistics taken from all test results
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default PassFailCard;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
totWorkFlow: {
|
||||
width: '21.125rem',
|
||||
height: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
workflowHeader: {
|
||||
fontWeight: 500,
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
detailsDiv: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
wfText: {
|
||||
fontWeight: 400,
|
||||
fontSize: '0.75rem',
|
||||
color: theme.palette.text.hint,
|
||||
marginTop: theme.spacing(2.25),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import { useQuery } from '@apollo/client';
|
||||
import { IconButton, Paper, Typography } from '@material-ui/core';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LIST_PROJECTS } from '../../../../graphql';
|
||||
import { Member, Project, Projects } from '../../../../models/graphql/user';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as TabActions from '../../../../redux/actions/tabs';
|
||||
import { history } from '../../../../redux/configureStore';
|
||||
import { getUserId } from '../../../../utils/auth';
|
||||
import {
|
||||
getProjectID,
|
||||
getProjectRole,
|
||||
} from '../../../../utils/getSearchParams';
|
||||
import useStyles from './styles';
|
||||
|
||||
const ProjectCard: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const userID = getUserId();
|
||||
const tabs = useActions(TabActions);
|
||||
|
||||
const [projectOwnerCount, setProjectOwnerCount] = useState<number>(0);
|
||||
const [projectOtherCount, setProjectOtherCount] = useState<number>(0);
|
||||
const [invitationsCount, setInvitationCount] = useState<number>(0);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
|
||||
const { data: dataProject } = useQuery<Projects>(LIST_PROJECTS, {
|
||||
onCompleted: () => {
|
||||
if (dataProject?.listProjects) {
|
||||
setProjects(dataProject?.listProjects);
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let projectOwner = 0;
|
||||
let projectInvitation = 0;
|
||||
let projectOther = 0;
|
||||
projects.forEach((project) => {
|
||||
project.members.forEach((member: Member) => {
|
||||
if (member.user_id === userID && member.role === 'Owner') {
|
||||
projectOwner++;
|
||||
} else if (
|
||||
member.user_id === userID &&
|
||||
member.invitation === 'Pending'
|
||||
) {
|
||||
projectInvitation++;
|
||||
} else if (
|
||||
member.user_id === userID &&
|
||||
member.role !== 'Owner' &&
|
||||
member.invitation === 'Accepted'
|
||||
) {
|
||||
projectOther++;
|
||||
}
|
||||
});
|
||||
});
|
||||
setProjectOwnerCount(projectOwner);
|
||||
setInvitationCount(projectInvitation);
|
||||
setProjectOtherCount(projectOther);
|
||||
}, [projects, dataProject]);
|
||||
|
||||
return (
|
||||
<Paper id="totWorkflows" className={classes.totWorkFlow}>
|
||||
<div className={classes.flexEnd}>
|
||||
<div className={classes.invitationBoxFlex}>
|
||||
{t('settings.teamingTab.invitations')}
|
||||
<Typography>{invitationsCount}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.projectInfoProjectStats}>
|
||||
<Typography>{projectOtherCount + projectOwnerCount}</Typography>
|
||||
{projectOtherCount + projectOwnerCount > 1 ? (
|
||||
<Typography>{t('settings.teamingTab.projects')}</Typography>
|
||||
) : (
|
||||
<Typography>{t('settings.teamingTab.project')}</Typography>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.flexEnd}>
|
||||
{t('home.NonAdmin.toProjects')}
|
||||
<IconButton
|
||||
className={classes.goToIcon}
|
||||
onClick={() => {
|
||||
tabs.changeSettingsTabs(1);
|
||||
history.push({
|
||||
pathname: '/settings',
|
||||
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src="./icons/goToIcon.svg" alt="go to" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectCard;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
totWorkFlow: {
|
||||
width: '21.125rem',
|
||||
maxHeight: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
flexEnd: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
goToIcon: {
|
||||
padding: theme.spacing(0, 0, 0, 1),
|
||||
},
|
||||
invitationBoxFlex: {
|
||||
borderRadius: '0.1875rem',
|
||||
border: `1px solid ${theme.palette.warning.main}`,
|
||||
padding: theme.spacing(0.75),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'& p': {
|
||||
fontSize: '0.875rem',
|
||||
color: theme.palette.warning.main,
|
||||
padding: theme.spacing(0, 0, 0, 1),
|
||||
},
|
||||
},
|
||||
projectInfoProjectStats: {
|
||||
display: 'flex',
|
||||
'& p:first-child': {
|
||||
fontSize: '3.75rem',
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
'& p:nth-child(2)': {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
fontSize: '1.5rem',
|
||||
padding: theme.spacing(0, 0, 2.875, 2.5),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Paper } from '@material-ui/core';
|
||||
import * as React from 'react';
|
||||
import { LocalQuickActionCard } from '../../../../components/LocalQuickActionCard';
|
||||
import useStyles from './styles';
|
||||
|
||||
const QuickActionsCard: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Paper id="totWorkflows" className={classes.totWorkFlow}>
|
||||
<LocalQuickActionCard
|
||||
className={classes.quickAction}
|
||||
variant="returningHome"
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickActionsCard;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
totWorkFlow: {
|
||||
width: '21.125rem',
|
||||
maxHeight: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
workflowHeader: {
|
||||
fontWeight: 500,
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
detailsDiv: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
wfText: {
|
||||
fontWeight: 400,
|
||||
fontSize: '0.75rem',
|
||||
color: theme.palette.text.hint,
|
||||
marginTop: theme.spacing(2.25),
|
||||
},
|
||||
quickAction: {
|
||||
'& ul > div': {
|
||||
margin: theme.spacing(0.5, 0),
|
||||
'& p': {
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { IconButton, Paper, Typography } from '@material-ui/core';
|
||||
import { RadialProgressChart } from 'litmus-ui';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { history } from '../../../../redux/configureStore';
|
||||
import {
|
||||
getProjectID,
|
||||
getProjectRole,
|
||||
} from '../../../../utils/getSearchParams';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface AverageResilienceScoreProps {
|
||||
value: number;
|
||||
}
|
||||
|
||||
const ResilienceScoreCard: React.FC<AverageResilienceScoreProps> = ({
|
||||
value,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Get selected projectID from the URL
|
||||
const projectID = getProjectID();
|
||||
// Set userRole
|
||||
const projectRole = getProjectRole();
|
||||
|
||||
return (
|
||||
<Paper id="totWorkflows" className={classes.totWorkFlow}>
|
||||
<div className={classes.detailsDiv}>
|
||||
<Typography className={classes.workflowHeader}>
|
||||
Average Resilience Score
|
||||
</Typography>
|
||||
<div className={classes.flexEnd}>
|
||||
{t('home.NonAdmin.toAnalytics')}
|
||||
<IconButton
|
||||
className={classes.goToIcon}
|
||||
onClick={() => {
|
||||
history.push({
|
||||
pathname: `/analytics`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src="./icons/goToIcon.svg" alt="go to" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: '6.4rem', width: '11.125rem' }}>
|
||||
<RadialProgressChart
|
||||
className={classes.radialGraph}
|
||||
arcWidth={10}
|
||||
iconSize="1rem"
|
||||
imageSrc="./icons/radialIcon.svg"
|
||||
radialData={{
|
||||
value,
|
||||
label: 'pass',
|
||||
baseColor: '#5B44BA',
|
||||
}}
|
||||
semiCircle
|
||||
heading="Based on test results"
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResilienceScoreCard;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
totWorkFlow: {
|
||||
width: '21.125rem',
|
||||
height: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
workflowHeader: {
|
||||
fontWeight: 500,
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
detailsDiv: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
flexEnd: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
goToIcon: {
|
||||
padding: theme.spacing(0, 0, 0, 1),
|
||||
},
|
||||
radialGraph: {
|
||||
marginBottom: theme.spacing(2),
|
||||
'& p:nth-child(1)': {
|
||||
fontSize: '1.27rem',
|
||||
},
|
||||
'& p:nth-child(2)': {
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { Avatar } from '@material-ui/core';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AnalyticsLinearProgressBar from '../../../../components/ProgressBar/AnalyticsLinearProgressBar';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface TotalWorkflowProps {
|
||||
workflow: number;
|
||||
average: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
const TotalWorkflows: React.FC<TotalWorkflowProps> = ({
|
||||
workflow,
|
||||
average,
|
||||
max,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.paper}>
|
||||
<Typography variant="h6" gutterBottom className={classes.heading}>
|
||||
<strong>{t('homeView.totalWorkflows.header')}</strong>
|
||||
</Typography>
|
||||
<div className={classes.contentDiv}>
|
||||
<Avatar className={classes.avatarStyle}>
|
||||
<img
|
||||
src="./icons/weeklyWorkflows.svg"
|
||||
alt="Ellipse Icon"
|
||||
className={classes.weeklyIcon}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className={classes.mainDiv}>
|
||||
<Typography variant="subtitle2">
|
||||
<strong>{t('homeView.totalWorkflows.subtitle')}:</strong>
|
||||
</Typography>
|
||||
<div className={classes.runsFlex}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
display="block"
|
||||
gutterBottom
|
||||
className={classes.avgCount}
|
||||
>
|
||||
{average} {t('homeView.totalWorkflows.perWeek')}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
display="inline"
|
||||
className={classes.maxCount}
|
||||
>
|
||||
{max}
|
||||
</Typography>
|
||||
</div>
|
||||
<AnalyticsLinearProgressBar
|
||||
value={average}
|
||||
maxValue={max}
|
||||
isInTable={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Typography variant="h5" gutterBottom className={classes.workflow}>
|
||||
<strong>
|
||||
{workflow > 1 ? `${workflow} workflows` : `${workflow} workflow`}
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" className={classes.avgDesc}>
|
||||
{t('homeView.totalWorkflows.desc')}
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TotalWorkflows;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
paper: {
|
||||
width: '21.125rem',
|
||||
maxHeight: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
heading: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1rem',
|
||||
// marginTop: theme.spacing(3.75),
|
||||
// marginLeft: theme.spacing(-6),
|
||||
},
|
||||
contentDiv: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'space-even',
|
||||
},
|
||||
mainDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(0, 2, 0, 2),
|
||||
},
|
||||
avgCount: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
maxCount: {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
workflow: {
|
||||
fontSize: '0.875rem',
|
||||
marginTop: theme.spacing(3.125),
|
||||
},
|
||||
avgDesc: {
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
avatarStyle: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
width: '2.5rem',
|
||||
height: '2.5rem',
|
||||
marginTop: theme.spacing(0.625),
|
||||
},
|
||||
weeklyIcon: {
|
||||
width: '0.9375rem',
|
||||
height: '0.9375rem',
|
||||
},
|
||||
runsFlex: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Paper, Typography } from '@material-ui/core';
|
||||
import * as React from 'react';
|
||||
import useStyles from './styles';
|
||||
|
||||
const WorkflowCard: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Paper id="totWorkflows" className={classes.totWorkFlow}>
|
||||
<Typography className={classes.workflowHeader}>
|
||||
Number of total Workflows
|
||||
</Typography>
|
||||
<div className={classes.detailsDiv}>
|
||||
<img src="./icons/wfIcon.svg" alt="workflow" />
|
||||
<div className={classes.wfDiv}>
|
||||
<Typography>Avg amount of workflows</Typography>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<Typography className={classes.totWorkflow}>{194} workflows</Typography>
|
||||
<Typography className={classes.wfText}>
|
||||
AVG is calculated from the max of workflows
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowCard;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
totWorkFlow: {
|
||||
width: '21.125rem',
|
||||
maxHeight: '12rem',
|
||||
padding: theme.spacing(3.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
workflowHeader: {
|
||||
fontWeight: 500,
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
detailsDiv: {
|
||||
marginTop: theme.spacing(2),
|
||||
display: 'flex',
|
||||
},
|
||||
wfDiv: {
|
||||
display: 'flex',
|
||||
marginLeft: theme.spacing(2),
|
||||
flexDirection: 'column',
|
||||
},
|
||||
totWorkflow: {
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
margin: theme.spacing(3.125, 0, 0.875, 0),
|
||||
},
|
||||
wfText: {
|
||||
fontWeight: 400,
|
||||
fontSize: '0.75rem',
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
import { useQuery } from '@apollo/client';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import * as _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Loader from '../../../components/Loader';
|
||||
import { WORKFLOW_LIST_DETAILS } from '../../../graphql';
|
||||
import {
|
||||
ExecutionData,
|
||||
WeightageMap,
|
||||
Workflow,
|
||||
WorkflowList,
|
||||
WorkflowListDataVars,
|
||||
} from '../../../models/graphql/workflowListData';
|
||||
import { getProjectID } from '../../../utils/getSearchParams';
|
||||
import { sortNumDesc } from '../../../utils/sort';
|
||||
import AgentCard from './AgentCard';
|
||||
import PassFailCard from './PassFailCard';
|
||||
import ProjectCard from './ProjectsCard';
|
||||
import QuickActionCard from './QuickActionCard';
|
||||
import ResilienceScoreCard from './ResilienceScoreCard';
|
||||
import useStyles from './styles';
|
||||
import TotalWorkflows from './TotalWorkflows';
|
||||
|
||||
interface Analyticsdata {
|
||||
avgWorkflows: number;
|
||||
maxWorkflows: number;
|
||||
passPercentage: number;
|
||||
failPercentage: number;
|
||||
avgResilienceScore: number;
|
||||
}
|
||||
|
||||
interface DatedResilienceScore {
|
||||
date: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface WorkflowRunData {
|
||||
tests_passed: number;
|
||||
tests_failed: number;
|
||||
resilience_score: number;
|
||||
}
|
||||
|
||||
interface ReturningHomeProps {
|
||||
callbackToSetDataPresent: (dataPresent: boolean) => void;
|
||||
currentStatus: boolean;
|
||||
}
|
||||
|
||||
const ReturningHome: React.FC<ReturningHomeProps> = ({
|
||||
callbackToSetDataPresent,
|
||||
currentStatus,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const [workflowDataPresent, setWorkflowDataPresent] = useState<boolean>(
|
||||
currentStatus
|
||||
);
|
||||
const [
|
||||
totalValidWorkflowRunsCount,
|
||||
setTotalValidWorkflowRunsCount,
|
||||
] = React.useState<number>(0);
|
||||
const [isChecking, setIsChecking] = useState<boolean>(true);
|
||||
const [analyticsData, setAnalyticsData] = useState<Analyticsdata>({
|
||||
avgWorkflows: 0,
|
||||
maxWorkflows: 0,
|
||||
passPercentage: 0,
|
||||
failPercentage: 0,
|
||||
avgResilienceScore: 0,
|
||||
});
|
||||
const projectID = getProjectID();
|
||||
|
||||
// Apollo query to get the scheduled workflow data
|
||||
const { data, loading, error } = useQuery<WorkflowList, WorkflowListDataVars>(
|
||||
WORKFLOW_LIST_DETAILS,
|
||||
{
|
||||
variables: { projectID, workflowIDs: [] },
|
||||
}
|
||||
);
|
||||
|
||||
const loadWorkflowAnalyticssData = () => {
|
||||
let totalValidRuns: number = 0;
|
||||
const timeSeriesArrayForAveragePerWeek: DatedResilienceScore[] = [];
|
||||
const totalValidWorkflowRuns: WorkflowRunData = {
|
||||
tests_passed: 0,
|
||||
tests_failed: 0,
|
||||
resilience_score: 0,
|
||||
};
|
||||
const workflowRunsPerWeek: number[] = [];
|
||||
if (data && data.ListWorkflow && data.ListWorkflow.length) {
|
||||
const sortedWorkflowsData = data.ListWorkflow.slice().sort(
|
||||
(a: Workflow, b: Workflow) => {
|
||||
const x = parseInt(a.created_at, 10);
|
||||
const y = parseInt(b.created_at, 10);
|
||||
sortNumDesc(y, x);
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
if (sortedWorkflowsData && sortedWorkflowsData.length) {
|
||||
sortedWorkflowsData.forEach((workflowData: Workflow) => {
|
||||
const runs = workflowData ? workflowData.workflow_runs : [];
|
||||
const workflowTimeSeriesData: DatedResilienceScore[] = [];
|
||||
if (data?.ListWorkflow.length === 1 && runs === null) {
|
||||
setWorkflowDataPresent(false);
|
||||
}
|
||||
try {
|
||||
runs.forEach((data) => {
|
||||
try {
|
||||
const executionData: ExecutionData = JSON.parse(
|
||||
data.execution_data
|
||||
);
|
||||
|
||||
const { nodes } = executionData;
|
||||
const workflowsRunResults: number[] = [];
|
||||
let totalExperimentsPassed: number = 0;
|
||||
let weightsSum: number = 0;
|
||||
let isValid: boolean = false;
|
||||
for (const key of Object.keys(nodes)) {
|
||||
const node = nodes[key];
|
||||
if (node.chaosData) {
|
||||
const { chaosData } = node;
|
||||
|
||||
if (
|
||||
chaosData.experimentVerdict === 'Pass' ||
|
||||
chaosData.experimentVerdict === 'Fail'
|
||||
) {
|
||||
const weightageMap: WeightageMap[] = workflowData
|
||||
? workflowData.weightages
|
||||
: [];
|
||||
// eslint-disable-next-line
|
||||
weightageMap.forEach((weightage) => {
|
||||
if (
|
||||
weightage.experiment_name === chaosData.experimentName
|
||||
) {
|
||||
if (chaosData.experimentVerdict === 'Pass') {
|
||||
workflowsRunResults.push(
|
||||
(weightage.weightage *
|
||||
parseInt(
|
||||
chaosData.probeSuccessPercentage,
|
||||
10
|
||||
)) /
|
||||
100
|
||||
);
|
||||
totalExperimentsPassed += 1;
|
||||
}
|
||||
if (chaosData.experimentVerdict === 'Fail') {
|
||||
workflowsRunResults.push(0);
|
||||
}
|
||||
if (
|
||||
chaosData.experimentVerdict === 'Pass' ||
|
||||
chaosData.experimentVerdict === 'Fail'
|
||||
) {
|
||||
weightsSum += weightage.weightage;
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (executionData.event_type === 'UPDATE' && isValid) {
|
||||
totalValidRuns += 1;
|
||||
|
||||
totalValidWorkflowRuns.tests_passed += totalExperimentsPassed;
|
||||
totalValidWorkflowRuns.tests_failed +=
|
||||
workflowsRunResults.length - totalExperimentsPassed;
|
||||
totalValidWorkflowRuns.resilience_score += workflowsRunResults.length
|
||||
? (workflowsRunResults.reduce((a, b) => a + b, 0) /
|
||||
weightsSum) *
|
||||
100
|
||||
: 0;
|
||||
workflowTimeSeriesData.push({
|
||||
date: data.last_updated,
|
||||
value: workflowsRunResults.length
|
||||
? (workflowsRunResults.reduce((a, b) => a + b, 0) /
|
||||
weightsSum) *
|
||||
100
|
||||
: 0,
|
||||
});
|
||||
timeSeriesArrayForAveragePerWeek.push({
|
||||
date: data.last_updated,
|
||||
value: workflowsRunResults.length
|
||||
? (workflowsRunResults.reduce((a, b) => a + b, 0) /
|
||||
weightsSum) *
|
||||
100
|
||||
: 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// checks for presence of valid workflow data
|
||||
if (totalValidRuns === 0) {
|
||||
setWorkflowDataPresent(false);
|
||||
}
|
||||
|
||||
setTotalValidWorkflowRunsCount(totalValidRuns);
|
||||
const testsPassedPercentage: number =
|
||||
(totalValidWorkflowRuns.tests_passed /
|
||||
(totalValidWorkflowRuns.tests_passed +
|
||||
totalValidWorkflowRuns.tests_failed)) *
|
||||
100;
|
||||
const weeklyGroupedResults = _.groupBy(
|
||||
timeSeriesArrayForAveragePerWeek,
|
||||
(data) => moment.unix(parseInt(data.date, 10)).startOf('isoWeek')
|
||||
);
|
||||
Object.keys(weeklyGroupedResults).forEach((week) => {
|
||||
workflowRunsPerWeek.push(weeklyGroupedResults[week].length);
|
||||
});
|
||||
setAnalyticsData({
|
||||
avgWorkflows:
|
||||
workflowRunsPerWeek.reduce((a, b) => a + b, 0) /
|
||||
workflowRunsPerWeek.length,
|
||||
maxWorkflows: Math.max(...workflowRunsPerWeek),
|
||||
passPercentage:
|
||||
totalValidRuns > 0 && testsPassedPercentage >= 0
|
||||
? testsPassedPercentage
|
||||
: 0,
|
||||
failPercentage:
|
||||
totalValidRuns > 0 && testsPassedPercentage >= 0
|
||||
? 100 - testsPassedPercentage
|
||||
: 0,
|
||||
avgResilienceScore:
|
||||
totalValidRuns > 0
|
||||
? totalValidWorkflowRuns.resilience_score / totalValidRuns
|
||||
: 0,
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!loading && !error && data && data.ListWorkflow.slice(0).length >= 1) {
|
||||
setWorkflowDataPresent(true);
|
||||
loadWorkflowAnalyticssData();
|
||||
} else if (loading === false) {
|
||||
setWorkflowDataPresent(false);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setIsChecking(false);
|
||||
}, 500);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
callbackToSetDataPresent(workflowDataPresent);
|
||||
}, [workflowDataPresent]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Row 1 */}
|
||||
{isChecking ? (
|
||||
<div className={classes.loader}>
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
<div className={classes.firstRow}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container item xs={12} spacing={2}>
|
||||
<Grid item xs={4}>
|
||||
<TotalWorkflows
|
||||
workflow={totalValidWorkflowRunsCount}
|
||||
average={parseFloat(analyticsData.avgWorkflows.toFixed(0))}
|
||||
max={analyticsData.maxWorkflows}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ResilienceScoreCard
|
||||
value={parseFloat(
|
||||
analyticsData.avgResilienceScore.toFixed(2)
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ProjectCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={3}>
|
||||
<Grid item xs={4}>
|
||||
<AgentCard />
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<PassFailCard
|
||||
passed={parseFloat(analyticsData.passPercentage.toFixed(2))}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<QuickActionCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReturningHome;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
userName: {
|
||||
fontSize: '2.5rem',
|
||||
margin: theme.spacing(1.75, 0, 2.75, 0),
|
||||
},
|
||||
firstRow: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
flexGrow: 1,
|
||||
},
|
||||
loader: {
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
Loading…
Reference in New Issue