diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-02-21 14:24:05 +0100 |
---|---|---|
committer | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-02-27 13:14:54 +0100 |
commit | 36969ad381fb8ff7f93daf9c40d801b69e3a1bac (patch) | |
tree | 48e752209e71a5c407421325797e5e828516b282 /server/sonar-web | |
parent | 87ecef1b3768eaa980083c466a5adf25e5748d44 (diff) | |
download | sonarqube-36969ad381fb8ff7f93daf9c40d801b69e3a1bac.tar.gz sonarqube-36969ad381fb8ff7f93daf9c40d801b69e3a1bac.zip |
rewrite remaining backbone modals in react
Diffstat (limited to 'server/sonar-web')
33 files changed, 581 insertions, 985 deletions
diff --git a/server/sonar-web/src/main/js/api/projectLinks.ts b/server/sonar-web/src/main/js/api/projectLinks.ts index e91b200c4fe..22c5fba17fd 100644 --- a/server/sonar-web/src/main/js/api/projectLinks.ts +++ b/server/sonar-web/src/main/js/api/projectLinks.ts @@ -17,30 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getJSON, post, postJSON } from '../helpers/request'; +import { ProjectLink } from '../app/types'; import throwGlobalError from '../app/utils/throwGlobalError'; - -export interface ProjectLink { - id: string; - name: string; - type: string; - url: string; -} +import { getJSON, post, postJSON } from '../helpers/request'; export function getProjectLinks(projectKey: string): Promise<ProjectLink[]> { - const url = '/api/project_links/search'; - const data = { projectKey }; - return getJSON(url, data).then(r => r.links, throwGlobalError); + return getJSON('/api/project_links/search', { projectKey }).then(r => r.links, throwGlobalError); } -export function deleteLink(linkId: string): Promise<void> { - const url = '/api/project_links/delete'; - const data = { id: linkId }; - return post(url, data); +export function deleteLink(linkId: string) { + return post('/api/project_links/delete', { id: linkId }).catch(throwGlobalError); } -export function createLink(projectKey: string, name: string, url: string): Promise<any> { - const apiURL = '/api/project_links/create'; - const data = { projectKey, name, url }; - return postJSON(apiURL, data).then(r => r.link); +export function createLink(projectKey: string, name: string, url: string): Promise<ProjectLink> { + return postJSON('/api/project_links/create', { projectKey, name, url }).then( + r => r.link, + throwGlobalError + ); } diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index dddeec8424e..13b8bac9f66 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -245,6 +245,13 @@ export interface PermissionTemplate { }>; } +export interface ProjectLink { + id: string; + name: string; + type: string; + url: string; +} + export interface Rule { isTemplate?: boolean; key: string; diff --git a/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts b/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts index b19336572b0..4e853881db2 100644 --- a/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts +++ b/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts @@ -33,7 +33,6 @@ import Modal from '../../components/controls/Modal'; import SearchBox from '../../components/controls/SearchBox'; import Select from '../../components/controls/Select'; import Tooltip from '../../components/controls/Tooltip'; -import ModalForm from '../../components/common/modal-form'; import SelectList from '../../components/SelectList'; import CoverageRating from '../../components/ui/CoverageRating'; import DuplicationsRating from '../../components/ui/DuplicationsRating'; @@ -63,9 +62,7 @@ const exposeLibraries = () => { Tooltip, Select, SelectList, - SearchBox, - // deprecated, used in Governance - ModalForm_deprecated: ModalForm + SearchBox }; }; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx index e4c9a697009..d909bc0faff 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx @@ -19,8 +19,8 @@ */ import * as React from 'react'; import { isProvided, getLinkName } from '../../project-admin/links/utils'; +import { ProjectLink } from '../../../app/types'; import BugTrackerIcon from '../../../components/ui/BugTrackerIcon'; -import { ProjectLink } from '../../../api/projectLinks'; interface Props { link: ProjectLink; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx index ed778b4cef7..d9db8d5b150 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import MetaLink from './MetaLink'; -import { getProjectLinks, ProjectLink } from '../../../api/projectLinks'; +import { getProjectLinks } from '../../../api/projectLinks'; import { orderLinks } from '../../project-admin/links/utils'; -import { LightComponent } from '../../../app/types'; +import { LightComponent, ProjectLink } from '../../../app/types'; import { translate } from '../../../helpers/l10n'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js index 52faac1bdc0..d1688ed106e 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js @@ -26,14 +26,13 @@ import UpdateForm from './UpdateForm'; import BulkUpdate from './BulkUpdate'; import FineGrainedUpdate from './FineGrainedUpdate'; import { reloadUpdateKeyPage } from './utils'; -import { fetchProjectModules, changeKey } from '../store/actions'; +import { changeKey, fetchProjectModules } from '../store/actions'; import { translate } from '../../../helpers/l10n'; import { addGlobalErrorMessage, - closeAllGlobalMessages, - addGlobalSuccessMessage + addGlobalSuccessMessage, + closeAllGlobalMessages } from '../../../store/globalMessages/duck'; -import { parseError } from '../../../helpers/request'; import RecentHistory from '../../../app/components/RecentHistory'; import { getProjectAdminProjectModules } from '../../../store/rootReducer'; @@ -55,29 +54,25 @@ class Key extends React.PureComponent { this.props.fetchProjectModules(this.props.component.key); } - handleChangeKey(key, newKey) { - return this.props - .changeKey(key, newKey) - .then(() => { - if (key === this.props.component.key) { - this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload')); - RecentHistory.remove(key); - reloadUpdateKeyPage(newKey); - } else { - this.props.addGlobalSuccessMessage(translate('update_key.key_updated')); - } - }) - .catch(e => { - parseError(e).then(this.props.addGlobalErrorMessage); - }); - } + handleChangeKey = (key, newKey) => { + return this.props.changeKey(key, newKey).then(() => { + if (key === this.props.component.key) { + this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload')); + RecentHistory.remove(key); + reloadUpdateKeyPage(newKey); + } else { + this.props.addGlobalSuccessMessage(translate('update_key.key_updated')); + } + }); + }; - handleChangeTab(tab, e) { - e.preventDefault(); - e.target.blur(); + handleChangeTab = event => { + event.preventDefault(); + event.currentTarget.blur(); + const { tab } = event.currentTarget.dataset; this.setState({ tab }); this.props.closeAllGlobalMessages(); - } + }; render() { const { component, modules } = this.props; @@ -88,7 +83,7 @@ class Key extends React.PureComponent { const { tab } = this.state; return ( - <div id="project-key" className="page page-limited"> + <div className="page page-limited" id="project-key"> <Helmet title={translate('update_key.page')} /> <Header /> @@ -96,7 +91,7 @@ class Key extends React.PureComponent { {noModules && ( <div> - <UpdateForm component={component} onKeyChange={this.handleChangeKey.bind(this)} /> + <UpdateForm component={component} onKeyChange={this.handleChangeKey} /> </div> )} @@ -106,19 +101,21 @@ class Key extends React.PureComponent { <ul className="tabs"> <li> <a - id="update-key-tab-bulk" className={tab === 'bulk' ? 'selected' : ''} + data-tab="bulk" href="#" - onClick={this.handleChangeTab.bind(this, 'bulk')}> + id="update-key-tab-bulk" + onClick={this.handleChangeTab}> {translate('update_key.bulk_update')} </a> </li> <li> <a - id="update-key-tab-fine" className={tab === 'fine' ? 'selected' : ''} + data-tab="fine" href="#" - onClick={this.handleChangeTab.bind(this, 'fine')}> + id="update-key-tab-fine" + onClick={this.handleChangeTab}> {translate('update_key.fine_grained_key_update')} </a> </li> @@ -131,9 +128,9 @@ class Key extends React.PureComponent { <FineGrainedUpdate component={component} modules={modules} - onKeyChange={this.handleChangeKey.bind(this)} - onSuccess={this.props.closeAllGlobalMessages} onError={this.props.addGlobalErrorMessage} + onKeyChange={this.handleChangeKey} + onSuccess={this.props.closeAllGlobalMessages} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js deleted file mode 100644 index 54564f82c27..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 React from 'react'; -import PropTypes from 'prop-types'; -import UpdateKeyConfirmation from './views/UpdateKeyConfirmation'; -import { translate } from '../../../helpers/l10n'; - -export default class UpdateForm extends React.PureComponent { - static propTypes = { - component: PropTypes.object.isRequired, - onKeyChange: PropTypes.func.isRequired - }; - - state = { newKey: null }; - - handleSubmit(e) { - e.preventDefault(); - - const newKey = this.refs.newKey.value; - - new UpdateKeyConfirmation({ - newKey, - component: this.props.component, - onChange: this.props.onKeyChange - }).render(); - } - - handleChange(e) { - const newKey = e.target.value; - this.setState({ newKey }); - } - - handleReset(e) { - e.preventDefault(); - this.setState({ newKey: null }); - } - - render() { - const value = this.state.newKey != null ? this.state.newKey : this.props.component.key; - - const hasChanged = value !== this.props.component.key; - - return ( - <form onSubmit={this.handleSubmit.bind(this)}> - <input - ref="newKey" - id="update-key-new-key" - className="input-super-large" - value={value} - type="text" - placeholder={translate('update_key.new_key')} - required={true} - onChange={this.handleChange.bind(this)} - /> - - <div className="spacer-top"> - <button id="update-key-submit" disabled={!hasChanged}> - {translate('update_verb')} - </button>{' '} - <button - id="update-key-reset" - disabled={!hasChanged} - onClick={this.handleReset.bind(this)}> - {translate('reset_verb')} - </button> - </div> - </form> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx new file mode 100644 index 00000000000..99dc58c1208 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx @@ -0,0 +1,85 @@ +/* + * 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 UpdateKeyConfirm from './UpdateKeyConfirm'; +import { Button, SubmitButton } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + component: { key: string; name: string }; + onKeyChange: (oldKey: string, newKey: string) => Promise<void>; +} + +interface State { + newKey?: string; +} + +export default class UpdateForm extends React.PureComponent<Props, State> { + state: State = {}; + + handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const newKey = event.currentTarget.value; + this.setState({ newKey }); + }; + + handleReset = () => { + this.setState({ newKey: undefined }); + }; + + render() { + const { component } = this.props; + const { newKey } = this.state; + const value = newKey != null ? newKey : component.key; + const hasChanged = value !== component.key; + + return ( + <UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}> + {({ onFormSubmit }) => ( + <form onSubmit={onFormSubmit}> + <input + className="input-super-large" + id="update-key-new-key" + onChange={this.handleChange} + placeholder={translate('update_key.new_key')} + required={true} + type="text" + value={value} + /> + + <div className="spacer-top"> + <SubmitButton disabled={!hasChanged} id="update-key-submit"> + {translate('update_verb')} + </SubmitButton> + + <Button + className="spacer-left" + disabled={!hasChanged} + id="update-key-reset" + onClick={this.handleReset} + type="reset"> + {translate('reset_verb')} + </Button> + </div> + </form> + )} + </UpdateKeyConfirm> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx new file mode 100644 index 00000000000..6bb2bcc7990 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx @@ -0,0 +1,65 @@ +/* + * 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 ConfirmButton, { ChildrenProps } from '../../../components/controls/ConfirmButton'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + children: (props: ChildrenProps) => React.ReactNode; + component: { key: string; name: string }; + newKey: string | undefined; + onConfirm: (oldKey: string, newKey: string) => Promise<void>; +} + +export default class UpdateKeyConfirm extends React.PureComponent<Props> { + handleConfirm = () => { + return this.props.newKey + ? this.props.onConfirm(this.props.component.key, this.props.newKey) + : Promise.reject(undefined); + }; + + render() { + const { children, component, newKey } = this.props; + + return ( + <ConfirmButton + confirmButtonText={translate('update_verb')} + modalBody={ + <> + {translateWithParameters('update_key.are_you_sure_to_change_key', component.name)} + <div className="spacer-top"> + {translate('update_key.old_key')} + {': '} + <strong>{component.key}</strong> + </div> + <div className="spacer-top"> + {translate('update_key.new_key')} + {': '} + <strong>{newKey}</strong> + </div> + </> + } + modalHeader={translate('update_key.page')} + onConfirm={this.handleConfirm}> + {children} + </ConfirmButton> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js deleted file mode 100644 index 4f4cf8ec976..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 React from 'react'; -import PropTypes from 'prop-types'; -import UpdateKeyConfirmation from './views/UpdateKeyConfirmation'; -import { translate } from '../../../helpers/l10n'; - -export default class UpdateKeyForm extends React.PureComponent { - static propTypes = { - component: PropTypes.object.isRequired - }; - - state = {}; - - componentWillMount() { - this.handleInputChange = this.handleInputChange.bind(this); - this.handleUpdateClick = this.handleUpdateClick.bind(this); - this.handleResetClick = this.handleResetClick.bind(this); - } - - handleInputChange(e) { - const key = e.target.value; - this.setState({ key }); - } - - handleUpdateClick(e) { - e.preventDefault(); - e.target.blur(); - - const newKey = this.refs.newKey.value; - - new UpdateKeyConfirmation({ - newKey, - component: this.props.component, - onChange: this.props.onKeyChange - }).render(); - } - - handleResetClick(e) { - e.preventDefault(); - e.target.blur(); - this.setState({ key: null }); - } - - render() { - const { component } = this.props; - - const value = this.state.key != null ? this.state.key : component.key; - - const hasChanged = this.state.key != null && this.state.key !== component.key; - - return ( - <div className="js-fine-grained-update" data-key={component.key}> - <input - ref="newKey" - className="input-super-large big-spacer-right" - type="text" - value={value} - onChange={this.handleInputChange} - /> - - <button disabled={!hasChanged} onClick={this.handleUpdateClick}> - {translate('update_verb')} - </button> - - <button className="spacer-left" disabled={!hasChanged} onClick={this.handleResetClick}> - {translate('reset_verb')} - </button> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx new file mode 100644 index 00000000000..48345dfaaff --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx @@ -0,0 +1,75 @@ +/* + * 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 UpdateKeyConfirm from './UpdateKeyConfirm'; +import { Button } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + component: { key: string; name: string }; + onKeyChange: (oldKey: string, newKey: string) => Promise<void>; +} + +interface State { + newKey?: string; +} + +export default class UpdateKeyForm extends React.PureComponent<Props, State> { + state: State = {}; + + handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const newKey = event.currentTarget.value; + this.setState({ newKey }); + }; + + handleResetClick = () => { + this.setState({ newKey: undefined }); + }; + + render() { + const { component } = this.props; + const { newKey } = this.state; + const value = newKey !== undefined ? newKey : component.key; + const hasChanged = newKey !== undefined && newKey !== component.key; + + return ( + <div className="js-fine-grained-update" data-key={component.key}> + <input + className="input-super-large big-spacer-right" + onChange={this.handleInputChange} + type="text" + value={value} + /> + + <UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}> + {({ onClick }) => ( + <Button disabled={!hasChanged} onClick={onClick}> + {translate('update_verb')} + </Button> + )} + </UpdateKeyConfirm> + + <Button className="spacer-left" disabled={!hasChanged} onClick={this.handleResetClick}> + {translate('reset_verb')} + </Button> + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs deleted file mode 100644 index bec83b7693e..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs +++ /dev/null @@ -1,30 +0,0 @@ -<form id="update-key-confirmation-form" autocomplete="off"> - <div class="modal-head"> - <h2>{{t 'update_key.page'}}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - {{tp 'update_key.are_you_sure_to_change_key' component.name}} - <div class="spacer-top"> - <div class="display-inline-block text-right" style="width: 80px;"> - {{t 'update_key.old_key'}}: - </div> - <div class="display-inline-block"> - {{component.key}} - </div> - </div> - <div class="spacer-top"> - <div class="display-inline-block text-right" style="width: 80px;"> - {{t 'update_key.new_key'}}: - </div> - <div class="display-inline-block"> - {{newKey}} - </div> - </div> - </div> - <div class="modal-foot"> - <i class="js-modal-spinner spinner spacer-right hidden"></i> - <button id="update-key-confirm">{{t 'update_verb'}}</button> - <a href="#" class="js-modal-close">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js deleted file mode 100644 index afdc1ccf3d7..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 Template from './UpdateKeyConfirmation.hbs'; -import ModalForm from '../../../../components/common/modal-form'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.showSpinner(); - - this.options.onChange(this.options.component.key, this.options.newKey); - this.destroy(); - }, - - serializeData() { - return { - component: this.options.component, - newKey: this.options.newKey - }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx b/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx new file mode 100644 index 00000000000..c87ae12e438 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx @@ -0,0 +1,111 @@ +/* + * 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 DeferredSpinner from '../../../components/common/DeferredSpinner'; +import SimpleModal from '../../../components/controls/SimpleModal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + onClose: () => void; + onSubmit: (name: string, url: string) => Promise<void>; +} + +interface State { + name: string; + url: string; +} + +export default class CreationModal extends React.PureComponent<Props, State> { + state: State = { name: '', url: '' }; + + handleSubmit = () => { + return this.props.onSubmit(this.state.name, this.state.url).then(this.props.onClose); + }; + + handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => { + this.setState({ url: event.currentTarget.value }); + }; + + render() { + const header = translate('project_links.create_new_project_link'); + + return ( + <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}> + {({ onCloseClick, onFormSubmit, submitting }) => ( + <form onSubmit={onFormSubmit}> + <header className="modal-head"> + <h2>{header}</h2> + </header> + + <div className="modal-body"> + <div className="modal-field"> + <label htmlFor="create-link-name"> + {translate('project_links.name')} + <em className="mandatory">*</em> + </label> + <input + autoFocus={true} + id="create-link-name" + maxLength={128} + name="name" + onChange={this.handleNameChange} + required={true} + type="text" + value={this.state.name} + /> + </div> + + <div className="modal-field"> + <label htmlFor="create-link-url"> + {translate('project_links.url')} + <em className="mandatory">*</em> + </label> + <input + id="create-link-url" + maxLength={128} + name="url" + onChange={this.handleUrlChange} + required={true} + type="text" + value={this.state.url} + /> + </div> + </div> + + <footer className="modal-foot"> + <DeferredSpinner className="spacer-right" loading={submitting} /> + <SubmitButton disabled={submitting} id="create-link-confirm"> + {translate('create')} + </SubmitButton> + <ResetButtonLink disabled={submitting} onClick={onCloseClick}> + {translate('cancel')} + </ResetButtonLink> + </footer> + </form> + )} + </SimpleModal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Header.js b/server/sonar-web/src/main/js/apps/project-admin/links/Header.js deleted file mode 100644 index 4ed55a4bb2d..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Header.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 React from 'react'; -import PropTypes from 'prop-types'; -import CreationModal from './views/CreationModal'; -import { translate } from '../../../helpers/l10n'; - -export default class Header extends React.PureComponent { - static propTypes = { - onCreate: PropTypes.func.isRequired - }; - - handleCreateClick(e) { - e.preventDefault(); - e.target.blur(); - new CreationModal({ - onCreate: this.props.onCreate - }).render(); - } - - render() { - return ( - <header className="page-header"> - <h1 className="page-title">{translate('project_links.page')}</h1> - <div className="page-actions"> - <button id="create-project-link" onClick={this.handleCreateClick.bind(this)}> - {translate('create')} - </button> - </div> - <div className="page-description">{translate('project_links.page.description')}</div> - </header> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx b/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx new file mode 100644 index 00000000000..b8e07c56fbb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx @@ -0,0 +1,73 @@ +/* + * 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 CreationModal from './CreationModal'; +import { Button } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + onCreate: (name: string, url: string) => Promise<void>; +} + +interface State { + creationModal: boolean; +} + +export default class Header extends React.PureComponent<Props, State> { + mounted = false; + state: State = { creationModal: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCreateClick = () => { + this.setState({ creationModal: true }); + }; + + handleCreationModalClose = () => { + if (this.mounted) { + this.setState({ creationModal: false }); + } + }; + + render() { + return ( + <> + <header className="page-header"> + <h1 className="page-title">{translate('project_links.page')}</h1> + <div className="page-actions"> + <Button id="create-project-link" onClick={this.handleCreateClick}> + {translate('create')} + </Button> + </div> + <div className="page-description">{translate('project_links.page.description')}</div> + </header> + {this.state.creationModal && ( + <CreationModal onClose={this.handleCreationModalClose} onSubmit={this.props.onCreate} /> + )} + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.js b/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx index 3124fb9c8af..74ea4df05ee 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.js +++ b/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx @@ -17,25 +17,21 @@ * 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 PropTypes from 'prop-types'; +import * as React from 'react'; import { isProvided, getLinkName } from './utils'; -import { translate } from '../../../helpers/l10n'; +import { ProjectLink } from '../../../app/types'; +import ConfirmButton from '../../../components/controls/ConfirmButton'; import BugTrackerIcon from '../../../components/ui/BugTrackerIcon'; +import { Button } from '../../../components/ui/buttons'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; -export default class LinkRow extends React.PureComponent { - static propTypes = { - link: PropTypes.object.isRequired, - onDelete: PropTypes.func.isRequired - }; - - handleDeleteClick(e) { - e.preventDefault(); - e.target.blur(); - this.props.onDelete(); - } +interface Props { + link: ProjectLink; + onDelete: (linkId: string) => Promise<void>; +} - renderIcon(iconClassName) { +export default class LinkRow extends React.PureComponent<Props> { + renderIcon = (iconClassName: string) => { if (iconClassName === 'icon-issue') { return ( <div className="display-inline-block text-top spacer-right"> @@ -49,9 +45,9 @@ export default class LinkRow extends React.PureComponent { <i className={iconClassName} /> </div> ); - } + }; - renderNameForProvided(link) { + renderNameForProvided = (link: ProjectLink) => { return ( <div> {this.renderIcon(`icon-${link.type}`)} @@ -65,9 +61,9 @@ export default class LinkRow extends React.PureComponent { </div> </div> ); - } + }; - renderName(link) { + renderName = (link: ProjectLink) => { if (isProvided(link)) { return this.renderNameForProvided(link); } @@ -80,19 +76,32 @@ export default class LinkRow extends React.PureComponent { </div> </div> ); - } + }; - renderDeleteButton(link) { + renderDeleteButton = (link: ProjectLink) => { if (isProvided(link)) { return null; } return ( - <button className="button-red js-delete-button" onClick={this.handleDeleteClick.bind(this)}> - {translate('delete')} - </button> + <ConfirmButton + confirmButtonText={translate('delete')} + confirmData={link.id} + isDestructive={true} + modalBody={translateWithParameters( + 'project_links.are_you_sure_to_delete_x_link', + link.name + )} + modalHeader={translate('project_links.delete_project_link')} + onConfirm={this.props.onDelete}> + {({ onClick }) => ( + <Button className="button-red js-delete-button" onClick={onClick}> + {translate('delete')} + </Button> + )} + </ConfirmButton> ); - } + }; render() { const { link } = this.props; diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js index e8a5318ba99..fa77e03c6b8 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js +++ b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js @@ -23,7 +23,6 @@ import Helmet from 'react-helmet'; import { connect } from 'react-redux'; import Header from './Header'; import Table from './Table'; -import DeletionModal from './views/DeletionModal'; import { fetchProjectLinks, deleteProjectLink, createProjectLink } from '../store/actions'; import { getProjectAdminProjectLinks } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; @@ -34,26 +33,17 @@ class Links extends React.PureComponent { links: PropTypes.array }; - componentWillMount() { - this.handleCreateLink = this.handleCreateLink.bind(this); - this.handleDeleteLink = this.handleDeleteLink.bind(this); - } - componentDidMount() { this.props.fetchProjectLinks(this.props.component.key); } - handleCreateLink(name, url) { + handleCreateLink = (name, url) => { return this.props.createProjectLink(this.props.component.key, name, url); - } + }; - handleDeleteLink(link) { - new DeletionModal({ link }) - .on('done', () => { - this.props.deleteProjectLink(this.props.component.key, link.id); - }) - .render(); - } + handleDeleteLink = linkId => { + return this.props.deleteProjectLink(this.props.component.key, linkId); + }; render() { return ( diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Table.js b/server/sonar-web/src/main/js/apps/project-admin/links/Table.js index ad5ea09f9ba..8c8c80788fb 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Table.js +++ b/server/sonar-web/src/main/js/apps/project-admin/links/Table.js @@ -29,10 +29,6 @@ export default class Table extends React.PureComponent { onDelete: PropTypes.func.isRequired }; - handleDeleteLink(link) { - this.props.onDelete(link); - } - renderHeader() { // keep empty cell for actions return ( @@ -50,12 +46,12 @@ export default class Table extends React.PureComponent { const orderedLinks = orderLinks(this.props.links); const linkRows = orderedLinks.map(link => ( - <LinkRow key={link.id} link={link} onDelete={this.handleDeleteLink.bind(this, link)} /> + <LinkRow key={link.id} link={link} onDelete={this.props.onDelete} /> )); return ( <div className="boxed-group boxed-group-inner"> - <table id="project-links" className="data zebra"> + <table className="data zebra" id="project-links"> {this.renderHeader()} <tbody>{linkRows}</tbody> </table> diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModal.js b/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModal.js deleted file mode 100644 index d00cb68e3be..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModal.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 Template from './CreationModalTemplate.hbs'; -import ModalForm from '../../../../components/common/modal-form'; -import { parseError } from '../../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - - const name = this.$('#create-link-name').val(); - const url = this.$('#create-link-url').val(); - - this.options - .onCreate(name, url) - .then(() => this.destroy()) - .catch(e => { - parseError(e).then(msg => this.showSingleError(msg)); - this.enableForm(); - }); - } -}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModalTemplate.hbs b/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModalTemplate.hbs deleted file mode 100644 index 6d7f25084b5..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModalTemplate.hbs +++ /dev/null @@ -1,22 +0,0 @@ -<form> - <div class="modal-head"> - <h2>{{t 'project_links.create_new_project_link'}}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - - <div class="modal-field"> - <label for="create-link-name">{{t 'project_links.name'}}<em class="mandatory">*</em></label> - <input id="create-link-name" name="name" type="text" maxlength="128" required> - </div> - - <div class="modal-field"> - <label for="create-link-url">{{t 'project_links.url'}}<em class="mandatory">*</em></label> - <input id="create-link-url" name="url" type="text" maxlength="2048" required> - </div> - </div> - <div class="modal-foot"> - <button id="create-link-confirm">{{t 'create'}}</button> - <a href="#" class="js-modal-close">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModal.js b/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModal.js deleted file mode 100644 index 7b12ab08d84..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModal.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 Template from './DeletionModalTemplate.hbs'; -import ModalForm from '../../../../components/common/modal-form'; -import { deleteLink } from '../../../../api/projectLinks'; -import { parseError } from '../../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - - deleteLink(this.options.link.id) - .then(() => { - this.trigger('done'); - this.destroy(); - }) - .catch(e => { - parseError(e).then(msg => this.showSingleError(msg)); - this.enableForm(); - }); - }, - - serializeData() { - return { link: this.options.link }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModalTemplate.hbs b/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModalTemplate.hbs deleted file mode 100644 index b8d744c83d9..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModalTemplate.hbs +++ /dev/null @@ -1,13 +0,0 @@ -<form> - <div class="modal-head"> - <h2>{{t 'project_links.delete_project_link'}}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - {{tp 'project_links.are_you_sure_to_delete_x_link' link.name}} - </div> - <div class="modal-foot"> - <button id="delete-link-confirm" class="button-red">{{t 'delete'}}</button> - <a href="#" class="js-modal-close">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js index 11c64002edc..189832011e1 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js @@ -17,8 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getProjectLinks, createLink } from '../../../api/projectLinks'; +import { getProjectLinks, createLink, deleteLink } from '../../../api/projectLinks'; import { getTree, changeKey as changeKeyApi } from '../../../api/components'; +import throwGlobalError from '../../../app/utils/throwGlobalError'; export const RECEIVE_PROJECT_LINKS = 'projectAdmin/RECEIVE_PROJECT_LINKS'; export const receiveProjectLinks = (projectKey, links) => ({ @@ -28,9 +29,12 @@ export const receiveProjectLinks = (projectKey, links) => ({ }); export const fetchProjectLinks = projectKey => dispatch => { - getProjectLinks(projectKey).then(links => { - dispatch(receiveProjectLinks(projectKey, links)); - }); + getProjectLinks(projectKey).then( + links => { + dispatch(receiveProjectLinks(projectKey, links)); + }, + () => {} + ); }; export const ADD_PROJECT_LINK = 'projectAdmin/ADD_PROJECT_LINK'; @@ -47,12 +51,19 @@ export const createProjectLink = (projectKey, name, url) => dispatch => { }; export const DELETE_PROJECT_LINK = 'projectAdmin/DELETE_PROJECT_LINK'; -export const deleteProjectLink = (projectKey, linkId) => ({ +export const deleteProjectLinkAction = (projectKey, linkId) => ({ type: DELETE_PROJECT_LINK, projectKey, linkId }); +export function deleteProjectLink(projectKey, linkId) { + return dispatch => + deleteLink(linkId).then(() => { + dispatch(deleteProjectLinkAction(projectKey, linkId)); + }); +} + export const RECEIVE_PROJECT_MODULES = 'projectAdmin/RECEIVE_PROJECT_MODULES'; const receiveProjectModules = (projectKey, modules) => ({ type: RECEIVE_PROJECT_MODULES, @@ -62,9 +73,12 @@ const receiveProjectModules = (projectKey, modules) => ({ export const fetchProjectModules = projectKey => dispatch => { const options = { qualifiers: 'BRC', s: 'name', ps: 500 }; - getTree(projectKey, options).then(r => { - dispatch(receiveProjectModules(projectKey, r.components)); - }); + getTree(projectKey, options).then( + r => { + dispatch(receiveProjectModules(projectKey, r.components)); + }, + () => {} + ); }; export const CHANGE_KEY = 'projectAdmin/CHANGE_KEY'; @@ -75,5 +89,8 @@ const changeKeyAction = (key, newKey) => ({ }); export const changeKey = (key, newKey) => dispatch => { - return changeKeyApi(key, newKey).then(() => dispatch(changeKeyAction(key, newKey))); + return changeKeyApi(key, newKey).then( + () => dispatch(changeKeyAction(key, newKey)), + throwGlobalError + ); }; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js index de38009e128..09d8baf401d 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js @@ -18,44 +18,55 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import ProjectsView from '../views/gate-projects-view'; +import escapeHtml from 'escape-html'; +import SelectList from '../../../components/SelectList'; +import { translate } from '../../../helpers/l10n'; +import { getBaseUrl } from '../../../helpers/urls'; export default class Projects extends React.PureComponent { componentDidMount() { - this.renderView(); + this.renderSelectList(); } - componentWillUpdate() { - this.destroyView(); - } - - componentDidUpdate() { - this.renderView(); - } + renderSelectList = () => { + if (!this.container) return; - componentWillUnmount() { - this.destroyView(); - } + const { qualityGate, edit, organization } = this.props; - destroyView() { - if (this.projectsView) { - this.projectsView.destroy(); + const extra = { gateId: qualityGate.id }; + let orgQuery = ''; + if (organization) { + extra.organization = organization; + orgQuery = '&organization=' + organization; } - } - - renderView() { - const { qualityGate, edit, organization } = this.props; - this.projectsView = new ProjectsView({ - qualityGate, - edit, - container: this.refs.container, - organization + // eslint-disable-next-line no-new + new SelectList({ + el: this.container, + width: '100%', + readOnly: !edit, + focusSearch: false, + dangerouslyUnescapedHtmlFormat: item => escapeHtml(item.name), + searchUrl: getBaseUrl() + `/api/qualitygates/search?gateId=${qualityGate.id}${orgQuery}`, + selectUrl: getBaseUrl() + '/api/qualitygates/select', + deselectUrl: getBaseUrl() + '/api/qualitygates/deselect', + extra, + selectParameter: 'projectId', + selectParameterValue: 'id', + labels: { + selected: translate('quality_gates.projects.with'), + deselected: translate('quality_gates.projects.without'), + all: translate('quality_gates.projects.all'), + noResults: translate('quality_gates.projects.noResults') + }, + tooltips: { + select: translate('quality_gates.projects.select_hint'), + deselect: translate('quality_gates.projects.deselect_hint') + } }); - this.projectsView.render(); - } + }; render() { - return <div ref="container" />; + return <div ref={node => (this.container = node)} />; } } 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 deleted file mode 100644 index 85ab920f067..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 Marionette from 'backbone.marionette'; -import escapeHtml from 'escape-html'; -import SelectList from '../../../components/SelectList'; -import { translate } from '../../../helpers/l10n'; - -export default Marionette.ItemView.extend({ - template: () => {}, - - onRender() { - const { qualityGate, organization } = this.options; - - const extra = { - gateId: qualityGate.id - }; - let orgQuery = ''; - if (organization) { - extra.organization = organization; - orgQuery = '&organization=' + organization; - } - - new SelectList({ - el: this.options.container, - width: '100%', - readOnly: !this.options.edit, - focusSearch: false, - dangerouslyUnescapedHtmlFormat(item) { - return escapeHtml(item.name); - }, - searchUrl: `${window.baseUrl}/api/qualitygates/search?gateId=${qualityGate.id}${orgQuery}`, - selectUrl: window.baseUrl + '/api/qualitygates/select', - deselectUrl: window.baseUrl + '/api/qualitygates/deselect', - extra, - selectParameter: 'projectId', - selectParameterValue: 'id', - labels: { - selected: translate('quality_gates.projects.with'), - deselected: translate('quality_gates.projects.without'), - all: translate('quality_gates.projects.all'), - noResults: translate('quality_gates.projects.noResults') - }, - tooltips: { - select: translate('quality_gates.projects.select_hint'), - deselect: translate('quality_gates.projects.deselect_hint') - } - }); - }, - - serializeData() { - return { - ...Marionette.ItemView.prototype.serializeData.apply(this, arguments), - canEdit: this.options.edit - }; - } -}); diff --git a/server/sonar-web/src/main/js/components/RestartModal/index.js b/server/sonar-web/src/main/js/components/RestartModal/index.js deleted file mode 100644 index c3d3f2669a4..00000000000 --- a/server/sonar-web/src/main/js/components/RestartModal/index.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 Template from './templates/template.hbs'; -import RestartingTemplate from './templates/restarting.hbs'; -import ModalForm from '../common/modal-form'; -import { restartAndWait } from '../../api/system'; - -const RestartModal = ModalForm.extend({ - template: Template, - restartingTemplate: RestartingTemplate, - - initialize() { - this.restarting = false; - }, - - getTemplate() { - return this.restarting ? this.restartingTemplate : this.template; - }, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.restarting = true; - this.render(); - restartAndWait().then(() => { - document.location.reload(); - }); - } -}); - -export default RestartModal; diff --git a/server/sonar-web/src/main/js/components/RestartModal/templates/restarting.hbs b/server/sonar-web/src/main/js/components/RestartModal/templates/restarting.hbs deleted file mode 100644 index bab7b505ab7..00000000000 --- a/server/sonar-web/src/main/js/components/RestartModal/templates/restarting.hbs +++ /dev/null @@ -1,14 +0,0 @@ -<form id="restart-server-form"> - <div class="modal-head"> - <h2>Restart Server</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - <p class="spacer-top spacer-bottom text-center"> - Server is restarting. This page will be automatically refreshed. - </p> - <p class="big-spacer-top spacer-bottom text-center"> - <i class="spinner"></i> - </p> - </div> -</form> diff --git a/server/sonar-web/src/main/js/components/RestartModal/templates/template.hbs b/server/sonar-web/src/main/js/components/RestartModal/templates/template.hbs deleted file mode 100644 index 6058532191c..00000000000 --- a/server/sonar-web/src/main/js/components/RestartModal/templates/template.hbs +++ /dev/null @@ -1,15 +0,0 @@ -<form id="restart-server-form"> - <div class="modal-head"> - <h2>Restart Server</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - <p class="spacer-top spacer-bottom"> - Are you sure you want to restart the server? - </p> - </div> - <div class="modal-foot"> - <button id="restart-server-submit">Restart</button> - <a href="#" class="js-modal-close" id="restart-server-cancel">Cancel</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/components/common/modal-form.js b/server/sonar-web/src/main/js/components/common/modal-form.js deleted file mode 100644 index b201fbf7f1f..00000000000 --- a/server/sonar-web/src/main/js/components/common/modal-form.js +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 ModalView from './modals'; - -export default ModalView.extend({ - ui() { - return { - messagesContainer: '.js-modal-messages' - }; - }, - - events() { - return { - ...ModalView.prototype.events.apply(this, arguments), - 'keydown input,textarea,select': 'onInputKeydown', - 'submit form': 'onFormSubmit' - }; - }, - - onRender() { - ModalView.prototype.onRender.apply(this, arguments); - const that = this; - setTimeout(() => { - that - .$(':tabbable') - .first() - .focus(); - }, 0); - }, - - onInputKeydown(e) { - if (e.keyCode === 27) { - // escape - this.destroy(); - } - }, - - onFormSubmit(e) { - e.preventDefault(); - }, - - showErrors(errors, warnings) { - const container = this.ui.messagesContainer.empty(); - if (Array.isArray(errors)) { - errors.forEach(error => { - const html = `<div class="alert alert-danger">${error.msg}</div>`; - container.append(html); - }); - } - if (Array.isArray(warnings)) { - warnings.forEach(warn => { - const html = `<div class="alert alert-warning">${warn.msg}</div>`; - container.append(html); - }); - } - this.ui.messagesContainer.scrollParent().scrollTop(0); - }, - - showSingleError(msg) { - this.showErrors([{ msg }], []); - }, - - disableForm() { - const form = this.$('form'); - this.disabledFields = form.find(':input:not(:disabled)'); - this.disabledFields.prop('disabled', true); - }, - - enableForm() { - if (this.disabledFields != null) { - this.disabledFields.prop('disabled', false); - } - }, - - showSpinner() { - this.$('.js-modal-spinner').removeClass('hidden'); - }, - - hideSpinner() { - this.$('.js-modal-spinner').addClass('hidden'); - } -}); diff --git a/server/sonar-web/src/main/js/components/common/modals.js b/server/sonar-web/src/main/js/components/common/modals.js deleted file mode 100644 index 6251b772ed0..00000000000 --- a/server/sonar-web/src/main/js/components/common/modals.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 $ from 'jquery'; -import Marionette from 'backbone.marionette'; -import key from 'keymaster'; - -const EVENT_SCOPE = 'modal'; - -export default Marionette.ItemView.extend({ - className: 'modal', - overlayClassName: 'modal-overlay', - htmlClassName: 'modal-open', - - events() { - return { - 'click .js-modal-close': 'onCloseClick' - }; - }, - - onRender() { - const that = this; - this.$el.detach().appendTo($('body')); - $('html').addClass(this.htmlClassName); - this.renderOverlay(); - this.keyScope = key.getScope(); - key.setScope('modal'); - key('escape', 'modal', () => { - that.destroy(); - return false; - }); - this.show(); - if (this.options.large) { - this.$el.addClass('modal-large'); - } - }, - - show() { - const that = this; - setTimeout(() => { - that.$el.addClass('in'); - $('.' + that.overlayClassName).addClass('in'); - }, 0); - }, - - onDestroy() { - $('html').removeClass(this.htmlClassName); - this.removeOverlay(); - key.deleteScope('modal'); - key.setScope(this.keyScope); - }, - - onCloseClick(e) { - e.preventDefault(); - this.destroy(); - }, - - renderOverlay() { - const overlay = $('.' + this.overlayClassName); - if (overlay.length === 0) { - $(`<div class="${this.overlayClassName}"></div>`).appendTo($('body')); - } - }, - - removeOverlay() { - $('.' + this.overlayClassName).remove(); - }, - - attachCloseEvents() { - const that = this; - $('body').on('click.' + EVENT_SCOPE, () => { - $('body').off('click.' + EVENT_SCOPE); - that.destroy(); - }); - } -}); diff --git a/server/sonar-web/src/main/js/components/common/selectable-collection-view.js b/server/sonar-web/src/main/js/components/common/selectable-collection-view.js deleted file mode 100644 index 41ce5d2f13b..00000000000 --- a/server/sonar-web/src/main/js/components/common/selectable-collection-view.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 Marionette from 'backbone.marionette'; - -export default Marionette.CollectionView.extend({ - initialize() { - this.resetSelectedIndex(); - this.listenTo(this.collection, 'reset', this.resetSelectedIndex); - }, - - childViewOptions(model, index) { - return { index }; - }, - - resetSelectedIndex() { - this.selectedIndex = 0; - }, - - onRender() { - this.selectCurrent(); - }, - - submitCurrent() { - const view = this.children.findByIndex(this.selectedIndex); - if (view != null) { - view.submit(); - } - }, - - selectCurrent() { - this.selectItem(this.selectedIndex); - }, - - selectNext() { - if (this.selectedIndex < this.collection.length - 1) { - this.deselectItem(this.selectedIndex); - this.selectedIndex++; - this.selectItem(this.selectedIndex); - } - }, - - selectPrev() { - if (this.selectedIndex > 0) { - this.deselectItem(this.selectedIndex); - this.selectedIndex--; - this.selectItem(this.selectedIndex); - } - }, - - selectItem(index) { - if (index >= 0 && index < this.collection.length) { - const view = this.children.findByIndex(index); - if (view != null) { - view.select(); - } - } - }, - - deselectItem(index) { - const view = this.children.findByIndex(index); - if (view != null) { - view.deselect(); - } - } -}); 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 0f05db2ac90..ab766a9eed6 100644 --- a/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx @@ -23,8 +23,13 @@ import DeferredSpinner from '../common/DeferredSpinner'; import { translate } from '../../helpers/l10n'; import { SubmitButton, ResetButtonLink } from '../ui/buttons'; +export interface ChildrenProps { + onClick: () => void; + onFormSubmit: (event: React.FormEvent<HTMLFormElement>) => void; +} + interface Props { - children: (props: { onClick: () => void }) => React.ReactNode; + children: (props: ChildrenProps) => React.ReactNode; confirmButtonText: string; confirmData?: string; isDestructive?: boolean; @@ -53,6 +58,13 @@ export default class ConfirmButton extends React.PureComponent<Props, State> { 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) { @@ -74,7 +86,10 @@ export default class ConfirmButton extends React.PureComponent<Props, State> { return ( <> - {this.props.children({ onClick: this.handleButtonClick })} + {this.props.children({ + onClick: this.handleButtonClick, + onFormSubmit: this.handleFormSubmit + })} {this.state.modal && ( <SimpleModal header={modalHeader} |