Skip to content

Commit

Permalink
fix: fix continuous execution and cron expression dynamic
Browse files Browse the repository at this point in the history
  • Loading branch information
ismay committed Jan 13, 2021
1 parent a95ba1f commit 76d216f
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 120 deletions.
13 changes: 13 additions & 0 deletions src/components/FormBaseFields/Radio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import { object } from 'prop-types'
import { Radio as CoreRadio } from '@dhis2/ui-core'

const Radio = ({ input, ...rest }) => {
return <CoreRadio {...input} {...rest} />
}

Radio.propTypes = {
input: object.isRequired,
}

export default Radio
19 changes: 19 additions & 0 deletions src/components/FormBaseFields/Radio.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { shallow } from 'enzyme'
import Radio from './Radio'

describe('<Radio>', () => {
it('renders correctly', () => {
const props = {
input: {
onChange: () => {},
name: 'name',
value: 'value',
},
label: 'label',
}
const wrapper = shallow(<Radio {...props} />)

expect(wrapper).toMatchSnapshot()
})
})
10 changes: 10 additions & 0 deletions src/components/FormBaseFields/__snapshots__/Radio.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Radio> renders correctly 1`] = `
<Radio
label="label"
name="name"
onChange={[Function]}
value="value"
/>
`;
3 changes: 2 additions & 1 deletion src/components/FormBaseFields/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import InputField from './InputField'
import Radio from './Radio'
import SelectField from './SelectField'
import Switch from './Switch'

export { InputField, SelectField, Switch }
export { InputField, SelectField, Switch, Radio }
48 changes: 40 additions & 8 deletions src/components/FormFields/ContinuousExecutionField.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,49 @@
import React from 'react'
import { Field } from 'react-final-form'
import { Switch } from '../../components/FormBaseFields'
import { Field, FormSpy } from 'react-final-form'
import { Radio } from '../FormBaseFields'
import CronField from './CronField'

// The key under which this field will be sent to the backend
export const FIELD_NAME = 'continuousExecution'

/**
* This field is slightly convoluted. For scheduling, the job can either be set to continuous
* execution, or to a cron expression. The display of the cron is controlled by the value of
* continuous execution. So this field actually has two purposes; set the value for continuous
* execution, and control the display of the cron expression field.
* It was designed as a radio button, which does not allow a boolean as a value. But the backend
* expects a boolean for the continuousExecution field. So we're setting true or false as a string.
* This does work as well, but eventually we'll have to revisit this design as this is hacky and
* more likely to break.
*/

const ContinuousExecutionField = () => (
<Field
name={FIELD_NAME}
component={Switch}
label="Continuous Execution"
type="checkbox"
/>
<FormSpy subscription={{ values: true }}>
{({ values }) => {
const continuousExecution = values[FIELD_NAME]

return (
<React.Fragment>
<p>When should the job run?</p>
<Field
label="At a set time/interval"
component={Radio}
name={FIELD_NAME}
type="radio"
value="false"
/>
{continuousExecution === 'false' && <CronField />}
<Field
label="Continuously"
component={Radio}
name={FIELD_NAME}
type="radio"
value="true"
/>
</React.Fragment>
)
}}
</FormSpy>
)

export default ContinuousExecutionField
79 changes: 61 additions & 18 deletions src/components/FormFields/CronField.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,74 @@
import React from 'react'
import { bool } from 'prop-types'
import { Field } from 'react-final-form'
import { func, string } from 'prop-types'
import { connect } from 'react-redux'
import { Field, FormSpy } from 'react-final-form'
import cronstrue from 'cronstrue'
import { getCronPreset } from '../../rootSelectors'
import { selectors, actions } from '../../data/cron-preset'
import { InputField } from '../../components/FormBaseFields'
import { requiredCron } from '../../services/validators'
import { ShowCronPresetButton } from '../Buttons'
import { requiredCron, validateCron } from '../../services/validators'

// The key under which this field will be sent to the backend
export const FIELD_NAME = 'cronExpression'
export const VALIDATOR = requiredCron

const CronField = ({ disabled }) => (
<Field
component={InputField}
name={FIELD_NAME}
validate={VALIDATOR}
label="CRON Expression"
type="text"
required={!disabled}
disabled={disabled}
/>
const DumbCronField = ({ cronPreset, clearPreset }) => (
<FormSpy subscription={{ values: true }}>
{({ form, values }) => {
let humanReadableCron = ''
const cronExpression = values[FIELD_NAME]

// Update the cron expression if a preset has been selected
if (cronPreset && cronExpression !== cronPreset) {
form.change(FIELD_NAME, cronPreset)

// Clear the preset to prevent further updates
clearPreset()
}

if (cronExpression && validateCron(cronExpression)) {
humanReadableCron = cronstrue.toString(cronExpression)
}

return (
<React.Fragment>
<Field
component={InputField}
name={FIELD_NAME}
validate={VALIDATOR}
label="CRON Expression"
type="text"
required
/>
<p>{humanReadableCron}</p>
<ShowCronPresetButton />
</React.Fragment>
)
}}
</FormSpy>
)

CronField.defaultProps = {
disabled: false,
DumbCronField.propTypes = {
cronPreset: string.isRequired,
clearPreset: func.isRequired,
}

const mapStateToProps = state => {
/* istanbul ignore next */
const cronPreset = getCronPreset(state)

/* istanbul ignore next */
return {
cronPreset: selectors.getPreset(cronPreset),
}
}

CronField.propTypes = {
disabled: bool,
const mapDispatchToProps = {
clearPreset: actions.clearPreset,
}

export default CronField
export default connect(
mapStateToProps,
mapDispatchToProps
)(DumbCronField)
78 changes: 0 additions & 78 deletions src/components/FormFields/JobScheduleField.js

This file was deleted.

7 changes: 4 additions & 3 deletions src/components/FormFields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import JobTypeField, {
FIELD_NAME as JOB_TYPE,
VALIDATOR as JOB_TYPE_VALIDATOR,
} from './JobTypeField'
import { FIELD_NAME as CONTINUOUS_EXECUTION } from './ContinuousExecutionField'
import JobScheduleField from './JobScheduleField'
import ContinuousExecutionField, {
FIELD_NAME as CONTINUOUS_EXECUTION,
} from './ContinuousExecutionField'
import ParameterCollectionField from './ParameterCollectionField'

const fieldNames = {
Expand All @@ -29,7 +30,7 @@ const validators = {

export {
JobNameField,
JobScheduleField,
ContinuousExecutionField,
CronField,
JobTypeField,
ParameterCollectionField,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Forms/JobForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { InlineError } from '../Errors'
import { DiscardFormButton } from '../Buttons'
import {
JobNameField,
JobScheduleField,
ContinuousExecutionField,
JobTypeField,
ParameterCollectionField,
fieldNames,
Expand All @@ -26,7 +26,7 @@ const JobForm = ({
onChange={({ pristine }) => setIsPristine(pristine)}
/>
<JobNameField />
<JobScheduleField />
<ContinuousExecutionField />
<JobTypeField />
<ParameterCollectionField jobType={values[fieldNames.JOB_TYPE]} />
<div>
Expand Down
23 changes: 13 additions & 10 deletions src/components/Forms/JobFormContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ const validate = values => {
CRON_VALIDATOR,
} = validators

const jobNameValue = values[JOB_NAME]
const cronValue = values[CRON]
const jobTypeValue = values[JOB_TYPE]
const continuousExecutionValue = values[CONTINUOUS_EXECUTION]
const jobName = values[JOB_NAME]
const cronExpression = values[CRON]
const jobType = values[JOB_TYPE]
const continuousExecution = values[CONTINUOUS_EXECUTION]

return {
[JOB_NAME]: JOB_NAME_VALIDATOR(jobNameValue),
// Only validate the cron if it's not set to continuous execution
[CRON]: continuousExecutionValue
const validation = {
[JOB_NAME]: JOB_NAME_VALIDATOR(jobName),
// Only validate the cron expression if the job is not set to continuous execution
[CRON]: continuousExecution
? undefined
: CRON_VALIDATOR(cronValue),
[JOB_TYPE]: JOB_TYPE_VALIDATOR(jobTypeValue),
: CRON_VALIDATOR(cronExpression),
[JOB_TYPE]: JOB_TYPE_VALIDATOR(jobType),
}

return validation
}

const DumbJobFormContainer = ({ setIsPristine, createJob }) => {
Expand All @@ -49,6 +51,7 @@ const DumbJobFormContainer = ({ setIsPristine, createJob }) => {
validate={validate}
component={JobForm}
setIsPristine={setIsPristine}
destroyOnUnregister
/>
)
}
Expand Down

0 comments on commit 76d216f

Please sign in to comment.