aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/app.js4
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js127
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js75
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js111
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js53
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/Header.js36
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/Key.js134
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js76
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js92
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs30
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js48
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/actions.js28
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/components.js47
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js50
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js19
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);