diff --git a/ui/src/features/components/JobForm/constants.js b/ui/src/features/components/JobForm/constants.js index ca10b0a1c..8b715ba0b 100644 --- a/ui/src/features/components/JobForm/constants.js +++ b/ui/src/features/components/JobForm/constants.js @@ -5,6 +5,7 @@ export const testTypes = { }; export const inputTypes = { + LIST: 'LIST', INPUT_LIST: 'INPUT_LIST', NUMERIC_INPUT: 'NUMERIC_INPUT', NUMERIC_WITH_SWITCH: 'NUMERIC_WITH_SWITCH', @@ -12,5 +13,8 @@ export const inputTypes = { SWITCHER_TWO_SIDES: 'SWITCHER_TWO_SIDES', TEXT_FIELD: 'TEXT_FIELD', RADIO: 'RADIO', - MULTI_SELECT: 'MULTI_SELECT' + MULTI_SELECT: 'MULTI_SELECT', + LINK_ACTION: 'LINK_ACTION', + DROPDOWN: 'DROPDOWN', + SUBMIT_BUTTON: 'SUBMIT_BUTTON' }; diff --git a/ui/src/features/components/JobForm/index.js b/ui/src/features/components/JobForm/index.js index fa6c94bed..cfb6f153a 100644 --- a/ui/src/features/components/JobForm/index.js +++ b/ui/src/features/components/JobForm/index.js @@ -26,6 +26,10 @@ import { faClock, faPlayCircle } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import IconButton from '../../../components/IconButton'; import { createJobRequest, createStateForEditJob } from './utils'; +import Button from '../../../components/Button'; +import SimpleTable from '../SimpleTable'; +import { chaosExperimentsForDropdown } from '../../redux/selectors/chaosExperimentsSelector'; +import Dropdown from '../../../components/Dropdown/Dropdown.export'; const DESCRIPTION = 'Predator executes tests through jobs. Use this form to specify the parameters for the job you want to execute.'; @@ -51,7 +55,6 @@ class Form extends React.Component { optionToValue: { 'Load test': 'load_test', 'Functional test': 'functional_test' }, valueToOption: { load_test: 'Load test', functional_test: 'Functional test' } }, - { group: 'section_a', bottom: [ @@ -88,7 +91,6 @@ class Form extends React.Component { } ], children: [ - { name: 'arrival_rate', key: 'arrival_rate', @@ -175,6 +177,7 @@ class Form extends React.Component { info: 'When the test finishes, a report will be sent to the emails included.', element: 'Email', type: inputTypes.INPUT_LIST, + values: (state) => state['emails'].map((value) => ({ value, label: value })), flexBasis: '49%' }, { @@ -186,13 +189,110 @@ class Form extends React.Component { type: inputTypes.MULTI_SELECT, options: (props) => props.webhooks, flexBasis: '49%' - } - ] }, - { + ...(props.experiments.length > 0 ? [{ group: 'section_c', + children: + [ + { + name: 'experiments', + key: 'experiments', + floatingLabelText: 'Running Experiments', + info: 'Chaos experiments running within the test', + element: 'RunningExperiments', + type: inputTypes.LIST, + rows: (state) => state.experiments.map((experiment, index) => { + return [ +
+ experiment name: + {experiment.experiment_name} + start at: + {experiment.start_after} +
+ ] + }), + flexBasis: '80%' + }, + { + name: 'add_experiment_option', + key: 'add_experiment_option', + floatingLabelText: '+Add Experiment', + onClick: () => { + this.setState({ + add_experiment_form_experiment_name: '', + add_experiment_form_experiment_id: '', + add_experiment_form_start_after: 0, + add_experiment_form_hidden: false + }) + }, + type: inputTypes.LINK_ACTION, + justifyContent: 'flex-end' + }, + { + name: 'add_experiment_form_experiment_name', + key: 'add_experiment_form_experiment_name', + floatingLabelText: 'Experiment', + list: (props) => props.experiments, + placeholder: 'Select experiment to run', + element: 'new_experiment', + selectedOption: (state) => ({ key: state.add_experiment_form_experiment_id, value: state.add_experiment_form_experiment_name }), + type: inputTypes.DROPDOWN, + onChange: ({ value, key }) => { + this.setState({ + add_experiment_form_experiment_name: value, + add_experiment_form_experiment_id: key + }) + }, + flexBasis: '49%', + hiddenCondition: (state) => state.add_experiment_form_hidden === true + }, + { + name: 'add_experiment_form_start_after', + key: 'add_experiment_form_start_after', + floatingLabelText: 'Start At', + info: 'When to start the experiment within the test timeframe (milliseconds)', + element: 'StartAt', + type: inputTypes.NUMERIC_INPUT, + justifyContent: 'flex-end', + height: '35px', + hiddenCondition: (state) => state.add_experiment_form_hidden === true + }, + { + name: 'add_experiment_submit', + key: 'add_experiment_submit', + floatingLabelText: 'Add', + element: 'AddExperiment', + type: inputTypes.SUBMIT_BUTTON, + inverted: true, + justifyContent: 'flex-end', + paddingTop: '22px', + height: '35px', + onClick: () => { + const newExperiment = { + experiment_id: this.state.add_experiment_form_experiment_id, + experiment_name: this.state.add_experiment_form_experiment_name, + start_after: this.state.add_experiment_form_start_after + } + const experiments = [...this.state.experiments, newExperiment] + this.state.experiments.push(newExperiment) + this.setState((prevState) => { + return { + ...prevState, + experiments: experiments, + add_experiment_form_hidden: true + } + }) + console.log(this.state.experiments) + }, + disabled: (state) => state.add_experiment_form_experiment_name.trim().length === 0 || state.add_experiment_form_start_after === 0, + hiddenCondition: (state) => state.add_experiment_form_hidden === true + } + ] + }] : []), + { + group: 'section_d', flexDirection: 'column', children: [ { @@ -233,6 +333,7 @@ class Form extends React.Component { run_immediately: false, emails: [], webhooks: [], + experiments: [], helpInfo: undefined, parallelism: undefined, max_virtual_users: undefined, @@ -252,7 +353,11 @@ class Form extends React.Component { parallelism: true, max_virtual_users: true }, - mode: 'Simple' + mode: 'Simple', + add_experiment_form_hidden: true, + add_experiment_form_experiment_id: '', + add_experiment_form_experiment_name: '', + add_experiment_form_start_after: 0 }; if (this.props.editMode) { @@ -283,6 +388,7 @@ class Form extends React.Component { componentDidMount () { this.props.getWebhooks(); + this.props.getChaosExperiments(); } componentDidUpdate (prevProps, prevState, snapshot) { @@ -424,6 +530,28 @@ class Form extends React.Component { const { cron_expression } = this.state; switch (oneItem.type) { + case inputTypes.LINK_ACTION: + return ( + +
+ {oneItem.floatingLabelText} +
+
+ ) + case inputTypes.SUBMIT_BUTTON: + return ( + + + + ) case inputTypes.SWITCHER: return ( @@ -440,7 +568,6 @@ class Form extends React.Component { ); case inputTypes.SWITCHER_TWO_SIDES: return ( -
{oneItem.leftOption}
{oneItem.rightOption}
- ); + case inputTypes.LIST: { + return ( + }> + + + ) + } case inputTypes.INPUT_LIST: return ( }> ({ value, label: value }))} + disabled={oneItem.disabled} + values={oneItem.values(this.state)} onAddItem={(evt) => this.handleInputListAdd(oneItem.name, evt)} onRemoveItem={evt => this.handleInputListRemove(oneItem.name, evt)} // validationFunc={validateFunc} @@ -471,7 +607,18 @@ class Form extends React.Component { ); - + case inputTypes.DROPDOWN: + return ( + }> + + + ) case inputTypes.TEXT_FIELD: return ( ); - case inputTypes.RADIO: return ( }> - - { - this.onChangeProperty(oneItem.name, value) - const newState = oneItem.newState && oneItem.newState(value); - this.setState(newState); - }} - disabled={this.state.disabled[oneItem.name]} - width={'80px'} - /> - - + + }> + + { + this.onChangeProperty(oneItem.name, value) + const newState = oneItem.newState && oneItem.newState(value); + this.setState(newState); + }} + disabled={this.state.disabled[oneItem.name]} + width={'80px'} + /> + + + ); case inputTypes.NUMERIC_WITH_SWITCH: return ( @@ -628,7 +777,8 @@ function mapStateToProps (state) { processingAction: processingCreateJob(state), serverError: createJobFailure(state), createJobSuccess: createJobSuccess(state), - webhooks: webhooksForDropdown(state) + webhooks: webhooksForDropdown(state), + experiments: chaosExperimentsForDropdown(state) }; } @@ -636,7 +786,8 @@ const mapDispatchToProps = { clearErrorOnCreateJob: Actions.clearErrorOnCreateJob, createJob: Actions.createJob, editJob: Actions.editJob, - getWebhooks: Actions.getWebhooks + getWebhooks: Actions.getWebhooks, + getChaosExperiments: Actions.getChaosExperiments }; export default connect(mapStateToProps, mapDispatchToProps)(Form); diff --git a/ui/src/features/components/JobForm/style.scss b/ui/src/features/components/JobForm/style.scss index d9831a35a..211187fec 100644 --- a/ui/src/features/components/JobForm/style.scss +++ b/ui/src/features/components/JobForm/style.scss @@ -18,4 +18,29 @@ letter-spacing: normal; color: #557eff; margin-left: 10px; -} \ No newline at end of file +} + +.actions-style { + cursor: pointer; + font-size: 13px; + font-weight: normal; + font-style: normal; + font-stretch: normal; + line-height: normal; + letter-spacing: normal; + color: #557eff; + margin-left: 10px; +} + +.list-container { + display: flex; + align-items: center; +} + +.list-item { + margin: 10px; + &__title { + color: #557eff; + + } +} diff --git a/ui/src/features/components/JobForm/utils.js b/ui/src/features/components/JobForm/utils.js index 3f494c190..fb4ce9322 100644 --- a/ui/src/features/components/JobForm/utils.js +++ b/ui/src/features/components/JobForm/utils.js @@ -29,6 +29,7 @@ export const createStateForEditJob = (job, dropdownWebhooks) => { export const createJobRequest = (opts) => { // job_type is for rerun + debugger; let body = { test_id: opts.test_id, type: opts.type || opts.job_type, @@ -38,6 +39,7 @@ export const createJobRequest = (opts) => { run_immediately: (opts.run_immediately === undefined) ? false : opts.run_immediately, emails: opts.emails, webhooks: opts.webhooks, + experiments: opts.experiments, notes: opts.notes, parallelism: opts.parallelism ? parseInt(opts.parallelism) : undefined, max_virtual_users: opts.max_virtual_users ? parseInt(opts.max_virtual_users) : undefined diff --git a/ui/src/features/configurationColumn.js b/ui/src/features/configurationColumn.js index 112d3e611..c98d29709 100644 --- a/ui/src/features/configurationColumn.js +++ b/ui/src/features/configurationColumn.js @@ -1,5 +1,5 @@ import { TableHeader } from '../components/ReactTable'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { get } from 'lodash'; import Checkbox from '../components/Checkbox/Checkbox'; diff --git a/ui/src/features/redux/selectors/chaosExperimentsSelector.js b/ui/src/features/redux/selectors/chaosExperimentsSelector.js index b9e4d5854..ad782c7de 100644 --- a/ui/src/features/redux/selectors/chaosExperimentsSelector.js +++ b/ui/src/features/redux/selectors/chaosExperimentsSelector.js @@ -1,6 +1,12 @@ +import { createSelector } from 'reselect'; + export const chaosExperimentsList = (state) => state.ChaosExperimentsReducer.get('chaosExperiments'); export const chaosExperimentsLoading = (state) => state.ChaosExperimentsReducer.get('chaosExperiments_loading'); export const chaosExperimentFailure = (state) => state.ChaosExperimentsReducer.get('chaosExperiment_error'); export const createChaosExperimentSuccess = (state) => state.ChaosExperimentsReducer.get('create_chaosExperiment_success'); export const deleteChaosExperimentSuccess = (state) => state.ChaosExperimentsReducer.get('delete_chaosExperiment_success'); export const deleteChaosExperimentFailure = (state) => state.ChaosExperimentsReducer.get('delete_chaosExperiment_failure'); + +export const chaosExperimentsForDropdown = createSelector(chaosExperimentsList, (experiments) => { + return experiments.map((experiment) => ({ key: experiment.id, value: experiment.name })); +});