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<void>;
organization?: string;
qualityGate: QualityGate;
this.setState({ name: event.currentTarget.value });
};
- onCopy = () => {
+ handleCopy = () => {
const { qualityGate, organization } = this.props;
const { name } = this.state;
const confirmDisable = !name || (qualityGate && qualityGate.name === name);
return (
- <ConfirmButton
+ <ConfirmModal
confirmButtonText={translate('copy')}
confirmDisable={confirmDisable}
- modalBody={
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required={true}
- size={50}
- type="text"
- value={name}
- />
- </div>
- }
- modalHeader={translate('quality_gates.copy')}
- onConfirm={this.onCopy}>
- {({ onClick }) => (
- <Button className="little-spacer-left" id="quality-gate-copy" onClick={onClick}>
- {translate('copy')}
- </Button>
- )}
- </ConfirmButton>
+ header={translate('quality_gates.copy')}
+ onClose={this.props.onClose}
+ onConfirm={this.handleCopy}>
+ <div className="modal-field">
+ <label htmlFor="quality-gate-form-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ required={true}
+ size={50}
+ type="text"
+ value={name}
+ />
+ </div>
+ </ConfirmModal>
);
}
}
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<void>;
organization?: string;
}
render() {
const { name } = this.state;
return (
- <ConfirmButton
+ <ConfirmModal
confirmButtonText={translate('save')}
confirmDisable={!name}
- modalBody={
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required={true}
- size={50}
- type="text"
- value={name}
- />
- </div>
- }
- modalHeader={translate('quality_gates.create')}
+ header={translate('quality_gates.create')}
+ onClose={this.props.onClose}
onConfirm={this.handleCreate}>
- {({ onClick }) => (
- <Button id="quality-gate-add" onClick={onClick}>
- {translate('create')}
- </Button>
- )}
- </ConfirmButton>
+ <div className="modal-field">
+ <label htmlFor="quality-gate-form-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ required={true}
+ size={50}
+ type="text"
+ value={name}
+ />
+ </div>
+ </ConfirmModal>
);
}
}
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<void>;
refreshList: () => Promise<void>;
}
<div className="pull-right">
{actions.rename && (
- <RenameQualityGateForm
- onRename={this.handleActionRefresh}
- organization={organization}
- qualityGate={qualityGate}
- />
+ <ModalButton
+ modal={({ onClose }) => (
+ <RenameQualityGateForm
+ onClose={onClose}
+ onRename={this.handleActionRefresh}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+ )}>
+ {({ onClick }) => (
+ <Button id="quality-gate-rename" onClick={onClick}>
+ {translate('rename')}
+ </Button>
+ )}
+ </ModalButton>
)}
{actions.copy && (
- <CopyQualityGateForm
- onCopy={this.handleActionRefresh}
- organization={organization}
- qualityGate={qualityGate}
- />
+ <ModalButton
+ modal={({ onClose }) => (
+ <CopyQualityGateForm
+ onClose={onClose}
+ onCopy={this.handleActionRefresh}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+ )}>
+ {({ onClick }) => (
+ <Button className="little-spacer-left" id="quality-gate-copy" onClick={onClick}>
+ {translate('copy')}
+ </Button>
+ )}
+ </ModalButton>
)}
{actions.setAsDefault && (
<Button
)}
{actions.delete && (
<DeleteQualityGateForm
- onDelete={this.handleActionRefresh}
+ onDelete={this.props.refreshList}
organization={organization}
qualityGate={qualityGate}
/>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Button } from '../../../components/ui/buttons';
import CreateQualityGateForm from '../components/CreateQualityGateForm';
import DocTooltip from '../../../components/docs/DocTooltip';
+import ModalButton from '../../../components/controls/ModalButton';
import { translate } from '../../../helpers/l10n';
interface Props {
<header className="page-header">
{canCreate && (
<div className="page-actions">
- <CreateQualityGateForm onCreate={refreshQualityGates} organization={organization} />
+ <ModalButton
+ modal={({ onClose }) => (
+ <CreateQualityGateForm
+ onClose={onClose}
+ onCreate={refreshQualityGates}
+ organization={organization}
+ />
+ )}>
+ {({ onClick }) => (
+ <Button id="quality-gate-add" onClick={onClick}>
+ {translate('create')}
+ </Button>
+ )}
+ </ModalButton>
</div>
)}
* 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<void>;
organization?: string;
qualityGate: QualityGate;
this.setState({ name: event.currentTarget.value });
};
- onRename = () => {
+ handleRename = () => {
const { qualityGate, organization } = this.props;
const { name } = this.state;
const confirmDisable = !name || (qualityGate && qualityGate.name === name);
return (
- <ConfirmButton
+ <ConfirmModal
confirmButtonText={translate('rename')}
confirmDisable={confirmDisable}
- modalBody={
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required={true}
- size={50}
- type="text"
- value={name}
- />
- </div>
- }
- modalHeader={translate('quality_gates.rename')}
- onConfirm={this.onRename}>
- {({ onClick }) => (
- <Button id="quality-gate-rename" onClick={onClick}>
- {translate('rename')}
- </Button>
- )}
- </ConfirmButton>
+ header={translate('quality_gates.rename')}
+ onClose={this.props.onClose}
+ onConfirm={this.handleRename}>
+ <div className="modal-field">
+ <label htmlFor="quality-gate-form-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ required={true}
+ size={50}
+ type="text"
+ value={name}
+ />
+ </div>
+ </ConfirmModal>
);
}
}
* 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<HTMLFormElement>) => void;
-}
+export { ChildrenProps } from './ModalButton';
interface Props {
children: (props: ChildrenProps) => React.ReactNode;
}
export default class ConfirmButton extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { modal: false };
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- handleButtonClick = () => {
- this.setState({ modal: true });
- };
-
- handleFormSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
- 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 (
+ <ConfirmModal
+ confirmButtonText={this.props.confirmButtonText}
+ confirmData={this.props.confirmData}
+ confirmDisable={this.props.confirmDisable}
+ header={this.props.modalHeader}
+ isDestructive={this.props.isDestructive}
+ onClose={onClose}
+ onConfirm={this.props.onConfirm}>
+ {this.props.modalBody}
+ </ConfirmModal>
+ );
};
render() {
- const { confirmButtonText, confirmDisable, isDestructive, modalBody, modalHeader } = this.props;
-
- return (
- <>
- {this.props.children({
- onClick: this.handleButtonClick,
- onFormSubmit: this.handleFormSubmit
- })}
- {this.state.modal && (
- <SimpleModal
- header={modalHeader}
- onClose={this.handleCloseModal}
- onSubmit={this.handleSubmit}>
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{modalHeader}</h2>
- </header>
-
- <div className="modal-body">{modalBody}</div>
-
- <footer className="modal-foot">
- <DeferredSpinner className="spacer-right" loading={submitting} />
- <SubmitButton
- className={isDestructive ? 'button-red' : undefined}
- disabled={submitting || confirmDisable}>
- {confirmButtonText}
- </SubmitButton>
- <ResetButtonLink disabled={submitting} onClick={onCloseClick}>
- {translate('cancel')}
- </ResetButtonLink>
- </footer>
- </form>
- )}
- </SimpleModal>
- )}
- </>
- );
+ return <ModalButton modal={this.renderConfirmModal}>{this.props.children}</ModalButton>;
}
}
--- /dev/null
+/*
+ * 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<void>;
+}
+
+export default class ConfirmModal extends React.PureComponent<Props> {
+ 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 (
+ <form onSubmit={onFormSubmit}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+ <div className="modal-body">{children}</div>
+ <footer className="modal-foot">
+ <DeferredSpinner className="spacer-right" loading={submitting} />
+ <SubmitButton
+ className={isDestructive ? 'button-red' : undefined}
+ disabled={submitting || confirmDisable}>
+ {confirmButtonText}
+ </SubmitButton>
+ <ResetButtonLink disabled={submitting} onClick={onCloseClick}>
+ {translate('cancel')}
+ </ResetButtonLink>
+ </footer>
+ </form>
+ );
+ };
+
+ render() {
+ const { header } = this.props;
+ return (
+ <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}>
+ {this.renderModalContent}
+ </SimpleModal>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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<HTMLFormElement>) => 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<Props, State> {
+ mounted = false;
+ state: State = { modal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleButtonClick = () => {
+ this.setState({ modal: true });
+ };
+
+ handleFormSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
+ 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 })}
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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(
+ <ConfirmModal
+ confirmButtonText="confirm"
+ confirmData="data"
+ header="title"
+ onClose={jest.fn()}
+ onConfirm={jest.fn()}>
+ <p>My confirm message</p>
+ </ConfirmModal>
+ );
+ 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(
+ <ConfirmModal
+ confirmButtonText="confirm"
+ confirmData="data"
+ header="title"
+ onClose={onClose}
+ onConfirm={onConfirm}>
+ <p>My confirm message</p>
+ </ConfirmModal>
+ );
+ 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();
+});
--- /dev/null
+/*
+ * 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(
+ <ModalButton modal={({ onClose }) => <button id="js-close" onClick={onClose} type="button" />}>
+ {({ onClick }) => <button id="js-open" onClick={onClick} type="button" />}
+ </ModalButton>
+ );
+
+ expect(wrapper.find('#js-open').exists()).toBeTruthy();
+ expect(wrapper.find('#js-close').exists()).toBeFalsy();
+ click(wrapper.find('#js-open'));
+ expect(wrapper.find('#js-close').exists()).toBeTruthy();
+ click(wrapper.find('#js-close'));
+ expect(wrapper.find('#js-close').exists()).toBeFalsy();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should confirm and close after confirm 1`] = `
+<footer
+ className="modal-foot"
+>
+ <DeferredSpinner
+ className="spacer-right"
+ loading={true}
+ timeout={100}
+ />
+ <SubmitButton
+ disabled={true}
+ >
+ confirm
+ </SubmitButton>
+ <ResetButtonLink
+ disabled={true}
+ onClick={[Function]}
+ >
+ cancel
+ </ResetButtonLink>
+</footer>
+`;
+
+exports[`should render correctly 1`] = `
+<SimpleModal
+ header="title"
+ onClose={[MockFunction]}
+ onSubmit={[Function]}
+/>
+`;
+
+exports[`should render correctly 2`] = `
+<Modal
+ contentLabel="title"
+ onRequestClose={[MockFunction]}
+>
+ <form
+ onSubmit={[Function]}
+ >
+ <header
+ className="modal-head"
+ >
+ <h2>
+ title
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <p>
+ My confirm message
+ </p>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <DeferredSpinner
+ className="spacer-right"
+ loading={false}
+ timeout={100}
+ />
+ <SubmitButton>
+ confirm
+ </SubmitButton>
+ <ResetButtonLink
+ disabled={false}
+ onClick={[Function]}
+ >
+ cancel
+ </ResetButtonLink>
+ </footer>
+ </form>
+</Modal>
+`;