Files
fleet/frontend/components/forms/queries/QueryForm/QueryForm.jsx
T
Mike Stone 92d91fdebc Handle server errors (#730)
* consistent error handling

* Display server errors in InviteUserForm

* Handle errors in Form component

* Refactors query form

* creates KolideAce component

* Renders QueryForm from query page and manage hosts page

* Moves ace editor and select targets dropdown to query form

* Render base errors in Form HOC

* LoginPage and ForgotPasswordPage server errors

* Ensure unique key for user blocks

* Adds base error to login form and forgot password form

* Adds base error to query form

* Adds base error to Pack Form

* Adds errors to confirm invite form

* Adds clearErrors action

* clear errors when confirm invite page unmounts

* Handle errors in the App Setting page

* Handle server errors in the User Settings Page

* Handle server errors in the User Management Page
2017-01-05 19:01:17 -05:00

295 lines
7.0 KiB
React

import React, { Component, PropTypes } from 'react';
import { size } from 'lodash';
import Button from 'components/buttons/Button';
import DropdownButton from 'components/buttons/DropdownButton';
import Dropdown from 'components/forms/fields/Dropdown';
import Form from 'components/forms/Form';
import formFieldInterface from 'interfaces/form_field';
import helpers from 'components/forms/queries/QueryForm/helpers';
import InputField from 'components/forms/fields/InputField';
import KolideAce from 'components/KolideAce';
import queryInterface from 'interfaces/query';
import SelectTargetsDropdown from 'components/forms/fields/SelectTargetsDropdown';
import targetInterface from 'interfaces/target';
import validateQuery from 'components/forms/validators/validate_query';
const baseClass = 'query-form';
const validate = (formData) => {
const errors = {};
const {
error: queryError,
valid: queryValid,
} = validateQuery(formData.query);
if (!queryValid) {
errors.query = queryError;
}
if (!formData.name) {
errors.name = 'Title must be present';
}
const valid = !size(errors);
return { valid, errors };
};
class QueryForm extends Component {
static propTypes = {
baseError: PropTypes.string,
fields: PropTypes.shape({
description: formFieldInterface.isRequired,
name: formFieldInterface.isRequired,
query: formFieldInterface.isRequired,
}).isRequired,
handleSubmit: PropTypes.func,
formData: queryInterface,
onCancel: PropTypes.func,
onFetchTargets: PropTypes.func,
onOsqueryTableSelect: PropTypes.func,
onRunQuery: PropTypes.func,
onStopQuery: PropTypes.func,
onTargetSelect: PropTypes.func,
onUpdate: PropTypes.func,
queryIsRunning: PropTypes.bool,
queryType: PropTypes.string,
selectedTargets: PropTypes.arrayOf(targetInterface),
targetsCount: PropTypes.number,
};
static defaultProps = {
queryType: 'query',
targetsCount: 0,
};
constructor (props) {
super(props);
this.state = { errors: {} };
}
onCancel = (evt) => {
evt.preventDefault();
const { onCancel: handleCancel } = this.props;
return handleCancel();
}
onLoad = (editor) => {
editor.setOptions({
enableLinking: true,
});
editor.on('linkClick', (data) => {
const { type, value } = data.token;
const { onOsqueryTableSelect } = this.props;
if (type === 'osquery-token') {
return onOsqueryTableSelect(value);
}
return false;
});
}
onUpdate = (evt) => {
evt.preventDefault();
const { fields } = this.props;
const { onUpdate: handleUpdate } = this.props;
const formData = {
description: fields.description.value,
name: fields.name.value,
query: fields.query.value,
};
const { valid, errors } = validate(formData);
if (valid) {
handleUpdate(formData);
return false;
}
this.setState({
errors: {
...this.state.errors,
...errors,
},
});
return false;
}
renderButtons = () => {
const { canSaveAsNew, canSaveChanges } = helpers;
const {
fields,
formData,
handleSubmit,
onRunQuery,
onStopQuery,
queryIsRunning,
queryType,
} = this.props;
const { onCancel, onUpdate } = this;
const dropdownBtnOptions = [{
disabled: !canSaveChanges(fields, formData),
label: 'Save Changes',
onClick: onUpdate,
}, {
disabled: !canSaveAsNew(fields, formData),
label: 'Save As New...',
onClick: handleSubmit,
}];
let runQueryButton;
if (queryIsRunning) {
runQueryButton = (
<Button
className={`${baseClass}__stop-query-btn`}
onClick={onStopQuery}
variant="alert"
>
Stop Query
</Button>
);
} else {
runQueryButton = (
<Button
className={`${baseClass}__run-query-btn`}
onClick={onRunQuery}
variant="brand"
>
Run Query
</Button>
);
}
if (queryType === 'label') {
return (
<div className={`${baseClass}__button-wrap`}>
<Button
className={`${baseClass}__save-changes-btn`}
onClick={onCancel}
variant="inverse"
>
Cancel
</Button>
<Button
className={`${baseClass}__save-as-new-btn`}
disabled={!canSaveAsNew(fields, formData)}
type="submit"
variant="brand"
>
Save Label
</Button>
</div>
);
}
return (
<div className={`${baseClass}__button-wrap`}>
<DropdownButton
className={`${baseClass}__save`}
options={dropdownBtnOptions}
variant="success"
>
Save
</DropdownButton>
{runQueryButton}
</div>
);
}
renderPlatformDropdown = () => {
const { fields, queryType } = this.props;
if (queryType !== 'label') {
return false;
}
const { platformOptions } = helpers;
return (
<Dropdown
{...fields.platform}
options={platformOptions}
/>
);
}
renderTargetsInput = () => {
const {
onFetchTargets,
onTargetSelect,
queryType,
selectedTargets,
targetsCount,
} = this.props;
if (queryType === 'label') {
return false;
}
return (
<div>
<SelectTargetsDropdown
onFetchTargets={onFetchTargets}
onSelect={onTargetSelect}
selectedTargets={selectedTargets}
targetsCount={targetsCount}
label="Select Targets"
/>
</div>
);
}
render () {
const { errors } = this.state;
const { baseError, fields, handleSubmit, queryIsRunning, queryType } = this.props;
const { onLoad, renderPlatformDropdown, renderButtons, renderTargetsInput } = this;
return (
<form className={`${baseClass}__wrapper body-wrap`} onSubmit={handleSubmit}>
<h1>{queryType === 'label' ? 'New Label Query' : 'New Query'}</h1>
<KolideAce
{...fields.query}
error={fields.query.error || errors.query}
onLoad={onLoad}
readOnly={queryIsRunning}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
/>
{baseError && <div className="form__base-error">{baseError}</div>}
{renderTargetsInput()}
<InputField
{...fields.name}
error={fields.name.error || errors.name}
inputClassName={`${baseClass}__query-title`}
label={queryType === 'label' ? 'Label title' : 'Query Title'}
/>
<InputField
{...fields.description}
inputClassName={`${baseClass}__query-description`}
label="Description"
type="textarea"
/>
{renderPlatformDropdown()}
{renderButtons()}
</form>
);
}
}
export default Form(QueryForm, {
fields: ['description', 'name', 'platform', 'query'],
validate,
});