@@ -92,6 +92,7 @@ public class BulkUpdateKeyAction implements ProjectsWsAction { | |||
PARAM_DRY_RUN, | |||
PARAM_PROJECT, PARAM_FROM, PARAM_TO, | |||
PARAM_PROJECT_ID, PARAM_PROJECT) | |||
.setDeprecatedSince("7.6") | |||
.setSince("6.1") | |||
.setPost(true) | |||
.setResponseExample(getClass().getResource("bulk_update_key-example.json")) |
@@ -222,27 +222,8 @@ export function searchComponents(data?: { | |||
return getJSON('/api/components/search', data); | |||
} | |||
/** | |||
* Change component's key | |||
*/ | |||
export function changeKey(from: string, to: string): Promise<void> { | |||
const url = '/api/projects/update_key'; | |||
const data = { from, to }; | |||
return post(url, data); | |||
} | |||
/** | |||
* Bulk change component's key | |||
*/ | |||
export function bulkChangeKey( | |||
project: string, | |||
from: string, | |||
to: string, | |||
dryRun: boolean = false | |||
): Promise<any> { | |||
const url = '/api/projects/bulk_update_key'; | |||
const data = { project, from, to, dryRun }; | |||
return postJSON(url, data); | |||
export function changeKey(data: { from: string; to: string }) { | |||
return post('/api/projects/update_key', data).catch(throwGlobalError); | |||
} | |||
export interface SuggestionsResponse { |
@@ -268,7 +268,7 @@ export default function startReactApp( | |||
/> | |||
<Route | |||
path="project/key" | |||
component={lazyLoad(() => import('../../apps/project-admin/key/Key'))} | |||
component={lazyLoad(() => import('../../apps/projectKey/Key'))} | |||
/> | |||
</Route> | |||
</Route> |
@@ -1,148 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import { connect } from 'react-redux'; | |||
import BulkUpdateForm from './BulkUpdateForm'; | |||
import BulkUpdateResults from './BulkUpdateResults'; | |||
import { reloadUpdateKeyPage } from './utils'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { bulkChangeKey } from '../../../api/components'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { | |||
addGlobalErrorMessage, | |||
addGlobalSuccessMessage, | |||
closeAllGlobalMessages | |||
} from '../../../store/globalMessages'; | |||
import RecentHistory from '../../../app/components/RecentHistory'; | |||
class BulkUpdate extends React.PureComponent { | |||
static propTypes = { | |||
component: PropTypes.object.isRequired, | |||
addGlobalErrorMessage: PropTypes.func.isRequired, | |||
addGlobalSuccessMessage: PropTypes.func.isRequired, | |||
closeAllGlobalMessages: PropTypes.func.isRequired | |||
}; | |||
state = { | |||
updating: false, | |||
updated: false, | |||
newComponentKey: null | |||
}; | |||
handleSubmit(replace, by) { | |||
this.loadResults(replace, by); | |||
} | |||
handleConfirm() { | |||
this.setState({ updating: true }); | |||
const { component } = this.props; | |||
const { replace, by } = this.state; | |||
bulkChangeKey(component.key, replace, by) | |||
.then(r => { | |||
const result = r.keys.find(result => result.key === component.key); | |||
const newComponentKey = result != null ? result.newKey : component.key; | |||
if (newComponentKey !== component.key) { | |||
RecentHistory.remove(component.key); | |||
} | |||
this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload')); | |||
this.setState({ updating: false }); | |||
reloadUpdateKeyPage(newComponentKey); | |||
}) | |||
.catch(e => { | |||
this.setState({ updating: false }); | |||
parseError(e).then(message => this.props.addGlobalErrorMessage(message)); | |||
}); | |||
} | |||
loadResults(replace, by) { | |||
const { component } = this.props; | |||
bulkChangeKey(component.key, replace, by, true) | |||
.then(r => { | |||
this.setState({ results: r.keys, replace, by }); | |||
this.props.closeAllGlobalMessages(); | |||
}) | |||
.catch(e => { | |||
this.setState({ results: null }); | |||
parseError(e).then(message => this.props.addGlobalErrorMessage(message)); | |||
}); | |||
} | |||
renderUpdating() { | |||
return ( | |||
<div id="project-key-bulk-update"> | |||
<i className="spinner" /> | |||
</div> | |||
); | |||
} | |||
render() { | |||
const { component } = this.props; | |||
const { updating, updated } = this.state; | |||
const { results, replace, by } = this.state; | |||
if (updating) { | |||
return this.renderUpdating(); | |||
} | |||
if (updated) { | |||
return this.renderUpdated(); | |||
} | |||
return ( | |||
<div id="project-key-bulk-update"> | |||
<header className="big-spacer-bottom"> | |||
<div className="spacer-bottom">{translate('update_key.bulk_change_description')}</div> | |||
<div> | |||
{translateWithParameters( | |||
'update_key.current_key_for_project_x_is_x', | |||
component.name, | |||
component.key | |||
)} | |||
</div> | |||
</header> | |||
<BulkUpdateForm onSubmit={this.handleSubmit.bind(this)} /> | |||
{results != null && ( | |||
<BulkUpdateResults | |||
by={by} | |||
onConfirm={this.handleConfirm.bind(this)} | |||
replace={replace} | |||
results={results} | |||
/> | |||
)} | |||
</div> | |||
); | |||
} | |||
} | |||
export default connect( | |||
null, | |||
{ | |||
addGlobalErrorMessage, | |||
addGlobalSuccessMessage, | |||
closeAllGlobalMessages | |||
} | |||
)(BulkUpdate); |
@@ -1,71 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { SubmitButton } from '../../../components/ui/buttons'; | |||
export default class BulkUpdateForm extends React.PureComponent { | |||
static propTypes = { | |||
onSubmit: PropTypes.func.isRequired | |||
}; | |||
handleSubmit = e => { | |||
e.preventDefault(); | |||
const replace = this.refs.replace.value; | |||
const by = this.refs.by.value; | |||
this.props.onSubmit(replace, by); | |||
}; | |||
render() { | |||
return ( | |||
<form onSubmit={this.handleSubmit}> | |||
<div className="modal-field"> | |||
<label htmlFor="bulk-update-replace">{translate('update_key.replace')}</label> | |||
<input | |||
id="bulk-update-replace" | |||
name="replace" | |||
placeholder={translate('update_key.replace_example')} | |||
ref="replace" | |||
required={true} | |||
type="text" | |||
/> | |||
</div> | |||
<div className="modal-field"> | |||
<label htmlFor="bulk-update-by">{translate('update_key.by')}</label> | |||
<input | |||
id="bulk-update-by" | |||
name="by" | |||
placeholder={translate('update_key.by_example')} | |||
ref="by" | |||
required={true} | |||
type="text" | |||
/> | |||
<SubmitButton className="big-spacer-left" id="bulk-update-see-results"> | |||
{translate('update_key.see_results')} | |||
</SubmitButton> | |||
</div> | |||
</form> | |||
); | |||
} | |||
} |
@@ -1,94 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import { some } from 'lodash'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
import { Button } from '../../../components/ui/buttons'; | |||
export default class BulkUpdateResults extends React.PureComponent { | |||
static propTypes = { | |||
results: PropTypes.array.isRequired, | |||
onConfirm: PropTypes.func.isRequired | |||
}; | |||
render() { | |||
const { results, replace, by } = this.props; | |||
const isEmpty = results.length === 0; | |||
const hasDuplications = some(results, r => r.duplicate); | |||
const canUpdate = !isEmpty && !hasDuplications; | |||
return ( | |||
<div className="big-spacer-top" id="bulk-update-simulation"> | |||
{isEmpty && ( | |||
<div className="spacer-bottom" id="bulk-update-nothing"> | |||
{translateWithParameters('update_key.no_key_to_update', replace)} | |||
</div> | |||
)} | |||
{hasDuplications && ( | |||
<div className="spacer-bottom" id="bulk-update-duplicate"> | |||
{translateWithParameters('update_key.cant_update_because_duplicate_keys', replace, by)} | |||
</div> | |||
)} | |||
{canUpdate && ( | |||
<div className="spacer-bottom"> | |||
{translate('update_key.keys_will_be_updated_as_follows')} | |||
</div> | |||
)} | |||
{!isEmpty && ( | |||
<table className="data zebra zebra-hover" id="bulk-update-results"> | |||
<thead> | |||
<tr> | |||
<th>{translate('update_key.old_key')}</th> | |||
<th>{translate('update_key.new_key')}</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{results.map(result => ( | |||
<tr data-key={result.key} key={result.key}> | |||
<td className="js-old-key">{result.key}</td> | |||
<td className="js-new-key"> | |||
{result.duplicate && ( | |||
<span className="spacer-right badge badge-danger"> | |||
{translate('update_key.duplicate_key')} | |||
</span> | |||
)} | |||
{result.newKey} | |||
</td> | |||
</tr> | |||
))} | |||
</tbody> | |||
</table> | |||
)} | |||
<div className="big-spacer-top"> | |||
{canUpdate && ( | |||
<Button id="bulk-update-confirm" onClick={this.props.onConfirm}> | |||
{translate('update_verb')} | |||
</Button> | |||
)} | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,46 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import UpdateKeyForm from './UpdateKeyForm'; | |||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
export default function FineGrainedUpdate(props) { | |||
const { component, modules } = props; | |||
const components = [component, ...modules]; | |||
return ( | |||
<div id="project-key-fine-grained-update"> | |||
<table className="data zebra"> | |||
<tbody> | |||
{components.map(component => ( | |||
<tr key={component.key}> | |||
<td className="width-40"> | |||
<QualifierIcon qualifier={component.qualifier} /> {component.name} | |||
</td> | |||
<td> | |||
<UpdateKeyForm component={component} onKeyChange={props.onKeyChange} /> | |||
</td> | |||
</tr> | |||
))} | |||
</tbody> | |||
</table> | |||
</div> | |||
); | |||
} |
@@ -1,156 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import Helmet from 'react-helmet'; | |||
import { connect } from 'react-redux'; | |||
import Header from './Header'; | |||
import UpdateForm from './UpdateForm'; | |||
import BulkUpdate from './BulkUpdate'; | |||
import FineGrainedUpdate from './FineGrainedUpdate'; | |||
import { reloadUpdateKeyPage } from './utils'; | |||
import { changeKey, fetchProjectModules } from '../store/actions'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { | |||
addGlobalErrorMessage, | |||
addGlobalSuccessMessage, | |||
closeAllGlobalMessages | |||
} from '../../../store/globalMessages'; | |||
import RecentHistory from '../../../app/components/RecentHistory'; | |||
import { getProjectAdminProjectModules } from '../../../store/rootReducer'; | |||
class Key extends React.PureComponent { | |||
static propTypes = { | |||
component: PropTypes.object, | |||
fetchProjectModules: PropTypes.func.isRequired, | |||
changeKey: PropTypes.func.isRequired, | |||
addGlobalErrorMessage: PropTypes.func.isRequired, | |||
addGlobalSuccessMessage: PropTypes.func.isRequired, | |||
closeAllGlobalMessages: PropTypes.func.isRequired | |||
}; | |||
state = { | |||
tab: 'bulk' | |||
}; | |||
componentDidMount() { | |||
this.props.fetchProjectModules(this.props.component.key); | |||
} | |||
handleChangeKey = (key, newKey) => { | |||
return this.props.changeKey(key, newKey).then(() => { | |||
if (key === this.props.component.key) { | |||
this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload')); | |||
RecentHistory.remove(key); | |||
reloadUpdateKeyPage(newKey); | |||
} else { | |||
this.props.addGlobalSuccessMessage(translate('update_key.key_updated')); | |||
} | |||
}); | |||
}; | |||
handleChangeTab = event => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
const { tab } = event.currentTarget.dataset; | |||
this.setState({ tab }); | |||
this.props.closeAllGlobalMessages(); | |||
}; | |||
render() { | |||
const { component, modules } = this.props; | |||
const noModules = modules != null && modules.length === 0; | |||
const hasModules = modules != null && modules.length > 0; | |||
const { tab } = this.state; | |||
return ( | |||
<div className="page page-limited" id="project-key"> | |||
<Helmet title={translate('update_key.page')} /> | |||
<Header /> | |||
{modules == null && <i className="spinner" />} | |||
{noModules && ( | |||
<div> | |||
<UpdateForm component={component} onKeyChange={this.handleChangeKey} /> | |||
</div> | |||
)} | |||
{hasModules && ( | |||
<div className="boxed-group boxed-group-inner"> | |||
<div className="big-spacer-bottom"> | |||
<ul className="tabs"> | |||
<li> | |||
<a | |||
className={tab === 'bulk' ? 'selected' : ''} | |||
data-tab="bulk" | |||
href="#" | |||
id="update-key-tab-bulk" | |||
onClick={this.handleChangeTab}> | |||
{translate('update_key.bulk_update')} | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
className={tab === 'fine' ? 'selected' : ''} | |||
data-tab="fine" | |||
href="#" | |||
id="update-key-tab-fine" | |||
onClick={this.handleChangeTab}> | |||
{translate('update_key.fine_grained_key_update')} | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
{tab === 'bulk' && <BulkUpdate component={component} />} | |||
{tab === 'fine' && ( | |||
<FineGrainedUpdate | |||
component={component} | |||
modules={modules} | |||
onError={this.props.addGlobalErrorMessage} | |||
onKeyChange={this.handleChangeKey} | |||
onSuccess={this.props.closeAllGlobalMessages} | |||
/> | |||
)} | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
modules: getProjectAdminProjectModules(state, ownProps.location.query.id) | |||
}); | |||
export default connect( | |||
mapStateToProps, | |||
{ | |||
fetchProjectModules, | |||
changeKey, | |||
addGlobalErrorMessage, | |||
addGlobalSuccessMessage, | |||
closeAllGlobalMessages | |||
} | |||
)(Key); |
@@ -1,65 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import ConfirmButton, { ChildrenProps } from '../../../components/controls/ConfirmButton'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
interface Props { | |||
children: (props: ChildrenProps) => React.ReactNode; | |||
component: { key: string; name: string }; | |||
newKey: string | undefined; | |||
onConfirm: (oldKey: string, newKey: string) => Promise<void>; | |||
} | |||
export default class UpdateKeyConfirm extends React.PureComponent<Props> { | |||
handleConfirm = () => { | |||
return this.props.newKey | |||
? this.props.onConfirm(this.props.component.key, this.props.newKey) | |||
: Promise.reject(undefined); | |||
}; | |||
render() { | |||
const { children, component, newKey } = this.props; | |||
return ( | |||
<ConfirmButton | |||
confirmButtonText={translate('update_verb')} | |||
modalBody={ | |||
<> | |||
{translateWithParameters('update_key.are_you_sure_to_change_key', component.name)} | |||
<div className="spacer-top"> | |||
{translate('update_key.old_key')} | |||
{': '} | |||
<strong>{component.key}</strong> | |||
</div> | |||
<div className="spacer-top"> | |||
{translate('update_key.new_key')} | |||
{': '} | |||
<strong>{newKey}</strong> | |||
</div> | |||
</> | |||
} | |||
modalHeader={translate('update_key.page')} | |||
onConfirm={this.handleConfirm}> | |||
{children} | |||
</ConfirmButton> | |||
); | |||
} | |||
} |
@@ -1,75 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import UpdateKeyConfirm from './UpdateKeyConfirm'; | |||
import { Button } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: { key: string; name: string }; | |||
onKeyChange: (oldKey: string, newKey: string) => Promise<void>; | |||
} | |||
interface State { | |||
newKey?: string; | |||
} | |||
export default class UpdateKeyForm extends React.PureComponent<Props, State> { | |||
state: State = {}; | |||
handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
const newKey = event.currentTarget.value; | |||
this.setState({ newKey }); | |||
}; | |||
handleResetClick = () => { | |||
this.setState({ newKey: undefined }); | |||
}; | |||
render() { | |||
const { component } = this.props; | |||
const { newKey } = this.state; | |||
const value = newKey !== undefined ? newKey : component.key; | |||
const hasChanged = newKey !== undefined && newKey !== component.key; | |||
return ( | |||
<div className="js-fine-grained-update" data-key={component.key}> | |||
<input | |||
className="input-super-large big-spacer-right" | |||
onChange={this.handleInputChange} | |||
type="text" | |||
value={value} | |||
/> | |||
<UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}> | |||
{({ onClick }) => ( | |||
<Button disabled={!hasChanged} onClick={onClick}> | |||
{translate('update_verb')} | |||
</Button> | |||
)} | |||
</UpdateKeyConfirm> | |||
<Button className="spacer-left" disabled={!hasChanged} onClick={this.handleResetClick}> | |||
{translate('reset_verb')} | |||
</Button> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,24 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
export const reloadUpdateKeyPage = componentKey => { | |||
setTimeout(() => { | |||
window.location = window.baseUrl + '/project/key?id=' + encodeURIComponent(componentKey); | |||
}, 3000); | |||
}; |
@@ -1,51 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getTree, changeKey as changeKeyApi } from '../../../api/components'; | |||
import throwGlobalError from '../../../app/utils/throwGlobalError'; | |||
export const RECEIVE_PROJECT_MODULES = 'projectAdmin/RECEIVE_PROJECT_MODULES'; | |||
const receiveProjectModules = (projectKey, modules) => ({ | |||
type: RECEIVE_PROJECT_MODULES, | |||
projectKey, | |||
modules | |||
}); | |||
export const fetchProjectModules = projectKey => dispatch => { | |||
getTree({ component: projectKey, qualifiers: 'BRC', s: 'name', ps: 500 }).then( | |||
r => { | |||
dispatch(receiveProjectModules(projectKey, r.components)); | |||
}, | |||
() => {} | |||
); | |||
}; | |||
export const CHANGE_KEY = 'projectAdmin/CHANGE_KEY'; | |||
const changeKeyAction = (key, newKey) => ({ | |||
type: CHANGE_KEY, | |||
key, | |||
newKey | |||
}); | |||
export const changeKey = (key, newKey) => dispatch => { | |||
return changeKeyApi(key, newKey).then( | |||
() => dispatch(changeKeyAction(key, newKey)), | |||
throwGlobalError | |||
); | |||
}; |
@@ -1,45 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { keyBy, omit } from 'lodash'; | |||
import { RECEIVE_PROJECT_MODULES, CHANGE_KEY } from './actions'; | |||
const components = (state = {}, action = {}) => { | |||
if (action.type === RECEIVE_PROJECT_MODULES) { | |||
const newComponentsByKey = keyBy(action.modules, 'key'); | |||
return { ...state, ...newComponentsByKey }; | |||
} | |||
if (action.type === CHANGE_KEY) { | |||
const oldComponent = state[action.key]; | |||
if (oldComponent != null) { | |||
const newComponent = { ...oldComponent, key: action.newKey }; | |||
return { | |||
...omit(state, action.key), | |||
[action.newKey]: newComponent | |||
}; | |||
} | |||
} | |||
return state; | |||
}; | |||
export default components; | |||
export const getComponentByKey = (state, key) => state[key]; |
@@ -1,49 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { RECEIVE_PROJECT_MODULES, CHANGE_KEY } from './actions'; | |||
const modulesByProject = (state = {}, action = {}) => { | |||
if (action.type === RECEIVE_PROJECT_MODULES) { | |||
const moduleKeys = action.modules.map(module => module.key); | |||
return { ...state, [action.projectKey]: moduleKeys }; | |||
} | |||
if (action.type === CHANGE_KEY) { | |||
const newState = {}; | |||
Object.keys(state).forEach(projectKey => { | |||
const moduleKeys = state[projectKey]; | |||
const changedKeyIndex = moduleKeys.indexOf(action.key); | |||
if (changedKeyIndex !== -1) { | |||
const newModuleKeys = [...moduleKeys]; | |||
newModuleKeys.splice(changedKeyIndex, 1, action.newKey); | |||
newState[projectKey] = newModuleKeys; | |||
} else { | |||
newState[projectKey] = moduleKeys; | |||
} | |||
}); | |||
return newState; | |||
} | |||
return state; | |||
}; | |||
export default modulesByProject; | |||
export const getProjectModules = (state, projectKey) => state[projectKey]; |
@@ -1,40 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { combineReducers } from 'redux'; | |||
import components, { getComponentByKey as nextGetComponentByKey } from './components'; | |||
import modulesByProject, { getProjectModules as nextGetProjectModules } from './modulesByProject'; | |||
const rootReducer = combineReducers({ | |||
components, | |||
modulesByProject | |||
}); | |||
export default rootReducer; | |||
export const getComponentByKey = (state, componentKey) => | |||
nextGetComponentByKey(state.components, componentKey); | |||
export const getProjectModules = (state, projectKey) => { | |||
const moduleKeys = nextGetProjectModules(state.modulesByProject, projectKey); | |||
if (moduleKeys == null) { | |||
return null; | |||
} | |||
return moduleKeys.map(moduleKey => getComponentByKey(state, moduleKey)); | |||
}; |
@@ -0,0 +1,55 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import { withRouter, WithRouterProps } from 'react-router'; | |||
import UpdateForm from './UpdateForm'; | |||
import { changeKey } from '../../api/components'; | |||
import RecentHistory from '../../app/components/RecentHistory'; | |||
import { translate } from '../../helpers/l10n'; | |||
interface Props { | |||
component: Pick<T.Component, 'key' | 'name'>; | |||
} | |||
export class Key extends React.PureComponent<Props & WithRouterProps> { | |||
handleChangeKey = (newKey: string) => { | |||
return changeKey({ from: this.props.component.key, to: newKey }).then(() => { | |||
RecentHistory.remove(this.props.component.key); | |||
this.props.router.replace({ pathname: '/project/key', query: { id: newKey } }); | |||
}); | |||
}; | |||
render() { | |||
const { component } = this.props; | |||
return ( | |||
<div className="page page-limited" id="project-key"> | |||
<Helmet title={translate('update_key.page')} /> | |||
<header className="page-header"> | |||
<h1 className="page-title">{translate('update_key.page')}</h1> | |||
<div className="page-description">{translate('update_key.page.description')}</div> | |||
</header> | |||
<UpdateForm component={component} onKeyChange={this.handleChangeKey} /> | |||
</div> | |||
); | |||
} | |||
} | |||
export default withRouter(Key); |
@@ -18,13 +18,13 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import UpdateKeyConfirm from './UpdateKeyConfirm'; | |||
import { Button, SubmitButton } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Button, SubmitButton } from '../../components/ui/buttons'; | |||
import { translate, translateWithParameters } from '../../helpers/l10n'; | |||
import ConfirmButton from '../../components/controls/ConfirmButton'; | |||
interface Props { | |||
component: { key: string; name: string }; | |||
onKeyChange: (oldKey: string, newKey: string) => Promise<void>; | |||
component: Pick<T.Component, 'key' | 'name'>; | |||
onKeyChange: (newKey: string) => Promise<void>; | |||
} | |||
interface State { | |||
@@ -50,7 +50,26 @@ export default class UpdateForm extends React.PureComponent<Props, State> { | |||
const hasChanged = value !== component.key; | |||
return ( | |||
<UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}> | |||
<ConfirmButton | |||
confirmButtonText={translate('update_verb')} | |||
confirmData={newKey} | |||
modalBody={ | |||
<> | |||
{translateWithParameters('update_key.are_you_sure_to_change_key', component.name)} | |||
<div className="spacer-top"> | |||
{translate('update_key.old_key')} | |||
{': '} | |||
<strong>{component.key}</strong> | |||
</div> | |||
<div className="spacer-top"> | |||
{translate('update_key.new_key')} | |||
{': '} | |||
<strong>{newKey}</strong> | |||
</div> | |||
</> | |||
} | |||
modalHeader={translate('update_key.page')} | |||
onConfirm={this.props.onKeyChange}> | |||
{({ onFormSubmit }) => ( | |||
<form onSubmit={onFormSubmit}> | |||
<input | |||
@@ -79,7 +98,7 @@ export default class UpdateForm extends React.PureComponent<Props, State> { | |||
</div> | |||
</form> | |||
)} | |||
</UpdateKeyConfirm> | |||
</ConfirmButton> | |||
); | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { WithRouterProps } from 'react-router'; | |||
import { Key } from '../Key'; | |||
import { changeKey } from '../../../api/components'; | |||
jest.mock('../../../api/components', () => ({ | |||
changeKey: jest.fn().mockResolvedValue(undefined) | |||
})); | |||
it('should render and change key', async () => { | |||
const withRouterProps = { router: { replace: jest.fn() } as any } as WithRouterProps; | |||
const wrapper = shallow(<Key component={{ key: 'foo', name: 'Foo' }} {...withRouterProps} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('UpdateForm').prop<Function>('onKeyChange')('bar'); | |||
await new Promise(setImmediate); | |||
expect(changeKey).toBeCalledWith({ from: 'foo', to: 'bar' }); | |||
expect(withRouterProps.router.replace).toBeCalledWith({ | |||
pathname: '/project/key', | |||
query: { id: 'bar' } | |||
}); | |||
}); |
@@ -17,14 +17,25 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import * as React from 'react'; | |||
import { shallow, ShallowWrapper } from 'enzyme'; | |||
import UpdateForm from '../UpdateForm'; | |||
import { change, click } from '../../../helpers/testUtils'; | |||
export default function Header() { | |||
return ( | |||
<header className="page-header"> | |||
<h1 className="page-title">{translate('update_key.page')}</h1> | |||
<div className="page-description">{translate('update_key.page.description')}</div> | |||
</header> | |||
it('should render', () => { | |||
const wrapper = shallow( | |||
<UpdateForm component={{ key: 'foo', name: 'Foo' }} onKeyChange={jest.fn()} /> | |||
); | |||
expect(getInner(wrapper)).toMatchSnapshot(); | |||
change(getInner(wrapper).find('input'), 'bar'); | |||
expect(getInner(wrapper)).toMatchSnapshot(); | |||
click(getInner(wrapper).find('Button')); | |||
expect(getInner(wrapper)).toMatchSnapshot(); | |||
}); | |||
function getInner(wrapper: ShallowWrapper) { | |||
// TODO find a better way to do this | |||
return wrapper.dive().dive(); | |||
} |
@@ -0,0 +1,37 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render and change key 1`] = ` | |||
<div | |||
className="page page-limited" | |||
id="project-key" | |||
> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="update_key.page" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
update_key.page | |||
</h1> | |||
<div | |||
className="page-description" | |||
> | |||
update_key.page.description | |||
</div> | |||
</header> | |||
<UpdateForm | |||
component={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
onKeyChange={[Function]} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,112 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render 1`] = ` | |||
<Fragment> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
className="input-super-large" | |||
id="update-key-new-key" | |||
onChange={[Function]} | |||
placeholder="update_key.new_key" | |||
required={true} | |||
type="text" | |||
value="foo" | |||
/> | |||
<div | |||
className="spacer-top" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
id="update-key-submit" | |||
> | |||
update_verb | |||
</SubmitButton> | |||
<Button | |||
className="spacer-left" | |||
disabled={true} | |||
id="update-key-reset" | |||
onClick={[Function]} | |||
type="reset" | |||
> | |||
reset_verb | |||
</Button> | |||
</div> | |||
</form> | |||
</Fragment> | |||
`; | |||
exports[`should render 2`] = ` | |||
<Fragment> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
className="input-super-large" | |||
id="update-key-new-key" | |||
onChange={[Function]} | |||
placeholder="update_key.new_key" | |||
required={true} | |||
type="text" | |||
value="bar" | |||
/> | |||
<div | |||
className="spacer-top" | |||
> | |||
<SubmitButton | |||
disabled={false} | |||
id="update-key-submit" | |||
> | |||
update_verb | |||
</SubmitButton> | |||
<Button | |||
className="spacer-left" | |||
disabled={false} | |||
id="update-key-reset" | |||
onClick={[Function]} | |||
type="reset" | |||
> | |||
reset_verb | |||
</Button> | |||
</div> | |||
</form> | |||
</Fragment> | |||
`; | |||
exports[`should render 3`] = ` | |||
<Fragment> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
className="input-super-large" | |||
id="update-key-new-key" | |||
onChange={[Function]} | |||
placeholder="update_key.new_key" | |||
required={true} | |||
type="text" | |||
value="foo" | |||
/> | |||
<div | |||
className="spacer-top" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
id="update-key-submit" | |||
> | |||
update_verb | |||
</SubmitButton> | |||
<Button | |||
className="spacer-left" | |||
disabled={true} | |||
id="update-key-reset" | |||
onClick={[Function]} | |||
type="reset" | |||
> | |||
reset_verb | |||
</Button> | |||
</div> | |||
</form> | |||
</Fragment> | |||
`; |
@@ -91,7 +91,7 @@ export default class SelectListListContainer extends React.PureComponent<Props, | |||
onCheck={this.handleBulkChange} | |||
thirdState={selectedElements.length > 0 && elements.length !== selectedElements.length}> | |||
<span className="big-spacer-left"> | |||
{translate('update_key.bulk_update')} | |||
{translate('bulk_change')} | |||
<DeferredSpinner className="spacer-left" loading={this.state.loading} timeout={10} /> | |||
</span> | |||
</Checkbox> |
@@ -24,7 +24,6 @@ import languages, * as fromLanguages from './languages'; | |||
import metrics, * as fromMetrics from './metrics'; | |||
import organizations, * as fromOrganizations from './organizations'; | |||
import users, * as fromUsers from './users'; | |||
import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer'; | |||
import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer'; | |||
export type Store = { | |||
@@ -36,7 +35,6 @@ export type Store = { | |||
users: fromUsers.State; | |||
// apps | |||
projectAdminApp: any; | |||
settingsApp: any; | |||
}; | |||
@@ -49,7 +47,6 @@ export default combineReducers<Store>({ | |||
users, | |||
// apps | |||
projectAdminApp, | |||
settingsApp | |||
}); | |||
@@ -128,7 +125,3 @@ export function isSettingsAppLoading(state: Store, key: string) { | |||
export function getSettingsAppValidationMessage(state: Store, key: string) { | |||
return fromSettingsApp.getValidationMessage(state.settingsApp, key); | |||
} | |||
export function getProjectAdminProjectModules(state: Store, projectKey: string) { | |||
return fromProjectAdminApp.getProjectModules(state.projectAdminApp, projectKey); | |||
} |
@@ -514,7 +514,7 @@ project_quality_profiles.page.description=Choose which profile is associated wit | |||
project_quality_gate.page=Quality Gate | |||
project_quality_gate.page.description=Choose which quality gate is associated with this project. | |||
update_key.page=Update Key | |||
update_key.page.description=Edit the keys of a project and/or its modules. Key changes must be made here BEFORE analyzing the project with the new keys, otherwise the analysis will simply create another project with the new key, rather than updating the existing project. | |||
update_key.page.description=Edit the key of a project. Key changes must be made here BEFORE analyzing the project with the new keys, otherwise the analysis will simply create another project with the new key, rather than updating the existing project. | |||
deletion.page=Deletion | |||
project_deletion.page.description=Delete this project. The operation cannot be undone. | |||
portfolio_deletion.page.description=This portfolio and its sub-portfolios will be deleted. If this portfolio is referenced by other entities, it will be removed from them. Independent entities referenced by this portfolio, such as projects and other top-level portfolios will not be deleted. This operation cannot be undone. | |||
@@ -1075,27 +1075,12 @@ project_activity.custom_metric.covered_lines=Covered Lines | |||
#------------------------------------------------------------------------------ | |||
# | |||
# PROJECT / MODULE "UPDATE KEY" PAGE | |||
# PROJECT "UPDATE KEY" PAGE | |||
# | |||
#------------------------------------------------------------------------------ | |||
update_key.bulk_update=Bulk Update | |||
update_key.fine_grained_key_update=Fine-grained Update | |||
update_key.old_key=Old Key | |||
update_key.new_key=New Key | |||
update_key.key_updated=The key has successfully been updated for all required resources. | |||
update_key.key_updated.reload=The key has successfully been updated for all required resources. This page will be reloaded shortly. | |||
update_key.bulk_change_description=The bulk update allows to replace a part of the current key(s) by another string on the current project and all its submodules - if applicable. | |||
update_key.current_key_for_project_x_is_x=The key of the "{0}" project is currently "{1}". | |||
update_key.replace=Replace | |||
update_key.by=By | |||
update_key.replace_example=org.myCompany | |||
update_key.by_example=com.myNewCompany | |||
update_key.cant_update_because_duplicate_keys=The replacement of "{0}" by "{1}" is impossible as it would result in duplicate keys (in red below): | |||
update_key.keys_will_be_updated_as_follows=The resources will be updated as follows: | |||
update_key.duplicate_key=Duplicate Key | |||
update_key.no_key_to_update=No key contains the string to replace ("{0}"). | |||
update_key.are_you_sure_to_change_key=Are you sure you want to change key of "{0}", as well as all its modules and resources? | |||
update_key.see_results=See Results | |||
update_key.are_you_sure_to_change_key=Are you sure you want to change key of "{0}"? | |||
#------------------------------------------------------------------------------ |