chore(litmus-portal): Added sync and terminate workflow feature and minor ux fixes (#2878)
* Added sync and terminate workflow and checks for upload manifest Signed-off-by: Amit Kumar Das <amit@chaosnative.com> * Fixed the delete schedule/workflow mutation Signed-off-by: Amit Kumar Das <amit@chaosnative.com> * Added minor UX fixes Signed-off-by: Amit Kumar Das <amit@chaosnative.com> * Minor styles fix Signed-off-by: Amit Kumar Das <amit@chaosnative.com>
This commit is contained in:
parent
c3720aee5c
commit
0a7e623356
|
@ -0,0 +1,4 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="20" height="20" rx="10" fill="#CA2C2C"/>
|
||||
<path d="M10.6321 7.92888C10.6321 8.71288 10.6041 9.42687 10.5481 10.0709C10.4921 10.7055 10.4221 11.3402 10.3381 11.9749H9.52614C9.44214 11.3402 9.37214 10.7055 9.31614 10.0709C9.26014 9.42687 9.23214 8.71288 9.23214 7.92888V5.29688H10.6321V7.92888ZM10.8701 14.2429C10.8701 14.4949 10.7861 14.7142 10.6181 14.9009C10.4501 15.0875 10.2215 15.1809 9.93214 15.1809C9.64281 15.1809 9.41414 15.0875 9.24614 14.9009C9.07814 14.7142 8.99414 14.4949 8.99414 14.2429C8.99414 13.9909 9.07814 13.7715 9.24614 13.5849C9.41414 13.3982 9.64281 13.3049 9.93214 13.3049C10.2215 13.3049 10.4501 13.3982 10.6181 13.5849C10.7861 13.7715 10.8701 13.9909 10.8701 14.2429Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 825 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.532 1.15413C6.54357 1.14875 4.63138 1.91887 3.20171 3.30085V1.15409C3.20171 0.829517 2.9386 0.566406 2.61403 0.566406C2.28945 0.566406 2.02638 0.829517 2.02638 1.15409V4.68014C2.02631 4.93319 2.18825 5.15788 2.42836 5.23783L5.95441 6.41317C6.26273 6.51575 6.59584 6.34892 6.69839 6.04059C6.80097 5.73227 6.63413 5.39916 6.32581 5.29661L3.72243 4.43039C6.12733 1.81151 10.2 1.63803 12.8188 4.04294C15.4377 6.44785 15.6112 10.5205 13.2063 13.1393C10.8014 15.7582 6.72876 15.9317 4.10988 13.5268C2.50429 12.0524 1.75383 9.86567 2.11573 7.71605C2.17265 7.3965 1.95978 7.09135 1.64027 7.0344C1.32075 6.97744 1.01557 7.19035 0.958612 7.50986C0.957992 7.51334 0.957407 7.51685 0.956856 7.52033C0.886507 7.94118 0.851109 8.36713 0.851074 8.7938C0.855861 13.0111 4.27347 16.4288 8.49078 16.4336C12.7101 16.445 16.1398 13.0338 16.1512 8.8145C16.1625 4.59519 12.7513 1.16552 8.532 1.15413Z" fill="#5B44BA"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1012 B |
|
@ -0,0 +1,6 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0104 6.23218H14.0104V3.23218" stroke="#5B44BA" stroke-width="0.78" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.1109 4.11091C4.62162 3.60019 5.22794 3.19506 5.89523 2.91866C6.56252 2.64226 7.27772 2.5 7.99999 2.5C8.72226 2.5 9.43746 2.64226 10.1047 2.91866C10.772 3.19506 11.3784 3.60019 11.8891 4.11091L14.0104 6.23223" stroke="#5B44BA" stroke-width="0.78" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.98956 9.76782H1.98956V12.7678" stroke="#5B44BA" stroke-width="0.78" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.8891 11.8891C11.3783 12.3999 10.772 12.805 10.1047 13.0814C9.43744 13.3578 8.72224 13.5001 7.99997 13.5001C7.2777 13.5001 6.5625 13.3578 5.89521 13.0814C5.22792 12.805 4.6216 12.3999 4.11088 11.8891L1.98956 9.76782" stroke="#5B44BA" stroke-width="0.78" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 983 B |
|
@ -0,0 +1,11 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2H8C5.51472 2 3.5 4.01472 3.5 6.5V9.5C3.5 11.9853 5.51472 14 8 14H8C10.4853 14 12.5 11.9853 12.5 9.5V6.5C12.5 4.01472 10.4853 2 8 2Z" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.5 8.5H14" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 8.5H3.5" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 10.5H3.61153" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 6.5H14" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 8.5V14" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.3885 10.5H14" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.49561 1.75L4.98094 3.16303" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.4956 1.75L11.0146 3.15896" stroke="#5B44BA" stroke-width="0.781818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7966 11.7478L9.29857 2.2478C9.16667 2.02029 8.97731 1.83143 8.74944 1.70015C8.52157 1.56886 8.26321 1.49976 8.00023 1.49976C7.73725 1.49976 7.47889 1.56886 7.25102 1.70015C7.02316 1.83143 6.83379 2.02029 6.70189 2.2478V2.24829L1.20384 11.7478C1.07184 11.9758 1.00221 12.2345 1.00195 12.4979C1.0017 12.7614 1.07083 13.0202 1.20239 13.2484C1.33395 13.4767 1.52331 13.6662 1.75139 13.798C1.97948 13.9298 2.23826 13.9992 2.5017 13.9993H13.4988C13.7622 13.9992 14.021 13.9298 14.2491 13.798C14.4772 13.6662 14.6665 13.4767 14.7981 13.2484C14.9296 13.0202 14.9988 12.7614 14.9985 12.4979C14.9983 12.2345 14.9286 11.9758 14.7966 11.7478ZM7.49974 6.49975C7.49974 6.36715 7.55242 6.23997 7.64619 6.1462C7.73996 6.05243 7.86713 5.99975 7.99974 5.99975C8.13235 5.99975 8.25953 6.05243 8.3533 6.1462C8.44706 6.23997 8.49974 6.36715 8.49974 6.49975V8.99975C8.49974 9.13236 8.44706 9.25954 8.3533 9.35331C8.25953 9.44708 8.13235 9.49975 7.99974 9.49975C7.86713 9.49975 7.73996 9.44708 7.64619 9.35331C7.55242 9.25954 7.49974 9.13236 7.49974 8.99975V6.49975ZM8.00011 11.9999C7.85177 11.9999 7.70677 11.9559 7.58343 11.8735C7.46009 11.7911 7.36396 11.674 7.3072 11.5369C7.25043 11.3999 7.23558 11.2491 7.26452 11.1036C7.29346 10.9581 7.36489 10.8245 7.46978 10.7196C7.57467 10.6147 7.7083 10.5433 7.85379 10.5143C7.99928 10.4854 8.15008 10.5003 8.28712 10.557C8.42417 10.6138 8.5413 10.7099 8.62371 10.8333C8.70612 10.9566 8.75011 11.1016 8.75011 11.2499C8.7501 11.4488 8.67108 11.6396 8.53043 11.7803C8.38978 11.9209 8.19902 11.9999 8.00011 11.9999Z" fill="#DBA017"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -421,6 +421,11 @@ chaosWorkflows:
|
|||
reliabilityDetails: Reliability Details
|
||||
experiments: Experiments
|
||||
lastRun: Last Run
|
||||
wfIssue: Seems like there has been some issue in workflow.
|
||||
runningFrom: Running from last
|
||||
min: mins.
|
||||
sync: Sync Workflow
|
||||
terminate: Terminate Workflow
|
||||
tableData:
|
||||
overallRR: 'Overall RR : '
|
||||
experimentsPassed: 'Experiments Passed : '
|
||||
|
@ -1075,9 +1080,11 @@ myhub:
|
|||
syncingRepo: Loading Charts, Please Wait...!
|
||||
preDefined: Pre-defined workflows
|
||||
chaosCharts: Chaos-charts
|
||||
noExp: No predefined workflows available with information in this Hub
|
||||
noPredefinedExp: No predefined workflows available with information in this Hub
|
||||
noExp: No experiments found
|
||||
lastSynced: 'Last synced at: '
|
||||
repoLink: 'Repository Link: '
|
||||
repoBranch: 'Repository Branch: '
|
||||
connectHubPage:
|
||||
connectHub: Connect a new chaos hub
|
||||
editHub: Edit hub configuration
|
||||
|
@ -1145,6 +1152,8 @@ customWorkflow:
|
|||
upload: Upload your
|
||||
yaml: YAML
|
||||
uploadFile: Select YAML file from your device
|
||||
errorUpload: Error while uploading YAML
|
||||
retryUpload: Retry Upload
|
||||
uploadSuccess: Successfully uploaded
|
||||
drag: Drag & Drop YAML file to upload
|
||||
chooseExp: Choose the experiment
|
||||
|
|
|
@ -89,12 +89,6 @@ export const DECLINE_INVITE = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const DELETE_SCHEDULE = gql`
|
||||
mutation deleteWorkflow($workflow_id: String!) {
|
||||
deleteChaosWorkflow(workflowid: $workflow_id)
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_SCHEDULE = gql`
|
||||
mutation updateChaos($ChaosWorkFlowInput: ChaosWorkFlowInput!) {
|
||||
updateChaosWorkflow(input: $ChaosWorkFlowInput) {
|
||||
|
@ -323,3 +317,18 @@ export const UPDATE_IMAGE_REGISTRY = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SYNC_WORKFLOW = gql`
|
||||
mutation syncWorkflow($workflowid: String!, $workflow_run_id: String!) {
|
||||
syncWorkflow(workflowid: $workflowid, workflow_run_id: $workflow_run_id)
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_WORKFLOW = gql`
|
||||
mutation deleteWorkflow($workflowid: String!, $workflow_run_id: String!) {
|
||||
deleteChaosWorkflow(
|
||||
workflowid: $workflowid
|
||||
workflow_run_id: $workflow_run_id
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -14,6 +14,7 @@ export const WORKFLOW_DETAILS_WITH_EXEC_DATA = gql`
|
|||
phase
|
||||
execution_data
|
||||
resiliency_score
|
||||
isRemoved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +34,7 @@ export const WORKFLOW_DETAILS = gql`
|
|||
resiliency_score
|
||||
experiments_passed
|
||||
total_experiments
|
||||
isRemoved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ export interface WorkflowRun {
|
|||
resiliency_score?: number;
|
||||
experiments_passed?: number;
|
||||
total_experiments?: number;
|
||||
isRemoved: boolean;
|
||||
}
|
||||
|
||||
interface GetWorkflowRunsOutput {
|
||||
|
@ -106,6 +107,7 @@ export interface WorkflowRunFilterInput {
|
|||
cluster_name?: string;
|
||||
workflow_status?: WorkflowStatus;
|
||||
date_range?: DateRange;
|
||||
isRemoved?: boolean | null;
|
||||
}
|
||||
|
||||
export interface WorkflowDataVars {
|
||||
|
|
|
@ -25,7 +25,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import YAML from 'yaml';
|
||||
import Loader from '../../../components/Loader';
|
||||
import {
|
||||
DELETE_SCHEDULE,
|
||||
DELETE_WORKFLOW,
|
||||
SCHEDULE_DETAILS,
|
||||
UPDATE_SCHEDULE,
|
||||
} from '../../../graphql';
|
||||
|
@ -75,7 +75,7 @@ const BrowseSchedule: React.FC = () => {
|
|||
);
|
||||
|
||||
// Apollo mutation to delete the selected schedule
|
||||
const [deleteSchedule] = useMutation<DeleteSchedule>(DELETE_SCHEDULE, {
|
||||
const [deleteSchedule] = useMutation<DeleteSchedule>(DELETE_WORKFLOW, {
|
||||
refetchQueries: [{ query: SCHEDULE_DETAILS, variables: { projectID } }],
|
||||
});
|
||||
|
||||
|
@ -188,7 +188,7 @@ const BrowseSchedule: React.FC = () => {
|
|||
|
||||
const deleteRow = (wfid: string) => {
|
||||
deleteSchedule({
|
||||
variables: { workflow_id: wfid },
|
||||
variables: { workflowid: wfid, workflow_run_id: '' },
|
||||
});
|
||||
};
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useQuery } from '@apollo/client';
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
|
@ -11,10 +11,15 @@ import {
|
|||
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import { ButtonFilled } from 'litmus-ui';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TimePopOver from '../../../components/TimePopOver';
|
||||
import { WORKFLOW_LIST_DETAILS } from '../../../graphql';
|
||||
import {
|
||||
DELETE_WORKFLOW,
|
||||
SYNC_WORKFLOW,
|
||||
WORKFLOW_LIST_DETAILS,
|
||||
} from '../../../graphql';
|
||||
import { WorkflowRun } from '../../../models/graphql/workflowData';
|
||||
import {
|
||||
WorkflowList,
|
||||
|
@ -30,21 +35,20 @@ import useStyles from './styles';
|
|||
|
||||
interface TableDataProps {
|
||||
data: Partial<WorkflowRun>;
|
||||
refetchQuery: any;
|
||||
}
|
||||
|
||||
const TableData: React.FC<TableDataProps> = ({ data }) => {
|
||||
const TableData: React.FC<TableDataProps> = ({ data, refetchQuery }) => {
|
||||
const classes = useStyles();
|
||||
const projectID = getProjectID();
|
||||
const projectRole = getProjectRole();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const nodeSelection = useActions(NodeSelectionActions);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
@ -79,6 +83,44 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
setPopAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* State variables for warning popover
|
||||
*/
|
||||
const [popWarningAnchorEl, setWarningPopAnchorEl] =
|
||||
React.useState<null | HTMLElement>(null);
|
||||
const isWarningOpen = Boolean(popWarningAnchorEl);
|
||||
const idWarning = isWarningOpen ? 'simple-popover' : undefined;
|
||||
const handleWarningPopOverClose = () => {
|
||||
setWarningPopAnchorEl(null);
|
||||
};
|
||||
const handleWarningPopOverClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setWarningPopAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync workflow to sync a chaos workflow
|
||||
*/
|
||||
const [syncWorkflow] = useMutation(SYNC_WORKFLOW, {
|
||||
onCompleted: (data) => {
|
||||
if (data.syncWorkflow) {
|
||||
handleWarningPopOverClose();
|
||||
refetchQuery();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete workflow mutation to delete a chaos workflow
|
||||
*/
|
||||
const [deleteWorkflow] = useMutation(DELETE_WORKFLOW, {
|
||||
onCompleted: (data) => {
|
||||
if (data.deleteChaosWorkflow) {
|
||||
handleWarningPopOverClose();
|
||||
refetchQuery();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function getResiliencyScoreColor(score: number) {
|
||||
if (score < 39) {
|
||||
return classes.less;
|
||||
|
@ -89,9 +131,98 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
return classes.high;
|
||||
}
|
||||
|
||||
// Function to find the time different in minutes
|
||||
const timeDiff = (currentTime: number, lastUpdated: string) => {
|
||||
const current = currentTime;
|
||||
const last = parseInt(lastUpdated, 10) * 1000;
|
||||
const timeDifference = (current - last) / (60 * 1000);
|
||||
return timeDifference;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableCell className={classes.tableDataStatus}>
|
||||
{/* Table cell for warning (if the workflow is in running state from 20 mins) */}
|
||||
<TableCell className={classes.warningTableCell}>
|
||||
{timeDiff(new Date().getTime(), data.last_updated ?? '') >= 20 &&
|
||||
data.phase?.toLowerCase() === 'running' ? (
|
||||
<IconButton onClick={handleWarningPopOverClick}>
|
||||
<img src="./icons/warning.svg" alt="warning" width="20" />
|
||||
</IconButton>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{/* Warning PopOver */}
|
||||
<Popover
|
||||
id={idWarning}
|
||||
open={isWarningOpen}
|
||||
anchorEl={popWarningAnchorEl}
|
||||
onClose={handleWarningPopOverClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
>
|
||||
<div className={classes.popoverWarning}>
|
||||
<Typography>
|
||||
{t('chaosWorkflows.browseWorkflows.wfIssue')}
|
||||
</Typography>
|
||||
<div className={classes.imageRunning}>
|
||||
<img
|
||||
src="/icons/running.svg"
|
||||
alt="running"
|
||||
className={classes.runningSmallIcon}
|
||||
/>{' '}
|
||||
<Typography className={classes.runningText}>
|
||||
{t('chaosWorkflows.browseWorkflows.runningFrom')}{' '}
|
||||
{Math.round(
|
||||
timeDiff(new Date().getTime(), data.last_updated ?? '')
|
||||
)}{' '}
|
||||
{t('chaosWorkflows.browseWorkflows.min')}
|
||||
</Typography>
|
||||
</div>
|
||||
{/* Buttons to sync and terminate the workflow */}
|
||||
<div className={classes.warningBtnDiv}>
|
||||
<ButtonFilled
|
||||
className={classes.syncBtn}
|
||||
onClick={() => {
|
||||
syncWorkflow({
|
||||
variables: {
|
||||
workflowid: data.workflow_id,
|
||||
workflow_run_id: data.workflow_run_id,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src="./icons/sync-wf.svg" alt="sync" />
|
||||
<Typography className={classes.waitingBtnText}>
|
||||
{t('chaosWorkflows.browseWorkflows.sync')}
|
||||
</Typography>
|
||||
</ButtonFilled>
|
||||
<ButtonFilled
|
||||
onClick={() => {
|
||||
deleteWorkflow({
|
||||
variables: {
|
||||
workflowid: data.workflow_id,
|
||||
workflow_run_id: data.workflow_run_id,
|
||||
},
|
||||
});
|
||||
}}
|
||||
className={classes.terminateText}
|
||||
>
|
||||
<img src="./icons/terminate-wf.svg" alt="terminate" />
|
||||
<Typography className={classes.waitingBtnText}>
|
||||
{t('chaosWorkflows.browseWorkflows.terminate')}
|
||||
</Typography>
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<CustomStatus status={data.phase ?? ''} />
|
||||
</TableCell>
|
||||
<TableCell
|
||||
|
@ -101,10 +232,11 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
nodeSelection.selectNode({
|
||||
pod_name: '',
|
||||
});
|
||||
history.push({
|
||||
pathname: `/workflows/${data.workflow_run_id}`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
if (data.phase?.toLowerCase() !== 'notavailable')
|
||||
history.push({
|
||||
pathname: `/workflows/${data.workflow_run_id}`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Typography className={classes.boldText} data-cy="workflowName">
|
||||
|
@ -235,10 +367,11 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
nodeSelection.selectNode({
|
||||
pod_name: '',
|
||||
});
|
||||
history.push({
|
||||
pathname: `/workflows/${data.workflow_run_id}`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
if (data.phase?.toLowerCase() !== 'notavailable')
|
||||
history.push({
|
||||
pathname: `/workflows/${data.workflow_run_id}`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={classes.expDiv} data-cy="workflowDetails">
|
||||
|
@ -255,10 +388,11 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
|
|||
<MenuItem
|
||||
value="Analysis"
|
||||
onClick={() => {
|
||||
history.push({
|
||||
pathname: `/workflows/analytics/${data.workflow_id}`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
if (data.phase?.toLowerCase() !== 'notavailable')
|
||||
history.push({
|
||||
pathname: `/workflows/analytics/${data.workflow_id}`,
|
||||
search: `?projectID=${projectID}&projectRole=${projectRole}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={classes.expDiv} data-cy="workflowAnalytics">
|
||||
|
|
|
@ -110,23 +110,23 @@ const BrowseWorkflow: React.FC = () => {
|
|||
);
|
||||
|
||||
// Query to get workflows
|
||||
const { subscribeToMore, data, error } = useQuery<Workflow, WorkflowDataVars>(
|
||||
WORKFLOW_DETAILS,
|
||||
{
|
||||
variables: {
|
||||
workflowRunsInput: {
|
||||
project_id: projectID,
|
||||
pagination: {
|
||||
page: paginationData.page,
|
||||
limit: paginationData.limit,
|
||||
},
|
||||
sort: sortData,
|
||||
filter: filters,
|
||||
const { subscribeToMore, data, error, refetch } = useQuery<
|
||||
Workflow,
|
||||
WorkflowDataVars
|
||||
>(WORKFLOW_DETAILS, {
|
||||
variables: {
|
||||
workflowRunsInput: {
|
||||
project_id: projectID,
|
||||
pagination: {
|
||||
page: paginationData.page,
|
||||
limit: paginationData.limit,
|
||||
},
|
||||
sort: sortData,
|
||||
filter: filters,
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
}
|
||||
);
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
// Using subscription to get realtime data
|
||||
useEffect(() => {
|
||||
|
@ -272,6 +272,7 @@ const BrowseWorkflow: React.FC = () => {
|
|||
<Table stickyHeader aria-label="simple table">
|
||||
<TableHead className={classes.tableHead}>
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
{/* Status */}
|
||||
<TableCell className={classes.headerStatus}>
|
||||
{t('chaosWorkflows.browseWorkflows.status')}
|
||||
|
@ -387,7 +388,7 @@ const BrowseWorkflow: React.FC = () => {
|
|||
data-cy="WorkflowRunsTableRow"
|
||||
key={dataRow.workflow_run_id}
|
||||
>
|
||||
<TableData data={dataRow} />
|
||||
<TableData data={dataRow} refetchQuery={refetch} />
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
|
|
|
@ -83,7 +83,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
},
|
||||
headerStatus: {
|
||||
paddingLeft: theme.spacing(10),
|
||||
paddingLeft: theme.spacing(4),
|
||||
},
|
||||
|
||||
workflowName: {
|
||||
|
@ -94,9 +94,6 @@ const useStyles = makeStyles((theme) => ({
|
|||
paddingLeft: theme.spacing(5),
|
||||
},
|
||||
|
||||
tableDataStatus: {
|
||||
paddingLeft: theme.spacing(8.5),
|
||||
},
|
||||
sortDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
@ -181,6 +178,53 @@ const useStyles = makeStyles((theme) => ({
|
|||
pointer: 'cursor',
|
||||
},
|
||||
},
|
||||
runningSmallIcon: {
|
||||
animation: 'runningNodeSpinAnimationSmall 2s ease-in-out infinite',
|
||||
},
|
||||
'@global': {
|
||||
'@keyframes runningNodeSpinAnimationSmall': {
|
||||
from: {
|
||||
transform: `rotate(0deg)`,
|
||||
},
|
||||
to: {
|
||||
transform: `rotate(360deg)`,
|
||||
},
|
||||
},
|
||||
},
|
||||
popoverWarning: {
|
||||
padding: theme.spacing(3.125, 2.6),
|
||||
width: 'fit-content',
|
||||
},
|
||||
runningText: {
|
||||
color: theme.palette.text.hint,
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
|
||||
// Warning pop-over styles
|
||||
warningTableCell: {
|
||||
maxWidth: '2.5rem',
|
||||
},
|
||||
imageRunning: {
|
||||
display: 'flex',
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
warningBtnDiv: {
|
||||
marginTop: theme.spacing(5),
|
||||
marginLeft: theme.spacing(2.5),
|
||||
},
|
||||
syncBtn: {
|
||||
backgroundColor: 'transparent !important',
|
||||
color: theme.palette.primary.main,
|
||||
marginRight: theme.spacing(1.25),
|
||||
},
|
||||
waitingBtnText: {
|
||||
fontSize: '0.75rem',
|
||||
marginLeft: theme.spacing(0.625),
|
||||
},
|
||||
terminateText: {
|
||||
backgroundColor: 'transparent !important',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
|
@ -19,6 +19,9 @@ const CustomStatus: React.FC<StatusProps> = ({ status }) => {
|
|||
if (status === 'Running' || status === 'Pending') {
|
||||
return setLabel(`${classes.status} ${classes.running}`);
|
||||
}
|
||||
if (status === 'NotAvailable') {
|
||||
return setLabel(`${classes.naStatus} ${classes.notAvailable}`);
|
||||
}
|
||||
return setLabel(`${classes.status} ${classes.failed}`);
|
||||
}, [status]);
|
||||
|
||||
|
|
|
@ -8,6 +8,17 @@ const useStyles = makeStyles((theme) => ({
|
|||
paddingTop: theme.spacing(0.375),
|
||||
paddingBottom: theme.spacing(0.375),
|
||||
},
|
||||
naStatus: {
|
||||
width: '4.8rem',
|
||||
textAlign: 'center',
|
||||
borderRadius: 3,
|
||||
paddingTop: theme.spacing(0.375),
|
||||
paddingBottom: theme.spacing(0.375),
|
||||
},
|
||||
notAvailable: {
|
||||
color: theme.palette.text.secondary,
|
||||
backgroundColor: theme.palette.status.pending,
|
||||
},
|
||||
completed: {
|
||||
color: theme.palette.success.main,
|
||||
backgroundColor: theme.palette.success.light,
|
||||
|
@ -17,8 +28,8 @@ const useStyles = makeStyles((theme) => ({
|
|||
backgroundColor: theme.palette.warning.light,
|
||||
},
|
||||
failed: {
|
||||
color: theme.palette.status.failed,
|
||||
backgroundColor: theme.palette.status.failed,
|
||||
color: theme.palette.error.main,
|
||||
backgroundColor: theme.palette.error.light,
|
||||
},
|
||||
statusFont: {
|
||||
fontSize: '0.725rem',
|
||||
|
|
|
@ -257,6 +257,18 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
noTemplatesDesc: {
|
||||
fontSize: '1rem',
|
||||
},
|
||||
|
||||
errorText: {
|
||||
marginLeft: theme.spacing(1.25),
|
||||
},
|
||||
errorBtn: {
|
||||
backgroundColor: 'transparent !important',
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: theme.spacing(2.5),
|
||||
},
|
||||
retryText: {
|
||||
marginLeft: theme.spacing(1.25),
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { AccordionDetails, Button, Paper, Typography } from '@material-ui/core';
|
||||
import { ButtonFilled } from 'litmus-ui';
|
||||
import localforage from 'localforage';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -19,6 +20,7 @@ const UploadYAML = () => {
|
|||
const { t } = useTranslation();
|
||||
const [uploadedYAML, setUploadedYAML] = useState('');
|
||||
const [fileName, setFileName] = useState<string | null>('');
|
||||
const [uploadError, setUploadError] = useState(false);
|
||||
const workflowAction = useActions(WorkflowActions);
|
||||
const { namespace } = useSelector((state: RootState) => state.workflowData);
|
||||
|
||||
|
@ -42,11 +44,19 @@ const UploadYAML = () => {
|
|||
const readFile = await file.text();
|
||||
setUploadedYAML(readFile);
|
||||
setFileName(file.name);
|
||||
const wfmanifest = updateEngineName(YAML.parse(readFile));
|
||||
const updatedManifest = updateNamespace(wfmanifest, namespace);
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: YAML.stringify(updatedManifest),
|
||||
});
|
||||
try {
|
||||
setUploadError(false);
|
||||
const wfmanifest = updateEngineName(YAML.parse(readFile));
|
||||
const updatedManifest = updateNamespace(wfmanifest, namespace);
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: YAML.stringify(updatedManifest),
|
||||
});
|
||||
} catch {
|
||||
setUploadError(true);
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
saveToLocalForage();
|
||||
};
|
||||
|
@ -61,15 +71,19 @@ const UploadYAML = () => {
|
|||
if ((extension === 'yaml' || extension === 'yml') && readFile) {
|
||||
readFile.text().then((response) => {
|
||||
setUploadedYAML(response);
|
||||
const wfmanifest = updateEngineName(YAML.parse(response));
|
||||
const updatedManifest = updateNamespace(wfmanifest, namespace);
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: YAML.stringify(updatedManifest),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: '',
|
||||
try {
|
||||
setUploadError(false);
|
||||
const wfmanifest = updateEngineName(YAML.parse(response));
|
||||
const updatedManifest = updateNamespace(wfmanifest, namespace);
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: YAML.stringify(updatedManifest),
|
||||
});
|
||||
} catch {
|
||||
setUploadError(true);
|
||||
workflowAction.setWorkflowManifest({
|
||||
manifest: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
saveToLocalForage();
|
||||
|
@ -88,7 +102,31 @@ const UploadYAML = () => {
|
|||
}}
|
||||
className={classes.uploadYAMLDiv}
|
||||
>
|
||||
{uploadedYAML === '' ? (
|
||||
{uploadError ? (
|
||||
<div className={classes.uploadSuccessDiv}>
|
||||
<img
|
||||
src="./icons/error-upload.svg"
|
||||
alt="upload error"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
<Typography className={classes.errorText}>
|
||||
{t('customWorkflow.createWorkflow.errorUpload')}
|
||||
</Typography>
|
||||
<ButtonFilled
|
||||
className={classes.errorBtn}
|
||||
onClick={() => {
|
||||
setUploadedYAML('');
|
||||
setUploadError(false);
|
||||
}}
|
||||
>
|
||||
<img src="./icons/retry.svg" alt="Retry" />
|
||||
<Typography className={classes.retryText}>
|
||||
{t('customWorkflow.createWorkflow.retryUpload')}
|
||||
</Typography>
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
) : uploadedYAML === '' ? (
|
||||
<div className={classes.uploadYAMLText} data-cy="uploadYAMLInput">
|
||||
<img
|
||||
src="./icons/upload-yaml.svg"
|
||||
|
|
|
@ -251,6 +251,7 @@ const WorkflowSettings = forwardRef((_, ref) => {
|
|||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
disabled
|
||||
className={classes.nsInput}
|
||||
label={t('createWorkflow.chooseWorkflow.label.namespace')}
|
||||
value={workflowData.namespace}
|
||||
|
|
|
@ -121,6 +121,20 @@ const MyHub: React.FC = () => {
|
|||
}
|
||||
}, [data]);
|
||||
|
||||
const filteredWorkflow =
|
||||
predefinedData?.GetPredefinedWorkflowList &&
|
||||
predefinedData?.GetPredefinedWorkflowList.filter((data: string) =>
|
||||
data.toLowerCase().includes(searchPredefined.trim())
|
||||
);
|
||||
|
||||
const filteredExperiment =
|
||||
totalExp &&
|
||||
totalExp.filter(
|
||||
(data) =>
|
||||
data.ChaosName.toLowerCase().includes(search.trim()) ||
|
||||
data.ExperimentName.toLowerCase().includes(search.trim())
|
||||
);
|
||||
|
||||
return loading || predefinedLoading ? (
|
||||
<Backdrop open className={classes.backdrop}>
|
||||
<Loader />
|
||||
|
@ -137,11 +151,13 @@ const MyHub: React.FC = () => {
|
|||
<Typography variant="h3" gutterBottom>
|
||||
{UserHub?.HubName}
|
||||
</Typography>
|
||||
<Typography variant="h5">
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{t('myhub.myhubChart.repoLink')}
|
||||
<strong>
|
||||
{UserHub?.RepoURL}/{UserHub?.RepoBranch}
|
||||
</strong>
|
||||
<strong>{UserHub?.RepoURL}</strong>
|
||||
</Typography>
|
||||
<Typography variant="h5">
|
||||
{t('myhub.myhubChart.repoBranch')}
|
||||
<strong>{UserHub?.RepoBranch}</strong>
|
||||
</Typography>
|
||||
<Typography className={classes.lastSyncText}>
|
||||
{t('myhub.myhubChart.lastSynced')}{' '}
|
||||
|
@ -173,11 +189,8 @@ const MyHub: React.FC = () => {
|
|||
changeSearch={handlePreDefinedSearch}
|
||||
/>
|
||||
<div className={classes.chartsGroup}>
|
||||
{predefinedData?.GetPredefinedWorkflowList.length > 0 ? (
|
||||
predefinedData?.GetPredefinedWorkflowList.filter(
|
||||
(data: string) =>
|
||||
data.toLowerCase().includes(searchPredefined.trim())
|
||||
).map((expName: string) => {
|
||||
{filteredWorkflow?.length > 0 ? (
|
||||
filteredWorkflow.map((expName: string) => {
|
||||
return (
|
||||
<ChartCard
|
||||
key={expName}
|
||||
|
@ -202,7 +215,7 @@ const MyHub: React.FC = () => {
|
|||
height="80px"
|
||||
/>
|
||||
<Typography variant="h5" className={classes.noExp}>
|
||||
{t('myhub.myhubChart.noExp')}
|
||||
{t('myhub.myhubChart.noPredefinedExp')}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
|
@ -232,27 +245,33 @@ const MyHub: React.FC = () => {
|
|||
<div className={classes.mainDiv}>
|
||||
<HeaderSection searchValue={search} changeSearch={changeSearch} />
|
||||
<div className={classes.chartsGroup}>
|
||||
{totalExp &&
|
||||
totalExp.length > 0 &&
|
||||
totalExp
|
||||
.filter(
|
||||
(data) =>
|
||||
data.ChaosName.toLowerCase().includes(search.trim()) ||
|
||||
data.ExperimentName.toLowerCase().includes(search.trim())
|
||||
)
|
||||
.map((expName: ChartName) => {
|
||||
return (
|
||||
<ChartCard
|
||||
key={`${expName.ChaosName}-${expName.ExperimentName}`}
|
||||
expName={expName}
|
||||
UserHub={UserHub}
|
||||
setSearch={setSearch}
|
||||
projectID={projectID}
|
||||
userRole={getProjectRole()}
|
||||
isPredefined={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{filteredExperiment?.length > 0 ? (
|
||||
filteredExperiment.map((expName: ChartName) => {
|
||||
return (
|
||||
<ChartCard
|
||||
key={`${expName.ChaosName}-${expName.ExperimentName}`}
|
||||
expName={expName}
|
||||
UserHub={UserHub}
|
||||
setSearch={setSearch}
|
||||
projectID={projectID}
|
||||
userRole={getProjectRole()}
|
||||
isPredefined={false}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src="/icons/no-experiment-found.svg"
|
||||
alt="no experiment"
|
||||
width="80px"
|
||||
height="80px"
|
||||
/>
|
||||
<Typography variant="h5" className={classes.noExp}>
|
||||
{t('myhub.myhubChart.noExp')}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
|
|
Loading…
Reference in New Issue