diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
15 files changed, 930 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/project-admin/app.js b/server/sonar-web/src/main/js/apps/project-admin/app.js index 7981c936e87..903ed046cb9 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/app.js +++ b/server/sonar-web/src/main/js/apps/project-admin/app.js @@ -26,6 +26,7 @@ import Deletion from './deletion/Deletion'; import QualityProfiles from './quality-profiles/QualityProfiles'; import QualityGate from './quality-gate/QualityGate'; import Links from './links/Links'; +import Key from './key/Key'; import rootReducer from './store/rootReducer'; import configureStore from '../../components/store/configureStore'; @@ -56,6 +57,9 @@ window.sonarqube.appStarted.then(options => { <Route path="/links" component={withComponent(Links)}/> + <Route + path="/key" + component={withComponent(Key)}/> </Router> </Provider> ), el); 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 new file mode 100644 index 00000000000..1624faddc3f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js @@ -0,0 +1,127 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 BulkUpdateForm from './BulkUpdateForm'; +import BulkUpdateResults from './BulkUpdateResults'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { bulkChangeKey } from '../../../api/components'; +import { getComponentUrl } from '../../../helpers/urls'; + +export default class BulkUpdate extends React.Component { + static propTypes = { + component: React.PropTypes.object.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; + this.setState({ + updating: false, + updated: true, + newComponentKey + }); + }); + } + + loadResults (replace, by) { + const { component } = this.props; + bulkChangeKey(component.key, replace, by, true).then(r => { + this.setState({ results: r.keys, replace, by }); + }); + } + + renderUpdating () { + return ( + <div id="project-key-bulk-update"> + <i className="spinner"/> + </div> + ); + } + + renderUpdated () { + return ( + <div id="project-key-bulk-update"> + <div className="alert alert-success"> + {translate('update_key.key_updated')} + {' '} + <a href={getComponentUrl(this.state.newComponentKey)}> + {translate('check_project')} + </a> + </div> + </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 + results={results} + replace={replace} + by={by} + onConfirm={this.handleConfirm.bind(this)}/> + )} + </div> + ); + } +} 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 new file mode 100644 index 00000000000..db31d98063d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { translate } from '../../../helpers/l10n'; + +export default class BulkUpdateForm extends React.Component { + static propTypes = { + onSubmit: React.PropTypes.func.isRequired + }; + + handleSubmit (e) { + e.preventDefault(); + this.refs.submit.blur(); + + const replace = this.refs.replace.value; + const by = this.refs.by.value; + + this.props.onSubmit(replace, by); + } + + render () { + return ( + <form onSubmit={this.handleSubmit.bind(this)}> + <div className="modal-field"> + <label htmlFor="bulk-update-replace"> + {translate('update_key.replace')} + </label> + <input + ref="replace" + id="bulk-update-replace" + name="replace" + type="text" + placeholder={translate('update_key.replace_example')} + required/> + </div> + + <div className="modal-field"> + <label htmlFor="bulk-update-by"> + {translate('update_key.by')} + </label> + <input + ref="by" + id="bulk-update-by" + name="by" + type="text" + placeholder={translate('update_key.by_example')} + required/> + <button + ref="submit" + id="bulk-update-see-results" + className="big-spacer-left"> + {translate('update_key.see_results')} + </button> + </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 new file mode 100644 index 00000000000..56f5531875b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 some from 'lodash/some'; +import { translateWithParameters, translate } from '../../../helpers/l10n'; + +export default class BulkUpdateResults extends React.Component { + static propTypes = { + results: React.PropTypes.array.isRequired, + onConfirm: React.PropTypes.func.isRequired + }; + + handleConfirm (e) { + e.preventDefault(); + e.target.blur(); + this.props.onConfirm(); + } + + 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 id="bulk-update-simulation" className="big-spacer-top"> + {isEmpty && ( + <div id="bulk-update-nothing" className="alert alert-warning"> + {translateWithParameters( + 'update_key.no_key_to_update', + replace + )} + </div> + )} + + {hasDuplications && ( + <div id="bulk-update-duplicate" className="alert alert-danger"> + {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 + id="bulk-update-results" + className="data zebra zebra-hover"> + <thead> + <tr> + <th>{translate('update_key.old_key')}</th> + <th>{translate('update_key.new_key')}</th> + </tr> + </thead> + <tbody> + {results.map(result => ( + <tr key={result.key} data-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.handleConfirm.bind(this)}> + {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 new file mode 100644 index 00000000000..6f7deacf0ed --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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/shared/qualifier-icon'; +import { translate } from '../../../helpers/l10n'; + +export default class FineGrainedUpdate extends React.Component { + render () { + const { component, modules } = this.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={this.props.onKeyChange}/> + </td> + </tr> + ))} + </tbody> + </table> + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Header.js b/server/sonar-web/src/main/js/apps/project-admin/key/Header.js new file mode 100644 index 00000000000..c730528a8a2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Header.js @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { translate } from '../../../helpers/l10n'; + +export default class Header extends React.Component { + render () { + 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> + ); + } +} 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 new file mode 100644 index 00000000000..47b61bc3e6e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 shallowCompare from 'react-addons-shallow-compare'; +import { connect } from 'react-redux'; +import Header from './Header'; +import UpdateForm from './UpdateForm'; +import BulkUpdate from './BulkUpdate'; +import FineGrainedUpdate from './FineGrainedUpdate'; +import { getProjectModules } from '../store/rootReducer'; +import { fetchProjectModules, changeKey } from '../store/actions'; +import { translate } from '../../../helpers/l10n'; + +class Key extends React.Component { + static propTypes = { + component: React.PropTypes.object.isRequired, + fetchProjectModules: React.PropTypes.func.isRequired, + changeKey: React.PropTypes.func.isRequired + }; + + state = { + tab: 'bulk' + }; + + componentDidMount () { + this.props.fetchProjectModules(this.props.component.key); + } + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + handleChangeKey (key, newKey) { + return this.props.changeKey(key, newKey).then(() => { + if (key === this.props.component.key) { + window.location = window.baseUrl + + '/project/key?id=' + encodeURIComponent(newKey); + } + }); + } + + handleChangeTab (tab, e) { + e.preventDefault(); + e.target.blur(); + this.setState({ tab }); + } + + 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 id="project-key" className="page page-limited"> + <Header/> + + {modules == null && ( + <i className="spinner"/> + )} + + {noModules && ( + <UpdateForm + component={component} + onKeyChange={this.handleChangeKey.bind(this)}/> + )} + + {hasModules && ( + <div> + <div className="big-spacer-bottom"> + <ul className="tabs"> + <li> + <a id="update-key-tab-bulk" + className={tab === 'bulk' ? 'selected' : ''} + href="#" + onClick={this.handleChangeTab.bind(this, 'bulk')}> + {translate('update_key.bulk_update')} + </a> + </li> + <li> + <a id="update-key-tab-fine" + className={tab === 'fine' ? 'selected' : ''} + href="#" + onClick={this.handleChangeTab.bind(this, 'fine')}> + {translate('update_key.fine_grained_key_update')} + </a> + </li> + </ul> + </div> + + {tab === 'bulk' && ( + <BulkUpdate component={component}/> + )} + + {tab === 'fine' && ( + <FineGrainedUpdate + component={component} + modules={modules} + onKeyChange={this.handleChangeKey.bind(this)}/> + )} + </div> + )} + </div> + ); + } +} + +const mapStateToProps = (state, ownProps) => ({ + modules: getProjectModules(state, ownProps.component.key) +}); + +export default connect( + mapStateToProps, + { fetchProjectModules, changeKey } +)(Key); 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 new file mode 100644 index 00000000000..701d2070957 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 UpdateKeyConfirmation from './views/UpdateKeyConfirmation'; +import { translate } from '../../../helpers/l10n'; + +export default class UpdateForm extends React.Component { + static propTypes = { + component: React.PropTypes.object.isRequired, + onKeyChange: React.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 }); + } + + 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 + onChange={this.handleChange.bind(this)}/> + + <div className="spacer-top"> + <button id="update-key-submit" disabled={!hasChanged}> + {translate('update_verb')} + </button> + </div> + </form> + ); + } +} 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 new file mode 100644 index 00000000000..f23d1f7069e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 UpdateKeyConfirmation from './views/UpdateKeyConfirmation'; +import { translate } from '../../../helpers/l10n'; + +export default class UpdateKeyForm extends React.Component { + static propTypes = { + component: React.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}/> + + <div className="button-group"> + <button disabled={!hasChanged} onClick={this.handleUpdateClick}> + {translate('update_verb')} + </button> + + <button disabled={!hasChanged} onClick={this.handleResetClick}> + {translate('reset_verb')} + </button> + </div> + </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 new file mode 100644 index 00000000000..bec83b7693e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs @@ -0,0 +1,30 @@ +<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 new file mode 100644 index 00000000000..9b1c4abd26e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import ModalForm from '../../../../components/common/modal-form'; +import Template from './UpdateKeyConfirmation.hbs'; +import { parseError } from '../../../code/utils'; + +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) + .then(() => this.destroy()) + .catch(e => { + parseError(e).then(msg => this.showSingleError(msg)); + this.hideSpinner(); + this.enableForm(); + }); + }, + + serializeData () { + return { + component: this.options.component, + newKey: this.options.newKey + }; + } +}); + 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 29a627e9d54..ca20001b763 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 @@ -30,6 +30,8 @@ import { dissociateGateWithProject } from '../../../api/quality-gates'; import { getProjectLinks, createLink } from '../../../api/projectLinks'; +import { getTree } from '../../../api/components'; +import { changeKey as changeKeyApi } from '../../../api/components'; import { addGlobalSuccessMessage } from '../../../components/store/globalMessages'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -157,3 +159,29 @@ export const deleteProjectLink = (projectKey, linkId) => ({ projectKey, linkId }); + +export const RECEIVE_PROJECT_MODULES = 'RECEIVE_PROJECT_MODULES'; +const receiveProjectModules = (projectKey, modules) => ({ + type: RECEIVE_PROJECT_MODULES, + 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)); + }); +}; + +export const CHANGE_KEY = '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))); +}; 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 new file mode 100644 index 00000000000..bc369003eee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/store/components.js @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 from 'lodash/keyBy'; +import omit from 'lodash/omit'; +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 new file mode 100644 index 00000000000..0b55882cf2d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 index 513e39dc67d..a5c56087ce9 100644 --- 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 @@ -27,6 +27,12 @@ import gates, { getAllGates as nextGetAllGates, getGate } from './gates'; import gateByProject, { getProjectGate as nextGetProjectGate } from './gateByProject'; import links, { getLink } from './links'; import linksByProject, { getLinks } from './linksByProject'; +import components, { + getComponentByKey as nextGetComponentByKey +} from './components'; +import modulesByProject, { + getProjectModules as nextGetProjectModules +} from './modulesByProject'; import globalMessages, { getGlobalMessages as nextGetGlobalMessages } from '../../../components/store/globalMessages'; @@ -38,6 +44,8 @@ const rootReducer = combineReducers({ gateByProject, links, linksByProject, + components, + modulesByProject, globalMessages }); @@ -69,5 +77,16 @@ export const getProjectLinks = (state, projectKey) => getLinks(state.linksByProject, projectKey) .map(linkId => getLinkById(state, linkId)); +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)); +}; + export const getGlobalMessages = state => nextGetGlobalMessages(state.globalMessages); |