From 97ec39015ba335edc7e77d1507416b35136de962 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 16 May 2018 16:49:45 +0200 Subject: [PATCH] Fix Quality gate app --- .../components/CopyQualityGateForm.tsx | 54 +++++----- .../components/CreateQualityGateForm.tsx | 50 +++++----- .../components/DetailsHeader.tsx | 45 ++++++--- .../quality-gates/components/ListHeader.tsx | 17 +++- .../components/RenameQualityGateForm.tsx | 54 +++++----- .../js/components/controls/ConfirmButton.tsx | 99 ++++--------------- .../js/components/controls/ConfirmModal.tsx | 89 +++++++++++++++++ .../js/components/controls/ModalButton.tsx | 80 +++++++++++++++ .../controls/__tests__/ConfirmModal-test.tsx | 60 +++++++++++ .../controls/__tests__/ModalButton-test.tsx | 38 +++++++ .../__snapshots__/ConfirmModal-test.tsx.snap | 76 ++++++++++++++ 11 files changed, 479 insertions(+), 183 deletions(-) create mode 100644 server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/ModalButton.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/ConfirmModal-test.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/ModalButton-test.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ConfirmModal-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx index 656dd635ee4..0d95becd65d 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx @@ -20,13 +20,13 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { copyQualityGate } from '../../../api/quality-gates'; -import ConfirmButton from '../../../components/controls/ConfirmButton'; -import { Button } from '../../../components/ui/buttons'; +import ConfirmModal from '../../../components/controls/ConfirmModal'; import { translate } from '../../../helpers/l10n'; import { getQualityGateUrl } from '../../../helpers/urls'; import { QualityGate } from '../../../app/types'; interface Props { + onClose: () => void; onCopy: () => Promise; organization?: string; qualityGate: QualityGate; @@ -50,7 +50,7 @@ export default class CopyQualityGateForm extends React.PureComponent { + handleCopy = () => { const { qualityGate, organization } = this.props; const { name } = this.state; @@ -70,35 +70,29 @@ export default class CopyQualityGateForm extends React.PureComponent - - - - } - modalHeader={translate('quality_gates.copy')} - onConfirm={this.onCopy}> - {({ onClick }) => ( - - )} - + header={translate('quality_gates.copy')} + onClose={this.props.onClose} + onConfirm={this.handleCopy}> +
+ + +
+ ); } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx index b984d962941..e9b20ed5a64 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx @@ -20,12 +20,12 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { createQualityGate } from '../../../api/quality-gates'; -import ConfirmButton from '../../../components/controls/ConfirmButton'; -import { Button } from '../../../components/ui/buttons'; +import ConfirmModal from '../../../components/controls/ConfirmModal'; import { translate } from '../../../helpers/l10n'; import { getQualityGateUrl } from '../../../helpers/urls'; interface Props { + onClose: () => void; onCreate: () => Promise; organization?: string; } @@ -65,35 +65,29 @@ export default class CreateQualityGateForm extends React.PureComponent - - - - } - modalHeader={translate('quality_gates.create')} + header={translate('quality_gates.create')} + onClose={this.props.onClose} onConfirm={this.handleCreate}> - {({ onClick }) => ( - - )} - +
+ + +
+ ); } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx index b87ceed7ad2..6f7343e0485 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx @@ -22,15 +22,16 @@ import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; import RenameQualityGateForm from './RenameQualityGateForm'; import CopyQualityGateForm from './CopyQualityGateForm'; import DeleteQualityGateForm from './DeleteQualityGateForm'; +import ModalButton from '../../../components/controls/ModalButton'; import { setQualityGateAsDefault } from '../../../api/quality-gates'; import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; import { QualityGate } from '../../../app/types'; interface Props { + onSetDefault: () => void; organization?: string; qualityGate: QualityGate; - onSetDefault: () => void; refreshItem: () => Promise; refreshList: () => Promise; } @@ -67,18 +68,38 @@ export default class DetailsHeader extends React.PureComponent {
{actions.rename && ( - + ( + + )}> + {({ onClick }) => ( + + )} + )} {actions.copy && ( - + ( + + )}> + {({ onClick }) => ( + + )} + )} {actions.setAsDefault && ( + )} +
)} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx index 5845c2c7cab..852ed3fb40f 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx @@ -18,13 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import ConfirmButton from '../../../components/controls/ConfirmButton'; +import ConfirmModal from '../../../components/controls/ConfirmModal'; import { renameQualityGate } from '../../../api/quality-gates'; -import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; import { QualityGate } from '../../../app/types'; interface Props { + onClose: () => void; onRename: () => Promise; organization?: string; qualityGate: QualityGate; @@ -44,7 +44,7 @@ export default class RenameQualityGateForm extends React.PureComponent { + handleRename = () => { const { qualityGate, organization } = this.props; const { name } = this.state; @@ -63,35 +63,29 @@ export default class RenameQualityGateForm extends React.PureComponent - - - - } - modalHeader={translate('quality_gates.rename')} - onConfirm={this.onRename}> - {({ onClick }) => ( - - )} - + header={translate('quality_gates.rename')} + onClose={this.props.onClose} + onConfirm={this.handleRename}> +
+ + +
+ ); } } 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 6d6f558bdcb..f9d609f9698 100644 --- a/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx @@ -18,15 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import SimpleModal from './SimpleModal'; -import DeferredSpinner from '../common/DeferredSpinner'; -import { translate } from '../../helpers/l10n'; -import { SubmitButton, ResetButtonLink } from '../ui/buttons'; +import ModalButton, { ChildrenProps, ModalProps } from './ModalButton'; +import ConfirmModal from './ConfirmModal'; -export interface ChildrenProps { - onClick: () => void; - onFormSubmit: (event: React.FormEvent) => void; -} +export { ChildrenProps } from './ModalButton'; interface Props { children: (props: ChildrenProps) => React.ReactNode; @@ -44,82 +39,22 @@ interface State { } export default class ConfirmButton extends React.PureComponent { - mounted = false; - state: State = { modal: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleButtonClick = () => { - this.setState({ modal: true }); - }; - - handleFormSubmit = (event?: React.FormEvent) => { - if (event) { - event.preventDefault(); - } - this.setState({ modal: true }); - }; - - handleSubmit = () => { - const result = this.props.onConfirm(this.props.confirmData); - if (result) { - return result.then(this.handleCloseModal, () => {}); - } else { - this.handleCloseModal(); - return undefined; - } - }; - - handleCloseModal = () => { - if (this.mounted) { - this.setState({ modal: false }); - } + renderConfirmModal = ({ onClose }: ModalProps) => { + return ( + + {this.props.modalBody} + + ); }; render() { - const { confirmButtonText, confirmDisable, isDestructive, modalBody, modalHeader } = this.props; - - return ( - <> - {this.props.children({ - onClick: this.handleButtonClick, - onFormSubmit: this.handleFormSubmit - })} - {this.state.modal && ( - - {({ onCloseClick, onFormSubmit, submitting }) => ( -
-
-

{modalHeader}

-
- -
{modalBody}
- -
- - - {confirmButtonText} - - - {translate('cancel')} - -
-
- )} -
- )} - - ); + return {this.props.children}; } } diff --git a/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx b/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx new file mode 100644 index 00000000000..631d0743d89 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx @@ -0,0 +1,89 @@ +/* + * 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 SimpleModal, { ChildrenProps } from './SimpleModal'; +import DeferredSpinner from '../common/DeferredSpinner'; +import { translate } from '../../helpers/l10n'; +import { SubmitButton, ResetButtonLink } from '../ui/buttons'; + +interface Props { + children: React.ReactNode; + confirmButtonText: string; + confirmData?: string; + confirmDisable?: boolean; + header: string; + isDestructive?: boolean; + onClose: () => void; + onConfirm: (data?: string) => void | Promise; +} + +export default class ConfirmModal extends React.PureComponent { + mounted = false; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleSubmit = () => { + const result = this.props.onConfirm(this.props.confirmData); + if (result) { + return result.then(this.props.onClose, () => {}); + } else { + this.props.onClose(); + return undefined; + } + }; + + renderModalContent = ({ onCloseClick, onFormSubmit, submitting }: ChildrenProps) => { + const { children, confirmButtonText, confirmDisable, header, isDestructive } = this.props; + return ( +
+
+

{header}

+
+
{children}
+
+ + + {confirmButtonText} + + + {translate('cancel')} + +
+
+ ); + }; + + render() { + const { header } = this.props; + return ( + + {this.renderModalContent} + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/controls/ModalButton.tsx b/server/sonar-web/src/main/js/components/controls/ModalButton.tsx new file mode 100644 index 00000000000..c153ee12b87 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/ModalButton.tsx @@ -0,0 +1,80 @@ +/* + * 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'; + +export interface ChildrenProps { + onClick: () => void; + onFormSubmit: (event: React.FormEvent) => void; +} + +export interface ModalProps { + onClose: () => void; +} + +export interface Props { + children: (props: ChildrenProps) => React.ReactNode; + modal: (props: ModalProps) => React.ReactNode; +} + +interface State { + modal: boolean; +} + +export default class ModalButton extends React.PureComponent { + mounted = false; + state: State = { modal: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleButtonClick = () => { + this.setState({ modal: true }); + }; + + handleFormSubmit = (event?: React.FormEvent) => { + if (event) { + event.preventDefault(); + } + this.setState({ modal: true }); + }; + + handleCloseModal = () => { + if (this.mounted) { + this.setState({ modal: false }); + } + }; + + render() { + return ( + <> + {this.props.children({ + onClick: this.handleButtonClick, + onFormSubmit: this.handleFormSubmit + })} + {this.state.modal && this.props.modal({ onClose: this.handleCloseModal })} + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ConfirmModal-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ConfirmModal-test.tsx new file mode 100644 index 00000000000..79858983491 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ConfirmModal-test.tsx @@ -0,0 +1,60 @@ +/* + * 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 ConfirmModal from '../ConfirmModal'; +import { submit, waitAndUpdate } from '../../../helpers/testUtils'; + +it('should render correctly', () => { + const wrapper = shallow( + +

My confirm message

+
+ ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('SimpleModal').dive()).toMatchSnapshot(); +}); + +it('should confirm and close after confirm', async () => { + const onClose = jest.fn(); + const onConfirm = jest.fn(() => Promise.resolve()); + const wrapper = shallow( + +

My confirm message

+
+ ); + const modalContent = wrapper.find('SimpleModal').dive(); + submit(modalContent.find('form')); + expect(onConfirm).toBeCalledWith('data'); + expect(modalContent.find('footer')).toMatchSnapshot(); + + await waitAndUpdate(wrapper); + expect(onClose).toHaveBeenCalled(); +}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ModalButton-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ModalButton-test.tsx new file mode 100644 index 00000000000..9b0376c4334 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ModalButton-test.tsx @@ -0,0 +1,38 @@ +/* + * 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 ModalButton from '../ModalButton'; +import { click } from '../../../helpers/testUtils'; + +it('should open/close modal', () => { + const wrapper = shallow( +