From 1a8e67e61d32f4f8b8cc79e0f6a9b026b4d3b78b Mon Sep 17 00:00:00 2001 From: Sayan Mondal Date: Thu, 8 Apr 2021 12:25:58 +0530 Subject: [PATCH] =?UTF-8?q?chore(scheduling):=20Added=20Edit=20Schedule=20?= =?UTF-8?q?Functionality=20+=20Fixed=20a=20CronWorkflow=20Bug=20?= =?UTF-8?q?=F0=9F=90=9B=20(#2657)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added Edit Schedule Functionality + Fixed a CronWorkflow Bug 🐛 * Reverting Sidebar text color * Resolved comments/suggestions * Adding missing translation Signed-off-by: Sayan Mondal --- .../public/locales/en/translation.yaml | 13 +- .../components/CreateWorkflowCard/index.tsx | 26 +- .../components/LocalQuickActionCard/index.tsx | 3 +- .../frontend/src/components/SideBar/styles.ts | 3 +- litmus-portal/frontend/src/constants.ts | 13 + .../frontend/src/containers/app/App.tsx | 15 +- .../frontend/src/models/redux/workflow.ts | 3 - .../src/pages/EditSchedule/Schedule.tsx | 767 ++++++++++++++++++ .../frontend/src/pages/EditSchedule/index.tsx | 504 ++++++++++++ .../frontend/src/pages/EditSchedule/styles.ts | 217 +++++ .../frontend/src/redux/reducers/workflow.ts | 3 - litmus-portal/frontend/src/utils/yamlUtils.ts | 4 + .../BrowseSchedule/TableData.tsx | 10 - .../CreateWorkflow/ScheduleWorkflow/index.tsx | 202 ++--- .../CreateWorkflow/ScheduleWorkflow/styles.ts | 22 +- .../CreateWorkflow/VerifyCommit/index.tsx | 71 +- .../CreateWorkflow/VerifyCommit/styles.ts | 26 +- 17 files changed, 1706 insertions(+), 196 deletions(-) create mode 100644 litmus-portal/frontend/src/constants.ts create mode 100644 litmus-portal/frontend/src/pages/EditSchedule/Schedule.tsx create mode 100644 litmus-portal/frontend/src/pages/EditSchedule/index.tsx create mode 100644 litmus-portal/frontend/src/pages/EditSchedule/styles.ts diff --git a/litmus-portal/frontend/public/locales/en/translation.yaml b/litmus-portal/frontend/public/locales/en/translation.yaml index 07afd1614..823af6c84 100644 --- a/litmus-portal/frontend/public/locales/en/translation.yaml +++ b/litmus-portal/frontend/public/locales/en/translation.yaml @@ -132,7 +132,7 @@ schedule: headingDesc: 'Click on test to see detailed log of your workflow' headingText: 'Schedule details:' description: 'Choose the right time to schedule your first workflow. Below you can find some options that may be convenient for you.' - save: 'Save Changes' + save: 'Save Changes' scheduleNow: Schedule now scheduleAfter: Schedule after some time scheduleAfterSometime: 'Choose the minutes, hours, or days when you want to start workflow' @@ -154,6 +154,15 @@ schedule: workflowBtn: Go to Workflow backBtn: Go Back +editSchedule: + title: Edit Schedule + verify: Verify + cancel: Cancel + save: Save Changes + edit: Edit + theSchedule: The Schedule + successfullyCreated: is successfully created + community: title: 'Community' heading: 'Litmus Insights' @@ -794,6 +803,8 @@ createWorkflow: errYaml: Error in CRD Yaml. codeIsFine: Your code is fine. youCanMoveOn: You can move on ! + test: test + YAML: YAML button: edit: Edit viewYaml: View YAML diff --git a/litmus-portal/frontend/src/components/CreateWorkflowCard/index.tsx b/litmus-portal/frontend/src/components/CreateWorkflowCard/index.tsx index ce2b81a03..4e33b9266 100644 --- a/litmus-portal/frontend/src/components/CreateWorkflowCard/index.tsx +++ b/litmus-portal/frontend/src/components/CreateWorkflowCard/index.tsx @@ -1,13 +1,8 @@ import { Card, CardActionArea, Typography } from '@material-ui/core'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import useActions from '../../redux/actions'; -import * as TemplateSelectionActions from '../../redux/actions/template'; -import * as WorkflowActions from '../../redux/actions/workflow'; -import { history } from '../../redux/configureStore'; import { ReactComponent as Arrow } from '../../svg/arrow.svg'; import { ReactComponent as ArrowDisabled } from '../../svg/arrow_disabled.svg'; -import { getProjectID, getProjectRole } from '../../utils/getSearchParams'; import useStyles from './styles'; interface CreateWorkflowCardProps { @@ -19,28 +14,9 @@ const CreateWorkflowCard: React.FC = ({ }) => { const { t } = useTranslation(); const classes = useStyles({ isDisabled }); - const template = useActions(TemplateSelectionActions); - const projectID = getProjectID(); - const userRole = getProjectRole(); - const workflowAction = useActions(WorkflowActions); - const handleCreateWorkflow = () => { - workflowAction.setWorkflowDetails({ - isCustomWorkflow: false, - customWorkflows: [], - }); - template.selectTemplate({ selectedTemplateID: 0, isDisable: true }); - history.push({ - pathname: '/create-workflow', - search: `?projectID=${projectID}&projectRole=${userRole}`, - }); - }; return ( - {} : handleCreateWorkflow} - className={classes.createCard} - data-cy="createWorkflow" - > + {t('home.workflow.heading')} diff --git a/litmus-portal/frontend/src/components/LocalQuickActionCard/index.tsx b/litmus-portal/frontend/src/components/LocalQuickActionCard/index.tsx index a9ff4b260..96c0e8762 100644 --- a/litmus-portal/frontend/src/components/LocalQuickActionCard/index.tsx +++ b/litmus-portal/frontend/src/components/LocalQuickActionCard/index.tsx @@ -2,6 +2,7 @@ import { QuickActionCard } from 'litmus-ui'; import React from 'react'; import { useTranslation } from 'react-i18next'; +import { constants } from '../../constants'; import { Role } from '../../models/graphql/user'; import useActions from '../../redux/actions'; import * as TabActions from '../../redux/actions/tabs'; @@ -97,7 +98,7 @@ const LocalQuickActionCard: React.FC = ({ ? { src: '/icons/survey.svg', alt: 'survey', - onClick: () => window.open('https://forms.gle/qMuVphRyEWCFqjD56'), + onClick: () => window.open(constants.FeedbackForm), text: t('quickActionCard.quickSurvey'), } : emptyData, diff --git a/litmus-portal/frontend/src/components/SideBar/styles.ts b/litmus-portal/frontend/src/components/SideBar/styles.ts index 19816360b..3ddb02158 100644 --- a/litmus-portal/frontend/src/components/SideBar/styles.ts +++ b/litmus-portal/frontend/src/components/SideBar/styles.ts @@ -7,9 +7,8 @@ const useStyles = makeStyles((theme: Theme) => ({ }, drawerPaper: { width: '100%', - backgroundColor: theme.palette.background.default, + backgroundColor: theme.palette.sidebarMenu, position: 'relative', - color: 'inherit', }, litmusDiv: { display: 'flex', diff --git a/litmus-portal/frontend/src/constants.ts b/litmus-portal/frontend/src/constants.ts new file mode 100644 index 000000000..14a2319b7 --- /dev/null +++ b/litmus-portal/frontend/src/constants.ts @@ -0,0 +1,13 @@ +export const constants = { + // Litmus HomePage [Component Used in -> LocalQuickActionCard] + FeedbackForm: 'https://forms.gle/KQp5qj8MUneMSxLp9', + + /** + * Schedule & Edit Schedule [Component Used in -> views/ScheduleWorkflow, + * pages/EditSchedule/Schedule] + * */ + recurringEveryHour: 'everyHr', + recurringEveryDay: 'everyDay', + recurringEveryWeek: 'everyWeek', + recurringEveryMonth: 'everyMonth', +}; diff --git a/litmus-portal/frontend/src/containers/app/App.tsx b/litmus-portal/frontend/src/containers/app/App.tsx index 39ffb32f6..56bb96664 100644 --- a/litmus-portal/frontend/src/containers/app/App.tsx +++ b/litmus-portal/frontend/src/containers/app/App.tsx @@ -25,6 +25,8 @@ const HomePage = lazy(() => import('../../pages/HomePage')); const Community = lazy(() => import('../../pages/Community')); const Settings = lazy(() => import('../../pages/Settings')); const Targets = lazy(() => import('../../pages/Targets')); +const EditSchedule = lazy(() => import('../../pages/EditSchedule')); +const SetNewSchedule = lazy(() => import('../../pages/EditSchedule/Schedule')); const ConnectTargets = lazy(() => import('../../pages/ConnectTarget')); const AnalyticsPage = lazy(() => import('../../pages/AnalyticsPage')); const AnalyticsDashboard = lazy( @@ -194,11 +196,16 @@ const Routes: React.FC = () => { path="/workflows/:workflowRunId" component={WorkflowDetails} /> - {/* */} + path="/workflows/schedule/:scheduleProjectID/:workflowName" + component={EditSchedule} + /> + { + // Initial Cron State + const [cronValue, setCronValue] = useState({ + minute: '*', + hour: '*', + day_month: '*', + month: '*', + day_week: '*', + }); + + const manifest = useSelector( + (state: RootState) => state.workflowManifest.manifest + ); + + // Redux States for Schedule + const workflowData: WorkflowData = useSelector( + (state: RootState) => state.workflowData + ); + + const { cronSyntax, scheduleType } = workflowData; + + const workflowAction = useActions(WorkflowActions); + const template = useActions(TemplateSelectionActions); + // Controls Radio Buttons + const [value, setValue] = React.useState( + workflowData.scheduleType.scheduleOnce + ); + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + }; + + // Controls inner radio buttons of Recurring Schedule + const [valueDef, setValueDef] = React.useState( + workflowData.scheduleType.recurringSchedule + ); + const handleChangeInstance = (event: React.ChangeEvent) => { + setValueDef(event.target.value); + }; + + const scheduleOnce = workflowOnce; + const scheduleMore = cronWorkflow; + + function EditYaml() { + const oldParsedYaml = YAML.parse(manifest); + let NewYaml: string; + if ( + oldParsedYaml.kind === 'Workflow' && + scheduleType.scheduleOnce !== 'now' + ) { + const oldParsedYaml = YAML.parse(manifest); + const newParsedYaml = YAML.parse(scheduleMore); + delete newParsedYaml.spec.workflowSpec; + newParsedYaml.spec.schedule = cronSyntax; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = fetchWorkflowNameFromManifest(manifest); + 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); + workflowAction.setWorkflowManifest({ + manifest: NewYaml, + }); + } + if ( + oldParsedYaml.kind === 'CronWorkflow' && + scheduleType.scheduleOnce === 'now' + ) { + const oldParsedYaml = YAML.parse(manifest); + const newParsedYaml = YAML.parse(scheduleOnce); + delete newParsedYaml.spec; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = fetchWorkflowNameFromManifest(manifest); + newParsedYaml.spec = oldParsedYaml.spec.workflowSpec; + newParsedYaml.metadata.labels = { + workflow_id: workflowData.workflow_id, + }; + NewYaml = YAML.stringify(newParsedYaml); + workflowAction.setWorkflowManifest({ + manifest: NewYaml, + }); + } + if ( + oldParsedYaml.kind === 'CronWorkflow' && + scheduleType.scheduleOnce !== 'now' + // && !isDisabled + ) { + const newParsedYaml = YAML.parse(manifest); + newParsedYaml.spec.schedule = cronSyntax; + // newParsedYaml.spec.suspend = false; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = fetchWorkflowNameFromManifest(manifest); + 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); + workflowAction.setWorkflowManifest({ + manifest: NewYaml, + }); + } + // if (oldParsedYaml.kind === 'CronWorkflow' && isDisabled === true) { + // const newParsedYaml = YAML.parse(yaml); + // newParsedYaml.spec.suspend = true; + // const tz = { + // timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC', + // }; + // Object.entries(tz).forEach(([key, value]) => { + // newParsedYaml.spec[key] = value; + // }); + // NewYaml = YAML.stringify(newParsedYaml); + // workflowAction.setWorkflowDetails({ + // link: NewLink, + // yaml: NewYaml, + // }); + // } + localforage.setItem('editSchedule', true); + } + + // UseEffect to update the cron syntax with changes + useEffect(() => { + const cronSyntax = `${cronValue.minute} ${cronValue.hour} ${cronValue.day_month} ${cronValue.month} ${cronValue.day_week}`; + if (value === 'now') + workflowAction.setWorkflowDetails({ + cronSyntax: '', + }); + else + workflowAction.setWorkflowDetails({ + cronSyntax, + }); + }, [cronValue]); + + const classes = useStyles(); + const { t } = useTranslation(); + + // Sets individual minutes + const [minute, setMinute] = React.useState( + workflowData.scheduleInput.hour_interval + ); + + // Sets Weekdays + const [days, setDays] = React.useState(workflowData.scheduleInput.weekday); + + // Sets Day in number + const [dates, setDates] = React.useState(workflowData.scheduleInput.day); + + // Sets Time + const [selectedTime, setSelectedTime] = React.useState( + new Date(workflowData.scheduleInput.time) + ); + + // Sets Date + const [selectedDate, setSelectedDate] = React.useState( + new Date(workflowData.scheduleInput.date) + ); + + // Function to validate the date and time for "Specific Time" radio button + const validateTime = (time: Date | null, date: Date | null) => { + if ( + value === 'specificTime' && + (time?.setSeconds(0) as number) <= new Date().setSeconds(0) && + (date?.getTime() as number) <= new Date().getTime() + ) { + const newTime = new Date(); + newTime.setMinutes(newTime.getMinutes() + 5); + setSelectedTime(newTime); + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + date, + time: newTime, + }, + }); + setCronValue({ + ...cronValue, + minute: newTime.getMinutes().toString(), + hour: newTime.getHours().toString(), + day_month: date?.getDate().toString(), + month: (date && date.getMonth() + 1)?.toString(), + }); + } else { + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + date, + time, + }, + }); + setCronValue({ + ...cronValue, + minute: time?.getMinutes().toString(), + hour: time?.getHours().toString(), + day_month: date?.getDate().toString(), + month: (date && date.getMonth() + 1)?.toString(), + }); + } + }; + + const handleTimeChange = (time: Date | null) => { + setSelectedTime(time); + validateTime(time, selectedDate); + }; + + const handleDateChange = (date: Date | null) => { + setSelectedDate(date); + validateTime(selectedTime, date); + }; + + // Function for recurring date change + const reccuringDateChange = (date: Date | null) => { + setSelectedTime(date); + setCronValue({ + ...cronValue, + minute: date?.getMinutes().toString(), + hour: date?.getHours().toString(), + }); + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + time: date, + }, + }); + }; + + // Stores dates in an array + const names: number[] = [1]; + for (let i = 1; i <= 30; i += 1) { + names[i] = i + 1; + } + const mins: number[] = [0]; + for (let i = 0; i <= 59; i += 1) { + mins[i] = i; + } + // Day names + const weekdays: string[] = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]; + + // UseEffect to update the values of CronSyntax on radio button change + useEffect(() => { + if (value === 'now') { + setValueDef(''); + setCronValue({ + minute: '', + hour: '', + day_month: '', + month: '', + day_week: '', + }); + } + if (value === 'specificTime') { + setValueDef(''); + setCronValue({ + minute: selectedTime?.getMinutes().toString(), + hour: selectedTime?.getHours().toString(), + day_month: selectedDate?.getDate().toString(), + month: (selectedDate && selectedDate.getMonth() + 1)?.toString(), + day_week: '*', + }); + if (workflowData.scheduleInput.time <= new Date()) { + const newTime = new Date(); + newTime.setMinutes(newTime.getMinutes() + 5); + setSelectedTime(newTime); + setCronValue({ + minute: newTime.getMinutes().toString(), + hour: newTime.getHours().toString(), + day_month: selectedDate?.getDate().toString(), + month: (selectedDate && selectedDate.getMonth() + 1)?.toString(), + day_week: '*', + }); + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + time: newTime, + }, + }); + } + } + if (valueDef === constants.recurringEveryHour) { + setCronValue({ + minute: minute.toString(), + hour: '0-23', + day_month: '*', + month: '*', + day_week: '*', + }); + } + if (valueDef === constants.recurringEveryDay) { + setCronValue({ + minute: selectedTime?.getMinutes().toString(), + hour: selectedTime?.getHours().toString(), + day_month: '*', + month: '*', + day_week: '0-6', + }); + } + if (valueDef === constants.recurringEveryWeek) { + setCronValue({ + minute: selectedTime?.getMinutes().toString(), + hour: selectedTime?.getHours().toString(), + day_month: '*', + month: '*', + day_week: days.slice(0, 3), + }); + } + if (valueDef === constants.recurringEveryMonth) { + setCronValue({ + minute: selectedTime?.getMinutes().toString(), + hour: selectedTime?.getHours().toString(), + day_month: dates.toString(), + month: '*', + day_week: '*', + }); + } + if (value === 'recurringSchedule' && valueDef === '') { + template.selectTemplate({ isDisable: true }); + } else { + template.selectTemplate({ isDisable: false }); + } + workflowAction.setWorkflowDetails({ + scheduleType: { + scheduleOnce: value, + recurringSchedule: valueDef, + }, + }); + }, [valueDef, value]); + + useEffect(() => { + EditYaml(); + }, [cronValue]); + + return ( + + + + {t('editSchedule.title')} + +
+
+
+ {/* Upper segment */} +
+
+ + {t('createWorkflow.scheduleWorkflow.header')} + + +
+ + {t('createWorkflow.scheduleWorkflow.info')} + +
+
+ calendar +
+ + + {/* Lower segment */} +
+ + + {/* options to choose schedule */} + + } + label={ + + {t('createWorkflow.scheduleWorkflow.radio.now')} + + } + /> + {value === 'specificTime' && ( +
+ + {t('createWorkflow.scheduleWorkflow.radio.future')} + +
+ { + handleDateChange(event); + }} + disabled={false} + /> + { + handleTimeChange(event); + }} + value={selectedTime} + ampm + disabled={false} + /> +
+
+ )} + + } + label={ + + {t('createWorkflow.scheduleWorkflow.radio.recurr')} + + } + /> + {value === 'recurringSchedule' && ( +
+ + {t('createWorkflow.scheduleWorkflow.radio.rightRecurr')} + + + {/* options to select time of recurring schedule */} +
+ + { + handleChangeInstance(event); + }} + > + + } + label={t( + 'createWorkflow.scheduleWorkflow.every.hr' + )} + /> + {valueDef === constants.recurringEveryHour && ( +
+
+ + {t('createWorkflow.scheduleWorkflow.at')} + + { + setMinute(event.target.value as number); + setCronValue({ + ...cronValue, + minute: (event.target + .value as number).toString(), + hour: '0-23', + }); + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + hour_interval: event.target + .value as number, + }, + }); + }} + value={minute} + /> + {minute === 0 || minute === 1 ? ( + + {t('createWorkflow.scheduleWorkflow.min')} + + ) : ( + + {t('createWorkflow.scheduleWorkflow.mins')} + + )} +
+
+ )} + + } + label={t( + 'createWorkflow.scheduleWorkflow.every.day' + )} + /> + {valueDef === constants.recurringEveryDay && ( +
+
+ + {t('createWorkflow.scheduleWorkflow.at')} + + { + setSelectedTime(date); + setCronValue({ + ...cronValue, + minute: date?.getMinutes().toString(), + hour: date?.getHours().toString(), + day_week: '0-6', + }); + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + time: date, + }, + }); + }} + value={selectedTime} + ampm + disabled={false} + /> +
+
+ )} + + } + label={t( + 'createWorkflow.scheduleWorkflow.every.week' + )} + /> + {valueDef === constants.recurringEveryWeek && ( +
+
+ + {t('createWorkflow.scheduleWorkflow.on')} + + + + + + {t('createWorkflow.scheduleWorkflow.at')} + + { + reccuringDateChange(date); + }} + value={selectedTime} + ampm + disabled={false} + /> +
+
+ )} + + } + label={t( + 'createWorkflow.scheduleWorkflow.every.month' + )} + /> + {valueDef === constants.recurringEveryMonth && ( +
+
+ + {t('createWorkflow.scheduleWorkflow.on')} + + { + setCronValue({ + ...cronValue, + day_month: (event.target + .value as number).toString(), + }); + setDates(event.target.value as number); + workflowAction.setWorkflowDetails({ + scheduleInput: { + ...workflowData.scheduleInput, + day: event.target.value as number, + }, + }); + }} + value={dates} + /> + + {t('createWorkflow.scheduleWorkflow.at')} + + { + reccuringDateChange(date); + }} + value={selectedTime} + ampm + disabled={false} + /> +
+
+ )} +
+
+
+
+ )} +
+
+
+
+
+ {/* Cancel and Save Button */} +
+ { + history.push({ + pathname: `/workflows/`, + search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`, + }); + }} + > + Cancel + + + history.push({ + pathname: `/workflows/schedule/${getProjectID()}/${fetchWorkflowNameFromManifest( + manifest + )}`, + search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`, + }) + } + > + {t('editSchedule.verify')} + +
+
+ ); +}; + +export default ScheduleWorkflow; diff --git a/litmus-portal/frontend/src/pages/EditSchedule/index.tsx b/litmus-portal/frontend/src/pages/EditSchedule/index.tsx new file mode 100644 index 000000000..f9e0c6e9f --- /dev/null +++ b/litmus-portal/frontend/src/pages/EditSchedule/index.tsx @@ -0,0 +1,504 @@ +import { useMutation, useQuery } from '@apollo/client'; +import Typography from '@material-ui/core/Typography'; +import EditIcon from '@material-ui/icons/Edit'; +import cronstrue from 'cronstrue'; +import { ButtonFilled, ButtonOutlined, Modal } from 'litmus-ui'; +import localforage from 'localforage'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import YAML from 'yaml'; +import AdjustedWeights from '../../components/AdjustedWeights'; +import BackButton from '../../components/Button/BackButton'; +import Loader from '../../components/Loader'; +import YamlEditor from '../../components/YamlEditor/Editor'; +import { parseYamlValidations } from '../../components/YamlEditor/Validations'; +import Scaffold from '../../containers/layouts/Scaffold'; +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 { getProjectID, getProjectRole } from '../../utils/getSearchParams'; +import { fetchWorkflowNameFromManifest } from '../../utils/yamlUtils'; +import useStyles from './styles'; + +interface URLParams { + workflowName: string; + scheduleProjectID: string; +} + +interface Weights { + experimentName: string; + weight: number; +} + +interface WorkflowProps { + name: string; + description: string; +} + +const EditSchedule: React.FC = () => { + const classes = useStyles(); + const { t } = useTranslation(); + + const [workflow, setWorkflow] = useState({ + name: '', + description: '', + }); + const [weights, setWeights] = useState([ + { + experimentName: '', + weight: 0, + }, + ]); + + const [open, setOpen] = React.useState(false); + const [finishModalOpen, setFinishModalOpen] = useState(false); + const [errorModal, setErrorModal] = useState(false); + + const template = useActions(TemplateSelectionActions); + const workflowData: WorkflowData = useSelector( + (state: RootState) => state.workflowData + ); + const workflowAction = useActions(WorkflowActions); + // Get Parameters from URL + const paramData: URLParams = useParams(); + const projectID = getProjectID(); + const userRole = getProjectRole(); + + // Apollo query to get the scheduled data + const { data, loading } = useQuery( + SCHEDULE_DETAILS, + { + variables: { projectID: paramData.scheduleProjectID }, + fetchPolicy: 'cache-and-network', + } + ); + + const manifest = useSelector( + (state: RootState) => state.workflowManifest.manifest + ); + + const wfDetails = + data && + data.getScheduledWorkflows.filter( + (wf) => wf.workflow_name === paramData.workflowName + )[0]; + const doc = new YAML.Document(); + const w: Weights[] = []; + const { cronSyntax, clusterid, clustername } = workflowData; + + const [createChaosWorkFlow, { error: workflowError }] = useMutation< + UpdateWorkflowResponse, + CreateWorkFlowInput + >(UPDATE_SCHEDULE, { + onCompleted: () => { + setFinishModalOpen(true); + }, + }); + + const handleMutation = () => { + if ( + workflow.name.length !== 0 && + workflow.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(manifest); + const yamlJson = JSON.stringify(yml, null, 2); // Converted to Stringified JSON + + const chaosWorkFlowInputs = { + workflow_id: wfDetails?.workflow_id, + workflow_manifest: yamlJson, + cronSyntax, + workflow_name: fetchWorkflowNameFromManifest(manifest), + workflow_description: workflow.description, + isCustomWorkflow: false, + weightages: weightData, + project_id: projectID, + cluster_id: clusterid, + }; + + createChaosWorkFlow({ + variables: { ChaosWorkFlowInput: chaosWorkFlowInputs }, + }); + } + }; + + useEffect(() => { + localforage.getItem('editSchedule').then((isCronEdited) => { + if (wfDetails !== undefined) { + for (let i = 0; i < wfDetails?.weightages.length; i++) { + w.push({ + experimentName: wfDetails?.weightages[i].experiment_name, + weight: wfDetails?.weightages[i].weightage, + }); + } + doc.contents = JSON.parse(wfDetails?.workflow_manifest); + workflowAction.setWorkflowManifest({ + manifest: isCronEdited === null ? doc.toString() : manifest, + }); + setWorkflow({ + name: wfDetails?.workflow_name, + description: wfDetails?.workflow_description, + }); + localforage.setItem('weights', w); + workflowAction.setWorkflowDetails({ + workflow_id: wfDetails?.workflow_id, + clusterid: wfDetails?.cluster_id, + cronSyntax: + isCronEdited === null ? wfDetails?.cronSyntax : 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 }); + setWeights(w); + }, [data]); + + const tabs = useActions(TabActions); + + const [yamlStatus, setYamlStatus] = React.useState( + `${t('createWorkflow.verifyCommit.codeIsFine')}` + ); + const [modified, setModified] = React.useState(false); + + const handleEditOpen = () => { + setModified(false); + setOpen(true); + }; + + const handleEditClose = () => { + setModified(true); + setOpen(false); + }; + + const handleNext = () => { + handleMutation(); + }; + + useEffect(() => { + const editorValidations = parseYamlValidations(manifest, classes); + const stateObject = { + markers: editorValidations.markers, + annotations: editorValidations.annotations, + }; + if (stateObject.annotations.length > 0) { + setYamlStatus(`${t('createWorkflow.verifyCommit.errYaml')}`); + } else { + setYamlStatus(`${t('createWorkflow.verifyCommit.codeIsFine')}`); + } + }, [modified]); + + const handleFinishModal = () => { + history.push({ + pathname: `/workflows`, + search: `?projectID=${projectID}&projectRole=${userRole}`, + }); + setFinishModalOpen(false); + }; + + const handleErrorModalClose = () => { + setErrorModal(false); + }; + + return ( + + {loading ? ( + + ) : ( + <> + + + {t('editSchedule.title')} + +
+
+ + {t('createWorkflow.verifyCommit.summary.header')} + + +
+
+
+ + {t('createWorkflow.verifyCommit.summary.workflowName')}: + +
+
+ + {fetchWorkflowNameFromManifest(manifest)} + +
+
+ +
+
+ + {t('createWorkflow.verifyCommit.summary.clustername')}: + +
+ + {clustername} + +
+ +
+
+ + {t('createWorkflow.verifyCommit.summary.desc')}: + +
+
+ {workflow.description} +
+
+
+
+ + {t('createWorkflow.verifyCommit.summary.schedule')}: + +
+
+ {cronSyntax === '' ? ( + + {t('createWorkflow.verifyCommit.summary.schedulingNow')} + + ) : ( + {cronstrue.toString(cronSyntax)} + )} + + + history.push({ + pathname: `/workflows/schedule/${projectID}/${fetchWorkflowNameFromManifest( + manifest + )}/set`, + search: `?projectID=${projectID}&projectRole=${userRole}`, + }) + } + > + + {t('editSchedule.edit')} + +
+
+
+
+ + {t('createWorkflow.verifyCommit.summary.adjustedWeights')} + : + +
+ {weights.length === 0 ? ( +
+ + {t('createWorkflow.verifyCommit.error')} + +
+ ) : ( +
+
+ {weights.map((Test) => ( + + ))} +
+
+ )} +
+
+
+ + {t('createWorkflow.verifyCommit.YAML')} + +
+
+ {weights.length === 0 ? ( + + {t('createWorkflow.verifyCommit.errYaml')} + + ) : ( + + {yamlStatus} + + {t('createWorkflow.verifyCommit.youCanMoveOn')} + + + )} +
+ + {t('createWorkflow.verifyCommit.button.viewYaml')} + +
+
+
+
+
+ {/* Cancel and Save Button */} +
+ { + history.push({ + pathname: `/workflows/`, + search: `?projectID=${projectID}&projectRole=${userRole}`, + }); + }} + > + {t('editSchedule.cancel')} + + handleNext()}> + {t('editSchedule.save')} + +
+ + + ✕ + + } + > + + + + {/* Finish Modal */} +
+ + + ✕ + +
+ } + > +
+ mark +
+ {t('editSchedule.theSchedule')} +
+ {workflow.name}, +
+ + {t('editSchedule.successfullyCreated')} + +
+
+ {t('workflowStepper.congratulationsSub1')}
{' '} + {t('workflowStepper.congratulationsSub2')} +
+
+ { + handleFinishModal(); + tabs.changeWorkflowsTabs(0); + history.push({ + pathname: '/workflows', + search: `?projectID=${projectID}&projectRole=${userRole}`, + }); + }} + > +
{t('workflowStepper.workflowBtn')}
+
+
+
+ + + ✕ + + } + > +
+ mark +
+ {t('workflowStepper.workflowFailed')} +
+
+ + {t('workflowStepper.error')} : {workflowError?.message} + +
+
+ { + setErrorModal(false); + }} + > +
{t('workflowStepper.back')}
+
+
+
+
+ + + )} +
+ ); +}; + +export default EditSchedule; diff --git a/litmus-portal/frontend/src/pages/EditSchedule/styles.ts b/litmus-portal/frontend/src/pages/EditSchedule/styles.ts new file mode 100644 index 000000000..94a418948 --- /dev/null +++ b/litmus-portal/frontend/src/pages/EditSchedule/styles.ts @@ -0,0 +1,217 @@ +import { makeStyles, Theme } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme: Theme) => ({ + horizontalLine: { + background: theme.palette.border.main, + }, + title: { + padding: theme.spacing(0, 2), + fontWeight: 700, + fontSize: '2rem', + [theme.breakpoints.up('lg')]: { + fontSize: '2.3rem', + }, + }, + root: { + background: theme.palette.background.paper, + color: theme.palette.text.primary, + padding: theme.spacing(1, 2), + margin: theme.spacing(2, 'auto'), + width: '100%', + flexDirection: 'column', + }, + + // Inner Container + innerContainer: { + margin: theme.spacing(4, 'auto'), + width: '95%', // Inner width of the container + }, + + suHeader: { + display: 'flex', + justifyContent: 'space-between', + }, + headerText: { + fontSize: '1.2rem', + [theme.breakpoints.up('lg')]: { + fontSize: '1.4rem', + }, + }, + description: { + margin: theme.spacing(3, 0), + fontSize: '1rem', + }, + bfinIcon: { + width: '7rem', + height: '6.31rem', + }, + + // Body + outerSum: { + display: 'flex', + flexDirection: 'column', + }, + summaryDiv: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'baseline', + margin: theme.spacing(1, 0), + }, + innerSumDiv: { + alignContent: 'center', + display: 'table-cell', + verticalAlign: 'middle', + width: '20%', + [theme.breakpoints.up('lg')]: { + width: '10%', + }, + }, + sumText: { + width: '100%', + margin: theme.spacing(2, 0), + fontWeight: 700, + fontSize: '1.2rem', + [theme.breakpoints.up('lg')]: { + fontSize: '1.4rem', + }, + }, + col1: { + alignContent: 'center', + color: theme.palette.highlight, + fontSize: '1rem', + paddingTop: theme.spacing(0.5), + verticalAlign: 'middle', + }, + schedule: { + fontSize: '0.85rem', + padding: theme.spacing(0.75, 0, 2, 0), + }, + col2: { + color: theme.palette.text.primary, + marginLeft: theme.spacing(5), + fontWeight: 700, + width: '75%', + }, + schCol2: { + width: '75%', + marginLeft: theme.spacing(5), + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + }, + clusterName: { + fontSize: '0.85rem', + marginLeft: theme.spacing(7), + paddingTop: theme.spacing(0.5), + }, + editButton: { + height: '1rem', + }, + editIcon: { + color: theme.palette.text.primary, + height: '0.8rem', + }, + link: { + fontSize: '0.875rem', + color: theme.palette.secondary.dark, + }, + adjWeights: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + + width: '80%', + [theme.breakpoints.up('lg')]: { + width: '90%', + }, + }, + config: { + height: '3rem', + fontSize: '0.9375rem', + color: theme.palette.text.disabled, + width: '30rem', + margin: theme.spacing(3.75, 0, 30, 0), + }, + typoCol2: { + fontSize: '1rem', + }, + textEdit: { + marginTop: theme.spacing(7.5), + }, + buttonOutlineText: { + padding: theme.spacing(1.5), + }, + errorText: { + color: theme.palette.error.main, + fontSize: '1rem', + marginLeft: theme.spacing(5), + }, + yamlFlex: { + display: 'flex', + flexDirection: 'column', + width: '75%', + marginLeft: theme.spacing(5), + }, + progress: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + flexGrow: 1, + marginLeft: theme.spacing(5), + }, + spacingHorizontal: { + margin: theme.spacing(0, 1), + }, + buttomPad: { + paddingBottom: theme.spacing(3.75), + }, + closeBtn: { + color: theme.palette.secondary.contrastText, + }, + buttonDiv: { + display: 'flex', + justifyContent: 'space-between', + width: '100%', + }, + verifyYAMLButton: { + width: '20%', + }, + bold: { + fontWeight: 700, + }, + + // Modal + modal: { + [theme.breakpoints.up('lg')]: { + padding: theme.spacing(10), + }, + padding: theme.spacing(3), + }, + heading: { + fontSize: '2rem', + textalign: 'center', + marginTop: theme.spacing(3), + color: theme.palette.text.primary, + }, + headWorkflow: { + fontsize: '2rem', + textalign: 'center', + color: theme.palette.text.primary, + marginTop: theme.spacing(3), + }, + button: { + color: theme.palette.text.primary, + textAlign: 'center', + marginTop: theme.spacing(5), + }, + closeButton: { + borderColor: theme.palette.border.main, + }, + successful: { + fontSize: '2.2rem', + fontWeight: 'bold', + margin: theme.spacing(2, 0), + }, +})); +export default useStyles; diff --git a/litmus-portal/frontend/src/redux/reducers/workflow.ts b/litmus-portal/frontend/src/redux/reducers/workflow.ts index bf9108656..1946d317f 100644 --- a/litmus-portal/frontend/src/redux/reducers/workflow.ts +++ b/litmus-portal/frontend/src/redux/reducers/workflow.ts @@ -8,9 +8,6 @@ import { import createReducer from './createReducer'; const initialState: WorkflowData = { - id: '', - isRecurring: false, - isDisabled: false, chaosEngineChanged: false, namespace: 'litmus', clusterid: '', diff --git a/litmus-portal/frontend/src/utils/yamlUtils.ts b/litmus-portal/frontend/src/utils/yamlUtils.ts index 924e13911..527b7adcf 100644 --- a/litmus-portal/frontend/src/utils/yamlUtils.ts +++ b/litmus-portal/frontend/src/utils/yamlUtils.ts @@ -70,6 +70,10 @@ const parsed = (yaml: string) => { } }; +export const fetchWorkflowNameFromManifest = (manifest: string) => { + return YAML.parse(manifest).metadata.name; +}; + export const getWorkflowParameter = (parameterString: string) => { return parameterString .substring(1, parameterString.length - 1) diff --git a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx index 61a9465c4..29758fbed 100644 --- a/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx +++ b/litmus-portal/frontend/src/views/ChaosWorkflows/BrowseSchedule/TableData.tsx @@ -24,7 +24,6 @@ import { RERUN_CHAOS_WORKFLOW } from '../../../graphql/mutations'; import { ScheduleWorkflow } from '../../../models/graphql/scheduleData'; import useActions from '../../../redux/actions'; import * as TabActions from '../../../redux/actions/tabs'; -import * as WorkflowActions from '../../../redux/actions/workflow'; import { history } from '../../../redux/configureStore'; import { ReactComponent as CrossMarkIcon } from '../../../svg/crossmark.svg'; import timeDifferenceForDate from '../../../utils/datesModifier'; @@ -63,8 +62,6 @@ const TableData: React.FC = ({ setPopAnchorEl(null); }; - const workflow = useActions(WorkflowActions); - const handlePopOverClick = (event: React.MouseEvent) => { setPopAnchorEl(event.currentTarget); }; @@ -125,13 +122,6 @@ const TableData: React.FC = ({ }); }; - // If regularity is not Once then set recurring schedule state to true - if (data.cronSyntax !== '') { - workflow.setWorkflowDetails({ - isRecurring: true, - }); - } - const [reRunChaosWorkFlow] = useMutation(RERUN_CHAOS_WORKFLOW, { onCompleted: () => { tabs.changeWorkflowsTabs(0); diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx index 19628ad76..ca72523d5 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx +++ b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/index.tsx @@ -18,11 +18,14 @@ import { useSelector } from 'react-redux'; import YAML from 'yaml'; import CustomDate from '../../../components/DateTime/CustomDate/index'; import CustomTime from '../../../components/DateTime/CustomTime/index'; +import { constants } from '../../../constants'; 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 { cronWorkflow, workflowOnce } from '../../../utils/workflowTemplate'; +import { fetchWorkflowNameFromManifest } from '../../../utils/yamlUtils'; import SetTime from './SetTime/index'; import useStyles from './styles'; @@ -54,6 +57,10 @@ const ScheduleWorkflow = forwardRef((_, ref) => { ); const workflow = useActions(WorkflowActions); const template = useActions(TemplateSelectionActions); + + const scheduleOnce = workflowOnce; + const scheduleMore = cronWorkflow; + // Controls Radio Buttons const [value, setValue] = React.useState( workflowData.scheduleType.scheduleOnce @@ -81,10 +88,6 @@ const ScheduleWorkflow = forwardRef((_, ref) => { workflow.setWorkflowDetails({ cronSyntax, }); - if (value === 'disable') - workflow.setWorkflowDetails({ - isDisabled: true, - }); }, [cronValue]); const classes = useStyles(); @@ -163,6 +166,76 @@ const ScheduleWorkflow = forwardRef((_, ref) => { validateTime(selectedTime, date); }; + function EditYaml() { + const oldParsedYaml = YAML.parse(manifest); + let NewYaml: string; + if ( + oldParsedYaml.kind === 'Workflow' && + workflowData.scheduleType.scheduleOnce !== 'now' + ) { + const oldParsedYaml = YAML.parse(manifest); + const newParsedYaml = YAML.parse(scheduleMore); + delete newParsedYaml.spec.workflowSpec; + newParsedYaml.spec.schedule = workflowData.cronSyntax; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = fetchWorkflowNameFromManifest(manifest); + 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.setWorkflowManifest({ + manifest: NewYaml, + }); + } + if ( + oldParsedYaml.kind === 'CronWorkflow' && + workflowData.scheduleType.scheduleOnce === 'now' + ) { + const oldParsedYaml = YAML.parse(manifest); + const newParsedYaml = YAML.parse(scheduleOnce); + delete newParsedYaml.spec; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = fetchWorkflowNameFromManifest(manifest); + newParsedYaml.spec = oldParsedYaml.spec.workflowSpec; + newParsedYaml.metadata.labels = { + workflow_id: workflowData.workflow_id, + }; + NewYaml = YAML.stringify(newParsedYaml); + workflow.setWorkflowManifest({ + manifest: NewYaml, + }); + } + if ( + oldParsedYaml.kind === 'CronWorkflow' && + workflowData.scheduleType.scheduleOnce !== 'now' + // && !isDisabled + ) { + const newParsedYaml = YAML.parse(manifest); + newParsedYaml.spec.schedule = workflowData.cronSyntax; + // newParsedYaml.spec.suspend = false; + delete newParsedYaml.metadata.generateName; + newParsedYaml.metadata.name = fetchWorkflowNameFromManifest(manifest); + 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.setWorkflowManifest({ + manifest: NewYaml, + }); + } + } + // Function for recurring date change const reccuringDateChange = (date: Date | null) => { setSelectedTime(date); @@ -202,9 +275,6 @@ const ScheduleWorkflow = forwardRef((_, ref) => { // UseEffect to update the values of CronSyntax on radio button change useEffect(() => { if (value === 'now') { - workflow.setWorkflowDetails({ - isDisabled: false, - }); setValueDef(''); setCronValue({ minute: '', @@ -214,11 +284,6 @@ const ScheduleWorkflow = forwardRef((_, ref) => { day_week: '', }); } - if (value === 'disable') { - workflow.setWorkflowDetails({ - isDisabled: true, - }); - } if (value === 'specificTime') { setValueDef(''); setCronValue({ @@ -247,10 +312,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => { }); } } - if (valueDef === 'everyHr') { - workflow.setWorkflowDetails({ - isDisabled: false, - }); + if (valueDef === constants.recurringEveryHour) { setCronValue({ minute: minute.toString(), hour: '0-23', @@ -259,10 +321,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => { day_week: '*', }); } - if (valueDef === 'everyDay') { - workflow.setWorkflowDetails({ - isDisabled: false, - }); + if (valueDef === constants.recurringEveryDay) { setCronValue({ minute: selectedTime?.getMinutes().toString(), hour: selectedTime?.getHours().toString(), @@ -271,10 +330,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => { day_week: '0-6', }); } - if (valueDef === 'everyWeek') { - workflow.setWorkflowDetails({ - isDisabled: false, - }); + if (valueDef === constants.recurringEveryWeek) { setCronValue({ minute: selectedTime?.getMinutes().toString(), hour: selectedTime?.getHours().toString(), @@ -283,10 +339,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => { day_week: days.slice(0, 3), }); } - if (valueDef === 'everyMonth') { - workflow.setWorkflowDetails({ - isDisabled: false, - }); + if (valueDef === constants.recurringEveryMonth) { setCronValue({ minute: selectedTime?.getMinutes().toString(), hour: selectedTime?.getHours().toString(), @@ -296,9 +349,6 @@ const ScheduleWorkflow = forwardRef((_, ref) => { }); } if (value === 'recurringSchedule' && valueDef === '') { - workflow.setWorkflowDetails({ - isDisabled: false, - }); template.selectTemplate({ isDisable: true }); } else { template.selectTemplate({ isDisable: false }); @@ -312,6 +362,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => { }, [valueDef, value]); function onNext() { + EditYaml(); return true; } @@ -354,56 +405,23 @@ const ScheduleWorkflow = forwardRef((_, ref) => { onChange={handleChange} > {/* options to choose schedule */} - {!workflowData.isRecurring ? ( - - } - label={ - - {t('createWorkflow.scheduleWorkflow.radio.now')} - - } - /> - ) : YAML.parse(manifest).spec.suspend === true ? ( - <> - ) : workflowData.isRecurring ? ( - - } - label={ - - Disable Schedule - - } - /> - ) : ( - <> - )} - {/* } + + } label={ - {t('createWorkflow.scheduleWorkflow.radio.specific')} + {t('createWorkflow.scheduleWorkflow.radio.now')} } - /> */} - {value === 'specificTime' ? ( + /> + {value === 'specificTime' && (
{t('createWorkflow.scheduleWorkflow.radio.future')} @@ -426,8 +444,6 @@ const ScheduleWorkflow = forwardRef((_, ref) => { />
- ) : ( - <> )} {
} /> - {value === 'recurringSchedule' ? ( + {value === 'recurringSchedule' && (
{t('createWorkflow.scheduleWorkflow.radio.rightRecurr')} @@ -460,7 +476,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => { }} > { } label={t('createWorkflow.scheduleWorkflow.every.hr')} /> - {valueDef === 'everyHr' ? ( + {valueDef === constants.recurringEveryHour && (
@@ -508,11 +524,9 @@ const ScheduleWorkflow = forwardRef((_, ref) => { )}
- ) : ( - <> )} { } label={t('createWorkflow.scheduleWorkflow.every.day')} /> - {valueDef === 'everyDay' ? ( + {valueDef === constants.recurringEveryDay && (
@@ -551,11 +565,9 @@ const ScheduleWorkflow = forwardRef((_, ref) => { />
- ) : ( - <> )} { 'createWorkflow.scheduleWorkflow.every.week' )} /> - {valueDef === 'everyWeek' ? ( + {valueDef === constants.recurringEveryWeek && (
@@ -633,11 +645,9 @@ const ScheduleWorkflow = forwardRef((_, ref) => { />
- ) : ( - <> )} { 'createWorkflow.scheduleWorkflow.every.month' )} /> - {valueDef === 'everyMonth' ? ( + {valueDef === constants.recurringEveryMonth && (
@@ -687,15 +697,11 @@ const ScheduleWorkflow = forwardRef((_, ref) => { />
- ) : ( - <> )}
- ) : ( - <> )} diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts index d834e0f7b..2a7f40dbe 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts +++ b/litmus-portal/frontend/src/views/CreateWorkflow/ScheduleWorkflow/styles.ts @@ -5,15 +5,23 @@ const useStyles = makeStyles((theme: Theme) => ({ background: theme.palette.background.paper, color: theme.palette.text.primary, padding: theme.spacing(0, 2), - margin: '0 auto', + margin: '1rem auto', width: '98%', - height: '100%', flexDirection: 'column', [theme.breakpoints.up('lg')]: { width: '99%', }, }, + title: { + padding: theme.spacing(0, 2), + fontWeight: 700, + fontSize: '2rem', + [theme.breakpoints.up('lg')]: { + fontSize: '2.3rem', + }, + }, + // Inner Container innerContainer: { margin: theme.spacing(4, 'auto'), @@ -28,7 +36,10 @@ const useStyles = makeStyles((theme: Theme) => ({ headerText: { marginTop: theme.spacing(1.25), - fontSize: '1.5625rem', + fontSize: '1.2rem', + [theme.breakpoints.up('lg')]: { + fontSize: '1.4rem', + }, }, schBody: { width: '32.18rem', @@ -68,6 +79,11 @@ const useStyles = makeStyles((theme: Theme) => ({ }, }, checked: {}, + buttonDiv: { + display: 'flex', + justifyContent: 'space-between', + width: '100%', + }, /* For recurring schedule options */ scRandom: { diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx b/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx index 99573ae65..ba196b95a 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx +++ b/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/index.tsx @@ -31,6 +31,7 @@ import * as WorkflowActions from '../../../redux/actions/workflow'; import { history } from '../../../redux/configureStore'; import { RootState } from '../../../redux/reducers'; import { getProjectID, getProjectRole } from '../../../utils/getSearchParams'; +import { fetchWorkflowNameFromManifest } from '../../../utils/yamlUtils'; import useStyles from './styles'; interface WorkflowProps { @@ -67,7 +68,7 @@ const VerifyCommit = forwardRef((_, ref) => { (state: RootState) => state.workflowData ); - const { clusterid, cronSyntax, isDisabled, clustername } = workflowData; + const { clusterid, cronSyntax, clustername } = workflowData; const manifest = useSelector( (state: RootState) => state.workflowManifest.manifest @@ -85,9 +86,6 @@ const VerifyCommit = forwardRef((_, ref) => { }); } }; - const fetchWorkflowNameFromManifest = (manifest: string) => { - return YAML.parse(manifest).metadata.name; - }; useEffect(() => { saveWorkflowGenerateName(manifest); @@ -111,7 +109,7 @@ const VerifyCommit = forwardRef((_, ref) => { }, []); const [yamlStatus, setYamlStatus] = React.useState( - 'Your code is fine. You can move on!' + `${t('createWorkflow.verifyCommit.codeIsFine')}` ); const [modified, setModified] = React.useState(false); @@ -245,6 +243,7 @@ const VerifyCommit = forwardRef((_, ref) => { localforage.removeItem('hasSetWorkflowData'); localforage.removeItem('weights'); localforage.removeItem('selectedHub'); + localforage.removeItem('editSchedule'); setFinishModalOpen(false); }; @@ -265,7 +264,7 @@ const VerifyCommit = forwardRef((_, ref) => {
- {t('createWorkflow.verifyCommit.header')} + {t('createWorkflow.verifyCommit.header')} {t('createWorkflow.verifyCommit.info')} @@ -280,7 +279,7 @@ const VerifyCommit = forwardRef((_, ref) => { - {t('createWorkflow.verifyCommit.summary.header')} + {t('createWorkflow.verifyCommit.summary.header')}
@@ -298,7 +297,6 @@ const VerifyCommit = forwardRef((_, ref) => { onChange={(e) => handleNameChange({ changedName: e.target.value }) } - disabled={workflowData.isRecurring} />
@@ -339,18 +337,7 @@ const VerifyCommit = forwardRef((_, ref) => {
- {/* - */} - {isDisabled ? ( - - {t('createWorkflow.verifyCommit.summary.disabled')} - - ) : cronSyntax === '' ? ( + {cronSyntax === '' ? ( {t('createWorkflow.verifyCommit.summary.schedulingNow')} @@ -360,9 +347,9 @@ const VerifyCommit = forwardRef((_, ref) => { )} -
+
- +
@@ -376,29 +363,25 @@ const VerifyCommit = forwardRef((_, ref) => { {weights.length === 0 ? (
- {t('createWorkflow.verifyCommit.error')} + {t('createWorkflow.verifyCommit.error')}
) : (
-
+
{WorkflowTestData.map((Test) => ( ))}
- + {t('createWorkflow.verifyCommit.button.edit')}
@@ -406,22 +389,28 @@ const VerifyCommit = forwardRef((_, ref) => {
- YAML: + + {t('createWorkflow.verifyCommit.YAML')} +
{weights.length === 0 ? ( - - {' '} - {t('createWorkflow.verifyCommit.errYaml')}{' '} + + {t('createWorkflow.verifyCommit.errYaml')} ) : ( - {yamlStatus}{' '} - {t('createWorkflow.verifyCommit.youCanMoveOn')} + {yamlStatus} + + {t('createWorkflow.verifyCommit.youCanMoveOn')} + )}
- + {t('createWorkflow.verifyCommit.button.viewYaml')}
@@ -467,7 +456,9 @@ const VerifyCommit = forwardRef((_, ref) => {
{workflow.name},
- {t('workflowStepper.successful')} + + {t('workflowStepper.successful')} +
{t('workflowStepper.congratulationsSub1')}
{' '} diff --git a/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/styles.ts b/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/styles.ts index 81cc3ca64..51ae31c98 100644 --- a/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/styles.ts +++ b/litmus-portal/frontend/src/views/CreateWorkflow/VerifyCommit/styles.ts @@ -28,6 +28,7 @@ const useStyles = makeStyles((theme: Theme) => ({ justifyContent: 'space-between', }, headerText: { + fontWeight: 700, fontSize: '1.2rem', [theme.breakpoints.up('lg')]: { fontSize: '1.4rem', @@ -64,8 +65,8 @@ const useStyles = makeStyles((theme: Theme) => ({ }, sumText: { width: '100%', - marginTop: theme.spacing(4.5), - marginBottom: theme.spacing(3), + margin: theme.spacing(2, 0), + fontWeight: 700, fontSize: '1.2rem', [theme.breakpoints.up('lg')]: { fontSize: '1.4rem', @@ -98,11 +99,12 @@ const useStyles = makeStyles((theme: Theme) => ({ marginLeft: theme.spacing(7), paddingTop: theme.spacing(0.5), }, - editButton1: { - marginLeft: theme.spacing(1), + editButton: { + height: '1rem', }, - editbtn: { - color: theme.palette.text.secondary, + editIcon: { + color: theme.palette.text.primary, + height: '0.8rem', }, link: { fontSize: '0.875rem', @@ -135,8 +137,12 @@ const useStyles = makeStyles((theme: Theme) => ({ buttonOutlineText: { padding: theme.spacing(1.5), }, + spacingHorizontal: { + margin: theme.spacing(0, 1), + }, errorText: { color: theme.palette.error.main, + fontWeight: 700, fontSize: '1rem', marginLeft: theme.spacing(5), }, @@ -148,6 +154,7 @@ const useStyles = makeStyles((theme: Theme) => ({ progress: { display: 'flex', flexDirection: 'row', + flexWrap: 'wrap', flexGrow: 1, marginLeft: theme.spacing(5), }, @@ -159,6 +166,9 @@ const useStyles = makeStyles((theme: Theme) => ({ marginTop: theme.spacing(-6), marginRight: theme.spacing(-2.5), }, + verifyYAMLButton: { + width: '60%', + }, // Modal modal: { @@ -190,6 +200,10 @@ const useStyles = makeStyles((theme: Theme) => ({ successful: { fontSize: '2.2rem', fontWeight: 'bold', + margin: theme.spacing(2, 0), + }, + bold: { + fontWeight: 700, }, })); export default useStyles;