diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-11-16 16:41:05 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-01-16 09:43:00 +0100 |
commit | a0acec09a79485117ab9c8e3c67a8446224d5de9 (patch) | |
tree | 8af9218cdbf66fbd11058ea99595eb14ce2e9c30 /server/sonar-web/src/main | |
parent | 9e553274fbe228c06b92527ace1446d08ee244c3 (diff) | |
download | sonarqube-a0acec09a79485117ab9c8e3c67a8446224d5de9.tar.gz sonarqube-a0acec09a79485117ab9c8e3c67a8446224d5de9.zip |
SONAR-11477 Drop UI for updating module keys (#958)
Diffstat (limited to 'server/sonar-web/src/main')
22 files changed, 295 insertions, 909 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index efbeedbaa52..60317a41e39 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -222,27 +222,8 @@ export function searchComponents(data?: { return getJSON('/api/components/search', data); } -/** - * Change component's key - */ -export function changeKey(from: string, to: string): Promise<void> { - const url = '/api/projects/update_key'; - const data = { from, to }; - return post(url, data); -} - -/** - * Bulk change component's key - */ -export function bulkChangeKey( - project: string, - from: string, - to: string, - dryRun: boolean = false -): Promise<any> { - const url = '/api/projects/bulk_update_key'; - const data = { project, from, to, dryRun }; - return postJSON(url, data); +export function changeKey(data: { from: string; to: string }) { + return post('/api/projects/update_key', data).catch(throwGlobalError); } export interface SuggestionsResponse { diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index 781120376f3..dca733a3587 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -268,7 +268,7 @@ export default function startReactApp( /> <Route path="project/key" - component={lazyLoad(() => import('../../apps/project-admin/key/Key'))} + component={lazyLoad(() => import('../../apps/projectKey/Key'))} /> </Route> </Route> diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js deleted file mode 100644 index 59ceb9da3b1..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js +++ /dev/null @@ -1,148 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { connect } from 'react-redux'; -import BulkUpdateForm from './BulkUpdateForm'; -import BulkUpdateResults from './BulkUpdateResults'; -import { reloadUpdateKeyPage } from './utils'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { bulkChangeKey } from '../../../api/components'; -import { parseError } from '../../../helpers/request'; -import { - addGlobalErrorMessage, - addGlobalSuccessMessage, - closeAllGlobalMessages -} from '../../../store/globalMessages'; -import RecentHistory from '../../../app/components/RecentHistory'; - -class BulkUpdate extends React.PureComponent { - static propTypes = { - component: PropTypes.object.isRequired, - addGlobalErrorMessage: PropTypes.func.isRequired, - addGlobalSuccessMessage: PropTypes.func.isRequired, - closeAllGlobalMessages: PropTypes.func.isRequired - }; - - state = { - updating: false, - updated: false, - newComponentKey: null - }; - - handleSubmit(replace, by) { - this.loadResults(replace, by); - } - - handleConfirm() { - this.setState({ updating: true }); - - const { component } = this.props; - const { replace, by } = this.state; - - bulkChangeKey(component.key, replace, by) - .then(r => { - const result = r.keys.find(result => result.key === component.key); - const newComponentKey = result != null ? result.newKey : component.key; - - if (newComponentKey !== component.key) { - RecentHistory.remove(component.key); - } - - this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload')); - this.setState({ updating: false }); - reloadUpdateKeyPage(newComponentKey); - }) - .catch(e => { - this.setState({ updating: false }); - parseError(e).then(message => this.props.addGlobalErrorMessage(message)); - }); - } - - loadResults(replace, by) { - const { component } = this.props; - bulkChangeKey(component.key, replace, by, true) - .then(r => { - this.setState({ results: r.keys, replace, by }); - this.props.closeAllGlobalMessages(); - }) - .catch(e => { - this.setState({ results: null }); - parseError(e).then(message => this.props.addGlobalErrorMessage(message)); - }); - } - - renderUpdating() { - return ( - <div id="project-key-bulk-update"> - <i className="spinner" /> - </div> - ); - } - - render() { - const { component } = this.props; - const { updating, updated } = this.state; - const { results, replace, by } = this.state; - - if (updating) { - return this.renderUpdating(); - } - - if (updated) { - return this.renderUpdated(); - } - - return ( - <div id="project-key-bulk-update"> - <header className="big-spacer-bottom"> - <div className="spacer-bottom">{translate('update_key.bulk_change_description')}</div> - <div> - {translateWithParameters( - 'update_key.current_key_for_project_x_is_x', - component.name, - component.key - )} - </div> - </header> - - <BulkUpdateForm onSubmit={this.handleSubmit.bind(this)} /> - - {results != null && ( - <BulkUpdateResults - by={by} - onConfirm={this.handleConfirm.bind(this)} - replace={replace} - results={results} - /> - )} - </div> - ); - } -} - -export default connect( - null, - { - addGlobalErrorMessage, - addGlobalSuccessMessage, - closeAllGlobalMessages - } -)(BulkUpdate); diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js deleted file mode 100644 index 07814359462..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { translate } from '../../../helpers/l10n'; -import { SubmitButton } from '../../../components/ui/buttons'; - -export default class BulkUpdateForm extends React.PureComponent { - static propTypes = { - onSubmit: PropTypes.func.isRequired - }; - - handleSubmit = e => { - e.preventDefault(); - - const replace = this.refs.replace.value; - const by = this.refs.by.value; - - this.props.onSubmit(replace, by); - }; - - render() { - return ( - <form onSubmit={this.handleSubmit}> - <div className="modal-field"> - <label htmlFor="bulk-update-replace">{translate('update_key.replace')}</label> - <input - id="bulk-update-replace" - name="replace" - placeholder={translate('update_key.replace_example')} - ref="replace" - required={true} - type="text" - /> - </div> - - <div className="modal-field"> - <label htmlFor="bulk-update-by">{translate('update_key.by')}</label> - <input - id="bulk-update-by" - name="by" - placeholder={translate('update_key.by_example')} - ref="by" - required={true} - type="text" - /> - <SubmitButton className="big-spacer-left" id="bulk-update-see-results"> - {translate('update_key.see_results')} - </SubmitButton> - </div> - </form> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js deleted file mode 100644 index a51599ae9d6..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { some } from 'lodash'; -import { translateWithParameters, translate } from '../../../helpers/l10n'; -import { Button } from '../../../components/ui/buttons'; - -export default class BulkUpdateResults extends React.PureComponent { - static propTypes = { - results: PropTypes.array.isRequired, - onConfirm: PropTypes.func.isRequired - }; - - render() { - const { results, replace, by } = this.props; - const isEmpty = results.length === 0; - const hasDuplications = some(results, r => r.duplicate); - const canUpdate = !isEmpty && !hasDuplications; - - return ( - <div className="big-spacer-top" id="bulk-update-simulation"> - {isEmpty && ( - <div className="spacer-bottom" id="bulk-update-nothing"> - {translateWithParameters('update_key.no_key_to_update', replace)} - </div> - )} - - {hasDuplications && ( - <div className="spacer-bottom" id="bulk-update-duplicate"> - {translateWithParameters('update_key.cant_update_because_duplicate_keys', replace, by)} - </div> - )} - - {canUpdate && ( - <div className="spacer-bottom"> - {translate('update_key.keys_will_be_updated_as_follows')} - </div> - )} - - {!isEmpty && ( - <table className="data zebra zebra-hover" id="bulk-update-results"> - <thead> - <tr> - <th>{translate('update_key.old_key')}</th> - <th>{translate('update_key.new_key')}</th> - </tr> - </thead> - <tbody> - {results.map(result => ( - <tr data-key={result.key} key={result.key}> - <td className="js-old-key">{result.key}</td> - <td className="js-new-key"> - {result.duplicate && ( - <span className="spacer-right badge badge-danger"> - {translate('update_key.duplicate_key')} - </span> - )} - {result.newKey} - </td> - </tr> - ))} - </tbody> - </table> - )} - - <div className="big-spacer-top"> - {canUpdate && ( - <Button id="bulk-update-confirm" onClick={this.props.onConfirm}> - {translate('update_verb')} - </Button> - )} - </div> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js b/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js deleted file mode 100644 index 43e17ebfded..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 UpdateKeyForm from './UpdateKeyForm'; -import QualifierIcon from '../../../components/icons-components/QualifierIcon'; - -export default function FineGrainedUpdate(props) { - const { component, modules } = props; - const components = [component, ...modules]; - - return ( - <div id="project-key-fine-grained-update"> - <table className="data zebra"> - <tbody> - {components.map(component => ( - <tr key={component.key}> - <td className="width-40"> - <QualifierIcon qualifier={component.qualifier} /> {component.name} - </td> - <td> - <UpdateKeyForm component={component} onKeyChange={props.onKeyChange} /> - </td> - </tr> - ))} - </tbody> - </table> - </div> - ); -} 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 deleted file mode 100644 index ee2791f3ded..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 Helmet from 'react-helmet'; -import { connect } from 'react-redux'; -import Header from './Header'; -import UpdateForm from './UpdateForm'; -import BulkUpdate from './BulkUpdate'; -import FineGrainedUpdate from './FineGrainedUpdate'; -import { reloadUpdateKeyPage } from './utils'; -import { changeKey, fetchProjectModules } from '../store/actions'; -import { translate } from '../../../helpers/l10n'; -import { - addGlobalErrorMessage, - addGlobalSuccessMessage, - closeAllGlobalMessages -} from '../../../store/globalMessages'; -import RecentHistory from '../../../app/components/RecentHistory'; -import { getProjectAdminProjectModules } from '../../../store/rootReducer'; - -class Key extends React.PureComponent { - static propTypes = { - component: PropTypes.object, - fetchProjectModules: PropTypes.func.isRequired, - changeKey: PropTypes.func.isRequired, - addGlobalErrorMessage: PropTypes.func.isRequired, - addGlobalSuccessMessage: PropTypes.func.isRequired, - closeAllGlobalMessages: PropTypes.func.isRequired - }; - - state = { - tab: 'bulk' - }; - - componentDidMount() { - 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')); - } - }); - }; - - handleChangeTab = event => { - event.preventDefault(); - event.currentTarget.blur(); - const { tab } = event.currentTarget.dataset; - this.setState({ tab }); - this.props.closeAllGlobalMessages(); - }; - - render() { - const { component, modules } = this.props; - - const noModules = modules != null && modules.length === 0; - const hasModules = modules != null && modules.length > 0; - - const { tab } = this.state; - - return ( - <div className="page page-limited" id="project-key"> - <Helmet title={translate('update_key.page')} /> - <Header /> - - {modules == null && <i className="spinner" />} - - {noModules && ( - <div> - <UpdateForm component={component} onKeyChange={this.handleChangeKey} /> - </div> - )} - - {hasModules && ( - <div className="boxed-group boxed-group-inner"> - <div className="big-spacer-bottom"> - <ul className="tabs"> - <li> - <a - className={tab === 'bulk' ? 'selected' : ''} - data-tab="bulk" - href="#" - id="update-key-tab-bulk" - onClick={this.handleChangeTab}> - {translate('update_key.bulk_update')} - </a> - </li> - <li> - <a - className={tab === 'fine' ? 'selected' : ''} - data-tab="fine" - href="#" - id="update-key-tab-fine" - onClick={this.handleChangeTab}> - {translate('update_key.fine_grained_key_update')} - </a> - </li> - </ul> - </div> - - {tab === 'bulk' && <BulkUpdate component={component} />} - - {tab === 'fine' && ( - <FineGrainedUpdate - component={component} - modules={modules} - onError={this.props.addGlobalErrorMessage} - onKeyChange={this.handleChangeKey} - onSuccess={this.props.closeAllGlobalMessages} - /> - )} - </div> - )} - </div> - ); - } -} - -const mapStateToProps = (state, ownProps) => ({ - modules: getProjectAdminProjectModules(state, ownProps.location.query.id) -}); - -export default connect( - mapStateToProps, - { - fetchProjectModules, - changeKey, - addGlobalErrorMessage, - addGlobalSuccessMessage, - closeAllGlobalMessages - } -)(Key); 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 deleted file mode 100644 index 8796aac4386..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx deleted file mode 100644 index 6d8a023ec03..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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/utils.js b/server/sonar-web/src/main/js/apps/project-admin/key/utils.js deleted file mode 100644 index 0a30626afa5..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/key/utils.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -export const reloadUpdateKeyPage = componentKey => { - setTimeout(() => { - window.location = window.baseUrl + '/project/key?id=' + encodeURIComponent(componentKey); - }, 3000); -}; 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 deleted file mode 100644 index cdb042caee9..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { getTree, changeKey as changeKeyApi } from '../../../api/components'; -import throwGlobalError from '../../../app/utils/throwGlobalError'; - -export const RECEIVE_PROJECT_MODULES = 'projectAdmin/RECEIVE_PROJECT_MODULES'; -const receiveProjectModules = (projectKey, modules) => ({ - type: RECEIVE_PROJECT_MODULES, - projectKey, - modules -}); - -export const fetchProjectModules = projectKey => dispatch => { - getTree({ component: projectKey, qualifiers: 'BRC', s: 'name', ps: 500 }).then( - r => { - dispatch(receiveProjectModules(projectKey, r.components)); - }, - () => {} - ); -}; - -export const CHANGE_KEY = 'projectAdmin/CHANGE_KEY'; -const changeKeyAction = (key, newKey) => ({ - type: CHANGE_KEY, - key, - newKey -}); - -export const changeKey = (key, newKey) => dispatch => { - return changeKeyApi(key, newKey).then( - () => dispatch(changeKeyAction(key, newKey)), - throwGlobalError - ); -}; diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/components.js b/server/sonar-web/src/main/js/apps/project-admin/store/components.js deleted file mode 100644 index d8e238365db..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/store/components.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { keyBy, omit } from 'lodash'; -import { RECEIVE_PROJECT_MODULES, CHANGE_KEY } from './actions'; - -const components = (state = {}, action = {}) => { - if (action.type === RECEIVE_PROJECT_MODULES) { - const newComponentsByKey = keyBy(action.modules, 'key'); - return { ...state, ...newComponentsByKey }; - } - - if (action.type === CHANGE_KEY) { - const oldComponent = state[action.key]; - if (oldComponent != null) { - const newComponent = { ...oldComponent, key: action.newKey }; - return { - ...omit(state, action.key), - [action.newKey]: newComponent - }; - } - } - - return state; -}; - -export default components; - -export const getComponentByKey = (state, key) => state[key]; diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js b/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js deleted file mode 100644 index 9b5014be2d1..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { RECEIVE_PROJECT_MODULES, CHANGE_KEY } from './actions'; - -const modulesByProject = (state = {}, action = {}) => { - if (action.type === RECEIVE_PROJECT_MODULES) { - const moduleKeys = action.modules.map(module => module.key); - return { ...state, [action.projectKey]: moduleKeys }; - } - - if (action.type === CHANGE_KEY) { - const newState = {}; - Object.keys(state).forEach(projectKey => { - const moduleKeys = state[projectKey]; - const changedKeyIndex = moduleKeys.indexOf(action.key); - if (changedKeyIndex !== -1) { - const newModuleKeys = [...moduleKeys]; - newModuleKeys.splice(changedKeyIndex, 1, action.newKey); - newState[projectKey] = newModuleKeys; - } else { - newState[projectKey] = moduleKeys; - } - }); - return newState; - } - - return state; -}; - -export default modulesByProject; - -export const getProjectModules = (state, projectKey) => state[projectKey]; diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js deleted file mode 100644 index 6bff2faed22..00000000000 --- a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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 { combineReducers } from 'redux'; -import components, { getComponentByKey as nextGetComponentByKey } from './components'; -import modulesByProject, { getProjectModules as nextGetProjectModules } from './modulesByProject'; - -const rootReducer = combineReducers({ - components, - modulesByProject -}); - -export default rootReducer; - -export const getComponentByKey = (state, componentKey) => - nextGetComponentByKey(state.components, componentKey); - -export const getProjectModules = (state, projectKey) => { - const moduleKeys = nextGetProjectModules(state.modulesByProject, projectKey); - if (moduleKeys == null) { - return null; - } - return moduleKeys.map(moduleKey => getComponentByKey(state, moduleKey)); -}; diff --git a/server/sonar-web/src/main/js/apps/projectKey/Key.tsx b/server/sonar-web/src/main/js/apps/projectKey/Key.tsx new file mode 100644 index 00000000000..38454a5bc00 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectKey/Key.tsx @@ -0,0 +1,55 @@ +/* + * 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 Helmet from 'react-helmet'; +import { withRouter, WithRouterProps } from 'react-router'; +import UpdateForm from './UpdateForm'; +import { changeKey } from '../../api/components'; +import RecentHistory from '../../app/components/RecentHistory'; +import { translate } from '../../helpers/l10n'; + +interface Props { + component: Pick<T.Component, 'key' | 'name'>; +} + +export class Key extends React.PureComponent<Props & WithRouterProps> { + handleChangeKey = (newKey: string) => { + return changeKey({ from: this.props.component.key, to: newKey }).then(() => { + RecentHistory.remove(this.props.component.key); + this.props.router.replace({ pathname: '/project/key', query: { id: newKey } }); + }); + }; + + render() { + const { component } = this.props; + return ( + <div className="page page-limited" id="project-key"> + <Helmet title={translate('update_key.page')} /> + <header className="page-header"> + <h1 className="page-title">{translate('update_key.page')}</h1> + <div className="page-description">{translate('update_key.page.description')}</div> + </header> + <UpdateForm component={component} onKeyChange={this.handleChangeKey} /> + </div> + ); + } +} + +export default withRouter(Key); diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx b/server/sonar-web/src/main/js/apps/projectKey/UpdateForm.tsx index 1ece13fef36..39dbe961d25 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectKey/UpdateForm.tsx @@ -18,13 +18,13 @@ * 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'; +import { Button, SubmitButton } from '../../components/ui/buttons'; +import { translate, translateWithParameters } from '../../helpers/l10n'; +import ConfirmButton from '../../components/controls/ConfirmButton'; interface Props { - component: { key: string; name: string }; - onKeyChange: (oldKey: string, newKey: string) => Promise<void>; + component: Pick<T.Component, 'key' | 'name'>; + onKeyChange: (newKey: string) => Promise<void>; } interface State { @@ -50,7 +50,26 @@ export default class UpdateForm extends React.PureComponent<Props, State> { const hasChanged = value !== component.key; return ( - <UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}> + <ConfirmButton + confirmButtonText={translate('update_verb')} + confirmData={newKey} + 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.props.onKeyChange}> {({ onFormSubmit }) => ( <form onSubmit={onFormSubmit}> <input @@ -79,7 +98,7 @@ export default class UpdateForm extends React.PureComponent<Props, State> { </div> </form> )} - </UpdateKeyConfirm> + </ConfirmButton> ); } } diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx b/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx new file mode 100644 index 00000000000..57c8aa569b4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { WithRouterProps } from 'react-router'; +import { Key } from '../Key'; +import { changeKey } from '../../../api/components'; + +jest.mock('../../../api/components', () => ({ + changeKey: jest.fn().mockResolvedValue(undefined) +})); + +it('should render and change key', async () => { + const withRouterProps = { router: { replace: jest.fn() } as any } as WithRouterProps; + const wrapper = shallow(<Key component={{ key: 'foo', name: 'Foo' }} {...withRouterProps} />); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('UpdateForm').prop<Function>('onKeyChange')('bar'); + await new Promise(setImmediate); + expect(changeKey).toBeCalledWith({ from: 'foo', to: 'bar' }); + expect(withRouterProps.router.replace).toBeCalledWith({ + pathname: '/project/key', + query: { id: 'bar' } + }); +}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Header.js b/server/sonar-web/src/main/js/apps/projectKey/__tests__/UpdateForm-test.tsx index 6c14beda91b..457575b7abf 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/Header.js +++ b/server/sonar-web/src/main/js/apps/projectKey/__tests__/UpdateForm-test.tsx @@ -17,14 +17,25 @@ * 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 { translate } from '../../../helpers/l10n'; +import * as React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import UpdateForm from '../UpdateForm'; +import { change, click } from '../../../helpers/testUtils'; -export default function Header() { - return ( - <header className="page-header"> - <h1 className="page-title">{translate('update_key.page')}</h1> - <div className="page-description">{translate('update_key.page.description')}</div> - </header> +it('should render', () => { + const wrapper = shallow( + <UpdateForm component={{ key: 'foo', name: 'Foo' }} onKeyChange={jest.fn()} /> ); + expect(getInner(wrapper)).toMatchSnapshot(); + + change(getInner(wrapper).find('input'), 'bar'); + expect(getInner(wrapper)).toMatchSnapshot(); + + click(getInner(wrapper).find('Button')); + expect(getInner(wrapper)).toMatchSnapshot(); +}); + +function getInner(wrapper: ShallowWrapper) { + // TODO find a better way to do this + return wrapper.dive().dive(); } diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap new file mode 100644 index 00000000000..da5365e6d5c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render and change key 1`] = ` +<div + className="page page-limited" + id="project-key" +> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="update_key.page" + /> + <header + className="page-header" + > + <h1 + className="page-title" + > + update_key.page + </h1> + <div + className="page-description" + > + update_key.page.description + </div> + </header> + <UpdateForm + component={ + Object { + "key": "foo", + "name": "Foo", + } + } + onKeyChange={[Function]} + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap new file mode 100644 index 00000000000..0a009777e05 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap @@ -0,0 +1,112 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +<Fragment> + <form + onSubmit={[Function]} + > + <input + className="input-super-large" + id="update-key-new-key" + onChange={[Function]} + placeholder="update_key.new_key" + required={true} + type="text" + value="foo" + /> + <div + className="spacer-top" + > + <SubmitButton + disabled={true} + id="update-key-submit" + > + update_verb + </SubmitButton> + <Button + className="spacer-left" + disabled={true} + id="update-key-reset" + onClick={[Function]} + type="reset" + > + reset_verb + </Button> + </div> + </form> +</Fragment> +`; + +exports[`should render 2`] = ` +<Fragment> + <form + onSubmit={[Function]} + > + <input + className="input-super-large" + id="update-key-new-key" + onChange={[Function]} + placeholder="update_key.new_key" + required={true} + type="text" + value="bar" + /> + <div + className="spacer-top" + > + <SubmitButton + disabled={false} + id="update-key-submit" + > + update_verb + </SubmitButton> + <Button + className="spacer-left" + disabled={false} + id="update-key-reset" + onClick={[Function]} + type="reset" + > + reset_verb + </Button> + </div> + </form> +</Fragment> +`; + +exports[`should render 3`] = ` +<Fragment> + <form + onSubmit={[Function]} + > + <input + className="input-super-large" + id="update-key-new-key" + onChange={[Function]} + placeholder="update_key.new_key" + required={true} + type="text" + value="foo" + /> + <div + className="spacer-top" + > + <SubmitButton + disabled={true} + id="update-key-submit" + > + update_verb + </SubmitButton> + <Button + className="spacer-left" + disabled={true} + id="update-key-reset" + onClick={[Function]} + type="reset" + > + reset_verb + </Button> + </div> + </form> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/components/SelectList/SelectListListContainer.tsx b/server/sonar-web/src/main/js/components/SelectList/SelectListListContainer.tsx index 96d969a33f7..829bee3f284 100644 --- a/server/sonar-web/src/main/js/components/SelectList/SelectListListContainer.tsx +++ b/server/sonar-web/src/main/js/components/SelectList/SelectListListContainer.tsx @@ -91,7 +91,7 @@ export default class SelectListListContainer extends React.PureComponent<Props, onCheck={this.handleBulkChange} thirdState={selectedElements.length > 0 && elements.length !== selectedElements.length}> <span className="big-spacer-left"> - {translate('update_key.bulk_update')} + {translate('bulk_change')} <DeferredSpinner className="spacer-left" loading={this.state.loading} timeout={10} /> </span> </Checkbox> diff --git a/server/sonar-web/src/main/js/store/rootReducer.ts b/server/sonar-web/src/main/js/store/rootReducer.ts index fa3890586fc..e4286c79a99 100644 --- a/server/sonar-web/src/main/js/store/rootReducer.ts +++ b/server/sonar-web/src/main/js/store/rootReducer.ts @@ -24,7 +24,6 @@ import languages, * as fromLanguages from './languages'; import metrics, * as fromMetrics from './metrics'; import organizations, * as fromOrganizations from './organizations'; import users, * as fromUsers from './users'; -import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer'; import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer'; export type Store = { @@ -36,7 +35,6 @@ export type Store = { users: fromUsers.State; // apps - projectAdminApp: any; settingsApp: any; }; @@ -49,7 +47,6 @@ export default combineReducers<Store>({ users, // apps - projectAdminApp, settingsApp }); @@ -128,7 +125,3 @@ export function isSettingsAppLoading(state: Store, key: string) { export function getSettingsAppValidationMessage(state: Store, key: string) { return fromSettingsApp.getValidationMessage(state.settingsApp, key); } - -export function getProjectAdminProjectModules(state: Store, projectKey: string) { - return fromProjectAdminApp.getProjectModules(state.projectAdminApp, projectKey); -} |