diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-02-02 11:34:03 +0100 |
---|---|---|
committer | Guillaume Jambet <guillaume.jambet@gmail.com> | 2018-03-01 15:21:05 +0100 |
commit | 827381f0e4e80b69ba67cd0128f47166eda916be (patch) | |
tree | 53068fededf21e19aa6684dd2e258f0f4f233e35 /server/sonar-web/src/main/js/components | |
parent | 85f7f977c021ea177d3f2442efa114997e313aa2 (diff) | |
download | sonarqube-827381f0e4e80b69ba67cd0128f47166eda916be.tar.gz sonarqube-827381f0e4e80b69ba67cd0128f47166eda916be.zip |
SONAR-10345 Add webhooks management actions
* SONAR-10345 Add the webhooks create/update form
* SONAR-10345 Add the webhooks delete action
* SONAR-10345 Add fields validation on webhook create page
Diffstat (limited to 'server/sonar-web/src/main/js/components')
11 files changed, 563 insertions, 1 deletions
diff --git a/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx b/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx index ab766a9eed6..354e11add9c 100644 --- a/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx @@ -110,7 +110,9 @@ export default class ConfirmButton extends React.PureComponent<Props, State> { disabled={submitting}> {confirmButtonText} </SubmitButton> - <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink> + <ResetButtonLink disabled={submitting} onClick={onCloseClick}> + {translate('cancel')} + </ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx b/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx new file mode 100644 index 00000000000..aae2cde6025 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import ModalValidationField from './ModalValidationField'; + +interface Props { + autoFocus?: boolean; + className?: string; + description?: string; + dirty: boolean; + disabled: boolean; + error: string | undefined; + id?: string; + label?: React.ReactNode; + name: string; + onBlur: (event: React.FocusEvent<any>) => void; + onChange: (event: React.ChangeEvent<any>) => void; + placeholder?: string; + touched: boolean; + type?: string; + value: string; +} + +export default function InputValidationField({ className, ...props }: Props) { + const { description, dirty, error, label, touched, ...inputProps } = props; + const modalValidationProps = { description, dirty, error, label, touched }; + return ( + <ModalValidationField {...modalValidationProps}> + {({ className: validationClassName }) => ( + <input className={classNames(className, validationClassName)} {...inputProps} /> + )} + </ModalValidationField> + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx b/server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx new file mode 100644 index 00000000000..503b58952e3 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import AlertErrorIcon from '../icons-components/AlertErrorIcon'; +import AlertSuccessIcon from '../icons-components/AlertSuccessIcon'; + +interface Props { + children: (props: { className?: string }) => React.ReactNode; + description?: string; + dirty: boolean; + error: string | undefined; + label?: React.ReactNode; + touched: boolean; +} + +export default function ModalValidationField(props: Props) { + const { description, dirty, error } = props; + + const isValid = dirty && props.touched && error === undefined; + const showError = dirty && props.touched && error !== undefined; + return ( + <div className="modal-validation-field"> + {props.label} + {props.children({ className: classNames({ 'has-error': showError, 'is-valid': isValid }) })} + {showError && <AlertErrorIcon className="little-spacer-top" />} + {isValid && <AlertSuccessIcon className="little-spacer-top" />} + {showError && <p className="text-danger">{error}</p>} + {description && <div className="modal-field-description">{description}</div>} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx b/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx new file mode 100644 index 00000000000..d52a0b31af8 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { withFormik, Form, FormikActions, FormikProps } from 'formik'; +import Modal from './Modal'; +import DeferredSpinner from '../common/DeferredSpinner'; +import { translate } from '../../helpers/l10n'; + +interface InnerFormProps<Values> { + children: (props: FormikProps<Values>) => React.ReactNode; + confirmButtonText: string; + header: string; + initialValues: Values; +} + +interface Props<Values> extends InnerFormProps<Values> { + isInitialValid?: boolean; + onClose: () => void; + validate: (data: Values) => void | object | Promise<object>; + onSubmit: (data: Values) => void | Promise<void>; +} + +export default class ValidationModal<Values> extends React.PureComponent<Props<Values>> { + handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.props.onClose(); + }; + + handleSubmit = (data: Values, { setSubmitting }: FormikActions<Values>) => { + const result = this.props.onSubmit(data); + if (result) { + result.then( + () => { + setSubmitting(false); + this.props.onClose(); + }, + () => { + setSubmitting(false); + } + ); + } else { + setSubmitting(false); + this.props.onClose(); + } + }; + + render() { + const { header } = this.props; + + const InnerForm = withFormik<InnerFormProps<Values>, Values>({ + handleSubmit: this.handleSubmit, + isInitialValid: this.props.isInitialValid, + mapPropsToValues: props => props.initialValues, + validate: this.props.validate + })(props => ( + <Form> + <div className="modal-head"> + <h2>{props.header}</h2> + </div> + + <div className="modal-body">{props.children(props)}</div> + + <footer className="modal-foot"> + <DeferredSpinner className="spacer-right" loading={props.isSubmitting} /> + <button disabled={props.isSubmitting || !props.isValid || !props.dirty} type="submit"> + {props.confirmButtonText} + </button> + <button + className="button-link" + disabled={props.isSubmitting} + onClick={this.handleCancelClick} + type="reset"> + {translate('cancel')} + </button> + </footer> + </Form> + )); + + return ( + <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <InnerForm + confirmButtonText={this.props.confirmButtonText} + header={header} + initialValues={this.props.initialValues}> + {this.props.children} + </InnerForm> + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx new file mode 100644 index 00000000000..e0864a1b8fc --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import InputValidationField from '../InputValidationField'; + +it('should render correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + <InputValidationField + description="Field description" + dirty={true} + disabled={false} + error="Bad formatting" + label="Foo field" + name="field" + onBlur={jest.fn()} + onChange={jest.fn()} + touched={true} + value="foo" + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx new file mode 100644 index 00000000000..ae9a2f74118 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ModalValidationField from '../ModalValidationField'; + +it('should display the field without any error/validation', () => { + expect(getWrapper({ description: 'Describe Foo.', touched: false })).toMatchSnapshot(); + expect(getWrapper({ dirty: false })).toMatchSnapshot(); +}); + +it('should display the field as valid', () => { + expect(getWrapper({ error: undefined })).toMatchSnapshot(); +}); + +it('should display the field with an error', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + <ModalValidationField + dirty={true} + error="Is required" + label={<label>Foo</label>} + touched={true} + {...props}> + {({ className }) => <input className={className} type="text" />} + </ModalValidationField> + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx new file mode 100644 index 00000000000..f3048542aa1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { FormikProps } from 'formik'; +import ValidationModal from '../ValidationModal'; + +it('should render correctly', () => { + const { wrapper, inner } = getWrapper(); + expect(wrapper).toMatchSnapshot(); + expect(inner).toMatchSnapshot(); +}); + +interface Values { + field: string; +} + +function getWrapper(props = {}) { + const wrapper = shallow( + <ValidationModal + confirmButtonText="confirm" + header="title" + initialValues={{ field: 'foo' }} + isInitialValid={true} + onClose={jest.fn()} + validate={(values: Values) => ({ field: values.field.length < 2 && 'Too small' })} + onSubmit={jest.fn(() => Promise.resolve())} + {...props}> + {(props: FormikProps<Values>) => ( + <form onSubmit={props.handleSubmit}> + <input + onChange={props.handleChange} + onBlur={props.handleBlur} + name="field" + type="text" + value={props.values.field} + /> + </form> + )} + </ValidationModal> + ); + return { + wrapper, + inner: wrapper + .childAt(0) + .dive() + .dive() + .dive() + }; +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap new file mode 100644 index 00000000000..8afa4d9aa5a --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<ModalValidationField + description="Field description" + dirty={true} + error="Bad formatting" + label="Foo field" + touched={true} +/> +`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap new file mode 100644 index 00000000000..dc901ce06f2 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the field as valid 1`] = ` +<div + className="modal-validation-field" +> + <label> + Foo + </label> + <input + className="is-valid" + type="text" + /> + <AlertSuccessIcon + className="little-spacer-top" + /> +</div> +`; + +exports[`should display the field with an error 1`] = ` +<div + className="modal-validation-field" +> + <label> + Foo + </label> + <input + className="has-error" + type="text" + /> + <AlertErrorIcon + className="little-spacer-top" + /> + <p + className="text-danger" + > + Is required + </p> +</div> +`; + +exports[`should display the field without any error/validation 1`] = ` +<div + className="modal-validation-field" +> + <label> + Foo + </label> + <input + className="" + type="text" + /> + <div + className="modal-field-description" + > + Describe Foo. + </div> +</div> +`; + +exports[`should display the field without any error/validation 2`] = ` +<div + className="modal-validation-field" +> + <label> + Foo + </label> + <input + className="" + type="text" + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap new file mode 100644 index 00000000000..898438ec774 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Modal + contentLabel="title" + onRequestClose={[MockFunction]} +> + <C + confirmButtonText="confirm" + header="title" + initialValues={ + Object { + "field": "foo", + } + } + /> +</Modal> +`; + +exports[`should render correctly 2`] = ` +<Form> + <div + className="modal-head" + > + <h2> + title + </h2> + </div> + <div + className="modal-body" + > + <form + onSubmit={[Function]} + > + <input + name="field" + onBlur={[Function]} + onChange={[Function]} + type="text" + value="foo" + /> + </form> + </div> + <footer + className="modal-foot" + > + <DeferredSpinner + className="spacer-right" + loading={false} + timeout={100} + /> + <button + disabled={true} + type="submit" + > + confirm + </button> + <button + className="button-link" + disabled={false} + onClick={[Function]} + type="reset" + > + cancel + </button> + </footer> +</Form> +`; diff --git a/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx new file mode 100644 index 00000000000..432461ad528 --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as theme from '../../app/theme'; +import { IconProps } from './types'; + +export default function AlertSuccessIcon({ className, fill = theme.green, size = 16 }: IconProps) { + return ( + <svg + className={className} + width={size} + height={size} + viewBox="0 0 16 16" + version="1.1" + xmlnsXlink="http://www.w3.org/1999/xlink" + xmlSpace="preserve"> + <path + style={{ fill }} + d="M12.607 6.554q0-0.25-0.161-0.411l-0.813-0.804q-0.17-0.17-0.402-0.17t-0.402 0.17l-3.643 3.634-2.018-2.018q-0.17-0.17-0.402-0.17t-0.402 0.17l-0.813 0.804q-0.161 0.161-0.161 0.411 0 0.241 0.161 0.402l3.232 3.232q0.17 0.17 0.402 0.17 0.241 0 0.411-0.17l4.848-4.848q0.161-0.161 0.161-0.402zM14.857 8q0 1.866-0.92 3.442t-2.496 2.496-3.442 0.92-3.442-0.92-2.496-2.496-0.92-3.442 0.92-3.442 2.496-2.496 3.442-0.92 3.442 0.92 2.496 2.496 0.92 3.442z" + /> + </svg> + ); +} |