chore(scheduling): Added Edit Schedule Functionality + Fixed a CronWorkflow Bug 🐛 (#2657)

* Added Edit Schedule Functionality + Fixed a CronWorkflow Bug 🐛
* Reverting Sidebar text color
* Resolved comments/suggestions
* Adding missing translation

Signed-off-by: Sayan Mondal <sayan@chaosnative.com>
This commit is contained in:
Sayan Mondal 2021-04-08 12:25:58 +05:30 committed by GitHub
parent 43d0e02810
commit 1a8e67e61d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1706 additions and 196 deletions

View File

@ -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

View File

@ -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<CreateWorkflowCardProps> = ({
}) => {
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 (
<Card
onClick={isDisabled ? () => {} : handleCreateWorkflow}
className={classes.createCard}
data-cy="createWorkflow"
>
<Card className={classes.createCard} data-cy="createWorkflow">
<CardActionArea className={classes.createCardAction}>
<Typography className={classes.createCardHeading}>
{t('home.workflow.heading')}

View File

@ -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<LocalQuickActionCardProps> = ({
? {
src: '/icons/survey.svg',
alt: 'survey',
onClick: () => window.open('https://forms.gle/qMuVphRyEWCFqjD56'),
onClick: () => window.open(constants.FeedbackForm),
text: t('quickActionCard.quickSurvey'),
}
: emptyData,

View File

@ -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',

View File

@ -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',
};

View File

@ -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}
/>
{/* <Route
<Route
exact
path="/workflows/schedule/:scheduleProjectID/:workflowName" // Check
component={SchedulePage}
/> */}
path="/workflows/schedule/:scheduleProjectID/:workflowName"
component={EditSchedule}
/>
<Route
exact
path="/workflows/schedule/:scheduleProjectID/:workflowName/set"
component={SetNewSchedule}
/>
<Route
exact
path="/workflows/template/:templateName"

View File

@ -28,9 +28,6 @@ export interface customWorkflow {
}
export interface WorkflowData {
id: string;
isRecurring: boolean;
isDisabled: boolean;
chaosEngineChanged: boolean;
namespace: string;
workflow_id?: string;

View File

@ -0,0 +1,767 @@
import {
Divider,
FormControl,
FormControlLabel,
Radio,
RadioGroup,
Select,
Typography,
} from '@material-ui/core';
import { ButtonFilled, ButtonOutlined } from 'litmus-ui';
import localforage from 'localforage';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import YAML from 'yaml';
import BackButton from '../../components/Button/BackButton';
import CustomDate from '../../components/DateTime/CustomDate/index';
import CustomTime from '../../components/DateTime/CustomTime/index';
import { constants } from '../../constants';
import Scaffold from '../../containers/layouts/Scaffold';
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 { history } from '../../redux/configureStore';
import { RootState } from '../../redux/reducers';
import { getProjectID, getProjectRole } from '../../utils/getSearchParams';
import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate';
import { fetchWorkflowNameFromManifest } from '../../utils/yamlUtils';
import SetTime from '../../views/CreateWorkflow/ScheduleWorkflow/SetTime';
import useStyles from '../../views/CreateWorkflow/ScheduleWorkflow/styles';
interface ScheduleSyntax {
minute: string | undefined;
hour: string | undefined;
day_month: string | undefined;
month: string | undefined;
day_week: string;
}
const ScheduleWorkflow = () => {
// Initial Cron State
const [cronValue, setCronValue] = useState<ScheduleSyntax>({
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<HTMLInputElement>) => {
setValue(event.target.value);
};
// Controls inner radio buttons of Recurring Schedule
const [valueDef, setValueDef] = React.useState(
workflowData.scheduleType.recurringSchedule
);
const handleChangeInstance = (event: React.ChangeEvent<HTMLInputElement>) => {
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<Date | null>(
new Date(workflowData.scheduleInput.time)
);
// Sets Date
const [selectedDate, setSelectedDate] = React.useState<Date | null>(
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 (
<Scaffold>
<BackButton />
<Typography className={classes.title}>
{t('editSchedule.title')}
</Typography>
<div className={classes.root}>
<div className={classes.innerContainer}>
<br />
{/* Upper segment */}
<div className={classes.scSegments}>
<div>
<Typography className={classes.headerText}>
<strong>{t('createWorkflow.scheduleWorkflow.header')}</strong>
</Typography>
<div className={classes.schBody}>
<Typography align="left" className={classes.description}>
{t('createWorkflow.scheduleWorkflow.info')}
</Typography>
</div>
</div>
<img
src="/icons/calendarWorkflowIcon.svg"
alt="calendar"
className={classes.calIcon}
/>
</div>
<Divider />
{/* Lower segment */}
<div className={classes.scFormControl}>
<FormControl component="fieldset" className={classes.formControl}>
<RadioGroup
data-cy="ScheduleOptions"
aria-label="schedule"
name="schedule"
value={value}
onChange={handleChange}
>
{/* options to choose schedule */}
<FormControlLabel
value="now"
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={
<Typography className={classes.radioText}>
{t('createWorkflow.scheduleWorkflow.radio.now')}
</Typography>
}
/>
{value === 'specificTime' && (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
{t('createWorkflow.scheduleWorkflow.radio.future')}
</Typography>
<div className={classes.innerSpecific}>
<CustomDate
selectedDate={selectedDate}
handleDateChange={(event) => {
handleDateChange(event);
}}
disabled={false}
/>
<CustomTime
handleDateChange={(event) => {
handleTimeChange(event);
}}
value={selectedTime}
ampm
disabled={false}
/>
</div>
</div>
)}
<FormControlLabel
value="recurringSchedule"
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={
<Typography className={classes.radioText}>
{t('createWorkflow.scheduleWorkflow.radio.recurr')}
</Typography>
}
/>
{value === 'recurringSchedule' && (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
{t('createWorkflow.scheduleWorkflow.radio.rightRecurr')}
</Typography>
{/* options to select time of recurring schedule */}
<div className={classes.innerRecurring}>
<FormControl component="fieldset">
<RadioGroup
aria-label="instanceDef"
name="instanceDef"
value={valueDef}
onChange={(event) => {
handleChangeInstance(event);
}}
>
<FormControlLabel
value={constants.recurringEveryHour}
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={t(
'createWorkflow.scheduleWorkflow.every.hr'
)}
/>
{valueDef === constants.recurringEveryHour && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
{t('createWorkflow.scheduleWorkflow.at')}
</Typography>
<SetTime
data={mins}
handleChange={(event) => {
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 ? (
<Typography>
{t('createWorkflow.scheduleWorkflow.min')}
</Typography>
) : (
<Typography>
{t('createWorkflow.scheduleWorkflow.mins')}
</Typography>
)}
</div>
</div>
)}
<FormControlLabel
value={constants.recurringEveryDay}
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={t(
'createWorkflow.scheduleWorkflow.every.day'
)}
/>
{valueDef === constants.recurringEveryDay && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
{t('createWorkflow.scheduleWorkflow.at')}
</Typography>
<CustomTime
handleDateChange={(date: Date | null) => {
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}
/>
</div>
</div>
)}
<FormControlLabel
value={constants.recurringEveryWeek}
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={t(
'createWorkflow.scheduleWorkflow.every.week'
)}
/>
{valueDef === constants.recurringEveryWeek && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
{t('createWorkflow.scheduleWorkflow.on')}
</Typography>
<FormControl className={classes.formControlDT}>
<Select
className={classes.select}
disableUnderline
value={days}
onChange={(e) => {
setCronValue({
...cronValue,
month: '*',
day_week: ((e.target
.value as unknown) as string).slice(
0,
3
),
});
setDays(
(e.target.value as unknown) as string
);
workflowAction.setWorkflowDetails({
scheduleInput: {
...workflowData.scheduleInput,
weekday: (e.target
.value as unknown) as string,
},
});
}}
label="days"
inputProps={{
name: 'days',
style: {
fontSize: '0.75rem',
height: '0.43rem',
},
}}
>
{weekdays.map((day) => (
<option
key={day}
className={classes.opt}
value={day}
>
{day}
</option>
))}
</Select>
</FormControl>
<Typography className={classes.scRandsub1}>
{t('createWorkflow.scheduleWorkflow.at')}
</Typography>
<CustomTime
handleDateChange={(date: Date | null) => {
reccuringDateChange(date);
}}
value={selectedTime}
ampm
disabled={false}
/>
</div>
</div>
)}
<FormControlLabel
value={constants.recurringEveryMonth}
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={t(
'createWorkflow.scheduleWorkflow.every.month'
)}
/>
{valueDef === constants.recurringEveryMonth && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
{t('createWorkflow.scheduleWorkflow.on')}
</Typography>
<SetTime
data={names}
handleChange={(event) => {
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}
/>
<Typography className={classes.scRandsub1}>
{t('createWorkflow.scheduleWorkflow.at')}
</Typography>
<CustomTime
handleDateChange={(date: Date | null) => {
reccuringDateChange(date);
}}
value={selectedTime}
ampm
disabled={false}
/>
</div>
</div>
)}
</RadioGroup>
</FormControl>
</div>
</div>
)}
</RadioGroup>
</FormControl>
</div>
</div>
</div>
{/* Cancel and Save Button */}
<div className={classes.buttonDiv} aria-label="buttons">
<ButtonOutlined
onClick={() => {
history.push({
pathname: `/workflows/`,
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
});
}}
>
Cancel
</ButtonOutlined>
<ButtonFilled
onClick={() =>
history.push({
pathname: `/workflows/schedule/${getProjectID()}/${fetchWorkflowNameFromManifest(
manifest
)}`,
search: `?projectID=${getProjectID()}&projectRole=${getProjectRole()}`,
})
}
>
{t('editSchedule.verify')}
</ButtonFilled>
</div>
</Scaffold>
);
};
export default ScheduleWorkflow;

View File

@ -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<WorkflowProps>({
name: '',
description: '',
});
const [weights, setWeights] = useState<experimentMap[]>([
{
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<Schedules, ScheduleDataVars>(
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 (
<Scaffold>
{loading ? (
<Loader />
) : (
<>
<BackButton />
<Typography className={classes.title}>
{t('editSchedule.title')}
</Typography>
<div className={classes.root}>
<div className={classes.innerContainer}>
<Typography className={classes.sumText}>
{t('createWorkflow.verifyCommit.summary.header')}
</Typography>
<div className={classes.outerSum}>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.summary.workflowName')}:
</Typography>
</div>
<div className={classes.col2} data-cy="WorkflowName">
<Typography>
{fetchWorkflowNameFromManifest(manifest)}
</Typography>
</div>
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.summary.clustername')}:
</Typography>
</div>
<Typography className={classes.schCol2}>
{clustername}
</Typography>
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.summary.desc')}:
</Typography>
</div>
<div className={classes.col2}>
<Typography>{workflow.description}</Typography>
</div>
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.summary.schedule')}:
</Typography>
</div>
<div className={classes.schCol2}>
{cronSyntax === '' ? (
<Typography>
{t('createWorkflow.verifyCommit.summary.schedulingNow')}
</Typography>
) : (
<Typography>{cronstrue.toString(cronSyntax)}</Typography>
)}
<ButtonOutlined
className={classes.editButton}
onClick={() =>
history.push({
pathname: `/workflows/schedule/${projectID}/${fetchWorkflowNameFromManifest(
manifest
)}/set`,
search: `?projectID=${projectID}&projectRole=${userRole}`,
})
}
>
<EditIcon className={classes.editIcon} data-cy="edit" />
{t('editSchedule.edit')}
</ButtonOutlined>
</div>
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.summary.adjustedWeights')}
:
</Typography>
</div>
{weights.length === 0 ? (
<div>
<Typography className={classes.col2}>
{t('createWorkflow.verifyCommit.error')}
</Typography>
</div>
) : (
<div className={classes.adjWeights}>
<div className={classes.progress}>
{weights.map((Test) => (
<AdjustedWeights
key={Test.weight}
testName={`${Test.experimentName} ${t(
'createWorkflow.verifyCommit.test'
)}`}
testValue={Test.weight}
spacing={false}
icon={false}
/>
))}
</div>
</div>
)}
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.YAML')}
</Typography>
</div>
<div className={classes.yamlFlex}>
{weights.length === 0 ? (
<Typography className={classes.spacingHorizontal}>
{t('createWorkflow.verifyCommit.errYaml')}
</Typography>
) : (
<Typography>
<b>{yamlStatus}</b>
<span className={classes.spacingHorizontal}>
{t('createWorkflow.verifyCommit.youCanMoveOn')}
</span>
</Typography>
)}
<br />
<ButtonFilled
className={classes.verifyYAMLButton}
onClick={handleEditOpen}
>
{t('createWorkflow.verifyCommit.button.viewYaml')}
</ButtonFilled>
</div>
</div>
</div>
</div>
</div>
{/* Cancel and Save Button */}
<div className={classes.buttonDiv} aria-label="buttons">
<ButtonOutlined
onClick={() => {
history.push({
pathname: `/workflows/`,
search: `?projectID=${projectID}&projectRole=${userRole}`,
});
}}
>
{t('editSchedule.cancel')}
</ButtonOutlined>
<ButtonFilled onClick={() => handleNext()}>
{t('editSchedule.save')}
</ButtonFilled>
</div>
<Modal
open={open}
onClose={handleEditClose}
width="60%"
modalActions={
<ButtonOutlined
onClick={handleEditClose}
className={classes.closeBtn}
>
&#x2715;
</ButtonOutlined>
}
>
<YamlEditor content={manifest} filename={workflow.name} readOnly />
</Modal>
{/* Finish Modal */}
<div>
<Modal
data-cy="FinishModal"
open={finishModalOpen}
onClose={handleFinishModal}
width="60%"
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
modalActions={
<div data-cy="GoToWorkflowButton">
<ButtonOutlined onClick={handleFinishModal}>
&#x2715;
</ButtonOutlined>
</div>
}
>
<div className={classes.modal}>
<img src="/icons/finish.svg" alt="mark" />
<div className={classes.heading}>
{t('editSchedule.theSchedule')}
<br />
<span className={classes.successful}>{workflow.name}</span>,
<br />
<span className={classes.bold}>
{t('editSchedule.successfullyCreated')}
</span>
</div>
<div className={classes.headWorkflow}>
{t('workflowStepper.congratulationsSub1')} <br />{' '}
{t('workflowStepper.congratulationsSub2')}
</div>
<div className={classes.button}>
<ButtonFilled
data-cy="selectFinish"
onClick={() => {
handleFinishModal();
tabs.changeWorkflowsTabs(0);
history.push({
pathname: '/workflows',
search: `?projectID=${projectID}&projectRole=${userRole}`,
});
}}
>
<div>{t('workflowStepper.workflowBtn')}</div>
</ButtonFilled>
</div>
</div>
</Modal>
<Modal
open={errorModal}
onClose={handleErrorModalClose}
width="60%"
modalActions={
<ButtonOutlined onClick={handleErrorModalClose}>
&#x2715;
</ButtonOutlined>
}
>
<div className={classes.modal}>
<img src="/icons/red-cross.svg" alt="mark" />
<div className={classes.heading}>
<strong>{t('workflowStepper.workflowFailed')}</strong>
</div>
<div className={classes.headWorkflow}>
<Typography>
{t('workflowStepper.error')} : {workflowError?.message}
</Typography>
</div>
<div className={classes.button}>
<ButtonFilled
data-cy="selectFinish"
onClick={() => {
setErrorModal(false);
}}
>
<div>{t('workflowStepper.back')}</div>
</ButtonFilled>
</div>
</div>
</Modal>
</div>
</>
)}
</Scaffold>
);
};
export default EditSchedule;

View File

@ -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;

View File

@ -8,9 +8,6 @@ import {
import createReducer from './createReducer';
const initialState: WorkflowData = {
id: '',
isRecurring: false,
isDisabled: false,
chaosEngineChanged: false,
namespace: 'litmus',
clusterid: '',

View File

@ -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)

View File

@ -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<TableDataProps> = ({
setPopAnchorEl(null);
};
const workflow = useActions(WorkflowActions);
const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => {
setPopAnchorEl(event.currentTarget);
};
@ -125,13 +122,6 @@ const TableData: React.FC<TableDataProps> = ({
});
};
// 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);

View File

@ -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 ? (
<FormControlLabel
value="now"
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={
<Typography className={classes.radioText}>
{t('createWorkflow.scheduleWorkflow.radio.now')}
</Typography>
}
/>
) : YAML.parse(manifest).spec.suspend === true ? (
<></>
) : workflowData.isRecurring ? (
<FormControlLabel
value="disable"
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={
<Typography className={classes.radioText}>
Disable Schedule
</Typography>
}
/>
) : (
<></>
)}
{/* <FormControlLabel
value="specificTime"
disabled
control={<Radio classes={{ root: classes.radio, checked: classes.checked }}/>}
<FormControlLabel
value="now"
control={
<Radio
classes={{
root: classes.radio,
checked: classes.checked,
}}
/>
}
label={
<Typography className={classes.radioText}>
{t('createWorkflow.scheduleWorkflow.radio.specific')}
{t('createWorkflow.scheduleWorkflow.radio.now')}
</Typography>
}
/> */}
{value === 'specificTime' ? (
/>
{value === 'specificTime' && (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
{t('createWorkflow.scheduleWorkflow.radio.future')}
@ -426,8 +444,6 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
/>
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="recurringSchedule"
@ -442,7 +458,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
</Typography>
}
/>
{value === 'recurringSchedule' ? (
{value === 'recurringSchedule' && (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
{t('createWorkflow.scheduleWorkflow.radio.rightRecurr')}
@ -460,7 +476,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
}}
>
<FormControlLabel
value="everyHr"
value={constants.recurringEveryHour}
control={
<Radio
classes={{
@ -471,7 +487,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
}
label={t('createWorkflow.scheduleWorkflow.every.hr')}
/>
{valueDef === 'everyHr' ? (
{valueDef === constants.recurringEveryHour && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
@ -508,11 +524,9 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
)}
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="everyDay"
value={constants.recurringEveryDay}
control={
<Radio
classes={{
@ -523,7 +537,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
}
label={t('createWorkflow.scheduleWorkflow.every.day')}
/>
{valueDef === 'everyDay' ? (
{valueDef === constants.recurringEveryDay && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
@ -551,11 +565,9 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
/>
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="everyWeek"
value={constants.recurringEveryWeek}
control={
<Radio
classes={{
@ -568,7 +580,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
'createWorkflow.scheduleWorkflow.every.week'
)}
/>
{valueDef === 'everyWeek' ? (
{valueDef === constants.recurringEveryWeek && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
@ -633,11 +645,9 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
/>
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="everyMonth"
value={constants.recurringEveryMonth}
control={
<Radio
classes={{
@ -650,7 +660,7 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
'createWorkflow.scheduleWorkflow.every.month'
)}
/>
{valueDef === 'everyMonth' ? (
{valueDef === constants.recurringEveryMonth && (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
@ -687,15 +697,11 @@ const ScheduleWorkflow = forwardRef((_, ref) => {
/>
</div>
</div>
) : (
<></>
)}
</RadioGroup>
</FormControl>
</div>
</div>
) : (
<></>
)}
</RadioGroup>
</FormControl>

View File

@ -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: {

View File

@ -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) => {
<div className={classes.suHeader}>
<div>
<Typography className={classes.headerText}>
<strong> {t('createWorkflow.verifyCommit.header')}</strong>
{t('createWorkflow.verifyCommit.header')}
</Typography>
<Typography className={classes.description}>
{t('createWorkflow.verifyCommit.info')}
@ -280,7 +279,7 @@ const VerifyCommit = forwardRef((_, ref) => {
<Divider />
<Typography className={classes.sumText}>
<strong>{t('createWorkflow.verifyCommit.summary.header')}</strong>
{t('createWorkflow.verifyCommit.summary.header')}
</Typography>
<div className={classes.outerSum}>
@ -298,7 +297,6 @@ const VerifyCommit = forwardRef((_, ref) => {
onChange={(e) =>
handleNameChange({ changedName: e.target.value })
}
disabled={workflowData.isRecurring}
/>
</div>
</div>
@ -339,18 +337,7 @@ const VerifyCommit = forwardRef((_, ref) => {
</Typography>
</div>
<div className={classes.schCol2}>
{/* <CustomDate disabled={edit} />
<CustomTime
handleDateChange={handleDateChange}
value={selectedDate}
ampm
disabled={edit}
/> */}
{isDisabled ? (
<Typography className={classes.schedule}>
{t('createWorkflow.verifyCommit.summary.disabled')}
</Typography>
) : cronSyntax === '' ? (
{cronSyntax === '' ? (
<Typography className={classes.schedule}>
{t('createWorkflow.verifyCommit.summary.schedulingNow')}
</Typography>
@ -360,9 +347,9 @@ const VerifyCommit = forwardRef((_, ref) => {
</Typography>
)}
<div className={classes.editButton1}>
<div className={classes.editButton}>
<IconButton>
<EditIcon className={classes.editbtn} data-cy="edit" />
<EditIcon className={classes.editIcon} data-cy="edit" />
</IconButton>
</div>
</div>
@ -376,29 +363,25 @@ const VerifyCommit = forwardRef((_, ref) => {
{weights.length === 0 ? (
<div>
<Typography className={classes.errorText}>
<strong>{t('createWorkflow.verifyCommit.error')}</strong>
{t('createWorkflow.verifyCommit.error')}
</Typography>
</div>
) : (
<div className={classes.adjWeights}>
<div
className={classes.progress}
style={{ flexWrap: 'wrap' }}
>
<div className={classes.progress}>
{WorkflowTestData.map((Test) => (
<AdjustedWeights
key={Test.weight}
testName={`${Test.experimentName} test`}
testName={`${Test.experimentName} ${t(
'createWorkflow.verifyCommit.test'
)}`}
testValue={Test.weight}
spacing={false}
icon={false}
/>
))}
</div>
<ButtonOutlined
disabled={workflowData.isRecurring}
data-cy="testRunButton"
>
<ButtonOutlined data-cy="testRunButton">
{t('createWorkflow.verifyCommit.button.edit')}
</ButtonOutlined>
</div>
@ -406,22 +389,28 @@ const VerifyCommit = forwardRef((_, ref) => {
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>YAML:</Typography>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.YAML')}
</Typography>
</div>
<div className={classes.yamlFlex}>
{weights.length === 0 ? (
<Typography>
{' '}
{t('createWorkflow.verifyCommit.errYaml')}{' '}
<Typography className={classes.spacingHorizontal}>
{t('createWorkflow.verifyCommit.errYaml')}
</Typography>
) : (
<Typography>
<b>{yamlStatus}</b>{' '}
{t('createWorkflow.verifyCommit.youCanMoveOn')}
<b>{yamlStatus}</b>
<span className={classes.spacingHorizontal}>
{t('createWorkflow.verifyCommit.youCanMoveOn')}
</span>
</Typography>
)}
<br />
<ButtonFilled style={{ width: '60%' }} onClick={handleOpen}>
<ButtonFilled
className={classes.verifyYAMLButton}
onClick={handleOpen}
>
{t('createWorkflow.verifyCommit.button.viewYaml')}
</ButtonFilled>
</div>
@ -467,7 +456,9 @@ const VerifyCommit = forwardRef((_, ref) => {
<br />
<span className={classes.successful}>{workflow.name}</span>,
<br />
<strong>{t('workflowStepper.successful')}</strong>
<span className={classes.bold}>
{t('workflowStepper.successful')}
</span>
</div>
<div className={classes.headWorkflow}>
{t('workflowStepper.congratulationsSub1')} <br />{' '}

View File

@ -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;