From 862b59c20a2012f84acc4abf7531cb2fa2b1f3ec Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 30 May 2017 16:40:23 +0200 Subject: [PATCH] rework quality profiles modals (#2123) --- .../apps/quality-profiles/components/App.js | 2 + .../components/AppContainer.js | 7 +- .../components/CopyProfileForm.js | 134 ++++++++++++ .../components/DeleteProfileForm.js | 120 +++++++++++ .../components/ProfileActions.js | 124 +++++++---- .../components/ProfileContainer.js | 3 + .../components/RenameProfileForm.js | 134 ++++++++++++ .../details/ChangeParentForm.js | 141 +++++++++++++ .../ChangeProjectsForm.js} | 72 +++++-- .../details/ProfileDetails.js | 1 + .../quality-profiles/details/ProfileHeader.js | 2 + .../details/ProfileInheritance.js | 41 +++- .../details/ProfileProjects.js | 31 ++- .../home/CreateProfileForm.js | 197 ++++++++++++++++++ .../quality-profiles/home/HomeContainer.js | 1 + .../apps/quality-profiles/home/PageHeader.js | 96 +++++---- .../quality-profiles/home/ProfilesList.js | 2 + .../quality-profiles/home/ProfilesListRow.js | 2 + .../home/RestoreProfileForm.js | 159 ++++++++++++++ .../js/apps/quality-profiles/propTypes.js | 3 +- ...quality-profiles-change-profile-parent.hbs | 21 -- .../quality-profiles-change-projects.hbs | 12 -- .../quality-profiles-copy-profile.hbs | 16 -- .../quality-profiles-create-profile.hbs | 34 --- .../quality-profiles-delete-profile.hbs | 18 -- .../quality-profiles-rename-profile.hbs | 16 -- .../quality-profiles-restore-profile.hbs | 37 ---- .../views/ChangeParentView.js | 68 ------ .../quality-profiles/views/CopyProfileView.js | 52 ----- .../views/CreateProfileView.js | 95 --------- .../views/DeleteProfileView.js | 55 ----- .../views/RenameProfileView.js | 51 ----- .../views/RestoreProfileView.js | 57 ----- .../resources/org/sonar/l10n/core.properties | 1 + 34 files changed, 1148 insertions(+), 657 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js rename server/sonar-web/src/main/js/apps/quality-profiles/{views/ChangeProjectsView.js => details/ChangeProjectsForm.js} (59%) create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-profile-parent.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-projects.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-copy-profile.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-delete-profile.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-rename-profile.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js index 564996b4c5c..e60087cd0fc 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js @@ -30,6 +30,7 @@ type Props = { children: React.Element<*>, currentUser: { permissions: { global: Array } }, languages: Array<*>, + onRequestFail: Object => void, organization: { name: string, canAdmin?: boolean, key: string } | null }; @@ -108,6 +109,7 @@ export default class App extends React.PureComponent { languages: finalLanguages, exporters: this.state.exporters, updateProfiles: this.updateProfiles, + onRequestFail: this.props.onRequestFail, organization: organization ? organization.key : null, canAdmin }); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js index b5ff40a9ace..a9809ffe0dc 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js @@ -20,6 +20,7 @@ import { connect } from 'react-redux'; import App from './App'; import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer'; +import { onFail } from '../../../store/rootActions'; const mapStateToProps = (state, ownProps) => ({ currentUser: getCurrentUser(state), @@ -29,4 +30,8 @@ const mapStateToProps = (state, ownProps) => ({ : null }); -export default connect(mapStateToProps)(App); +const mapDispatchToProps = dispatch => ({ + onRequestFail: error => onFail(dispatch)(error) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js new file mode 100644 index 00000000000..3ced39bc3e3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import Modal from 'react-modal'; +import type { Profile } from '../propTypes'; +import { copyProfile } from '../../../api/quality-profiles'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +type Props = { + onClose: () => void, + onCopy: string => void, + onRequestFail: Object => void, + profile: Profile +}; + +type State = { + loading: boolean, + name: ?string +}; + +export default class CopyProfileForm extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { loading: false, name: null }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleNameChange = (event: { currentTarget: HTMLInputElement }) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + + const { name } = this.state; + + if (name != null) { + this.setState({ loading: true }); + copyProfile(this.props.profile.key, name).then( + profile => this.props.onCopy(profile.name), + error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + } + ); + } + }; + + render() { + const { profile } = this.props; + const header = translateWithParameters( + 'quality_profiles.copy_x_title', + profile.name, + profile.languageName + ); + const submitDisabled = + this.state.loading || !this.state.name || this.state.name === profile.name; + + return ( + + +
+
+

{header}

+
+
+
+ + +
+
+
+ {this.state.loading && } + + + {translate('cancel')} + +
+ + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js new file mode 100644 index 00000000000..03ca6da00cd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import Modal from 'react-modal'; +import type { Profile } from '../propTypes'; +import { deleteProfile } from '../../../api/quality-profiles'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +type Props = { + onClose: () => void, + onDelete: () => void, + onRequestFail: Object => void, + profile: Profile +}; + +type State = { + loading: boolean +}; + +export default class DeleteProfileForm extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { loading: false, name: null }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + this.setState({ loading: true }); + deleteProfile(this.props.profile.key).then(this.props.onDelete, error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + }); + }; + + render() { + const { profile } = this.props; + const header = translate('quality_profiles.delete_confirm_title'); + + return ( + + +
+
+

{header}

+
+
+
+ {profile.childrenCount > 0 + ?
+
+ {translate('quality_profiles.this_profile_has_descendants')} +
+

+ {translateWithParameters( + 'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants', + profile.name, + profile.languageName + )} +

+
+ :

+ {translateWithParameters( + 'quality_profiles.are_you_sure_want_delete_profile_x', + profile.name, + profile.languageName + )} +

} +
+
+ {this.state.loading && } + + + {translate('cancel')} + +
+ + + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js index ec8f51ee0a0..af02d8d9539 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js @@ -20,9 +20,9 @@ // @flow import React from 'react'; import { Link } from 'react-router'; -import RenameProfileView from '../views/RenameProfileView'; -import CopyProfileView from '../views/CopyProfileView'; -import DeleteProfileView from '../views/DeleteProfileView'; +import RenameProfileForm from './RenameProfileForm'; +import CopyProfileForm from './CopyProfileForm'; +import DeleteProfileForm from './DeleteProfileForm'; import { translate } from '../../../helpers/l10n'; import { getRulesUrl } from '../../../helpers/urls'; import { setDefaultProfile } from '../../../api/quality-profiles'; @@ -32,13 +32,21 @@ import type { Profile } from '../propTypes'; type Props = { canAdmin: boolean, fromList: boolean, + onRequestFail: Object => void, organization: ?string, profile: Profile, updateProfiles: () => Promise<*> }; +type State = { + copyFormOpen: boolean, + deleteFormOpen: boolean, + renameFormOpen: boolean +}; + export default class ProfileActions extends React.PureComponent { props: Props; + state: State; static defaultProps = { fromList: false @@ -48,32 +56,50 @@ export default class ProfileActions extends React.PureComponent { router: React.PropTypes.object }; - handleRenameClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - new RenameProfileView({ profile: this.props.profile }) - .on('done', (newName: string) => { - this.props.updateProfiles().then(() => { - if (!this.props.fromList) { - this.context.router.replace( - getProfilePath(newName, this.props.profile.language, this.props.organization) - ); - } - }); - }) - .render(); + constructor(props: Props) { + super(props); + this.state = { + copyFormOpen: false, + deleteFormOpen: false, + renameFormOpen: false + }; + } + + handleRenameClick = (event: Event) => { + event.preventDefault(); + this.setState({ renameFormOpen: true }); }; - handleCopyClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - new CopyProfileView({ profile: this.props.profile }) - .on('done', profile => { - this.props.updateProfiles().then(() => { - this.context.router.push( - getProfilePath(profile.name, profile.language, this.props.organization) - ); - }); - }) - .render(); + handleProfileRename = (name: string) => { + this.closeRenameForm(); + this.props.updateProfiles().then(() => { + if (!this.props.fromList) { + this.context.router.replace( + getProfilePath(name, this.props.profile.language, this.props.organization) + ); + } + }); + }; + + closeRenameForm = () => { + this.setState({ renameFormOpen: false }); + }; + + handleCopyClick = (event: Event) => { + event.preventDefault(); + this.setState({ copyFormOpen: true }); + }; + + handleProfileCopy = (name: string) => { + this.props.updateProfiles().then(() => { + this.context.router.push( + getProfilePath(name, this.props.profile.language, this.props.organization) + ); + }); + }; + + closeCopyForm = () => { + this.setState({ copyFormOpen: false }); }; handleSetDefaultClick = (e: SyntheticInputEvent) => { @@ -81,14 +107,18 @@ export default class ProfileActions extends React.PureComponent { setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles); }; - handleDeleteClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - new DeleteProfileView({ profile: this.props.profile }) - .on('done', () => { - this.context.router.replace(getProfilesPath(this.props.organization)); - this.props.updateProfiles(); - }) - .render(); + handleDeleteClick = (event: Event) => { + event.preventDefault(); + this.setState({ deleteFormOpen: true }); + }; + + handleProfileDelete = () => { + this.context.router.replace(getProfilesPath(this.props.organization)); + this.props.updateProfiles(); + }; + + closeDeleteForm = () => { + this.setState({ deleteFormOpen: false }); }; render() { @@ -152,6 +182,30 @@ export default class ProfileActions extends React.PureComponent { {translate('delete')} } + + {this.state.copyFormOpen && + } + + {this.state.deleteFormOpen && + } + + {this.state.renameFormOpen && + } ); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js index ed1d683ed1c..ceda3d91f5f 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js @@ -31,6 +31,7 @@ type Props = { pathname: string, query: { key?: string, language: string, name: string } }, + onRequestFail: Object => void, organization: ?string, profiles: Array, router: { replace: () => void }, @@ -78,6 +79,7 @@ export default class ProfileContainer extends React.PureComponent { } const child = React.cloneElement(this.props.children, { + onRequestFail: this.props.onRequestFail, organization, profile, profiles, @@ -89,6 +91,7 @@ export default class ProfileContainer extends React.PureComponent { void, + onRename: string => void, + onRequestFail: Object => void, + profile: Profile +}; + +type State = { + loading: boolean, + name: ?string +}; + +export default class RenameProfileForm extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { loading: false, name: null }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleNameChange = (event: { currentTarget: HTMLInputElement }) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + + const { name } = this.state; + + if (name != null) { + this.setState({ loading: true }); + renameProfile(this.props.profile.key, name).then( + () => this.props.onRename(name), + error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + } + ); + } + }; + + render() { + const { profile } = this.props; + const header = translateWithParameters( + 'quality_profiles.rename_x_title', + profile.name, + profile.languageName + ); + const submitDisabled = + this.state.loading || !this.state.name || this.state.name === profile.name; + + return ( + + +
+
+

{header}

+
+
+
+ + +
+
+
+ {this.state.loading && } + + + {translate('cancel')} + +
+ + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js new file mode 100644 index 00000000000..5f0aeb88690 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import Modal from 'react-modal'; +import Select from 'react-select'; +import { sortBy } from 'lodash'; +import { changeProfileParent } from '../../../api/quality-profiles'; +import { translate } from '../../../helpers/l10n'; +import type { Profile } from '../propTypes'; + +type Props = { + onChange: () => void, + onClose: () => void, + onRequestFail: Object => void, + profile: Profile, + profiles: Array +}; + +type State = { + loading: boolean, + selected: ?string +}; + +export default class ChangeParentForm extends React.PureComponent { + mounted: boolean; + props: Props; + state: State = { + loading: false, + selected: null + }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleSelectChange = (option: { value: string }) => { + this.setState({ selected: option.value }); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + + const parent = this.state.selected; + + if (parent != null) { + this.setState({ loading: true }); + changeProfileParent(this.props.profile.key, parent).then(this.props.onChange).catch(error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + }); + } + }; + + render() { + const { profiles } = this.props; + + const options = [ + { label: translate('none'), value: '' }, + ...sortBy(profiles, 'name').map(profile => ({ label: profile.name, value: profile.key })) + ]; + + const submitDisabled = + this.state.loading || + this.state.selected == null || + this.state.selected === this.props.profile.parentKey; + + return ( + + +
+
+

{translate('quality_profiles.change_parent')}

+
+
+
+ + +
+
+ + +

+ {translate('quality_profiles.optional_configuration_file')} +

+
+ ))} +
} + +
+ {this.state.loading && } + {!this.state.preloading && + } + + {translate('cancel')} + +
+ + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js index df44389a9c6..70389154ed4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js @@ -28,6 +28,7 @@ type Props = { canAdmin: boolean, languages: Array<{ key: string, name: string }>, location: { query: { [string]: string } }, + onRequestFail: Object => void, organization?: string, profiles: Array, updateProfiles: () => Promise<*> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js index df6191a8300..6a276e243ee 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js @@ -19,76 +19,63 @@ */ // @flow import React from 'react'; -import CreateProfileView from '../views/CreateProfileView'; -import RestoreProfileView from '../views/RestoreProfileView'; +import CreateProfileForm from './CreateProfileForm'; +import RestoreProfileForm from './RestoreProfileForm'; import RestoreBuiltInProfilesView from '../views/RestoreBuiltInProfilesView'; +import type { Profile } from '../propTypes'; import { getProfilePath } from '../utils'; import { translate } from '../../../helpers/l10n'; -import { getImporters } from '../../../api/quality-profiles'; type Props = { canAdmin: boolean, languages: Array<{ key: string, name: string }>, + onRequestFail: Object => void, organization: ?string, updateProfiles: () => Promise<*> }; +type State = { + createFormOpen: boolean, + restoreFormOpen: boolean +}; + export default class PageHeader extends React.PureComponent { - mounted: boolean; props: Props; static contextTypes = { router: React.PropTypes.object }; - state = {}; + state: State = { + createFormOpen: false, + restoreFormOpen: false + }; - componentDidMount() { - this.mounted = true; - } + handleCreateClick = (event: Event & { currentTarget: HTMLButtonElement }) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState({ createFormOpen: true }); + }; - componentWillUnmount() { - this.mounted = false; - } + handleCreate = (profile: Profile) => { + this.props.updateProfiles().then(() => { + this.context.router.push( + getProfilePath(profile.name, profile.language, this.props.organization) + ); + }); + }; - retrieveImporters() { - if (this.state.importers) { - return Promise.resolve(this.state.importers); - } else { - return getImporters().then(importers => { - this.setState({ importers }); - return importers; - }); - } - } + closeCreateForm = () => { + this.setState({ createFormOpen: false }); + }; - handleCreateClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - e.target.blur(); - this.retrieveImporters().then(importers => { - new CreateProfileView({ - languages: this.props.languages, - organization: this.props.organization, - importers - }) - .on('done', profile => { - this.props.updateProfiles().then(() => { - this.context.router.push( - getProfilePath(profile.name, profile.language, this.props.organization) - ); - }); - }) - .render(); - }); + handleRestoreClick = (event: Event) => { + event.preventDefault(); + this.setState({ restoreFormOpen: true }); }; - handleRestoreClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - new RestoreProfileView({ - organization: this.props.organization - }) - .on('done', this.props.updateProfiles) - .render(); + closeRestoreForm = () => { + this.setState({ restoreFormOpen: false }); }; handleRestoreBuiltIn = (e: SyntheticInputEvent) => { @@ -139,6 +126,23 @@ export default class PageHeader extends React.PureComponent {
{translate('quality_profiles.intro2')}
+ + {this.state.restoreFormOpen && + } + + {this.state.createFormOpen && + } ); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js index c2b0681f686..dbcf7840677 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js @@ -30,6 +30,7 @@ type Props = { canAdmin: boolean, languages: Array<{ key: string, name: string }>, location: { query: { [string]: string } }, + onRequestFail: Object => void, organization: ?string, profiles: Array, updateProfiles: () => Promise<*> @@ -43,6 +44,7 @@ export default class ProfilesList extends React.PureComponent { void, organization: ?string, profile: Profile, updateProfiles: () => Promise<*> @@ -160,6 +161,7 @@ export default class ProfilesListRow extends React.PureComponent { void, + onRequestFail: Object => void, + onRestore: Function, + organization: ?string +}; + +type State = { + loading: boolean, + profile?: { name: string }, + ruleFailures?: number, + ruleSuccesses?: number +}; + +export default class RestoreProfileForm extends React.PureComponent { + form: HTMLFormElement; + mounted: boolean; + props: Props; + state: State = { loading: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + + this.setState({ loading: true }); + + const data = new FormData(this.form); + if (this.props.organization) { + data.append('organization', this.props.organization); + } + + restoreQualityProfile(data).then( + response => { + if (this.mounted) { + this.setState({ + loading: false, + profile: response.profile, + ruleFailures: response.ruleFailures, + ruleSuccesses: response.ruleSuccesses + }); + } + this.props.onRestore(); + }, + error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + } + ); + }; + + render() { + const header = translate('quality_profiles.restore_profile'); + + const { loading, profile, ruleFailures, ruleSuccesses } = this.state; + + return ( + + +
(this.form = node)}> + +
+

{header}

+
+ +
+ {profile != null && ruleSuccesses != null + ? ruleFailures + ?
+ {translateWithParameters( + 'quality_profiles.restore_profile.warning', + profile.name, + ruleSuccesses, + ruleFailures + )} +
+ :
+ {translateWithParameters( + 'quality_profiles.restore_profile.success', + profile.name, + ruleSuccesses + )} +
+ :
+ + +
} +
+ + {ruleSuccesses == null + ?
+ {loading && } + + + {translate('cancel')} + +
+ : } + + + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js b/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js index f8438011916..92af7067b7b 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js @@ -37,7 +37,8 @@ export type Profile = { userUpdatedAt?: string, lastUsed?: string, rulesUpdatedAt: string, - depth: number + depth: number, + childrenCount: number }; export type Exporter = { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-profile-parent.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-profile-parent.hbs deleted file mode 100644 index 94c7e29c60a..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-profile-parent.hbs +++ /dev/null @@ -1,21 +0,0 @@ -
- - - -
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-projects.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-projects.hbs deleted file mode 100644 index 09e591327e2..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-projects.hbs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-copy-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-copy-profile.hbs deleted file mode 100644 index 6cc034f1209..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-copy-profile.hbs +++ /dev/null @@ -1,16 +0,0 @@ -
- - - -
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs deleted file mode 100644 index acdc7f93805..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs +++ /dev/null @@ -1,34 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-delete-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-delete-profile.hbs deleted file mode 100644 index 17438526340..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-delete-profile.hbs +++ /dev/null @@ -1,18 +0,0 @@ -
- - - -
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-rename-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-rename-profile.hbs deleted file mode 100644 index 3da132f8f4f..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-rename-profile.hbs +++ /dev/null @@ -1,16 +0,0 @@ -
- - - -
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs deleted file mode 100644 index 7c22c83aab5..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs +++ /dev/null @@ -1,37 +0,0 @@ -
- - - - - -
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js deleted file mode 100644 index 912aecb10cb..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import ModalFormView from '../../../components/common/modal-form'; -import Template from '../templates/quality-profiles-change-profile-parent.hbs'; -import { changeProfileParent } from '../../../api/quality-profiles'; - -export default ModalFormView.extend({ - template: Template, - - onRender() { - ModalFormView.prototype.onRender.apply(this, arguments); - this.$('select').select2({ - allowClear: false, - width: '250px', - minimumResultsForSearch: 50 - }); - }, - - onFormSubmit() { - ModalFormView.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - let parent = this.$('#change-profile-parent').val(); - if (parent === '#none') { - parent = ''; - } - changeProfileParent(this.options.profile.key, parent) - .then(() => { - this.destroy(); - this.trigger('done'); - }) - .catch(e => { - if (e.response.status === 400) { - this.enableForm(); - e.response.json().then(r => this.showErrors(r.errors, r.warnings)); - } - }); - }, - - serializeData() { - const { profile } = this.options; - const profiles = this.options.profiles.filter( - p => p !== profile && p.language === profile.language - ); - return { ...profile, profiles }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js deleted file mode 100644 index 711946b8bfa..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import ModalFormView from '../../../components/common/modal-form'; -import Template from '../templates/quality-profiles-copy-profile.hbs'; -import { copyProfile } from '../../../api/quality-profiles'; - -export default ModalFormView.extend({ - template: Template, - - onFormSubmit() { - ModalFormView.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - const name = this.$('#copy-profile-name').val(); - copyProfile(this.options.profile.key, name) - .then(profile => { - this.destroy(); - this.trigger('done', profile); - }) - .catch(e => { - if (e.response.status === 400) { - this.enableForm(); - e.response.json().then(r => this.showErrors(r.errors, r.warnings)); - } - }); - }, - - serializeData() { - return this.options.profile; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js deleted file mode 100644 index fb74811ec69..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import $ from 'jquery'; -import ModalFormView from '../../../components/common/modal-form'; -import Template from '../templates/quality-profiles-create-profile.hbs'; -import { createQualityProfile } from '../../../api/quality-profiles'; - -export default ModalFormView.extend({ - template: Template, - - events() { - return { - ...ModalFormView.prototype.events.apply(this, arguments), - 'change #create-profile-language': 'onLanguageChange' - }; - }, - - onFormSubmit() { - ModalFormView.prototype.onFormSubmit.apply(this, arguments); - - const form = this.$('form')[0]; - const data = new FormData(form); - - createQualityProfile(data) - .then(r => { - this.trigger('done', r.profile); - this.destroy(); - }) - .catch(e => { - e.response.json().then(r => this.showErrors(r.errors, r.warnings)); - }); - }, - - onRender() { - ModalFormView.prototype.onRender.apply(this, arguments); - this.$('select').select2({ - width: '250px', - minimumResultsForSearch: 50 - }); - this.onLanguageChange(); - }, - - onLanguageChange() { - const that = this; - const language = this.$('#create-profile-language').val(); - const importers = this.getImportersForLanguages(language); - this.$('.js-importer').each(function() { - that.emptyInput($(this)); - $(this).addClass('hidden'); - }); - importers.forEach(importer => { - that.$(`.js-importer[data-key="${importer.key}"]`).removeClass('hidden'); - }); - }, - - emptyInput(e) { - e.wrap('
').closest('form').get(0).reset(); - e.unwrap(); - }, - - getImportersForLanguages(language) { - if (language != null) { - return this.options.importers.filter(importer => importer.languages.indexOf(language) !== -1); - } else { - return []; - } - }, - - serializeData() { - return { - ...ModalFormView.prototype.serializeData.apply(this, arguments), - languages: this.options.languages, - importers: this.options.importers, - organization: this.options.organization - }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js deleted file mode 100644 index ec4c5f878a4..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import ModalFormView from '../../../components/common/modal-form'; -import Template from '../templates/quality-profiles-delete-profile.hbs'; -import { deleteProfile } from '../../../api/quality-profiles'; - -export default ModalFormView.extend({ - template: Template, - - modelEvents: { - destroy: 'destroy' - }, - - onFormSubmit() { - ModalFormView.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - deleteProfile(this.options.profile.key) - .then(() => { - this.destroy(); - this.trigger('done'); - }) - .catch(e => { - if (e.response.status === 400) { - this.enableForm(); - e.response.json().then(r => this.showErrors(r.errors, r.warnings)); - } - }); - }, - - serializeData() { - return this.options.profile; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js deleted file mode 100644 index 57e3e5c5e67..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import ModalFormView from '../../../components/common/modal-form'; -import Template from '../templates/quality-profiles-rename-profile.hbs'; -import { renameProfile } from '../../../api/quality-profiles'; - -export default ModalFormView.extend({ - template: Template, - - onFormSubmit() { - ModalFormView.prototype.onFormSubmit.apply(this, arguments); - this.sendRequest(); - }, - - sendRequest() { - const name = this.$('#rename-profile-name').val(); - renameProfile(this.options.profile.key, name) - .then(() => { - this.destroy(); - this.trigger('done', name); - }) - .catch(e => { - if (e.response.status === 400) { - this.enableForm(); - e.response.json().then(r => this.showErrors(r.errors, r.warnings)); - } - }); - }, - - serializeData() { - return this.options.profile; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js deleted file mode 100644 index d2393e5053d..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import ModalFormView from '../../../components/common/modal-form'; -import Template from '../templates/quality-profiles-restore-profile.hbs'; -import { restoreQualityProfile } from '../../../api/quality-profiles'; - -export default ModalFormView.extend({ - template: Template, - - onFormSubmit(e) { - ModalFormView.prototype.onFormSubmit.apply(this, arguments); - const data = new FormData(e.currentTarget); - - this.disableForm(); - - restoreQualityProfile(data) - .then(r => { - this.profile = r.profile; - this.ruleSuccesses = r.ruleSuccesses; - this.ruleFailures = r.ruleFailures; - this.render(); - this.trigger('done'); - }) - .catch(e => { - this.enableForm(); - e.response.json().then(r => this.showErrors(r.errors, r.warnings)); - }); - }, - - serializeData() { - return { - ...ModalFormView.prototype.serializeData.apply(this, arguments), - organization: this.options.organization, - profile: this.profile, - ruleSuccesses: this.ruleSuccesses, - ruleFailures: this.ruleFailures - }; - } -}); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index aba765f0df2..f6e665b65ee 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1394,6 +1394,7 @@ quality_profiles.no_projects_associated_to_profile=No projects are explicitly as quality_profiles.projects_warning=List of projects explicitly associated to this Quality profile : quality_profiles.including_x_overriding.suffix=, incl. {0} overriding quality_profiles.set_parent=Set parent +quality_profiles.parent=Parent: quality_profiles.inherit_rules_from_profile=Inherit rules configuration from the profile quality_profiles.no_version=no version quality_profiles.last_version_x_with_date=last version {0} ({1}) -- 2.39.5