From 5ad038a64902ed3d12f369667f4e67b9dadfcea7 Mon Sep 17 00:00:00 2001
From: Imre Toth <imre.toth@ess.eu>
Date: Mon, 4 Oct 2021 15:45:28 +0200
Subject: [PATCH] ICSHWI-7866: Implementing UI changes

---
 src/api/SwaggerApi.js                         |  10 ++
 src/components/IOC/IOCCreateDialog.js         |  31 +++-
 src/components/IOC/IOCDeployDialog.js         |  12 +-
 src/components/IOC/IOCManage.js               |   9 +-
 src/components/common/Helper.js               |   2 +-
 .../navigation/GlobalAppBar/GlobalAppBar.js   | 153 ++++++++++--------
 .../statistics/DeploymentLineChart.js         |   3 +-
 src/views/statistics/StatisticsView.js        |   4 +-
 8 files changed, 135 insertions(+), 89 deletions(-)

diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js
index 65302f0d..a941c55a 100644
--- a/src/api/SwaggerApi.js
+++ b/src/api/SwaggerApi.js
@@ -504,4 +504,14 @@ export function useDeployedIOCStatistics() {
   const method = callAndUnpack(api.apis.Statistics.iocDeploymentHistory, unpackDeployedIOCStat)
 
   return useAsync({ fcn: method, call: false });
+}
+
+export function unpackAllowedGitProjects(projectList) {
+  return projectList.toString().split(",");
+}
+
+export function useAllowedGitProjects() {
+  const api = useContext(apiContext);
+  const method = callAndUnpack((repoUrl) => api.apis.Git.listProjects(), unpackAllowedGitProjects)
+  return useAsync({ fcn: method, call: false, init: [] });
 }
\ No newline at end of file
diff --git a/src/components/IOC/IOCCreateDialog.js b/src/components/IOC/IOCCreateDialog.js
index e7e41f4f..85ae5c80 100644
--- a/src/components/IOC/IOCCreateDialog.js
+++ b/src/components/IOC/IOCCreateDialog.js
@@ -1,7 +1,7 @@
 import React, { useEffect, useState } from "react";
 import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from "@material-ui/core";
 import { Autocomplete } from "@material-ui/lab";
