aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/project-admin/key
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-08-10 18:16:54 +0200
committerGitHub <noreply@github.com>2016-08-10 18:16:54 +0200
commitc12f3d56cb99478c36242fd122258ebf0d26f740 (patch)
tree2a7cae9094a9773bf5ba621bb3214f52c2c6126d /server/sonar-web/src/main/js/apps/project-admin/key
parent6d55c79eed10aefdda9381cb82acdac47c824ec6 (diff)
downloadsonarqube-c12f3d56cb99478c36242fd122258ebf0d26f740.tar.gz
sonarqube-c12f3d56cb99478c36242fd122258ebf0d26f740.zip
SONAR-7919 Rewrite "Update Key" project page (#1140)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/project-admin/key')
-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
10 files changed, 782 insertions, 0 deletions
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
+ };
+ }
+});
+