From 7137a69fc7c3fa08cbf4bec3250492a0ef10d7b9 Mon Sep 17 00:00:00 2001 From: Sayan Mondal Date: Wed, 13 Jan 2021 11:40:00 +0530 Subject: [PATCH] Polishing UI, Adding Delete Schedule Modal + Disable Back button on Editing a Schedule + Minor UI Bug Fixes (#2398) * Adding Edit Schedule Feature * Polishing UI, Adding Delete Schedule Modal + Disable Back button on edit schedule * Minor UI bug fixes Signed-off-by: Sayan Mondal --- .../public/locales/en/translation.yaml | 22 +- .../components/Button/ButtonFilled/index.tsx | 6 +- .../components/Button/ButtonFilled/styles.ts | 6 + .../src/components/CustomText/index.tsx | 30 +- .../Targets/ConnectTarget/index.tsx | 22 +- .../components/WorkflowCard/CardContent.tsx | 22 +- .../src/components/WorkflowCard/styles.ts | 11 +- .../src/components/WorkflowStepper/index.tsx | 25 +- .../src/components/WorkflowStepper/styles.ts | 4 + .../frontend/src/containers/app/App.tsx | 2 +- litmus-portal/frontend/src/graphql/index.ts | 2 +- .../frontend/src/graphql/mutations.ts | 12 + .../src/graphql/{quries.ts => queries.ts} | 0 .../src/models/graphql/createWorkflowData.ts | 9 + .../frontend/src/models/redux/workflow.ts | 2 + .../src/pages/SchedulePage/SetTime/index.tsx | 102 -- .../src/pages/SchedulePage/SetTime/styles.ts | 20 - .../frontend/src/pages/SchedulePage/index.tsx | 897 ++++++++++-------- .../frontend/src/pages/SchedulePage/styles.ts | 166 ---- .../src/pages/SchedulePage/templates.ts | 4 + .../frontend/src/redux/reducers/workflow.ts | 1 + litmus-portal/frontend/src/svg/crossmark.svg | 5 + litmus-portal/frontend/src/utils/trim.ts | 5 + .../WorkflowComparisonTable/index.tsx | 2 +- .../BrowseSchedule/TableData.tsx | 94 +- .../ChaosWorkflows/BrowseSchedule/styles.ts | 28 + .../ChaosWorkflows/BrowseTemplate/styles.ts | 6 +- .../views/ChaosWorkflows/Templates/index.tsx | 2 +- .../CreateWorkflow/ChooseWorkflow/index.tsx | 44 +- .../CustomWorkflow/CreateWorkflow/index.tsx | 20 +- .../CustomWorkflow/TuneWorkflow/index.tsx | 16 +- .../CreateWorkflow/ReliabilityScore/index.tsx | 4 +- .../CreateWorkflow/ScheduleWorkflow/index.tsx | 72 +- .../CreateWorkflow/ScheduleWorkflow/styles.ts | 1 + .../CreateWorkflow/TuneWorkflow/index.tsx | 2 +- .../CreateWorkflow/VerifyCommit/index.tsx | 18 +- .../Invitation/SentInvitations/TableData.tsx | 2 +- .../TeammingTab/InviteNew/Invite/index.tsx | 5 +- .../views/Settings/TeammingTab/tableData.tsx | 2 +- 39 files changed, 923 insertions(+), 770 deletions(-) rename litmus-portal/frontend/src/graphql/{quries.ts => queries.ts} (100%) delete mode 100644 litmus-portal/frontend/src/pages/SchedulePage/SetTime/index.tsx delete mode 100644 litmus-portal/frontend/src/pages/SchedulePage/SetTime/styles.ts delete mode 100644 litmus-portal/frontend/src/pages/SchedulePage/styles.ts create mode 100644 litmus-portal/frontend/src/pages/SchedulePage/templates.ts create mode 100644 litmus-portal/frontend/src/svg/crossmark.svg create mode 100644 litmus-portal/frontend/src/utils/trim.ts diff --git a/litmus-portal/frontend/public/locales/en/translation.yaml b/litmus-portal/frontend/public/locales/en/translation.yaml index 70d7c2fb5..42d7b1337 100644 --- a/litmus-portal/frontend/public/locales/en/translation.yaml +++ b/litmus-portal/frontend/public/locales/en/translation.yaml @@ -52,6 +52,13 @@ customWorkflowCard: workflowStepper: continueError: To continue, please check the error in code. + aNewChaosWorkflow: A new chaos workflow, + successful: is successfully created! + congratulationsSub1: Congratulations on creating your workflow! Now information about + congratulationsSub2: it will be displayed on the workflow schedules. + workflowBtn: Go to Workflow + finish: Finish + next: Next ###################################### ############ Pages ############# @@ -103,6 +110,15 @@ schedule: at: at At: At scheduleOn: 'On' + missingPerm: Missing sufficient permissions :( + requiredPerm: Looks like you do not have the required permission to create a new workflow on this project. + contact: Contact portal administrator to upgrade your permission. + chaosWorkflow: Chaos workflow, + successful: was successfully updated! + congratulationsSub1: Chaos workflow updated successfully! Now information about + congratulationsSub2: it will be displayed on the workflow schedules. + workflowBtn: Go to Workflow + backBtn: Go Back workflowUnderground: heading: 'Click on test to see detailed log of your workflow' @@ -377,7 +393,7 @@ createWorkflow: reliabilityScore: header: Adjust the weights of the experiments in the workflow info: You have selected - infoNext: tests in the “Kubernetes conformance test” workflow. Successful outcome of each test carries a certain weight. We have pre-selected weights for each test for you. However, you may review and modify the weigtage against. + infoNext: tests in the “Kubernetes conformance test” workflow. Successful outcome of each test carries a certain weight. We have pre-selected weights for each test for you. However, you may review and modify the weightage against. infoNextStrong: The weights are relative to each other. testHeading: Kubernetes conformance test testInfo: Compare the importance of the items above and launch a demo version of Kubernetes conformance test to see how it works. @@ -415,6 +431,10 @@ createWorkflow: future: Select date and time to start workflow in future recurr: Recurring Schedule rightRecurr: Choose the right recurring time to start your workflow + modalHeader: Are you sure you want to delete this Schedule + modalSubheader: This action is irreversible + cancelBtn: Cancel + deleteBtn: Delete toggleComponent: pass: Pass fail: Fail diff --git a/litmus-portal/frontend/src/components/Button/ButtonFilled/index.tsx b/litmus-portal/frontend/src/components/Button/ButtonFilled/index.tsx index 51a27565e..263a5c22e 100644 --- a/litmus-portal/frontend/src/components/Button/ButtonFilled/index.tsx +++ b/litmus-portal/frontend/src/components/Button/ButtonFilled/index.tsx @@ -8,6 +8,7 @@ interface ButtonFilledProps { ) => void; isPrimary: boolean; isDisabled?: boolean; + isWarning?: boolean; styles?: Object; type?: any; } @@ -16,6 +17,7 @@ const ButtonFilled: React.FC = ({ children, isPrimary, isDisabled, + isWarning, styles, type, }) => { @@ -29,7 +31,9 @@ const ButtonFilled: React.FC = ({ type={type} onClick={handleClick} className={ - isPrimary + isWarning + ? `${classes.button} ${classes.buttonWarning}` + : isPrimary ? `${classes.button} ${classes.buttonPrimary}` : `${classes.button} ${classes.buttonSecondary}` } diff --git a/litmus-portal/frontend/src/components/Button/ButtonFilled/styles.ts b/litmus-portal/frontend/src/components/Button/ButtonFilled/styles.ts index be6d7abee..05cfe602c 100644 --- a/litmus-portal/frontend/src/components/Button/ButtonFilled/styles.ts +++ b/litmus-portal/frontend/src/components/Button/ButtonFilled/styles.ts @@ -20,6 +20,12 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.primary.dark, }, }, + buttonWarning: { + backgroundColor: theme.palette.error.dark, + '&:hover': { + backgroundColor: theme.palette.error.dark, + }, + }, })); export default useStyles; diff --git a/litmus-portal/frontend/src/components/CustomText/index.tsx b/litmus-portal/frontend/src/components/CustomText/index.tsx index b0dfd9e02..4421dad94 100644 --- a/litmus-portal/frontend/src/components/CustomText/index.tsx +++ b/litmus-portal/frontend/src/components/CustomText/index.tsx @@ -7,6 +7,7 @@ interface CustomTextProps { value: string; id: string; onchange: (val: string) => void; + isEditable?: boolean; } const useStyles = makeStyles((theme: Theme) => ({ @@ -23,7 +24,12 @@ const useStyles = makeStyles((theme: Theme) => ({ })); // Editable text field used to edit and save the input in the text box -const CustomText: React.FC = ({ value, id, onchange }) => { +const CustomText: React.FC = ({ + value, + id, + onchange, + isEditable, +}) => { const [isDisabled, setIsDisabled] = React.useState(true); const [newValue, setNewValue] = React.useState(value); @@ -58,15 +64,19 @@ const CustomText: React.FC = ({ value, id, onchange }) => { }} onChange={handleChange} /> - {isDisabled ? ( - - - - ) : ( - - - - )} + {isEditable ? ( + <> + {isDisabled ? ( + + + + ) : ( + + + + )} + + ) : null} ); }; diff --git a/litmus-portal/frontend/src/components/Targets/ConnectTarget/index.tsx b/litmus-portal/frontend/src/components/Targets/ConnectTarget/index.tsx index fbbd6a6b6..76874e8f9 100644 --- a/litmus-portal/frontend/src/components/Targets/ConnectTarget/index.tsx +++ b/litmus-portal/frontend/src/components/Targets/ConnectTarget/index.tsx @@ -1,24 +1,24 @@ +import { useLazyQuery, useMutation } from '@apollo/client'; import { Typography } from '@material-ui/core'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { useMutation, useLazyQuery } from '@apollo/client'; import { useSelector } from 'react-redux'; -import { history } from '../../../redux/configureStore'; -import ButtonOutline from '../../Button/ButtonOutline'; -import TargetCopy from '../TargetCopy'; -import useStyles from './styles'; import Scaffold from '../../../containers/layouts/Scaffold'; +import Unimodal from '../../../containers/layouts/Unimodal'; +import { GET_CLUSTER, USER_CLUSTER_REG } from '../../../graphql'; import { + Cluster, CreateClusterInput, CreateClusterInputResponse, - Cluster, } from '../../../models/graphql/clusterData'; -import { USER_CLUSTER_REG, GET_CLUSTER } from '../../../graphql'; +import { history } from '../../../redux/configureStore'; import { RootState } from '../../../redux/reducers'; -import Loader from '../../Loader'; -import ButtonFilled from '../../Button/ButtonFilled'; -import Unimodal from '../../../containers/layouts/Unimodal'; import BackButton from '../../Button/BackButton'; +import ButtonFilled from '../../Button/ButtonFilled'; +import ButtonOutline from '../../Button/ButtonOutline'; +import Loader from '../../Loader'; +import TargetCopy from '../TargetCopy'; +import useStyles from './styles'; const ConnectTarget = () => { const classes = useStyles(); @@ -145,7 +145,7 @@ const ConnectTarget = () => { hasCloseBtn >
- mark + mark {t('ConnectTargets.title')}
diff --git a/litmus-portal/frontend/src/components/WorkflowCard/CardContent.tsx b/litmus-portal/frontend/src/components/WorkflowCard/CardContent.tsx index 45ace4cd6..e036af437 100644 --- a/litmus-portal/frontend/src/components/WorkflowCard/CardContent.tsx +++ b/litmus-portal/frontend/src/components/WorkflowCard/CardContent.tsx @@ -1,9 +1,11 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ +import { Tooltip, Zoom } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { preDefinedWorkflowData } from '../../models/predefinedWorkflow'; import { RootState } from '../../redux/reducers'; +import trimString from '../../utils/trim'; import parsed from '../../utils/yamlUtils'; import useStyles from './styles'; @@ -104,11 +106,27 @@ const CardContent: React.FC = ({
Contributed by {provider}
{description ? ( -
{description}
+ description.length > 30 ? ( + +
+ {trimString(description, 31)} +
+
+ ) : ( +
+ {trimString(description, 31)} +
+ ) ) : ( )} - {description ? description.length < 28 ?
:
: }
{/*
diff --git a/litmus-portal/frontend/src/components/WorkflowCard/styles.ts b/litmus-portal/frontend/src/components/WorkflowCard/styles.ts index 5fae2d407..5b6eca3c3 100644 --- a/litmus-portal/frontend/src/components/WorkflowCard/styles.ts +++ b/litmus-portal/frontend/src/components/WorkflowCard/styles.ts @@ -5,7 +5,7 @@ const useStyles = makeStyles((theme) => ({ customCard: { background: theme.palette.cards.background, - height: '15.625rem', + height: '16rem', width: '11.875rem', borderRadius: 3, fontSize: '0.875rem', @@ -23,12 +23,20 @@ const useStyles = makeStyles((theme) => ({ width: '70%', }, + // Tooltip + tooltip: { + '.MuiTooltip-tooltip': { + maxWidth: '18.75rem', + }, + }, + // CardContent card: { width: theme.spacing(23), background: theme.palette.background.paper, borderRadius: 3, + height: '16rem', overflow: 'hidden', fontSize: 14, margin: theme.spacing(1), @@ -77,6 +85,7 @@ const useStyles = makeStyles((theme) => ({ // CARD CONTENT cardContent: { color: theme.palette.text.primary, + height: '16rem', }, title: { diff --git a/litmus-portal/frontend/src/components/WorkflowStepper/index.tsx b/litmus-portal/frontend/src/components/WorkflowStepper/index.tsx index 817b41fe1..8c9b97b24 100644 --- a/litmus-portal/frontend/src/components/WorkflowStepper/index.tsx +++ b/litmus-portal/frontend/src/components/WorkflowStepper/index.tsx @@ -18,10 +18,11 @@ import { import { experimentMap, WorkflowData } from '../../models/redux/workflow'; import useActions from '../../redux/actions'; import * as TabActions from '../../redux/actions/tabs'; -import * as WorkflowActions from '../../redux/actions/workflow'; import * as TemplateSelectionActions from '../../redux/actions/template'; +import * as WorkflowActions from '../../redux/actions/workflow'; import { history } from '../../redux/configureStore'; import { RootState } from '../../redux/reducers'; +import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate'; import parsed from '../../utils/yamlUtils'; import ChooseWorkflow from '../../views/CreateWorkflow/ChooseWorkflow/index'; import ReliablityScore from '../../views/CreateWorkflow/ReliabilityScore'; @@ -34,7 +35,6 @@ import ButtonOutline from '../Button/ButtonOutline'; import QontoConnector from './quontoConnector'; import useStyles from './styles'; import useQontoStepIconStyles from './useQontoStepIconStyles'; -import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate'; function getSteps(): string[] { return [ @@ -98,7 +98,7 @@ function getStepContent( gotoStep(page)} /> ); case 1: - return ; + return ; case 2: return ; case 3: @@ -106,7 +106,9 @@ function getStepContent( case 4: return ; case 5: - return gotoStep(page)} />; + return ( + gotoStep(page)} /> + ); default: return ( gotoStep(page)} /> @@ -390,19 +392,20 @@ const CustomStepper = () => { >
mark
- A new chaos workflow, + {t('workflowStepper.aNewChaosWorkflow')}
- was successfully created! + {name}, +
+ {t('workflowStepper.successful')}
- Congratulations on creating your first workflow! Now - information about
it will be displayed on the main - screen of the application. + {t('workflowStepper.congratulationsSub1')}
{' '} + {t('workflowStepper.congratulationsSub2')}
{ history.push('/workflows'); }} > -
Back to workflow
+
{t('workflowStepper.workflowBtn')}
diff --git a/litmus-portal/frontend/src/components/WorkflowStepper/styles.ts b/litmus-portal/frontend/src/components/WorkflowStepper/styles.ts index 8f441e9c8..6d6a2e2b2 100644 --- a/litmus-portal/frontend/src/components/WorkflowStepper/styles.ts +++ b/litmus-portal/frontend/src/components/WorkflowStepper/styles.ts @@ -64,6 +64,10 @@ const useStyles = makeStyles((theme: Theme) => ({ textAlign: 'center', marginTop: theme.spacing(6), }, + successful: { + fontSize: '2.2rem', + fontWeight: 'bold', + }, })); export default useStyles; diff --git a/litmus-portal/frontend/src/containers/app/App.tsx b/litmus-portal/frontend/src/containers/app/App.tsx index 075b39ab2..ae5cad790 100644 --- a/litmus-portal/frontend/src/containers/app/App.tsx +++ b/litmus-portal/frontend/src/containers/app/App.tsx @@ -112,7 +112,7 @@ const Routes: React.FC = ({ isOwner, isProjectAvailable }) => { /> = ({ - start, - end, - interval, - label, - type, -}) => { - const classes = useStyles(); - const [anchorEl, setAnchorEl] = React.useState(null); - const [age, setAge] = React.useState(0); - const open = Boolean(anchorEl); - const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { - setAge((event.target.value as unknown) as number); - setAnchorEl(null); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - const size = (end - start) / interval; - - const names: number[] = [start]; - for (let i = 1; i <= size; i += 1) { - names[i] = names[i - 1] + interval; - } - - return ( -
- - - - - - {names.map((name) => ( - { - setAge(name); - setAnchorEl(null); - }} - > - {name} {type} - - ))} - -
- ); -}; -export default SetTime; diff --git a/litmus-portal/frontend/src/pages/SchedulePage/SetTime/styles.ts b/litmus-portal/frontend/src/pages/SchedulePage/SetTime/styles.ts deleted file mode 100644 index 3448d8faf..000000000 --- a/litmus-portal/frontend/src/pages/SchedulePage/SetTime/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { makeStyles, Theme } from '@material-ui/core/styles'; - -const useStyles = makeStyles((theme: Theme) => ({ - button: { - fontSize: '0.75rem', - '&:hover': { - focusVisible: 'none', - background: theme.palette.secondary.contrastText, - }, - marginTop: theme.spacing(0.7), - textTransform: 'none', - fontWeight: 'normal', - }, - textField: { - width: '4.4375rem', - height: '2.75rem', - marginLeft: theme.spacing(1.875), - }, -})); -export default useStyles; diff --git a/litmus-portal/frontend/src/pages/SchedulePage/index.tsx b/litmus-portal/frontend/src/pages/SchedulePage/index.tsx index 5f12579c4..c01b0df2f 100644 --- a/litmus-portal/frontend/src/pages/SchedulePage/index.tsx +++ b/litmus-portal/frontend/src/pages/SchedulePage/index.tsx @@ -1,403 +1,554 @@ -import { - Divider, - FormControl, - FormControlLabel, - Radio, - RadioGroup, - Select, - Typography, -} from '@material-ui/core'; -import React from 'react'; +import { useMutation, useQuery } from '@apollo/client'; +import Step from '@material-ui/core/Step'; +import { StepIconProps } from '@material-ui/core/StepIcon'; +import StepLabel from '@material-ui/core/StepLabel'; +import Stepper from '@material-ui/core/Stepper'; +import Typography from '@material-ui/core/Typography'; +import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import YAML from 'yaml'; import ButtonFilled from '../../components/Button/ButtonFilled'; import ButtonOutline from '../../components/Button/ButtonOutline'; -import CustomDate from '../../components/DateTime/CustomDate/index'; -import CustomTime from '../../components/DateTime/CustomTime/index'; +import Loader from '../../components/Loader'; +import QontoConnector from '../../components/WorkflowStepper/quontoConnector'; +import useStyles from '../../components/WorkflowStepper/styles'; +import useQontoStepIconStyles from '../../components/WorkflowStepper/useQontoStepIconStyles'; import Scaffold from '../../containers/layouts/Scaffold'; -import SetTime from './SetTime/index'; -import useStyles from './styles'; +import Unimodal from '../../containers/layouts/Unimodal'; +import { UPDATE_SCHEDULE } from '../../graphql/mutations'; +import { SCHEDULE_DETAILS } from '../../graphql/queries'; +import { + CreateWorkFlowInput, + UpdateWorkflowResponse, + WeightMap, +} from '../../models/graphql/createWorkflowData'; +import { ScheduleDataVars, Schedules } from '../../models/graphql/scheduleData'; +import { experimentMap, WorkflowData } from '../../models/redux/workflow'; +import useActions from '../../redux/actions'; +import * as TabActions from '../../redux/actions/tabs'; +import * as TemplateSelectionActions from '../../redux/actions/template'; +import * as WorkflowActions from '../../redux/actions/workflow'; +import { history } from '../../redux/configureStore'; +import { RootState } from '../../redux/reducers'; +import parsed from '../../utils/yamlUtils'; +import ChooseWorkflow from '../../views/CreateWorkflow/ChooseWorkflow/index'; +import ReliablityScore from '../../views/CreateWorkflow/ReliabilityScore'; +import ScheduleWorkflow from '../../views/CreateWorkflow/ScheduleWorkflow'; +import TuneWorkflow from '../../views/CreateWorkflow/TuneWorkflow/index'; +import VerifyCommit from '../../views/CreateWorkflow/VerifyCommit'; +import ChooseAWorkflowCluster from '../../views/CreateWorkflow/WorkflowCluster'; +import { cronWorkflow, workflowOnce } from './templates'; -const SchedulePage: React.FC = () => { - const { t } = useTranslation(); - const start = 0; - const end = 10; - const interval = 2; +interface URLParams { + workflowName: string; + projectID: string; +} - const classes = useStyles(); - // controls radio buttons - const [value, setValue] = React.useState('now'); - const handleChange = (event: React.ChangeEvent) => { - setValue(event.target.value); - }; +interface Weights { + experimentName: string; + weight: number; +} - // controls inner radio buttons of recurring schedule - const [valueDef, setValueDef] = React.useState(''); - const handleChangeInstance = (event: React.ChangeEvent) => { - setValueDef(event.target.value); - }; +function getSteps(): string[] { + return [ + 'Target Cluster', + 'Choose a workflow', + 'Tune workflow', + 'Reliability score', + 'Schedule', + 'Verify and Commit', + ]; +} - // sets weekdays - const [days, setDays] = React.useState('Monday'); +function QontoStepIcon(props: StepIconProps) { + const classes = useQontoStepIconStyles(); + const { active, completed } = props; - // sets dates - const [dates, setDates] = React.useState(1); - - // stores dates in an array - const names: number[] = [1]; - for (let i = 1; i <= 30; i += 1) { - names[i] = i + 1; + if (completed) { + return ( +
+ Not Completed Icon +
+ ); + } + if (active) { + return ( +
+
+
+ ); } - const weekdays: string[] = [ - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - 'Sunday', - ]; - const [selectedDate, setSelectedDate] = React.useState( - new Date(Date.now()) + return ( +
+ {/* */} +
+
+
+
+ ); +} + +function getStepContent( + stepIndex: number, + gotoStep: (page: number) => void +): React.ReactNode { + switch (stepIndex) { + case 0: + return ( + gotoStep(page)} /> + ); + case 1: + return ; + case 2: + return ; + case 3: + return ; + case 4: + return ; + case 5: + return ( + gotoStep(page)} + /> + ); + default: + return ( + gotoStep(page)} /> + ); + } +} + +const EditScheduledWorkflow = () => { + const classes = useStyles(); + const { t } = useTranslation(); + const template = useActions(TemplateSelectionActions); + const workflowData: WorkflowData = useSelector( + (state: RootState) => state.workflowData + ); + const workflow = useActions(WorkflowActions); + // Get Parameters from URL + const paramData: URLParams = useParams(); + + // Apollo query to get the scheduled data + const { data, loading } = useQuery( + SCHEDULE_DETAILS, + { + variables: { projectID: paramData.projectID }, + fetchPolicy: 'cache-and-network', + } ); - const handleDateChange = (date: Date | null) => { - setSelectedDate(date); + const wfDetails = + data && + data.getScheduledWorkflows.filter( + (wf) => wf.workflow_name === paramData.workflowName + )[0]; + const doc = new YAML.Document(); + const a: Weights[] = []; + useEffect(() => { + if (wfDetails !== undefined) { + for (let i = 0; i < wfDetails?.weightages.length; i++) { + a.push({ + experimentName: wfDetails?.weightages[i].experiment_name, + weight: wfDetails?.weightages[i].weightage, + }); + } + doc.contents = JSON.parse(wfDetails?.workflow_manifest); + workflow.setWorkflowDetails({ + workflow_id: wfDetails?.workflow_id, + name: wfDetails?.workflow_name, + yaml: doc.toString(), + id: 0, + description: wfDetails?.workflow_description, + weights: a, + isCustomWorkflow: wfDetails?.isCustomWorkflow, + clusterid: wfDetails?.cluster_id, + cronSyntax: wfDetails?.cronSyntax, + scheduleType: { + scheduleOnce: + wfDetails?.cronSyntax === '' ? 'now' : 'recurringSchedule', + recurringSchedule: '', + }, + scheduleInput: { + hour_interval: 0, + day: 1, + weekday: 'Monday', + time: new Date(), + date: new Date(), + }, + }); + } + template.selectTemplate({ selectTemplateID: 0, isDisable: false }); + }, [data]); + + const { + yaml, + weights, + description, + isCustomWorkflow, + cronSyntax, + name, + clusterid, + scheduleType, + } = workflowData; + + const [activeStep, setActiveStep] = React.useState(4); + + const selectedProjectID = useSelector( + (state: RootState) => state.userData.selectedProjectID + ); + const isDisable = useSelector( + (state: RootState) => state.selectTemplate.isDisable + ); + const userRole = useSelector((state: RootState) => state.userData.userRole); + const tabs = useActions(TabActions); + const scheduleOnce = workflowOnce; + const scheduleMore = cronWorkflow; + const [invalidYaml, setinValidYaml] = React.useState(false); + const steps = getSteps(); + + function EditYaml() { + const oldParsedYaml = YAML.parse(yaml); + const NewLink: string = ' '; + let NewYaml: string = ' '; + if ( + oldParsedYaml.kind === 'Workflow' && + scheduleType.scheduleOnce !== 'now' + ) { + const oldParsedYaml = YAML.parse(yaml); + const newParsedYaml = YAML.parse(scheduleMore); + delete newParsedYaml.spec.workflowSpec; + newParsedYaml.spec.schedule = cronSyntax; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = workflowData.name; + newParsedYaml.metadata.labels = { + workflow_id: workflowData.workflow_id, + }; + newParsedYaml.spec.workflowSpec = oldParsedYaml.spec; + const tz = { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC', + }; + Object.entries(tz).forEach(([key, value]) => { + newParsedYaml.spec[key] = value; + }); + NewYaml = YAML.stringify(newParsedYaml); + workflow.setWorkflowDetails({ + link: NewLink, + yaml: NewYaml, + }); + } + if ( + oldParsedYaml.kind === 'CronWorkflow' && + scheduleType.scheduleOnce === 'now' + ) { + const oldParsedYaml = YAML.parse(yaml); + const newParsedYaml = YAML.parse(scheduleOnce); + delete newParsedYaml.spec; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = workflowData.name; + newParsedYaml.spec = oldParsedYaml.spec.workflowSpec; + newParsedYaml.metadata.labels = { + workflow_id: workflowData.workflow_id, + }; + NewYaml = YAML.stringify(newParsedYaml); + workflow.setWorkflowDetails({ + link: NewLink, + yaml: NewYaml, + }); + } + if ( + oldParsedYaml.kind === 'CronWorkflow' && + scheduleType.scheduleOnce !== 'now' + ) { + const newParsedYaml = YAML.parse(yaml); + newParsedYaml.spec.schedule = cronSyntax; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = workflowData.name; + newParsedYaml.metadata.labels = { workflow_id: workflowData.workflow_id }; + const tz = { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC', + }; + Object.entries(tz).forEach(([key, value]) => { + newParsedYaml.spec[key] = value; + }); + NewYaml = YAML.stringify(newParsedYaml); + workflow.setWorkflowDetails({ + link: NewLink, + yaml: NewYaml, + }); + } + } + + const handleNext = () => { + if (activeStep === 2) { + const tests = parsed(yaml); + const arr: experimentMap[] = []; + const hashMap = new Map(); + weights.forEach((weight) => { + hashMap.set(weight.experimentName, weight.weight); + }); + tests.forEach((test) => { + let value = 10; + if (hashMap.has(test)) { + value = hashMap.get(test); + } + arr.push({ experimentName: test, weight: value }); + }); + workflow.setWorkflowDetails({ + weights: arr, + }); + if (arr.length === 0) { + setinValidYaml(true); + } else { + setinValidYaml(false); + setActiveStep((prevActiveStep) => prevActiveStep + 1); + } + } else if (activeStep === 4) { + EditYaml(); + setActiveStep((prevActiveStep) => prevActiveStep + 1); + } else { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + } }; + + const [open, setOpen] = React.useState(false); + + const handleBack = () => { + if (activeStep === 2) { + setinValidYaml(false); + } else if (activeStep === 4 && isDisable === true) { + template.selectTemplate({ isDisable: false }); + } + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const [createChaosWorkFlow] = useMutation< + UpdateWorkflowResponse, + CreateWorkFlowInput + >(UPDATE_SCHEDULE, { + onCompleted: () => { + setOpen(true); + }, + }); + + const handleMutation = () => { + if (name.length !== 0 && description.length !== 0 && weights.length !== 0) { + const weightData: WeightMap[] = []; + + weights.forEach((data) => { + weightData.push({ + experiment_name: data.experimentName, + weightage: data.weight, + }); + }); + + /* JSON.stringify takes 3 parameters [object to be converted, + a function to alter the conversion, spaces to be shown in final result for indentation ] */ + const yml = YAML.parse(yaml); + const yamlJson = JSON.stringify(yml, null, 2); // Converted to Stringified JSON + + const chaosWorkFlowInputs = { + workflow_id: wfDetails?.workflow_id, + workflow_manifest: yamlJson, + cronSyntax, + workflow_name: name, + workflow_description: description, + isCustomWorkflow, + weightages: weightData, + project_id: selectedProjectID, + cluster_id: clusterid, + }; + + // console.log(chaosWorkFlowInputs); + createChaosWorkFlow({ + variables: { ChaosWorkFlowInput: chaosWorkFlowInputs }, + }); + } + }; + + const handleOpen = () => { + handleMutation(); + setOpen(true); + }; + + const handleClose = () => { + history.push('/workflows'); + setOpen(false); + }; + + function gotoStep({ page }: { page: number }) { + setActiveStep(page); + } + + // Check correct permissions for user + if (userRole === 'Viewer') + return ( + <> + + {t('schedule.missingPerm')} + + + {t('schedule.requiredPerm')} + +
+ + {t('schedule.contact')} + + + {/* Back Button */} +
+ history.goBack()}> + {t('schedule.backBtn')} + +
+ + ); + return ( -
- - {t('schedule.heading')} - - - {t('schedule.headingDesc')} - + {loading ? ( + + ) : (
-
- {/* Upper segment */} -
+ } + className={classes.stepper} + alternativeLabel + > + {steps.map((label, i) => ( + + {activeStep === i ? ( + +
+ {label} +
+
+ ) : ( + +
+ {label} +
+
+ )} +
+ ))} +
+
+
- - {t('schedule.headingText')} - - -
- - {t('schedule.description')} - -
-
- calendar -
- - - {/* Lower segment */} -
- - - {/* options to choose schedule */} - } - label={ - - {t('schedule.scheduleNow')} - - } - /> - - } - label={ - - {t('schedule.scheduleAfter')} - - } - /> - {value === 'afterSometime' ? ( -
- - {t('schedule.scheduleAfterSometime')} - -
- - {t('schedule.after')} - - - - -
+
+ mark +
+ {t('schedule.chaosWorkflow')} +
+ {t('schedule.successful')}
- ) : ( - <> - )} - } - label={ - - {t('schedule.scheduleSpecificTime')} - - } - /> - - {value === 'specificTime' ? ( -
- - {t('schedule.scheduleFuture')} - -
- - -
+
+ {t('schedule.congratulationsSub1')} +
+ {t('schedule.congratulationsSub2')}
- ) : ( - <> - )} - } - label={ - - {t('schedule.scheduleRecurring')} - - } - /> - {value === 'recurringScedule' ? ( -
- - {t('schedule.scheduleRecurringTime')} - - - {/* options to select time of recurring schedule */} -
- - - } - label="Every Hour" - /> - {valueDef === 'everyHr' ? ( -
-
- - {t('schedule.At')} - - -
-
- ) : ( - <> - )} - } - label="Every Day " - /> - {valueDef === 'everyDay' ? ( -
-
- - {t('schedule.At')} - - -
-
- ) : ( - <> - )} - } - label="Every Week " - /> - {valueDef === 'everyWeek' ? ( -
-
- - {t('schedule.scheduleOn')} - - - - - - {t('schedule.at')} - - -
-
- ) : ( - <> - )} - } - label="Every Month" - /> - {valueDef === 'everyMonth' ? ( -
-
- - {t('schedule.scheduleOn')} - - - - - - {t('schedule.at')} - - -
-
- ) : ( - <> - )} -
-
-
+
+ { + setOpen(false); + tabs.changeWorkflowsTabs(0); + history.push('/workflows'); + }} + > +
{t('workflowStepper.workflowBtn')}
+
- ) : ( - <> - )} - - -
- -
- {}}> - Cancel - -
- {}}> - {t('schedule.save')} - +
+ + {getStepContent(activeStep, (page: number) => + gotoStep({ page }) + )} +
+ {/* Control Buttons */} + +
+ {activeStep === steps.length - 2 ? ( + + Back + + ) : activeStep !== 1 ? ( + + Back + + ) : null} + {activeStep === steps.length - 1 ? ( + +
{t('workflowStepper.finish')}
+
+ ) : ( + handleNext()} + isPrimary + isDisabled={isDisable} + > +
+ {t('workflowStepper.next')} + next +
+
+ )} + {invalidYaml ? ( + + {t('workflowStepper.continueError')} + + ) : null}
-
+ )} ); }; -export default SchedulePage; +export default EditScheduledWorkflow; diff --git a/litmus-portal/frontend/src/pages/SchedulePage/styles.ts b/litmus-portal/frontend/src/pages/SchedulePage/styles.ts deleted file mode 100644 index c89386489..000000000 --- a/litmus-portal/frontend/src/pages/SchedulePage/styles.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { makeStyles, Theme } from '@material-ui/core/styles'; - -const useStyles = makeStyles((theme: Theme) => ({ - rootContainer: { - paddingTop: 50, - paddingLeft: 30, - }, - root: { - backgroundColor: 'rgba(255, 255, 255, 0.6)', - width: '90%', - marginTop: 30, - border: 1, - borderColor: theme.palette.text.disabled, - borderRadius: '0.1875rem', - }, - scHeader: { - paddingLeft: theme.spacing(3.75), - paddingRight: theme.spacing(3.75), - paddingTop: theme.spacing(3.75), - paddingBottom: theme.spacing(3.75), - }, - - mainHeader: { - marginTop: theme.spacing(1.25), - fontSize: '36px', - }, - - headerDesc: { - fontSize: '16px', - }, - /* styles for upper and lower segment */ - scSegments: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-even', - }, - - headerText: { - marginTop: theme.spacing(1.25), - fontSize: '1.5625rem', - }, - schBody: { - width: '32.18rem', - }, - captionText: { - fontSize: '0.75rem', - color: theme.palette.text.disabled, - }, - schLater: { - marginLeft: theme.spacing(3.75), - }, - - radioText: { - fontSize: '0.875rem', - }, - description: { - width: '32.18rem', - marginTop: theme.spacing(3.25), - marginBottom: theme.spacing(7.5), - fontSize: '1rem', - }, - - calIcon: { - width: '7rem', - height: '6.31rem', - marginTop: theme.spacing(5), - marginLeft: 'auto', - }, - - scFormControl: { - marginTop: theme.spacing(5), - }, - - /* For recurring schedule options */ - scRandom: { - display: 'flex', - flexDirection: 'row', - marginLeft: theme.spacing(1.625), - marginBottom: theme.spacing(4.125), - height: '2.75rem', - alignItems: 'center', - }, - - formControl: { - margin: theme.spacing(1), - }, - - /* for option- after sometime */ - wtDateTime: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - width: '19.2rem', - height: '2.75rem', - marginTop: theme.spacing(2.125), - marginBottom: theme.spacing(4.125), - }, - - /* for option- at specific time */ - innerSpecific: { - marginTop: theme.spacing(2.125), - marginBottom: theme.spacing(4.125), - display: 'flex', - flexDirection: 'row', - alignItems: 'stretch', - }, - - innerRecurring: { - marginTop: theme.spacing(2.125), - }, - - /* for each options of recurring schedule */ - scRandsub1: { - margin: theme.spacing(1.875), - fontSize: '0.75rem', - color: theme.palette.text.disabled, - }, - - /* for selecting weekdays */ - formControlDT: { - margin: theme.spacing(1), - minWidth: '6.6025rem', - minHeight: '2.75rem', - '&:select': { - focusVisible: 'none', - background: theme.palette.secondary.contrastText, - }, - }, - - /* for selecting date of every month */ - formControlMonth: { - margin: theme.spacing(1), - minWidth: '5.3125rem', - minHeight: '2.75rem', - }, - - /* for each select */ - select: { - padding: theme.spacing(2.5), - border: '1px solid #D1D2D7', - borderRadius: '0.1875rem', - fontSize: '0.75rem', - height: '2.75rem', - }, - /* for each option */ - opt: { - marginBottom: theme.spacing(1), - marginLeft: theme.spacing(0.2), - marginRight: theme.spacing(0.2), - paddingLeft: theme.spacing(1), - borderRadius: '0.0625rem', - '&:hover': { - background: '#D1D2D7', - }, - }, - /* style for submit section */ - submitDiv: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-even', - marginTop: theme.spacing(6.25), - }, -})); - -export default useStyles; diff --git a/litmus-portal/frontend/src/pages/SchedulePage/templates.ts b/litmus-portal/frontend/src/pages/SchedulePage/templates.ts new file mode 100644 index 000000000..1001c6c32 --- /dev/null +++ b/litmus-portal/frontend/src/pages/SchedulePage/templates.ts @@ -0,0 +1,4 @@ +export const workflowOnce = + '{\r\n "apiVersion": "argoproj.io/v1alpha1",\r\n "kind": "Workflow",\r\n "metadata": {\r\n "generateName": "argowf-chaos-node-cpu-hog-",\r\n "namespace": "litmus"\r\n },\r\n "spec": null\r\n}'; +export const cronWorkflow = + '{\r\n "apiVersion": "argoproj.io/v1alpha1",\r\n "kind": "CronWorkflow",\r\n "metadata": {\r\n "name": "argo-chaos-pod-memory-cron-wf",\r\n "namespace": "litmus"\r\n },\r\n "spec": {\r\n "schedule": "0 * * * *",\r\n "concurrencyPolicy": "Forbid",\r\n "startingDeadlineSeconds": 0,\r\n "workflowSpec": null\r\n }\r\n}'; diff --git a/litmus-portal/frontend/src/redux/reducers/workflow.ts b/litmus-portal/frontend/src/redux/reducers/workflow.ts index 9777e374d..ec99e401c 100644 --- a/litmus-portal/frontend/src/redux/reducers/workflow.ts +++ b/litmus-portal/frontend/src/redux/reducers/workflow.ts @@ -14,6 +14,7 @@ const initialState: WorkflowData = { description: '', weights: [], isCustomWorkflow: false, + isRecurring: false, namespace: 'litmus', clusterid: '', cronSyntax: '', diff --git a/litmus-portal/frontend/src/svg/crossmark.svg b/litmus-portal/frontend/src/svg/crossmark.svg new file mode 100644 index 000000000..603ed1e9d --- /dev/null +++ b/litmus-portal/frontend/src/svg/crossmark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/litmus-portal/frontend/src/utils/trim.ts b/litmus-portal/frontend/src/utils/trim.ts new file mode 100644 index 000000000..d3682c135 --- /dev/null +++ b/litmus-portal/frontend/src/utils/trim.ts @@ -0,0 +1,5 @@ +const trimString = (string: string, length: number): string => { + return string.length > length ? `${string.substring(0, length)}...` : string; +}; + +export default trimString; diff --git a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseAnalytics/WorkflowComparisonTable/index.tsx b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseAnalytics/WorkflowComparisonTable/index.tsx index f8cbf15f9..d90bc5b05 100644 --- a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseAnalytics/WorkflowComparisonTable/index.tsx +++ b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseAnalytics/WorkflowComparisonTable/index.tsx @@ -24,7 +24,7 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import Loader from '../../../../components/Loader'; -import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/quries'; +import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/queries'; import { ExecutionData, WeightageMap, diff --git a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx index ebc6c7bfa..7af3d3f94 100644 --- a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx +++ b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx @@ -8,31 +8,47 @@ import { Typography, } from '@material-ui/core'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import GetAppIcon from '@material-ui/icons/GetApp'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import cronstrue from 'cronstrue'; import moment from 'moment'; import React from 'react'; -import cronstrue from 'cronstrue'; -import YAML from 'yaml'; -import GetAppIcon from '@material-ui/icons/GetApp'; +import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; +import YAML from 'yaml'; +import ButtonFilled from '../../../components/Button/ButtonFilled'; +import ButtonOutline from '../../../components/Button/ButtonOutline'; +import Unimodal from '../../../containers/layouts/Unimodal'; import { ScheduleWorkflow } from '../../../models/graphql/scheduleData'; -import useStyles from './styles'; -import ExperimentPoints from './ExperimentPoints'; +import useActions from '../../../redux/actions'; +import * as WorkflowActions from '../../../redux/actions/workflow'; +import { history } from '../../../redux/configureStore'; import { RootState } from '../../../redux/reducers'; +import { ReactComponent as CrossMarkIcon } from '../../../svg/crossmark.svg'; +import ExperimentPoints from './ExperimentPoints'; +import useStyles from './styles'; interface TableDataProps { data: ScheduleWorkflow; deleteRow: (wfid: string) => void; } +interface Weights { + experimentName: string; + weight: number; +} + const TableData: React.FC = ({ data, deleteRow }) => { const classes = useStyles(); + const { t } = useTranslation(); // States for PopOver to display Experiment Weights const [anchorEl, setAnchorEl] = React.useState(null); const [popAnchorEl, setPopAnchorEl] = React.useState( null ); + const [isModalOpen, setIsModalOpen] = React.useState(false); + const open = Boolean(anchorEl); const isOpen = Boolean(popAnchorEl); const id = isOpen ? 'simple-popover' : undefined; @@ -40,6 +56,7 @@ const TableData: React.FC = ({ data, deleteRow }) => { setPopAnchorEl(null); }; + const workflow = useActions(WorkflowActions); const userData = useSelector((state: RootState) => state.userData); const handlePopOverClick = (event: React.MouseEvent) => { @@ -77,6 +94,19 @@ const TableData: React.FC = ({ data, deleteRow }) => { return 'Date not available'; }; + const editSchedule = () => { + history.push( + `/workflows/schedule/${data.project_id}/${data.workflow_name}` + ); + }; + + // If regularity is not Once then set recurring schedule state to true + if (data.cronSyntax !== '') { + workflow.setWorkflowDetails({ + isRecurring: true, + }); + } + return ( <> @@ -171,6 +201,22 @@ const TableData: React.FC = ({ data, deleteRow }) => { open={open} onClose={handleClose} > + {data.cronSyntax !== '' ? ( + editSchedule()}> +
+ Edit Schedule + + Edit Schedule + +
+
+ ) : ( + <> + )} @@ -188,10 +234,7 @@ const TableData: React.FC = ({ data, deleteRow }) => {
{userData.userRole !== 'Viewer' ? ( - deleteRow(data.workflow_id)} - > + setIsModalOpen(true)}>
= ({ data, deleteRow }) => { ) : null} + {isModalOpen ? ( + +
+ + + {t('createWorkflow.scheduleWorkflow.modalHeader')} + + + {t('createWorkflow.scheduleWorkflow.modalSubheader')} + +
+ setIsModalOpen(false)} + > + {t('createWorkflow.scheduleWorkflow.cancelBtn')} + + { + deleteRow(data.workflow_id); + setIsModalOpen(false); + }} + > + {t('createWorkflow.scheduleWorkflow.deleteBtn')} + +
+
+
+ ) : ( + <> + )} ); }; diff --git a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/styles.ts b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/styles.ts index 721d4c928..318e47113 100644 --- a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/styles.ts +++ b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/styles.ts @@ -181,6 +181,34 @@ const useStyles = makeStyles((theme) => ({ : theme.palette.error.dark, fontWeight: 500, }, + + // Modal + modalDiv: { + display: 'flex', + flexDirection: 'column', + height: '25rem', + marginTop: '10%', + alignItems: 'center', + justifyContent: 'center', + }, + modalHeader: { + fontSize: '2.125rem', + fontWeight: 400, + marginBottom: theme.spacing(2.5), + marginTop: theme.spacing(2.5), + width: '31.25rem', + }, + modalConfirm: { + fontSize: '1.25rem', + marginBottom: theme.spacing(5), + width: '31.25rem', + }, + modalBtns: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + width: '16rem', + }, })); export default useStyles; diff --git a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseTemplate/styles.ts b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseTemplate/styles.ts index 479be275f..b9457d783 100644 --- a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseTemplate/styles.ts +++ b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseTemplate/styles.ts @@ -59,8 +59,10 @@ const useStyles = makeStyles((theme: Theme) => ({ // Experiment Details experimentWrapperDiv: { - display: 'inline-block', - margin: '1.6rem 0', + display: 'grid', + margin: '2rem', + gridTemplateColumns: '1fr 1fr 1fr 1fr', + gridGap: '1.5rem', }, tests: { width: '17rem', diff --git a/litmus-portal/frontend/src/views/ChaosWorkflows/Templates/index.tsx b/litmus-portal/frontend/src/views/ChaosWorkflows/Templates/index.tsx index d404e2f7f..24467550f 100644 --- a/litmus-portal/frontend/src/views/ChaosWorkflows/Templates/index.tsx +++ b/litmus-portal/frontend/src/views/ChaosWorkflows/Templates/index.tsx @@ -15,7 +15,7 @@ const Templates = () => { const testWeights: number[] = []; // Setting initial selected template ID to 0 - template.selectTemplate({ selectedTemplateID: 0, isDisable: true }); + template.selectTemplate({ selectedTemplateID: -1, isDisable: true }); const selectWorkflow = (index: number) => { // Updating template ID to the selected one diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/ChooseWorkflow/index.tsx b/litmus-portal/frontend/src/views/CreateWorkflow/ChooseWorkflow/index.tsx index ca0c9f8c9..35ff74262 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/ChooseWorkflow/index.tsx +++ b/litmus-portal/frontend/src/views/CreateWorkflow/ChooseWorkflow/index.tsx @@ -1,14 +1,15 @@ import { Typography } from '@material-ui/core'; import Divider from '@material-ui/core/Divider'; import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; import ButtonFilled from '../../../components/Button/ButtonFilled'; import ButtonOutline from '../../../components/Button/ButtonOutline'; import InputField from '../../../components/InputField'; import PredifinedWorkflows from '../../../components/PredifinedWorkflows'; import workflowsList from '../../../components/PredifinedWorkflows/data'; import Unimodal from '../../../containers/layouts/Unimodal'; +import { WorkflowData } from '../../../models/redux/workflow'; import useActions from '../../../redux/actions'; import * as TemplateSelectionActions from '../../../redux/actions/template'; import * as WorkflowActions from '../../../redux/actions/workflow'; @@ -18,7 +19,11 @@ import useStyles, { CssTextField } from './styles'; // import { getWkfRunCount } from "../../utils"; -const ChooseWorkflow: React.FC = () => { +interface ChooseWorkflowProps { + isEditable?: boolean; +} + +const ChooseWorkflow: React.FC = ({ isEditable }) => { const classes = useStyles(); const { t } = useTranslation(); @@ -30,6 +35,9 @@ const ChooseWorkflow: React.FC = () => { const selectedTemplateID = useSelector( (state: RootState) => state.selectTemplate.selectedTemplateID ); + const workflowData: WorkflowData = useSelector( + (state: RootState) => state.workflowData + ); const [open, setOpen] = React.useState(false); const isSuccess = React.useRef(false); @@ -92,17 +100,24 @@ const ChooseWorkflow: React.FC = () => { const selectWorkflow = (index: number) => { template.selectTemplate({ selectedTemplateID: index, isDisable: false }); - const timeStampBasedWorkflowName: string = `argowf-chaos-${ - workflowsList[index].title - }-${Math.round(new Date().getTime() / 1000)}`; + const timeStampBasedWorkflowName: string = isEditable + ? `argowf-chaos-${workflowsList[index].title}-${Math.round( + new Date().getTime() / 1000 + )}` + : workflowData.name; workflow.setWorkflowDetails({ name: timeStampBasedWorkflowName, link: workflowsList[index].chaosWkfCRDLink, id: workflowsList[index].workflowID, yaml: 'none', - description: workflowsList[index].description, - isCustomWorkflow: workflowsList[index].isCustom, + description: isEditable + ? workflowsList[index].description + : workflowData.description, + isCustomWorkflow: isEditable + ? workflowsList[index].isCustom + : workflowData.isCustomWorkflow, + isRecurring: false, }); setWorkflowData({ @@ -119,16 +134,22 @@ const ChooseWorkflow: React.FC = () => { useEffect(() => { const index = selectedTemplateID; - const timeStampBasedWorkflowName: string = `argowf-chaos-${ - workflowsList[index].title - }-${Math.round(new Date().getTime() / 1000)}`; + const timeStampBasedWorkflowName: string = isEditable + ? `argowf-chaos-${workflowsList[index].title}-${Math.round( + new Date().getTime() / 1000 + )}` + : workflowData.name; + workflow.setWorkflowDetails({ name: timeStampBasedWorkflowName, link: workflowsList[index].chaosWkfCRDLink, id: workflowsList[index].workflowID, yaml: 'none', description: workflowsList[index].description, - isCustomWorkflow: workflowsList[index].isCustom, + isCustomWorkflow: isEditable + ? workflowsList[index].isCustom + : workflowData.isCustomWorkflow, + isRecurring: false, }); setWorkflowData({ @@ -199,6 +220,7 @@ const ChooseWorkflow: React.FC = () => { { data-cy="testRunButton" >
- Play icon + Play icon {t('createWorkflow.reliabilityScore.button.demo')} diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx index 156cddaf0..49cb600dc 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx +++ b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx @@ -8,17 +8,18 @@ import { Typography, } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import YAML from 'yaml'; import CustomDate from '../../../components/DateTime/CustomDate/index'; import CustomTime from '../../../components/DateTime/CustomTime/index'; import { WorkflowData } from '../../../models/redux/workflow'; import useActions from '../../../redux/actions'; +import * as TemplateSelectionActions from '../../../redux/actions/template'; +import * as WorkflowActions from '../../../redux/actions/workflow'; import { RootState } from '../../../redux/reducers'; import SetTime from './SetTime/index'; import useStyles from './styles'; -import * as WorkflowActions from '../../../redux/actions/workflow'; -import * as TemplateSelectionActions from '../../../redux/actions/template'; interface ScheduleSyntax { minute: string | undefined; @@ -71,6 +72,10 @@ const ScheduleWorkflow: React.FC = () => { workflow.setWorkflowDetails({ cronSyntax, }); + if (value === 'disable') + workflow.setWorkflowDetails({ + isDisabled: true, + }); }, [cronValue]); const classes = useStyles(); @@ -188,6 +193,9 @@ const ScheduleWorkflow: React.FC = () => { // UseEffect to update the values of CronSyntax on radio button change useEffect(() => { if (value === 'now') { + workflow.setWorkflowDetails({ + isDisabled: false, + }); setValueDef(''); setCronValue({ minute: '', @@ -197,6 +205,11 @@ const ScheduleWorkflow: React.FC = () => { day_week: '', }); } + if (value === 'disable') { + workflow.setWorkflowDetails({ + isDisabled: true, + }); + } if (value === 'specificTime') { setValueDef(''); setCronValue({ @@ -226,6 +239,9 @@ const ScheduleWorkflow: React.FC = () => { } } if (valueDef === 'everyHr') { + workflow.setWorkflowDetails({ + isDisabled: false, + }); setCronValue({ minute: minute.toString(), hour: '0-23', @@ -235,6 +251,9 @@ const ScheduleWorkflow: React.FC = () => { }); } if (valueDef === 'everyDay') { + workflow.setWorkflowDetails({ + isDisabled: false, + }); setCronValue({ minute: selectedTime?.getMinutes().toString(), hour: selectedTime?.getHours().toString(), @@ -244,6 +263,9 @@ const ScheduleWorkflow: React.FC = () => { }); } if (valueDef === 'everyWeek') { + workflow.setWorkflowDetails({ + isDisabled: false, + }); setCronValue({ minute: selectedTime?.getMinutes().toString(), hour: selectedTime?.getHours().toString(), @@ -253,6 +275,9 @@ const ScheduleWorkflow: React.FC = () => { }); } if (valueDef === 'everyMonth') { + workflow.setWorkflowDetails({ + isDisabled: false, + }); setCronValue({ minute: selectedTime?.getMinutes().toString(), hour: selectedTime?.getHours().toString(), @@ -262,6 +287,9 @@ const ScheduleWorkflow: React.FC = () => { }); } if (value === 'recurringSchedule' && valueDef === '') { + workflow.setWorkflowDetails({ + isDisabled: false, + }); template.selectTemplate({ isDisable: true }); } else { template.selectTemplate({ isDisable: false }); @@ -279,7 +307,7 @@ const ScheduleWorkflow: React.FC = () => {
{/* Upper segment */}
-
+
{t('createWorkflow.scheduleWorkflow.header')} @@ -308,16 +336,30 @@ const ScheduleWorkflow: React.FC = () => { onChange={handleChange} > {/* options to choose schedule */} - } - label={ - - {t('createWorkflow.scheduleWorkflow.radio.now')} - - } - /> - } + label={ + + {t('createWorkflow.scheduleWorkflow.radio.now')} + + } + /> + ) : YAML.parse(workflowData.yaml).spec.suspend === false ? ( + } + label={ + + Disable Schedule + + } + /> + ) : ( + <> + )} + {/* } @@ -326,7 +368,7 @@ const ScheduleWorkflow: React.FC = () => { {t('createWorkflow.scheduleWorkflow.radio.specific')} } - /> + /> */} {value === 'specificTime' ? (
diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts index 3277b64df..afd7a7f8b 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts +++ b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts @@ -40,6 +40,7 @@ const useStyles = makeStyles((theme: Theme) => ({ radioText: { fontSize: '0.875rem', + color: theme.palette.text.primary, }, description: { width: '32.18rem', diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/TuneWorkflow/index.tsx b/litmus-portal/frontend/src/views/CreateWorkflow/TuneWorkflow/index.tsx index bc20501ef..13bbb2bd5 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/TuneWorkflow/index.tsx +++ b/litmus-portal/frontend/src/views/CreateWorkflow/TuneWorkflow/index.tsx @@ -1,9 +1,9 @@ import { Typography } from '@material-ui/core'; import Divider from '@material-ui/core/Divider'; import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import YAML from 'yaml'; -import { useTranslation } from 'react-i18next'; import Loader from '../../../components/Loader'; import YamlEditor from '../../../components/YamlEditor/Editor'; import { WorkflowData } from '../../../models/redux/workflow'; diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx b/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx index 817692fb9..735bc5562 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx +++ b/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx @@ -1,10 +1,10 @@ import { Divider, IconButton, Typography } from '@material-ui/core'; -import React, { useEffect } from 'react'; -import { useSelector } from 'react-redux'; import EditIcon from '@material-ui/icons/Edit'; import cronstrue from 'cronstrue'; -import YAML from 'yaml'; +import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import YAML from 'yaml'; import AdjustedWeights from '../../../components/AdjustedWeights'; import ButtonFilled from '../../../components/Button/ButtonFilled'; import ButtonOutline from '../../../components/Button/ButtonOutline/index'; @@ -23,9 +23,13 @@ import useStyles from './styles'; interface VerifyCommitProps { gotoStep: (page: number) => void; + isEditable?: boolean; } -const VerifyCommit: React.FC = ({ gotoStep }) => { +const VerifyCommit: React.FC = ({ + gotoStep, + isEditable, +}) => { const classes = useStyles(); const { t } = useTranslation(); @@ -114,7 +118,7 @@ const VerifyCommit: React.FC = ({ gotoStep }) => {
bfinance @@ -139,6 +143,7 @@ const VerifyCommit: React.FC = ({ gotoStep }) => { onchange={(changedName: string) => handleNameChange({ changedName }) } + isEditable={workflowData.isRecurring ? false : isEditable} />
@@ -172,6 +177,7 @@ const VerifyCommit: React.FC = ({ gotoStep }) => { onchange={(changedDesc: string) => handleDescChange({ changedDesc }) } + isEditable={isEditable} />
@@ -233,7 +239,7 @@ const VerifyCommit: React.FC = ({ gotoStep }) => {
{/*
*/} gotoStep(3)} data-cy="testRunButton" > diff --git a/litmus-portal/frontend/src/views/Settings/TeammingTab/Invitation/SentInvitations/TableData.tsx b/litmus-portal/frontend/src/views/Settings/TeammingTab/Invitation/SentInvitations/TableData.tsx index 4c65e5a55..b38003e32 100644 --- a/litmus-portal/frontend/src/views/Settings/TeammingTab/Invitation/SentInvitations/TableData.tsx +++ b/litmus-portal/frontend/src/views/Settings/TeammingTab/Invitation/SentInvitations/TableData.tsx @@ -17,7 +17,7 @@ import { REMOVE_INVITATION, SEND_INVITE, } from '../../../../../graphql/mutations'; -import { GET_USER } from '../../../../../graphql/quries'; +import { GET_USER } from '../../../../../graphql/queries'; import { MemberInvitation, MemberInviteNew, diff --git a/litmus-portal/frontend/src/views/Settings/TeammingTab/InviteNew/Invite/index.tsx b/litmus-portal/frontend/src/views/Settings/TeammingTab/InviteNew/Invite/index.tsx index 6cad7bbd3..82047f912 100644 --- a/litmus-portal/frontend/src/views/Settings/TeammingTab/InviteNew/Invite/index.tsx +++ b/litmus-portal/frontend/src/views/Settings/TeammingTab/InviteNew/Invite/index.tsx @@ -10,11 +10,12 @@ import { Typography, } from '@material-ui/core'; import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; import ButtonFilled from '../../../../../components/Button/ButtonFilled'; import Loader from '../../../../../components/Loader'; -import { ALL_USERS, GET_USER, SEND_INVITE } from '../../../../../graphql'; +import { SEND_INVITE } from '../../../../../graphql/mutations'; +import { ALL_USERS, GET_USER } from '../../../../../graphql/queries'; import { MemberInviteNew, UserInvite, diff --git a/litmus-portal/frontend/src/views/Settings/TeammingTab/tableData.tsx b/litmus-portal/frontend/src/views/Settings/TeammingTab/tableData.tsx index 3c5f698a9..8722adc46 100644 --- a/litmus-portal/frontend/src/views/Settings/TeammingTab/tableData.tsx +++ b/litmus-portal/frontend/src/views/Settings/TeammingTab/tableData.tsx @@ -9,7 +9,7 @@ import ButtonOutline from '../../../components/Button/ButtonOutline'; import Loader from '../../../components/Loader'; import Unimodal from '../../../containers/layouts/Unimodal'; import { REMOVE_INVITATION } from '../../../graphql/mutations'; -import { GET_USER } from '../../../graphql/quries'; +import { GET_USER } from '../../../graphql/queries'; import { MemberInvitation } from '../../../models/graphql/invite'; import { Member } from '../../../models/graphql/user'; import { RootState } from '../../../redux/reducers';