-import { useNamingNames } from "../../api/SwaggerApi";
+import { useNamingNames, useAllowedGitProjects } from "../../api/SwaggerApi";
 import { useTypingTimer } from "../common/SearchBoxFilter/TypingTimer";
 
 const useStyles = makeStyles((theme) => ({
@@ -15,6 +15,8 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) {
   const [names, getNames] = useNamingNames();
   const [name, setName] = useState();
   const [nameQuery, onNameKeyUp] = useTypingTimer({interval: 500});
+  const [allowedGitProjects, getAllowedGitProjects] = useAllowedGitProjects();
+  const [gitUrl, setGitUrl] = useState();
 
   const handleClose = () => {
     setOpen(false);
@@ -24,18 +26,20 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) {
 
   useEffect(() => getNames(nameQuery), [nameQuery, getNames]);
 
+  useEffect(() => {
+    getAllowedGitProjects();
+  }, [getAllowedGitProjects]);
+
   const onSubmit = (event) => {
     event.preventDefault();
-    const { git: gitText } = event.currentTarget.elements;
-    const git = gitText.value;
 
     submitCallback({
-        sourceUrl: git, 
+        sourceUrl: gitUrl,
         externalNameId: name ? parseInt(name.id) : undefined });
   };
 
   return (
-    <Dialog open={open} onClose={handleClose}>
+    <Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
       <form onSubmit={onSubmit}>
         <DialogTitle id="form-dialog-title" data-testid="create-dialog-title">Create new IOC</DialogTitle>
         <DialogContent>
@@ -50,9 +54,20 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) {
             onInputChange={(event, value, reason) => {event && onNameKeyUp(event.nativeEvent)}}
           />
 
-          <TextField autoComplete="off" className={classes.textField} id="description" label="Description"
-            variant="outlined" fullWidth disabled value={name? name.description : ""} />
-          <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="outlined" fullWidth required />
+          <TextField autoComplete="off" className={classes.textField} id="description" label="--- IOC Description from CCDB ---"
+            variant="standard" fullWidth disabled value={name? name.description : ""} />
+          
+          <Autocomplete
+            className={classes.textField}
+            autoHighlight
+            id="version"
+            options={allowedGitProjects}
+            defaultValue = { "" }
+            getOptionLabel={(option) => {return option}}
+            onChange={(event, value, reason) => setGitUrl(value)}
+            renderInput={(params) => <TextField {...params} label="Git repository" variant="outlined" fullWidth required />}
+          />
+
         </DialogContent>
         <DialogActions>
           <Button onClick={handleClose} color="primary">Cancel</Button>
diff --git a/src/components/IOC/IOCDeployDialog.js b/src/components/IOC/IOCDeployDialog.js
index bd31d6b9..926d6341 100644
--- a/src/components/IOC/IOCDeployDialog.js
+++ b/src/components/IOC/IOCDeployDialog.js
@@ -17,14 +17,13 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
   const [query, onHostKeyUp] = useTypingTimer({interval: 500});
   const [tagOrCommitId, getTagOrCommitId] = useTagsAndCommitIds([]);
   const [gitRepo, setGitRepo] = useState(init.git || "");
+  const [gitVersion, setGitVersion] = useState(init.version || "");
 
   const handleClose = () => {
     setOpen(false);
   };
 
   console.log({...init});
-  console.log(hosts);
-  console.log(host);
 
   useEffect(() => getHosts(`fqdn:"${query}"`), [query, getHosts, getTagOrCommitId]);
 
@@ -49,9 +48,9 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
       <form onSubmit={onSubmit}>
         <DialogTitle id="form-dialog-title">{ hasActiveDeployment ? "Deploy new revision" : "Deploy" }</DialogTitle>
         <DialogContent>
-          <TextField autoComplete="off" className={classes.textField} id="comment" label="Deployment comment" variant="outlined" fullWidth required/>
-          <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="outlined" defaultValue={init.git || ""} fullWidth
-            onChange={(event) => setGitRepo(event.target.value)} required/>
+          <TextField autoComplete="off" className={classes.textField} id="comment" label="Deployment comment" variant="outlined" fullWidth/>
+          <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="standard" defaultValue={init.git || ""} fullWidth
+            onChange={(event) => setGitRepo(event.target.value)} disabled required/>
           
           <Autocomplete
             className={classes.textField}
@@ -61,6 +60,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
             defaultValue = { init.version || "" }
             onFocus={(event) => {getTagOrCommitId(gitRepo);}}
             getOptionLabel={(option) => {return option}}
+            onChange={(event, value, reason) => setGitVersion(value)}
             disabled={(!gitRepo) || (gitRepo.trim() === "")}
             renderInput={(params) => <TextField {...params} label="Git reference" variant="outlined" fullWidth required />}
           />
@@ -87,7 +87,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
         </DialogContent>
         <DialogActions>
           <Button onClick={handleClose} color="primary">Cancel</Button>
-          <Button color="primary" variant="contained" type="submit">Deploy</Button>
+          <Button color="primary" variant="contained" type="submit" disabled={init?.version === gitVersion}>Deploy</Button>
         </DialogActions>
       </form>
     </Dialog>
diff --git a/src/components/IOC/IOCManage.js b/src/components/IOC/IOCManage.js
index 6df6c951..a5e9946e 100644
--- a/src/components/IOC/IOCManage.js
+++ b/src/components/IOC/IOCManage.js
@@ -8,6 +8,7 @@ import { SimpleModal } from "../../components/common/SimpleModal/SimpleModal";
 import { useUpdateAndDeployIoc, useCreateUndeployment } from "../../api/SwaggerApi";
 import GitRefLink from "./GitRefLink";
 import IOCAlerts from "./IOCAlerts";
+import { Link } from 'react-router-dom';
 
 export function IOCManage({ ioc, getIOC }) {
   const [deployDialogOpen, setDeployDialogOpen] = useState(false);
@@ -38,7 +39,13 @@ export function IOCManage({ ioc, getIOC }) {
         // active, 
       } = ioc;
       const { sourceUrl: git } = ioc.latestVersion;
-      const host = ioc.activeDeployment?.host.host;
+      let host = ioc.activeDeployment?.host.host;
+
+      //create link if host is set
+      if (ioc.activeDeployment) {
+        host = <Typography><Link to={`/hosts/${ioc.activeDeployment.host.csEntryId}`}>{ioc.activeDeployment.host.host}</Link></Typography>
+      }
+
       return {
         "CCDB name": <Typography><MuiLink href={`${window.CCDB_ADDRESS}?name=${ioc.latestVersion.namingName}`} 
                         target="_blank" rel="noreferrer">{ioc.latestVersion.namingName}</MuiLink></Typography>,
diff --git a/src/components/common/Helper.js b/src/components/common/Helper.js
index b07df13f..71bb82fb 100644
--- a/src/components/common/Helper.js
+++ b/src/components/common/Helper.js
@@ -118,5 +118,5 @@ export function iocHasNameDeviation(ioc) {
     return false;
   }
 
-  return ioc?.latestVersion?.namingName !== ioc?.activeDeployment?.namingName;
+  return ioc?.latestVersion?.namingName !== ioc?.activeDeployment?.iocName;
 }
diff --git a/src/components/navigation/GlobalAppBar/GlobalAppBar.js b/src/components/navigation/GlobalAppBar/GlobalAppBar.js
index 03d02448..50565b8c 100644
--- a/src/components/navigation/GlobalAppBar/GlobalAppBar.js
+++ b/src/components/navigation/GlobalAppBar/GlobalAppBar.js
@@ -55,10 +55,10 @@ const useStyles = makeStyles((theme) => ({
     marginLeft: theme.spacing(1),
   },
   typography: {
-      padding: theme.spacing(2),
+    padding: theme.spacing(2),
   },
   notificationButton: {
-      marginRight: theme.spacing(2),
+    marginRight: theme.spacing(2),
   },
   content: {
     marginTop: theme.spacing(1),
@@ -75,20 +75,20 @@ const StyledMenu = withStyles({
   },
 })((props) => (
   <>
-  <div style={{ ...props.theme.mixins.toolbar }} />
-  <Menu
-    elevation={0}
-    getContentAnchorEl={null}
-    anchorOrigin={{
-      vertical: 'bottom',
-      horizontal: 'center',
-    }}
-    transformOrigin={{
-      vertical: 'top',
-      horizontal: 'center',
-    }}
-    {...props}
-  />
+    <div style={{ ...props.theme.mixins.toolbar }} />
+    <Menu
+      elevation={0}
+      getContentAnchorEl={null}
+      anchorOrigin={{
+        vertical: 'bottom',
+        horizontal: 'center',
+      }}
+      transformOrigin={{
+        vertical: 'top',
+        horizontal: 'center',
+      }}
+      {...props}
+    />
   </>
 ));
 
@@ -141,14 +141,14 @@ function MenuListItem({ selectedUrl, handleListItemClick, url, icon, text, toolt
   console.log(currentUrl);
   return (
     <>
-    { url.includes("help") ? <Divider className={classes.helpDivider}/> : <></>}
-    <Tooltip title={tooltip ? text : ""} placement="right" arrow>
-      <ListItem button component={Link} to={url} selected={currentUrl.endsWith(url)}
-        onClick={(event) => handleListItemClick(event, url)}>
-        <ListItemIcon className={classes.menuIcon}>{icon}</ListItemIcon>
-        <ListItemText primary={text} primaryTypographyProps={{variant: "h5"}}/>
-      </ListItem>
-    </Tooltip>
+      {url.includes("help") ? <Divider className={classes.helpDivider} /> : <></>}
+      <Tooltip title={tooltip ? text : ""} placement="right" arrow>
+        <ListItem button component={Link} to={url} selected={currentUrl.endsWith(url)}
+          onClick={(event) => handleListItemClick(event, url)}>
+          <ListItemIcon className={classes.menuIcon}>{icon}</ListItemIcon>
+          <ListItemText primary={text} primaryTypographyProps={{ variant: "h5" }} />
+        </ListItem>
+      </Tooltip>
     </>
   );
 }
@@ -157,7 +157,7 @@ function MenuListItems({ menuItems, selectedUrl, handleListItemClick, drawerOpen
   return (
     <>
       {menuItems.map(({ text, url, icon }, index) => (
-        <MenuListItem key={text} tooltip={!drawerOpen} selectedUrl={selectedUrl} handleListItemClick={handleListItemClick} url={url} icon={icon} text={text}/>
+        <MenuListItem key={text} tooltip={!drawerOpen} selectedUrl={selectedUrl} handleListItemClick={handleListItemClick} url={url} icon={icon} text={text} />
       ))}
     </>
   );
@@ -191,20 +191,20 @@ export function ProfileMenu() {
   return (
     <>
       <Avatar src={user.avatar} variant="circular" onClick={handleClick} aria-controls="profile-menu"
-                  aria-haspopup="true"/>
+        aria-haspopup="true" />
       <StyledMenu
         id="profile-menu"
-            anchorEl={anchorEl}
-            keepMounted
-            open={Boolean(anchorEl)}
-            onClose={handleClose}
-            theme={theme}
-          >
-        <StyledMenuItem onClick={() => {handleClose(); logout();}}>
+        anchorEl={anchorEl}
+        keepMounted
+        open={Boolean(anchorEl)}
+        onClose={handleClose}
+        theme={theme}
+      >
+        <StyledMenuItem onClick={() => { handleClose(); logout(); }}>
           <ListItemIcon>
             <LockOpen fontSize="small" />
           </ListItemIcon>
-          <ListItemText primary="Logout" primaryTypographyProps={{variant: "h5"}} />
+          <ListItemText primary="Logout" primaryTypographyProps={{ variant: "h5" }} />
         </StyledMenuItem>
       </StyledMenu>
     </>
@@ -214,12 +214,12 @@ export function ProfileMenu() {
 export function NotificationMenu() {
   const [anchorEl, setAnchorEl] = useState(null);
   const classes = useStyles();
-  const {deploymentNotifications, clearAll} = useContext(notificationContext);
+  const { deploymentNotifications, clearAll } = useContext(notificationContext);
 
   const handleClick = (event) => {
-    if (deploymentNotifications().length > 0) {
-      setAnchorEl(event.currentTarget);
-    }
+    // if (deploymentNotifications().length > 0) {
+    setAnchorEl(event.currentTarget);
+    // }
   };
 
   const handleClose = () => {
@@ -229,19 +229,53 @@ export function NotificationMenu() {
   function statusFromNotification(text) {
     const lowerText = text.toLowerCase();
 
-    if(lowerText.includes("pending")) {
+    if (lowerText.includes("pending")) {
       return "pending";
-    } else if(lowerText.includes("queued")) {
+    } else if (lowerText.includes("queued")) {
       return "queued";
-    } else if(lowerText.includes("running")) {
+    } else if (lowerText.includes("running")) {
       return "running";
-    } else if(lowerText.includes("successful")) {
+    } else if (lowerText.includes("successful")) {
       return "successful";
-    } else if(lowerText.includes("failed")) {
+    } else if (lowerText.includes("failed")) {
       return "failed";
     }
   }
 
+  function haveNotification() {
+    return (
+      <>
+        <Grid item xs={12}>
+          <List className={classes.notificationList}>
+            {deploymentNotifications().map((notification) => {
+              return (
+                <ListItem key={notification}>
+                  <ListItemIcon>
+                    <DeploymentStatusIcon status={statusFromNotification(notification)} />
+                  </ListItemIcon>
+                  <Typography>{notification}</Typography>
+                </ListItem>
+              );
+            })}
+          </List>
+        </Grid>
+        <Grid item xs={2}>
+          <Box display="flex" justifyContent="flex-end" mx={1} mb={1}>
+            <IconButton color="inherit" onClick={() => { clearAll(); handleClose(); }}>
+              <Tooltip title="Clear all notifications" arrow>
+                <DeleteIcon />
+              </Tooltip>
+            </IconButton>
+          </Box>
+        </Grid>
+      </>
+    )
+  }
+
+  function noNotification() {
+    return <Typography inset="true" style={{ margin: '4px' }}>You have no notifications!</Typography>;
+  }
+
   return (
     <>
       <IconButton color="inherit" onClick={handleClick} className={classes.notificationButton}>
@@ -263,29 +297,8 @@ export function NotificationMenu() {
         }}
       >
         <Grid container spacing={0} justify="flex-end">
-          <Grid item xs={12}>
-            <List className={classes.notificationList}>
-            {deploymentNotifications().map((notification) => {
-              return (
-              <ListItem key={notification}>
-                  <ListItemIcon>
-                    <DeploymentStatusIcon status={statusFromNotification(notification)} />
-                  </ListItemIcon>
-                  <Typography>{notification}</Typography>
-                </ListItem>
-              );
-            })}
-            </List>
-          </Grid>
-          <Grid item xs={2}>
-            <Box display="flex" justifyContent="flex-end" mx={1} mb={1}>
-              <IconButton color="inherit" onClick={() => {clearAll(); handleClose();}}>
-                <Tooltip title="Clear all notifications" arrow>
-                  <DeleteIcon />
-                </Tooltip>
-              </IconButton>
-            </Box>
-          </Grid>
+          {(deploymentNotifications().length > 0) ? haveNotification() : noNotification()}
+
         </Grid>
       </Popover>
     </>
@@ -315,7 +328,7 @@ export function GlobalAppBar({ children }) {
 
   const makeLink = (text, url, icon) => ({ text, url, icon });
   const menuItems = [
-    makeLink("Explore IOCs", "/iocs", <CCCEControlSymbol/>),
+    makeLink("Explore IOCs", "/iocs", <CCCEControlSymbol />),
     makeLink("Explore IOC hosts", "/hosts", <Storage />),
     makeLink("Deployment log", "/deployments", <Assignment />),
     makeLink("Statistics", "/statistics", <TrendingUp />),
@@ -326,13 +339,13 @@ export function GlobalAppBar({ children }) {
     <>
       <ButtonAppBar homeUrl="/home" homeClick={handleLeftMenuItemClick} title={title} button={button} profileButton={profile} notificationsButton={notifications} />
       <MenuDrawer open={drawerOpen} toggleDrawer={toggleDrawer}>
-        <MenuListItems menuItems={menuItems} selectedUrl={selectedUrl} 
-                       handleListItemClick={handleLeftMenuItemClick} drawerOpen={drawerOpen}/>
+        <MenuListItems menuItems={menuItems} selectedUrl={selectedUrl}
+          handleListItemClick={handleLeftMenuItemClick} drawerOpen={drawerOpen} />
       </MenuDrawer>
       <GlobalAppBarContext.Provider value={value}>
         <div style={{ ...theme.mixins.toolbar }} />
         <div className={classes.content}>
-        {children}
+          {children}
         </div>
       </GlobalAppBarContext.Provider>
     </>
diff --git a/src/components/statistics/DeploymentLineChart.js b/src/components/statistics/DeploymentLineChart.js
index 3b8e19cc..4926d264 100644
--- a/src/components/statistics/DeploymentLineChart.js
+++ b/src/components/statistics/DeploymentLineChart.js
@@ -83,7 +83,8 @@ export default function DeploymentLineChart({title, chartLabel, hook}) {
             },
             y: {
                 ticks: {
-                    color: '#495057'
+                    color: '#495057',
+                    stepSize: 1
                 },
                 grid: {
                     color: '#ebedef'
diff --git a/src/views/statistics/StatisticsView.js b/src/views/statistics/StatisticsView.js
index 035e7485..143ff906 100644
--- a/src/views/statistics/StatisticsView.js
+++ b/src/views/statistics/StatisticsView.js
@@ -54,7 +54,7 @@ export function StatisticsView() {
             <Box m={2} />
 
             <Paper>
-                <DeploymentLineChart title="Number of IOCs deployed by the Deployment tool" chartLabel="Deployed IOCs in time" hook={useDeployedIOCStatistics}/>
+                <DeploymentLineChart title="Number of successful deployments by the Deployment tool" chartLabel="Deployed IOCs in time" hook={useDeployedIOCStatistics}/>
             </Paper>
             
             <Box m={2} />
@@ -66,7 +66,7 @@ export function StatisticsView() {
             <Box m={2} />
 
             <Paper>
-                <DeploymentLineChart title="IOC deployment history graph (Propmetheus)" chartLabel="Active IOCs in time" hook={useActiveIOCHistory}/>
+                <DeploymentLineChart title="IOC deployment history graph (Prometheus)" chartLabel="Active IOCs in time" hook={useActiveIOCHistory}/>
             </Paper>
 
         </RootContainer>
-- 
GitLab