From e3409799650831a9632ae1cc51e00c3e544f87ef Mon Sep 17 00:00:00 2001
From: manorlh <44364426+manorlh@users.noreply.github.com>
Date: Thu, 16 May 2019 18:13:33 +0300
Subject: [PATCH] feat: rerun feature, search by enter (#137)
* feat: rerun feature,search-enter
---
ui/src/components/SearchBar/index.js | 8 ++-
.../PerformanceUI/components/JobForm/index.js | 28 +++-----
.../instance/configurationColumn.js | 64 ++++++++++++++++---
.../PerformanceUI/instance/get-jobs.js | 59 +++++++++++------
.../instance/get-last-reports.js | 58 ++++++++++++-----
.../instance/get-test-reports.js | 62 ++++++++++++++----
.../PerformanceUI/instance/get-tests.js | 4 --
.../PerformanceUI/instance/redux/action.js | 3 +-
.../instance/redux/actions/jobsActions.js | 4 --
.../instance/redux/reducers/jobsReducer.js | 2 -
.../instance/redux/selectors/jobsSelector.js | 1 -
.../PerformanceUI/instance/requestBuilder.js | 21 ++++++
12 files changed, 222 insertions(+), 92 deletions(-)
create mode 100644 ui/src/features/PerformanceUI/instance/requestBuilder.js
diff --git a/ui/src/components/SearchBar/index.js b/ui/src/components/SearchBar/index.js
index 7a13abcc9..cb490fb42 100644
--- a/ui/src/components/SearchBar/index.js
+++ b/ui/src/components/SearchBar/index.js
@@ -17,13 +17,19 @@ class SearchBar extends React.Component {
)
}
+
+ handleEnter=(e)=>{
+ if (e.key === 'Enter') {
+ this.props.onSearch(this.state.value)
+ }
+ }
}
export default SearchBar;
diff --git a/ui/src/features/PerformanceUI/components/JobForm/index.js b/ui/src/features/PerformanceUI/components/JobForm/index.js
index e80c4328d..b6aac43fd 100644
--- a/ui/src/features/PerformanceUI/components/JobForm/index.js
+++ b/ui/src/features/PerformanceUI/components/JobForm/index.js
@@ -21,6 +21,7 @@ import TextArea from '../../../../components/TextArea';
import MultiValueInput from '../../../../components/MultiValueInput';
import UiSwitcher from '../../../../components/UiSwitcher';
import {filter} from 'lodash';
+import {createJobRequest} from '../../instance/requestBuilder';
const DESCRIPTION = 'Predator executes tests through jobs. Use this form to specify the parameters for the job you want to execute.';
const inputTypes = {
@@ -228,7 +229,7 @@ class Form extends React.Component {
}
render() {
- const {closeDialog, processingAction} = this.props;
+ const {closeDialog, processingAction, serverError,clearErrorOnCreateJob} = this.props;
return (
@@ -248,7 +249,10 @@ class Form extends React.Component {
- {this.props.serverError ? : null}
+ { serverError &&
+ {clearErrorOnCreateJob()}} showMessage={serverError}/>
+ }
+
@@ -300,7 +304,6 @@ class Form extends React.Component {
);
default:
- console.log('oneItem', oneItem)
return (
{
-
- let body = {
+ this.props.createJob(createJobRequest(Object.assign({}, this.state, {
test_id: this.props.data.id,
- arrival_rate: parseInt(this.state.arrival_rate),
duration: parseInt(this.state.duration) * 60,
- ramp_to: this.state.ramp_to ? parseInt(this.state.ramp_to) : undefined,
- environment: this.state.environment,
- run_immediately: (this.state.run_immediately === undefined) ? false : this.state.run_immediately,
- emails: this.state.emails,
- webhooks: this.state.webhooks,
- notes: this.state.notes,
- parallelism: this.state.parallelism ? parseInt(this.state.parallelism) : undefined,
- max_virtual_users: this.state.max_virtual_users ? parseInt(this.state.max_virtual_users) : undefined
- };
- if (this.state.cron_expression) {//should exist and not empty
- body.cron_expression = this.state.cron_expression
- }
- body = JSON.parse(JSON.stringify(body));
- this.props.createJob(body);
+ })));
};
}
diff --git a/ui/src/features/PerformanceUI/instance/configurationColumn.js b/ui/src/features/PerformanceUI/instance/configurationColumn.js
index 985b8b0aa..16b000f3b 100644
--- a/ui/src/features/PerformanceUI/instance/configurationColumn.js
+++ b/ui/src/features/PerformanceUI/instance/configurationColumn.js
@@ -5,7 +5,7 @@ import Moment from 'moment';
import prettySeconds from 'pretty-seconds';
import 'font-awesome/css/font-awesome.min.css';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
-import {faEye, faCloudDownloadAlt, faStopCircle, faTrashAlt, faPen} from '@fortawesome/free-solid-svg-icons'
+import {faEye,faRedo, faRunning, faCloudDownloadAlt, faStopCircle, faTrashAlt, faPen} from '@fortawesome/free-solid-svg-icons'
import classnames from 'classnames';
import css from './configurationColumn.scss';
import env from "../../../App/common/env";
@@ -14,7 +14,7 @@ import TooltipWrapper from '../../../components/TooltipWrapper';
import {getTimeFromCronExpr} from './utils';
-export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView, onRawView,onStop, onDelete, onEdit, onRunTest }) => {
+export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView, onRawView, onStop, onDelete, onEdit, onRunTest}) => {
const columns = [
{
@@ -69,7 +69,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
Edit
),
- accessor: data => data.type ==='basic' ? onEdit(data)}/> : 'N/A',
+ accessor: data => data.type ==='basic' ? {
+ e.stopPropagation();
+ onEdit(data)}}/> : 'N/A',
className: css['small-header'],
headerClassName: css['small-header']
},
@@ -226,7 +228,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
Report
),
- accessor: data => onReportView(data)}/>,
+ accessor: data => {
+ e.stopPropagation();
+ onReportView(data)}}/>,
className: css['small-header'],
headerClassName: css['small-header']
},
@@ -237,7 +241,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
Grafana
),
- accessor: data => window.open(data.grafana_report, '_blank')}/>,
+ accessor: data => {
+ e.stopPropagation();
+ window.open(data.grafana_report, '_blank')}}/>,
className: css['small-header'],
headerClassName: css['small-header']
},
@@ -248,7 +254,37 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
Raw
),
- accessor: data => onRawView(data)}/>,
+ accessor: data => {
+ e.stopPropagation();
+ onRawView(data)}}/>,
+ className: css['small-header'],
+ headerClassName: css['small-header'],
+
+ },
+ {
+ id: 'rerun',
+ Header: () => (
+
+ Rerun
+
+ ),
+ accessor: data => {
+ e.stopPropagation();
+ onRunTest(data)}}/>,
+ className: css['small-header'],
+ headerClassName: css['small-header'],
+
+ },
+ {
+ id: 'run_now',
+ Header: () => (
+
+ Run Now
+
+ ),
+ accessor: data => {
+ e.stopPropagation();
+ onRunTest(data)}}/>,
className: css['small-header'],
headerClassName: css['small-header'],
@@ -260,7 +296,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
Delete
),
- accessor: data => onDelete(data)}/>,
+ accessor: data => {
+ e.stopPropagation();
+ onDelete(data)}}/>,
className: css['small-header'],
headerClassName: css['small-header']
},
@@ -271,7 +309,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
Run Test
),
- accessor: data => onRunTest(data)}/>,
+ accessor: data => {
+ e.stopPropagation();
+ onRunTest(data)}}/>,
className: css['small-header'],
headerClassName: css['small-header']
}, {
@@ -282,7 +322,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
),
accessor: data => ( window.open(`${env.PREDATOR_URL}/jobs/${data.job_id}/runs/${data.report_id}/logs`, '_blank')}/>),
+ onClick={(e) => {
+ e.stopPropagation();
+ window.open(`${env.PREDATOR_URL}/jobs/${data.job_id}/runs/${data.report_id}/logs`, '_blank')}}/>),
className: css['small-header'],
headerClassName: css['small-header']
}, {
@@ -294,7 +336,9 @@ export const getColumns = ({columnsNames, sortHeader = '', onSort, onReportView,
),
accessor: (data) => {
const disabled = (data.status !== 'in_progress' && data.status !== 'started');
- return ( onStop(data)}/>)
+ return ( {
+ e.stopPropagation();
+ onStop(data)}}/>)
},
className: css['small-header'],
headerClassName: css['small-header']
diff --git a/ui/src/features/PerformanceUI/instance/get-jobs.js b/ui/src/features/PerformanceUI/instance/get-jobs.js
index aa045fdfb..4e611001b 100644
--- a/ui/src/features/PerformanceUI/instance/get-jobs.js
+++ b/ui/src/features/PerformanceUI/instance/get-jobs.js
@@ -5,10 +5,10 @@ import {
job,
processingGetJobs,
errorOnGetJobs,
- errorOnGetJob,
processingDeleteJob,
deleteJobSuccess,
- getJobsWithTestNameAndLastRun
+ getJobsWithTestNameAndLastRun,
+ createJobSuccess
} from './redux/selectors/jobsSelector';
import { tests } from './redux/selectors/testsSelector';
import { reports } from './redux/selectors/reportsSelector';
@@ -21,11 +21,12 @@ import Page from '../../../components/Page';
import { ReactTableComponent } from '../../../components/ReactTable';
import { getColumns } from './configurationColumn';
import _ from 'lodash';
+import {createJobRequest} from './requestBuilder';
const noDataMsg = 'There is no data to display.';
const errorMsgGetTests = 'Error occurred while trying to get all jobs.';
const REFRESH_DATA_INTERVAL = 30000;
-const columnsNames = ['test_name', 'environment', 'duration', 'arrival_rate', 'ramp_to', 'parallelism', 'max_virtual_users', 'cron_expression', 'last_run', 'raw', 'delete'];
+const columnsNames = ['test_name', 'environment', 'duration', 'arrival_rate', 'ramp_to', 'parallelism', 'max_virtual_users', 'cron_expression', 'last_run', 'run_now', 'raw', 'delete'];
const DESCRIPTION = 'Scheduled jobs configured with a cron expression.';
class getJobs extends React.Component {
@@ -37,7 +38,8 @@ class getJobs extends React.Component {
deleteDialog: false,
openViewJob: false,
jobToDelete: undefined,
- sortedJobs: []
+ sortedJobs: [],
+ rerunJob: null
};
}
@@ -69,6 +71,14 @@ class getJobs extends React.Component {
this.setState({ openViewJob: job });
};
+ onRunTest = (job) => {
+ const request = createJobRequest(job);
+ delete request.cron_expression;
+ request.run_immediately = true;
+ this.props.createJob(request);
+ this.setState({rerunJob:job});
+ };
+
submitDelete = () => {
this.props.deleteJob(this.state.jobToDelete.id);
this.props.getAllJobs();
@@ -127,9 +137,10 @@ class getJobs extends React.Component {
columnsNames,
onSort: this.onSort,
onRawView: this.onRawView,
+ onRunTest: this.onRunTest,
onDelete: this.onDelete
});
-
+ const feedbackMessage = this.generateFeedbackMessage();
return (
: null}
- {/* //TODO seems deleteError will not work */}
- {
this.props.getAllJobs();
this.props.clearDeleteJobSuccess();
+ this.props.createJobSuccess(undefined);
this.setState({
- jobToDelete: undefined
+ jobToDelete: undefined,
+ rerunJob: null
});
}}
- />
+ />}
);
}
+
+ generateFeedbackMessage = ()=>{
+ if(this.state.jobToDelete && this.state.jobToDelete.id){
+ return `Job deleted successfully: ${this.state.jobToDelete.id}`
+ }
+ if(this.props.jobSuccess && this.state.rerunJob){
+ return `Job created successfully: ${this.props.jobSuccess.id}`;
+ }
+
+ }
}
function mapStateToProps(state) {
@@ -184,11 +202,12 @@ function mapStateToProps(state) {
job: job(state),
processingGetJobs: processingGetJobs(state),
errorOnGetJobs: errorOnGetJobs(state),
- errorOnGetJob: errorOnGetJob(state),
processingDeleteJob: processingDeleteJob(state),
deleteJobSuccess: deleteJobSuccess(state),
tests: tests(state),
- reports: reports(state)
+ reports: reports(state),
+ jobSuccess: createJobSuccess(state)
+
};
}
@@ -197,11 +216,13 @@ const mapDispatchToProps = {
clearErrorOnGetJobs: Actions.clearErrorOnGetJobs,
getAllJobs: Actions.getJobs,
getJob: Actions.getJob,
+ createJob: Actions.createJob,
deleteJob: Actions.deleteJob,
clearDeleteJobSuccess: Actions.clearDeleteJobSuccess,
clearErrorOnDeleteJob: Actions.clearErrorOnDeleteJob,
getTests: Actions.getTests,
- getAllReports: Actions.getLastReports
+ getAllReports: Actions.getLastReports,
+ createJobSuccess: Actions.createJobSuccess,
};
export default connect(mapStateToProps, mapDispatchToProps)(getJobs);
diff --git a/ui/src/features/PerformanceUI/instance/get-last-reports.js b/ui/src/features/PerformanceUI/instance/get-last-reports.js
index f77743818..4f2d1019c 100644
--- a/ui/src/features/PerformanceUI/instance/get-last-reports.js
+++ b/ui/src/features/PerformanceUI/instance/get-last-reports.js
@@ -1,7 +1,7 @@
import React from 'react';
import {connect} from 'react-redux';
import * as selectors from './redux/selectors/reportsSelector';
-import {errorOnStopRunningJob, stopRunningJobSuccess} from './redux/selectors/jobsSelector';
+import {createJobSuccess, errorOnStopRunningJob, stopRunningJobSuccess} from './redux/selectors/jobsSelector';
import {tests} from './redux/selectors/testsSelector';
import Snackbar from 'material-ui/Snackbar';
import style from './style.scss';
@@ -10,6 +10,7 @@ import * as Actions from './redux/action';
import Page from '../../../components/Page';
import _ from 'lodash';
import Report from '../components/Report';
+import {createJobRequest} from './requestBuilder';
import {ReactTableComponent} from './../../../components/ReactTable';
import {getColumns} from './configurationColumn'
@@ -17,8 +18,8 @@ import {getColumns} from './configurationColumn'
const REFRESH_DATA_INTERVAL = 30000;
const columnsNames = ['test_name', 'start_time', 'end_time', 'duration', 'status', 'arrival_rate',
- 'ramp_to', 'last_success_rate', 'last_rps', 'parallelism', 'notes', 'report', 'grafana_report', 'raw', 'logs', 'stop'];
-const DESCRIPTION='Reports give you insight into the performance of your API. Predator generates a report for each test that is executed.';
+ 'ramp_to', 'last_success_rate', 'last_rps', 'parallelism', 'notes', 'report', 'grafana_report', 'rerun', 'raw', 'logs', 'stop'];
+const DESCRIPTION = 'Reports give you insight into the performance of your API. Predator generates a report for each test that is executed.';
class getReports extends React.Component {
constructor(props) {
@@ -27,7 +28,9 @@ class getReports extends React.Component {
this.state = {
showReport: false,
sortedReports: [],
- sortHeader: ''
+ sortHeader: '',
+ rerunJob: null
+
};
}
@@ -42,6 +45,13 @@ class getReports extends React.Component {
this.setState({openViewReport: report});
};
+ onRunTest = (job) => {
+ const requestBody = createJobRequest(job);
+ delete requestBody.cron_expression;
+ requestBody.run_immediately = true;
+ this.props.createJob(requestBody);
+ this.setState({rerunJob: job});
+ };
closeViewReportDialog = () => {
this.setState({
@@ -114,9 +124,10 @@ class getReports extends React.Component {
onSort: this.onSort,
onReportView: this.onReportView,
onRawView: this.onRawView,
- onStop: this.onStop
+ onStop: this.onStop,
+ onRunTest: this.onRunTest
});
-
+ const feedbackMessage = this.generateFeedbackMessage();
return (
@@ -139,27 +150,37 @@ class getReports extends React.Component {
?