diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-11-30 16:05:46 +0100 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-12-14 17:03:35 +0100 |
commit | c37f8a7202925bec2d9a27961ea10f125c259c91 (patch) | |
tree | 9930cb4e5d1e7a003c3ac1f1082b2cade3b375e1 /server/sonar-web/src/main | |
parent | 42b5ea685c8ee46cf05625879300aa3eb3529897 (diff) | |
download | sonarqube-c37f8a7202925bec2d9a27961ea10f125c259c91.tar.gz sonarqube-c37f8a7202925bec2d9a27961ea10f125c259c91.zip |
SONAR-10151 Refactor Quality Gates modals to Typescript
Diffstat (limited to 'server/sonar-web/src/main')
18 files changed, 667 insertions, 377 deletions
diff --git a/server/sonar-web/src/main/js/api/quality-gates.ts b/server/sonar-web/src/main/js/api/quality-gates.ts index d2402fcdb46..90b03cbe338 100644 --- a/server/sonar-web/src/main/js/api/quality-gates.ts +++ b/server/sonar-web/src/main/js/api/quality-gates.ts @@ -52,31 +52,42 @@ export function fetchQualityGates(): Promise<{ return getJSON('/api/qualitygates/list').catch(throwGlobalError); } -export function fetchQualityGate(id: string): Promise<QualityGate> { +export function fetchQualityGate(id: number): Promise<QualityGate> { return getJSON('/api/qualitygates/show', { id }).catch(throwGlobalError); } -export function createQualityGate(name: string): Promise<any> { - return postJSON('/api/qualitygates/create', { name }); +export function createQualityGate(data: { + name: string; + organization?: string; +}): Promise<QualityGate> { + return postJSON('/api/qualitygates/create', data).catch(throwGlobalError); } -export function deleteQualityGate(id: string): Promise<void> { +export function deleteQualityGate(id: number): Promise<void> { return post('/api/qualitygates/destroy', { id }); } -export function renameQualityGate(id: string, name: string): Promise<void> { - return post('/api/qualitygates/rename', { id, name }); +export function renameQualityGate(data: { + id: number; + name: string; + organization?: string; +}): Promise<void | Response> { + return post('/api/qualitygates/rename', data).catch(throwGlobalError); } -export function copyQualityGate(id: string, name: string): Promise<any> { - return postJSON('/api/qualitygates/copy', { id, name }); +export function copyQualityGate(data: { + id: number; + name: string; + organization?: string; +}): Promise<QualityGate> { + return postJSON('/api/qualitygates/copy', data).catch(throwGlobalError); } -export function setQualityGateAsDefault(id: string): Promise<void | Response> { +export function setQualityGateAsDefault(id: number): Promise<void | Response> { return post('/api/qualitygates/set_as_default', { id }).catch(throwGlobalError); } -export function createCondition(gateId: string, condition: RequestData): Promise<any> { +export function createCondition(gateId: number, condition: RequestData): Promise<any> { return postJSON('/api/qualitygates/create_condition', { ...condition, gateId }); } @@ -84,7 +95,7 @@ export function updateCondition(condition: RequestData): Promise<any> { return postJSON('/api/qualitygates/update_condition', condition); } -export function deleteCondition(id: string): Promise<void> { +export function deleteCondition(id: number): Promise<void> { return post('/api/qualitygates/delete_condition', { id }); } 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 new file mode 100644 index 00000000000..ceeb416cc63 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 PropTypes from 'prop-types'; +import Modal from '../../../components/controls/Modal'; +import { copyQualityGate, QualityGate } from '../../../api/quality-gates'; +import { getQualityGateUrl } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + qualityGate: QualityGate; + onCopy: (newQualityGate: QualityGate) => void; + onClose: () => void; + organization?: string; +} + +interface State { + loading: boolean; + name: string; +} + +export default class CopyQualityGateForm extends React.PureComponent<Props, State> { + mounted: boolean; + + static contextTypes = { + router: PropTypes.object + }; + + constructor(props: Props) { + super(props); + this.state = { loading: false, name: props.qualityGate.name }; + } + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { + event.preventDefault(); + const { qualityGate, organization } = this.props; + const { name } = this.state; + if (name) { + this.setState({ loading: true }); + copyQualityGate({ id: qualityGate.id, name, organization }).then( + qualityGate => { + this.props.onCopy(qualityGate); + this.props.onClose(); + this.context.router.push( + getQualityGateUrl(String(qualityGate.id), this.props.organization) + ); + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + }; + + render() { + const { qualityGate } = this.props; + const { loading, name } = this.state; + const header = translate('quality_gates.copy'); + const submitDisabled = loading || !name || (qualityGate && qualityGate.name === name); + + return ( + <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <form id="quality-gate-form" onSubmit={this.handleFormSubmit}> + <div className="modal-head"> + <h2>{header}</h2> + </div> + <div className="modal-body"> + <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> + </div> + <div className="modal-foot"> + {loading && <i className="spinner spacer-right" />} + <button disabled={submitDisabled} className="js-confirm"> + {translate('copy')} + </button> + <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </div> + </form> + </Modal> + ); + } +} 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 new file mode 100644 index 00000000000..1171c189e4a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx @@ -0,0 +1,127 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 PropTypes from 'prop-types'; +import Modal from '../../../components/controls/Modal'; +import { createQualityGate, QualityGate } from '../../../api/quality-gates'; +import { translate } from '../../../helpers/l10n'; +import { getQualityGateUrl } from '../../../helpers/urls'; + +interface Props { + onCreate: (qualityGate: QualityGate) => void; + onClose: () => void; + organization?: string; +} + +interface State { + loading: boolean; + name: string; +} + +export default class CreateQualityGateForm extends React.PureComponent<Props, State> { + mounted: boolean; + + static contextTypes = { + router: PropTypes.object + }; + + state = { loading: false, name: '' }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { + event.preventDefault(); + const { organization } = this.props; + const { name } = this.state; + if (name) { + this.setState({ loading: true }); + createQualityGate({ name, organization }).then( + qualityGate => { + this.props.onCreate(qualityGate); + this.context.router.push(getQualityGateUrl(String(qualityGate.id), organization)); + this.props.onClose(); + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + }; + + render() { + const { loading, name } = this.state; + const header = translate('quality_gates.rename'); + const submitDisabled = loading || !name; + + return ( + <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <form id="quality-gate-form" onSubmit={this.handleFormSubmit}> + <div className="modal-head"> + <h2>{header}</h2> + </div> + <div className="modal-body"> + <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> + </div> + <div className="modal-foot"> + {loading && <i className="spinner spacer-right" />} + <button disabled={submitDisabled} className="js-confirm"> + {translate('save')} + </button> + <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </div> + </form> + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx new file mode 100644 index 00000000000..ab80d7e9df9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx @@ -0,0 +1,106 @@ +import {} from '../../overview/qualityGate/QualityGate'; +/* + * SonarQube + * Copyright (C) 2009-2017 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 PropTypes from 'prop-types'; +import Modal from '../../../components/controls/Modal'; +import { getQualityGatesUrl } from '../../../helpers/urls'; +import { deleteQualityGate, QualityGate } from '../../../api/quality-gates'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + onClose: () => void; + onDelete: (qualityGate: QualityGate) => void; + organization?: string; + qualityGate: QualityGate; +} + +interface State { + loading: boolean; +} + +export default class DeleteQualityGateForm extends React.PureComponent<Props, State> { + mounted: boolean; + + static contextTypes = { + router: PropTypes.object + }; + + state: State = { loading: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { + event.preventDefault(); + const { organization, qualityGate } = this.props; + this.setState({ loading: true }); + deleteQualityGate(qualityGate.id).then( + () => { + this.props.onDelete(qualityGate); + this.context.router.replace(getQualityGatesUrl(organization)); + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + render() { + const { qualityGate } = this.props; + const header = translate('quality_gates.delete'); + + return ( + <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <form id="delete-profile-form" onSubmit={this.handleFormSubmit}> + <div className="modal-head"> + <h2>{header}</h2> + </div> + <div className="modal-body"> + <p> + {translateWithParameters('quality_gates.delete.confirm.message', qualityGate.name)} + </p> + </div> + <div className="modal-foot"> + {this.state.loading && <i className="spinner spacer-right" />} + <button className="js-delete button-red" disabled={this.state.loading}> + {translate('delete')} + </button> + <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </div> + </form> + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js index 2d3a5ad3beb..b6ff64b5c87 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js @@ -23,10 +23,6 @@ import Helmet from 'react-helmet'; import { fetchQualityGate, setQualityGateAsDefault } from '../../../api/quality-gates'; import DetailsHeader from './DetailsHeader'; import DetailsContent from './DetailsContent'; -import RenameView from '../views/rename-view'; -import CopyView from '../views/copy-view'; -import DeleteView from '../views/delete-view'; -import { getQualityGatesUrl, getQualityGateUrl } from '../../../helpers/urls'; export default class Details extends React.PureComponent { static contextTypes = { @@ -50,49 +46,8 @@ export default class Details extends React.PureComponent { () => {} ); - handleRenameClick = () => { - const { qualityGate, onRename } = this.props; - new RenameView({ - qualityGate, - onRename: (qualityGate, newName) => { - onRename(qualityGate, newName); - } - }).render(); - }; - - handleCopyClick = () => { - const { qualityGate, onCopy, organization } = this.props; - const { router } = this.context; - new CopyView({ - qualityGate, - onCopy: newQualityGate => { - onCopy(newQualityGate); - router.push(getQualityGateUrl(newQualityGate.id, organization && organization.key)); - } - }).render(); - }; - - handleSetAsDefaultClick = () => { - const { qualityGate, onSetAsDefault } = this.props; - if (!qualityGate.isDefault) { - setQualityGateAsDefault(qualityGate.id).then(() => onSetAsDefault(qualityGate), () => {}); - } - }; - - handleDeleteClick = () => { - const { qualityGate, onDelete, organization } = this.props; - const { router } = this.context; - new DeleteView({ - qualityGate, - onDelete: qualityGate => { - onDelete(qualityGate); - router.replace(getQualityGatesUrl(organization && organization.key)); - } - }).render(); - }; - render() { - const { qualityGate, metrics } = this.props; + const { organization, metrics, qualityGate } = this.props; const { onAddCondition, onDeleteCondition, onSaveCondition } = this.props; if (!qualityGate) { @@ -104,11 +59,11 @@ export default class Details extends React.PureComponent { <Helmet title={qualityGate.name} /> <DetailsHeader qualityGate={qualityGate} - onRename={this.handleRenameClick} - onCopy={this.handleCopyClick} - onSetAsDefault={this.handleSetAsDefaultClick} - onDelete={this.handleDeleteClick} - organization={this.props.organization} + onRename={this.props.onRename} + onCopy={this.props.onCopy} + onSetAsDefault={this.props.onSetAsDefault} + onDelete={this.props.onDelete} + organization={organization && organization.key} /> <DetailsContent diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.js b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx index 71ce93cf1c5..aa3d836b237 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx @@ -17,34 +17,59 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; +import RenameQualityGateForm from './RenameQualityGateForm'; +import CopyQualityGateForm from './CopyQualityGateForm'; +import DeleteQualityGateForm from './DeleteQualityGateForm'; +import { QualityGate, setQualityGateAsDefault } from '../../../api/quality-gates'; import { translate } from '../../../helpers/l10n'; -export default class DetailsHeader extends React.PureComponent { - handleRenameClick = e => { +interface Props { + qualityGate: QualityGate; + onRename: (qualityGate: QualityGate, newName: string) => void; + onCopy: (newQualityGate: QualityGate) => void; + onSetAsDefault: (qualityGate: QualityGate) => void; + onDelete: (qualityGate: QualityGate) => void; + organization?: string; +} + +interface State { + openPopup?: string; +} + +export default class DetailsHeader extends React.PureComponent<Props, State> { + state = { openPopup: undefined }; + + handleRenameClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { e.preventDefault(); - this.props.onRename(); + this.setState({ openPopup: 'rename' }); }; - handleCopyClick = e => { + handleCopyClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { e.preventDefault(); - this.props.onCopy(); + this.setState({ openPopup: 'copy' }); }; - handleSetAsDefaultClick = e => { + handleSetAsDefaultClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { e.preventDefault(); - this.props.onSetAsDefault(); + const { qualityGate, onSetAsDefault } = this.props; + if (!qualityGate.isDefault) { + setQualityGateAsDefault(qualityGate.id).then(() => onSetAsDefault(qualityGate), () => {}); + } }; - handleDeleteClick = e => { + handleDeleteClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { e.preventDefault(); - this.props.onDelete(); + this.setState({ openPopup: 'delete' }); }; + handleClosePopup = () => this.setState({ openPopup: undefined }); + render() { - const { qualityGate } = this.props; - const actions = qualityGate.actions || {}; + const { organization, qualityGate } = this.props; + const { openPopup } = this.state; + const actions = qualityGate.actions || ({} as any); return ( <div className="layout-page-header-panel layout-page-main-header issues-main-header"> <div className="layout-page-header-panel-inner layout-page-main-header-inner"> @@ -84,6 +109,32 @@ export default class DetailsHeader extends React.PureComponent { {translate('delete')} </button> )} + {openPopup === 'rename' && ( + <RenameQualityGateForm + onRename={this.props.onRename} + organization={organization} + onClose={this.handleClosePopup} + qualityGate={qualityGate} + /> + )} + + {openPopup === 'copy' && ( + <CopyQualityGateForm + onCopy={this.props.onCopy} + organization={organization} + onClose={this.handleClosePopup} + qualityGate={qualityGate} + /> + )} + + {openPopup === 'delete' && ( + <DeleteQualityGateForm + onDelete={this.props.onDelete} + organization={organization} + onClose={this.handleClosePopup} + qualityGate={qualityGate} + /> + )} </div> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.js b/server/sonar-web/src/main/js/apps/quality-gates/components/List.js index d29633c5102..bc22c41a817 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/List.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.js @@ -29,7 +29,7 @@ export default function List({ organization, qualityGates }) { {qualityGates.map(qualityGate => ( <Link key={qualityGate.id} - to={getQualityGateUrl(qualityGate.id, organization && organization.key)} + to={getQualityGateUrl(String(qualityGate.id), organization && organization.key)} activeClassName="active" className="list-group-item" data-id={qualityGate.id}> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.js b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.js deleted file mode 100644 index 2801cd954bb..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import CreateView from '../views/create-view'; -import { translate } from '../../../helpers/l10n'; - -export default function ListHeader({ canCreate, onAdd }) { - function handleAddClick(e) { - e.preventDefault(); - new CreateView({ onAdd }).render(); - } - - return ( - <header className="page-header"> - <h1 className="page-title">{translate('quality_gates.page')}</h1> - {canCreate && ( - <div className="page-actions"> - <button id="quality-gate-add" onClick={handleAddClick}> - {translate('create')} - </button> - </div> - )} - </header> - ); -} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx new file mode 100644 index 00000000000..d9ec9060188 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 CreateQualityGateForm from '../components/CreateQualityGateForm'; +import { QualityGate } from '../../../api/quality-gates'; +import { translate } from '../../../helpers/l10n'; +import { Organization } from '../../../app/types'; + +interface Props { + canCreate: boolean; + onAdd: (qualityGate: QualityGate) => void; + organization?: Organization; +} + +interface State { + createQualityGateOpen: boolean; +} + +export default class ListHeader extends React.PureComponent<Props, State> { + state = { createQualityGateOpen: false }; + + openCreateQualityGateForm = () => this.setState({ createQualityGateOpen: true }); + closeCreateQualityGateForm = () => this.setState({ createQualityGateOpen: false }); + + render() { + const { organization } = this.props; + + return ( + <header className="page-header"> + <h1 className="page-title">{translate('quality_gates.page')}</h1> + {this.props.canCreate && ( + <div className="page-actions"> + <button id="quality-gate-add" onClick={this.openCreateQualityGateForm}> + {translate('create')} + </button> + </div> + )} + {this.state.createQualityGateOpen && ( + <CreateQualityGateForm + onClose={this.closeCreateQualityGateForm} + onCreate={this.props.onAdd} + organization={organization && organization.key} + /> + )} + </header> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js index 8d13fbae35e..96c3ed76f9a 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js @@ -56,19 +56,11 @@ export default class QualityGatesApp extends Component { updateStore({ actions, qualityGates }); if (qualityGates && qualityGates.length === 1 && !actions.create) { this.context.router.replace( - getQualityGateUrl(qualityGates[0].id, organization && organization.key) + getQualityGateUrl(String(qualityGates[0].id), organization && organization.key) ); } }); - handleAdd = qualityGate => { - const { addQualityGate, organization } = this.props; - const { router } = this.context; - - addQualityGate(qualityGate); - router.push(getQualityGateUrl(qualityGate.id, organization && organization.key)); - }; - render() { const { children, qualityGates, actions, organization } = this.props; const defaultTitle = translate('quality_gates.page'); @@ -81,7 +73,11 @@ export default class QualityGatesApp extends Component { <div className="layout-page-side" style={{ top }}> <div className="layout-page-side-inner"> <div className="layout-page-filters"> - <ListHeader canCreate={actions && actions.create} onAdd={this.handleAdd} /> + <ListHeader + canCreate={actions && actions.create} + onAdd={this.props.addQualityGate} + organization={organization} + /> {qualityGates && <List organization={organization} qualityGates={qualityGates} />} </div> </div> 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 new file mode 100644 index 00000000000..b4a453fb678 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 Modal from '../../../components/controls/Modal'; +import { QualityGate, renameQualityGate } from '../../../api/quality-gates'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + qualityGate: QualityGate; + onRename: (qualityGate: QualityGate, newName: string) => void; + onClose: () => void; + organization?: string; +} + +interface State { + loading: boolean; + name: string; +} + +export default class RenameQualityGateForm extends React.PureComponent<Props, State> { + mounted: boolean; + + constructor(props: Props) { + super(props); + this.state = { loading: false, name: props.qualityGate.name }; + } + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { + event.preventDefault(); + const { qualityGate, organization } = this.props; + const { name } = this.state; + if (name) { + this.setState({ loading: true }); + renameQualityGate({ id: qualityGate.id, name, organization }).then( + () => { + this.props.onRename(qualityGate, name); + this.props.onClose(); + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + }; + + render() { + const { qualityGate } = this.props; + const { loading, name } = this.state; + const header = translate('quality_gates.rename'); + const submitDisabled = loading || !name || (qualityGate && qualityGate.name === name); + + return ( + <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <form id="quality-gate-form" onSubmit={this.handleFormSubmit}> + <div className="modal-head"> + <h2>{header}</h2> + </div> + <div className="modal-body"> + <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> + </div> + <div className="modal-foot"> + {loading && <i className="spinner spacer-right" />} + <button disabled={submitDisabled} className="js-confirm"> + {translate('rename')} + </button> + <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </div> + </form> + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-form.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-form.hbs deleted file mode 100644 index bc6e623018e..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-form.hbs +++ /dev/null @@ -1,20 +0,0 @@ -<form id="quality-gate-form"> - <div class="modal-head"> - <h2>{{t 'quality_gates' method }}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - <div class="modal-field"> - <label for="quality-gate-form-name">{{t 'name'}} <em class="mandatory">*</em></label> - <input id="quality-gate-form-name" type="text" size="50" maxlength="100" value="{{name}}" required> - </div> - </div> - <div class="modal-foot"> - <button id="quality-gate-form-submit"> - {{#eq method 'rename'}}{{t 'save'}}{{/eq}} - {{#eq method 'copy'}}{{t 'copy'}}{{/eq}} - {{#eq method 'create'}}{{t 'create'}}{{/eq}} - </button> - <a id="quality-gate-form-cancel" href="#" class="js-modal-close">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-delete.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-delete.hbs deleted file mode 100644 index c146c581bae..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-delete.hbs +++ /dev/null @@ -1,17 +0,0 @@ -<form id="delete-gate-form"> - <div class="modal-head"> - <h2>{{t 'quality_gates.delete'}}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - {{#if isDefault}} - {{tp 'quality_gates.delete.confirm.default' name}} - {{else}} - {{tp 'quality_gates.delete.confirm.message' name}} - {{/if}} - </div> - <div class="modal-foot"> - <button id="delete-gate-submit">{{t 'delete'}}</button> - <a href="#" class="js-modal-close" id="delete-gate-cancel">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/copy-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/copy-view.js deleted file mode 100644 index a9b6da0d2f2..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/copy-view.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 ModalForm from '../../../components/common/modal-form'; -import Template from '../templates/quality-gate-form.hbs'; -import { copyQualityGate } from '../../../api/quality-gates'; -import { parseError } from '../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - const { id } = this.options.qualityGate; - const name = this.$('#quality-gate-form-name').val(); - - copyQualityGate(id, name).then( - qualityGate => { - this.destroy(); - this.options.onCopy(qualityGate); - }, - error => { - this.enableForm(); - parseError(error).then(msg => this.showErrors([{ msg }])); - } - ); - }, - - serializeData() { - return { method: 'copy', ...this.options.qualityGate }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/create-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/create-view.js deleted file mode 100644 index 3580b45fcf6..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/create-view.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 ModalForm from '../../../components/common/modal-form'; -import Template from '../templates/quality-gate-form.hbs'; -import { createQualityGate } from '../../../api/quality-gates'; -import { parseError } from '../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - const name = this.$('#quality-gate-form-name').val(); - - createQualityGate(name).then( - qualityGate => { - this.destroy(); - this.options.onAdd(qualityGate); - }, - error => { - this.enableForm(); - parseError(error).then(msg => this.showErrors([{ msg }])); - } - ); - }, - - serializeData() { - return { method: 'create' }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/delete-view.js deleted file mode 100644 index 3327a6d3a10..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/delete-view.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 ModalForm from '../../../components/common/modal-form'; -import Template from '../templates/quality-gates-delete.hbs'; -import { deleteQualityGate } from '../../../api/quality-gates'; -import { parseError } from '../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - const { id } = this.options.qualityGate; - - deleteQualityGate(id).then( - () => { - this.destroy(); - this.options.onDelete(this.options.qualityGate); - }, - error => { - this.enableForm(); - parseError(error).then(msg => this.showErrors([{ msg }])); - } - ); - }, - - serializeData() { - return this.options.qualityGate; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js index 7909394dfe4..1b54745a857 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js @@ -25,9 +25,20 @@ import { translate } from '../../../helpers/l10n'; export default Marionette.ItemView.extend({ template: () => {}, + initialize(options) { + this.organization = options.organization; + }, + onRender() { const { qualityGate } = this.options; + const extra = { + gateId: qualityGate.id + }; + if (this.organization) { + extra.organization = this.organization.key; + } + new SelectList({ el: this.options.container, width: '100%', @@ -39,9 +50,7 @@ export default Marionette.ItemView.extend({ searchUrl: window.baseUrl + '/api/qualitygates/search?gateId=' + qualityGate.id, selectUrl: window.baseUrl + '/api/qualitygates/select', deselectUrl: window.baseUrl + '/api/qualitygates/deselect', - extra: { - gateId: qualityGate.id - }, + extra, selectParameter: 'projectId', selectParameterValue: 'id', labels: { diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/rename-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/rename-view.js deleted file mode 100644 index 2614ea6af67..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/rename-view.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 ModalForm from '../../../components/common/modal-form'; -import Template from '../templates/quality-gate-form.hbs'; -import { renameQualityGate } from '../../../api/quality-gates'; -import { parseError } from '../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - const { id } = this.options.qualityGate; - const name = this.$('#quality-gate-form-name').val(); - - renameQualityGate(id, name).then( - () => { - this.destroy(); - this.options.onRename(this.options.qualityGate, name); - }, - error => { - this.enableForm(); - parseError(error).then(msg => this.showErrors([{ msg }])); - } - ); - }, - - serializeData() { - return { method: 'rename', ...this.options.qualityGate }; - } -}); |