]> source.dussan.org Git - sonarqube.git/commitdiff
rework quality profiles modals (#2123)
authorStas Vilchik <stas-vilchik@users.noreply.github.com>
Tue, 30 May 2017 14:40:23 +0000 (16:40 +0200)
committerGitHub <noreply@github.com>
Tue, 30 May 2017 14:40:23 +0000 (16:40 +0200)
35 files changed:
server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js
server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js
server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-profile-parent.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-change-projects.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-copy-profile.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-delete-profile.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-rename-profile.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js [deleted file]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 564996b4c5ce8bf79cd1d14f14ce062ffad2fbcb..e60087cd0fc4475fbfbb8d6f0073f5125745e8c2 100644 (file)
@@ -30,6 +30,7 @@ type Props = {
   children: React.Element<*>,
   currentUser: { permissions: { global: Array<string> } },
   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
     });
index b5ff40a9ace86c3a4629f4cd310a205422638380..a9809ffe0dc46c737a7995e866e1c4d1ab0fdcda 100644 (file)
@@ -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 (file)
index 0000000..3ced39b
--- /dev/null
@@ -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 (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <form id="copy-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="copy-profile-name">
+                {translate('quality_profiles.copy_new_name')}<em className="mandatory">*</em>
+              </label>
+              <input
+                autoFocus={true}
+                id="copy-profile-name"
+                maxLength="100"
+                name="name"
+                onChange={this.handleNameChange}
+                required={true}
+                size="50"
+                type="text"
+                value={this.state.name != null ? this.state.name : profile.name}
+              />
+            </div>
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} id="copy-profile-submit">
+              {translate('copy')}
+            </button>
+            <a href="#" id="copy-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+
+      </Modal>
+    );
+  }
+}
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 (file)
index 0000000..03ca6da
--- /dev/null
@@ -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 (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <form id="delete-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+          <div className="modal-body">
+            <div className="js-modal-messages" />
+            {profile.childrenCount > 0
+              ? <div>
+                  <div className="alert alert-warning">
+                    {translate('quality_profiles.this_profile_has_descendants')}
+                  </div>
+                  <p>
+                    {translateWithParameters(
+                      'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants',
+                      profile.name,
+                      profile.languageName
+                    )}
+                  </p>
+                </div>
+              : <p>
+                  {translateWithParameters(
+                    'quality_profiles.are_you_sure_want_delete_profile_x',
+                    profile.name,
+                    profile.languageName
+                  )}
+                </p>}
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button className="button-red" disabled={this.state.loading} id="delete-profile-submit">
+              {translate('delete')}
+            </button>
+            <a href="#" id="delete-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+
+      </Modal>
+    );
+  }
+}
index ec8f51ee0a0e5e3b2d15d9e4f411bea9487551e0..af02d8d95396c5341b66f2ee22b1c592e32f60c9 100644 (file)
@@ -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')}
             </a>
           </li>}
+
+        {this.state.copyFormOpen &&
+          <CopyProfileForm
+            onClose={this.closeCopyForm}
+            onCopy={this.handleProfileCopy}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+          />}
+
+        {this.state.deleteFormOpen &&
+          <DeleteProfileForm
+            onClose={this.closeDeleteForm}
+            onDelete={this.handleProfileDelete}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+          />}
+
+        {this.state.renameFormOpen &&
+          <RenameProfileForm
+            onClose={this.closeRenameForm}
+            onRename={this.handleProfileRename}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+          />}
       </ul>
     );
   }
index ed1d683ed1c0fbd036adf7983e71243ee6fecb1c..ceda3d91f5fd09a571065271597ec2c3a6a818f0 100644 (file)
@@ -31,6 +31,7 @@ type Props = {
     pathname: string,
     query: { key?: string, language: string, name: string }
   },
+  onRequestFail: Object => void,
   organization: ?string,
   profiles: Array<Profile>,
   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 {
         <Helmet title={profile.name} />
         <ProfileHeader
           canAdmin={this.props.canAdmin}
+          onRequestFail={this.props.onRequestFail}
           organization={organization}
           profile={profile}
           updateProfiles={this.props.updateProfiles}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js
new file mode 100644 (file)
index 0000000..9a2af87
--- /dev/null
@@ -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 { renameProfile } from '../../../api/quality-profiles';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+type Props = {
+  onClose: () => 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 (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <form id="rename-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="rename-profile-name">
+                {translate('quality_profiles.new_name')}<em className="mandatory">*</em>
+              </label>
+              <input
+                autoFocus={true}
+                id="rename-profile-name"
+                maxLength="100"
+                name="name"
+                onChange={this.handleNameChange}
+                required={true}
+                size="50"
+                type="text"
+                value={this.state.name != null ? this.state.name : profile.name}
+              />
+            </div>
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} id="rename-profile-submit">
+              {translate('rename')}
+            </button>
+            <a href="#" id="rename-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+
+      </Modal>
+    );
+  }
+}
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 (file)
index 0000000..5f0aeb8
--- /dev/null
@@ -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<Profile>
+};
+
+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 (
+      <Modal
+        isOpen={true}
+        contentLabel={translate('quality_profiles.change_parent')}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <form id="change-profile-parent-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>{translate('quality_profiles.change_parent')}</h2>
+          </div>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="change-profile-parent">
+                {translate('quality_profiles.parent')}: <em className="mandatory">*</em>
+              </label>
+              <Select
+                clearable={false}
+                id="change-profile-parent"
+                name="parentKey"
+                onChange={this.handleSelectChange}
+                options={options}
+                value={
+                  this.state.selected != null
+                    ? this.state.selected
+                    : this.props.profile.parentKey || ''
+                }
+              />
+            </div>
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} id="change-profile-parent-submit">
+              {translate('change_verb')}
+            </button>
+            <a href="#" id="change-profile-parent-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js
new file mode 100644 (file)
index 0000000..6f46189
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * 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 escapeHtml from 'escape-html';
+import type { Profile } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+  onClose: () => void,
+  organization: ?string,
+  profile: Profile
+};
+
+export default class ChangeProjectsForm extends React.PureComponent {
+  container: HTMLElement;
+  props: Props;
+
+  componentDidMount() {
+    this.renderSelectList();
+  }
+
+  handleCloseClick = (event: Event) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  renderSelectList() {
+    const { key } = this.props.profile;
+
+    const searchUrl =
+      window.baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key);
+
+    new window.SelectList({
+      searchUrl,
+      el: this.container,
+      width: '100%',
+      readOnly: false,
+      focusSearch: false,
+      dangerouslyUnescapedHtmlFormat: item => escapeHtml(item.name),
+      selectUrl: window.baseUrl + '/api/qualityprofiles/add_project',
+      deselectUrl: window.baseUrl + '/api/qualityprofiles/remove_project',
+      extra: { profileKey: key },
+      selectParameter: 'projectUuid',
+      selectParameterValue: 'uuid',
+      labels: {
+        selected: translate('quality_gates.projects.with'),
+        deselected: translate('quality_gates.projects.without'),
+        all: translate('quality_gates.projects.all'),
+        noResults: translate('quality_gates.projects.noResults')
+      },
+      tooltips: {
+        select: translate('quality_profiles.projects.select_hint'),
+        deselect: translate('quality_profiles.projects.deselect_hint')
+      }
+    });
+  }
+
+  render() {
+    const header = translate('projects');
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <div className="modal-head">
+          <h2>{header}</h2>
+        </div>
+
+        <div className="modal-body">
+          <div id="profile-projects" ref={node => (this.container = node)} />
+        </div>
+
+        <div className="modal-foot">
+          <a href="#" onClick={this.handleCloseClick}>{translate('close')}</a>
+        </div>
+
+      </Modal>
+    );
+  }
+}
index 7b7a296536c44aa1af2a098474f342b6ff3ec02f..9417f4cf0c90d2d37455db16166dd1485628a137 100644 (file)
@@ -28,6 +28,7 @@ import type { Profile, Exporter } from '../propTypes';
 type Props = {
   canAdmin: boolean,
   exporters: Array<Exporter>,
+  onRequestFail: Object => void,
   organization: ?string,
   profile: Profile,
   profiles: Array<Profile>,
index 5c66dfcfcacb7f750c1acb27da72471eb6723c93..02e1b508ff2d08594f2a3366c4f49705038e3307 100644 (file)
@@ -34,6 +34,7 @@ import type { Profile } from '../propTypes';
 
 type Props = {
   canAdmin: boolean,
+  onRequestFail: Object => void,
   organization: ?string,
   profile: Profile,
   updateProfiles: () => Promise<*>
@@ -136,6 +137,7 @@ export default class ProfileHeader extends React.PureComponent {
                 </button>
                 <ProfileActions
                   canAdmin={this.props.canAdmin}
+                  onRequestFail={this.props.onRequestFail}
                   organization={organization}
                   profile={profile}
                   updateProfiles={this.props.updateProfiles}
index cc8a8a9f535a146e3cd7041190cd7e18884a1ac7..ba11b5a9e5d3576a0e1c1848a1d6ea8e5563a927 100644 (file)
 import React from 'react';
 import classNames from 'classnames';
 import ProfileInheritanceBox from './ProfileInheritanceBox';
-import ChangeParentView from '../views/ChangeParentView';
+import ChangeParentForm from './ChangeParentForm';
 import { translate } from '../../../helpers/l10n';
 import { getProfileInheritance } from '../../../api/quality-profiles';
 import type { Profile } from '../propTypes';
 
 type Props = {
   canAdmin: boolean,
+  onRequestFail: Object => void,
   organization: ?string,
   profile: Profile,
   profiles: Array<Profile>,
@@ -45,6 +46,7 @@ type ProfileInheritanceDetails = {
 type State = {
   ancestors?: Array<ProfileInheritanceDetails>,
   children?: Array<ProfileInheritanceDetails>,
+  formOpen: boolean,
   loading: boolean,
   profile?: ProfileInheritanceDetails
 };
@@ -53,6 +55,7 @@ export default class ProfileInheritance extends React.PureComponent {
   mounted: boolean;
   props: Props;
   state: State = {
+    formOpen: false,
     loading: true
   };
 
@@ -85,14 +88,23 @@ export default class ProfileInheritance extends React.PureComponent {
     });
   }
 
-  handleChangeParent = (e: SyntheticInputEvent) => {
-    e.preventDefault();
-    new ChangeParentView({ profile: this.props.profile, profiles: this.props.profiles })
-      .on('done', () => this.props.updateProfiles())
-      .render();
+  handleChangeParentClick = (event: Event) => {
+    event.preventDefault();
+    this.setState({ formOpen: true });
+  };
+
+  closeForm = () => {
+    this.setState({ formOpen: false });
+  };
+
+  handleParentChange = () => {
+    this.props.updateProfiles();
+    this.closeForm();
   };
 
   render() {
+    const { profile, profiles } = this.props;
+
     const highlightCurrent =
       !this.state.loading &&
       this.state.ancestors != null &&
@@ -109,7 +121,7 @@ export default class ProfileInheritance extends React.PureComponent {
             {translate('quality_profiles.profile_inheritance')}
           </h2>
           {this.props.canAdmin &&
-            <button className="pull-right js-change-parent" onClick={this.handleChangeParent}>
+            <button className="pull-right js-change-parent" onClick={this.handleChangeParentClick}>
               {translate('quality_profiles.change_parent')}
             </button>}
         </header>
@@ -123,7 +135,7 @@ export default class ProfileInheritance extends React.PureComponent {
                     className="js-inheritance-ancestor"
                     depth={index}
                     key={ancestor.key}
-                    language={this.props.profile.language}
+                    language={profile.language}
                     organization={this.props.organization}
                     profile={ancestor}
                   />
@@ -133,7 +145,7 @@ export default class ProfileInheritance extends React.PureComponent {
                 className={currentClassName}
                 depth={this.state.ancestors ? this.state.ancestors.length : 0}
                 displayLink={false}
-                language={this.props.profile.language}
+                language={profile.language}
                 organization={this.props.organization}
                 profile={this.state.profile}
               />
@@ -144,13 +156,22 @@ export default class ProfileInheritance extends React.PureComponent {
                     className="js-inheritance-child"
                     depth={this.state.ancestors ? this.state.ancestors.length + 1 : 0}
                     key={child.key}
-                    language={this.props.profile.language}
+                    language={profile.language}
                     organization={this.props.organization}
                     profile={child}
                   />
                 ))}
             </tbody>
           </table>}
+
+        {this.state.formOpen &&
+          <ChangeParentForm
+            onChange={this.handleParentChange}
+            onClose={this.closeForm}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+            profiles={profiles.filter(p => p !== profile && p.language === profile.language)}
+          />}
       </div>
     );
   }
index 171d4ab8e0d508cec343baf3b2506dbebd8994da..e3b08402dd653662593af7730cfaabaae604e529 100644 (file)
@@ -20,7 +20,7 @@
 // @flow
 import React from 'react';
 import { Link } from 'react-router';
-import ChangeProjectsView from '../views/ChangeProjectsView';
+import ChangeProjectsForm from './ChangeProjectsForm';
 import QualifierIcon from '../../../components/shared/QualifierIcon';
 import { getProfileProjects } from '../../../api/quality-profiles';
 import { translate } from '../../../helpers/l10n';
@@ -34,6 +34,7 @@ type Props = {
 };
 
 type State = {
+  formOpen: boolean,
   loading: boolean,
   more?: boolean,
   projects: ?Array<*>
@@ -43,6 +44,7 @@ export default class ProfileProjects extends React.PureComponent {
   mounted: boolean;
   props: Props;
   state: State = {
+    formOpen: false,
     loading: true,
     projects: null
   };
@@ -79,15 +81,15 @@ export default class ProfileProjects extends React.PureComponent {
     });
   }
 
-  handleChange(e: SyntheticInputEvent) {
-    e.preventDefault();
-    e.target.blur();
-    new ChangeProjectsView({
-      loadProjects: this.props.updateProfiles,
-      organization: this.props.organization,
-      profile: this.props.profile
-    }).render();
-  }
+  handleChangeClick = (event: Event) => {
+    event.preventDefault();
+    this.setState({ formOpen: true });
+  };
+
+  closeForm = () => {
+    this.setState({ formOpen: false });
+    this.props.updateProfiles();
+  };
 
   renderDefault() {
     return (
@@ -143,13 +145,20 @@ export default class ProfileProjects extends React.PureComponent {
           {this.props.canAdmin &&
             !this.props.profile.isDefault &&
             <div className="pull-right">
-              <button className="js-change-projects" onClick={this.handleChange.bind(this)}>
+              <button className="js-change-projects" onClick={this.handleChangeClick}>
                 {translate('quality_profiles.change_projects')}
               </button>
             </div>}
         </header>
 
         {this.props.profile.isDefault ? this.renderDefault() : this.renderProjects()}
+
+        {this.state.formOpen &&
+          <ChangeProjectsForm
+            onClose={this.closeForm}
+            organization={this.props.organization}
+            profile={this.props.profile}
+          />}
       </div>
     );
   }
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js
new file mode 100644 (file)
index 0000000..4219dbd
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * 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 { getImporters, createQualityProfile } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+  languages: Array<{ key: string, name: string }>,
+  onClose: () => void,
+  onCreate: Function,
+  onRequestFail: Object => void,
+  organization: ?string
+};
+
+type State = {
+  importers: Array<{ key: string, languages: Array<string>, name: string }>,
+  language?: string,
+  loading: boolean,
+  name: string,
+  preloading: boolean
+};
+
+export default class CreateProfileForm extends React.PureComponent {
+  form: HTMLFormElement;
+  mounted: boolean;
+  props: Props;
+  state: State = { importers: [], loading: false, name: '', preloading: true };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchImporters();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchImporters() {
+    getImporters().then(importers => {
+      if (this.mounted) {
+        this.setState({ importers, preloading: false });
+      }
+    });
+  }
+
+  handleCancelClick = (event: Event) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleNameChange = (event: { currentTarget: HTMLInputElement }) => {
+    this.setState({ name: event.currentTarget.value });
+  };
+
+  handleLanguageChange = (option: { value: string }) => {
+    this.setState({ language: option.value });
+  };
+
+  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);
+    }
+
+    createQualityProfile(data).then(
+      response => this.props.onCreate(response.profile),
+      error => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+        this.props.onRequestFail(error);
+      }
+    );
+  };
+
+  render() {
+    const header = translate('quality_profiles.new_profile');
+
+    const languages = sortBy(this.props.languages, 'name');
+    const selectedLanguage = this.state.language || languages[0].key;
+    const importers = this.state.importers.filter(importer =>
+      importer.languages.includes(selectedLanguage)
+    );
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <form
+          id="create-profile-form"
+          onSubmit={this.handleFormSubmit}
+          ref={node => (this.form = node)}>
+
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+
+          {this.state.preloading
+            ? <div className="modal-body"><i className="spinner" /></div>
+            : <div className="modal-body">
+                <div className="modal-field">
+                  <label htmlFor="create-profile-name">
+                    {translate('name')}<em className="mandatory">*</em>
+                  </label>
+                  <input
+                    autoFocus={true}
+                    id="create-profile-name"
+                    maxLength="100"
+                    name="name"
+                    onChange={this.handleNameChange}
+                    required={true}
+                    size="50"
+                    type="text"
+                    value={this.state.name}
+                  />
+                </div>
+                <div className="modal-field spacer-bottom">
+                  <label htmlFor="create-profile-language">
+                    {translate('language')}<em className="mandatory">*</em>
+                  </label>
+                  <Select
+                    clearable={false}
+                    id="create-profile-language"
+                    name="language"
+                    onChange={this.handleLanguageChange}
+                    options={languages.map(language => ({
+                      label: language.name,
+                      value: language.key
+                    }))}
+                    value={selectedLanguage}
+                  />
+                </div>
+                {importers.map(importer => (
+                  <div
+                    className="modal-field spacer-bottom js-importer"
+                    data-key={importer.key}
+                    key={importer.key}>
+                    <label htmlFor={'create-profile-form-backup-' + importer.key}>
+                      {importer.name}
+                    </label>
+                    <input
+                      id={'create-profile-form-backup-' + importer.key}
+                      name={'backup_' + importer.key}
+                      type="file"
+                    />
+                    <p className="note">
+                      {translate('quality_profiles.optional_configuration_file')}
+                    </p>
+                  </div>
+                ))}
+              </div>}
+
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            {!this.state.preloading &&
+              <button disabled={this.state.loading} id="create-profile-submit">
+                {translate('create')}
+              </button>}
+            <a href="#" id="create-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+
+      </Modal>
+    );
+  }
+}
index df44389a9c66c15f79cf9626211f044e728ed177..70389154ed4a79eab0fd6805be0f5f4c28a17829 100644 (file)
@@ -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<Profile>,
   updateProfiles: () => Promise<*>
index df6191a83008881bbf2257cc7698ce438d67023d..6a276e243eeb4d6dcaec274619970b47642cf55e 100644 (file)
  */
 // @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 {
           <br />
           {translate('quality_profiles.intro2')}
         </div>
+
+        {this.state.restoreFormOpen &&
+          <RestoreProfileForm
+            onClose={this.closeRestoreForm}
+            onRequestFail={this.props.onRequestFail}
+            onRestore={this.props.updateProfiles}
+            organization={this.props.organization}
+          />}
+
+        {this.state.createFormOpen &&
+          <CreateProfileForm
+            languages={this.props.languages}
+            onClose={this.closeCreateForm}
+            onRequestFail={this.props.onRequestFail}
+            onCreate={this.handleCreate}
+            organization={this.props.organization}
+          />}
       </header>
     );
   }
index c2b0681f68694e7b6471c507b21263cf3f75615c..dbcf7840677288ad8b37ccabd766e50d1ebf816d 100644 (file)
@@ -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<Profile>,
   updateProfiles: () => Promise<*>
@@ -43,6 +44,7 @@ export default class ProfilesList extends React.PureComponent {
       <ProfilesListRow
         canAdmin={this.props.canAdmin}
         key={profile.key}
+        onRequestFail={this.props.onRequestFail}
         organization={this.props.organization}
         profile={profile}
         updateProfiles={this.props.updateProfiles}
index 85dcb48c1d3a9c316f629d7ec69c3e4d6c26b238..ec8bcf086e4d48bfaa2448ff3c9ea02c9e18aded 100644 (file)
@@ -30,6 +30,7 @@ import type { Profile } from '../propTypes';
 
 type Props = {
   canAdmin: boolean,
+  onRequestFail: Object => void,
   organization: ?string,
   profile: Profile,
   updateProfiles: () => Promise<*>
@@ -160,6 +161,7 @@ export default class ProfilesListRow extends React.PureComponent {
               <ProfileActions
                 canAdmin={this.props.canAdmin}
                 fromList={true}
+                onRequestFail={this.props.onRequestFail}
                 organization={this.props.organization}
                 profile={this.props.profile}
                 updateProfiles={this.props.updateProfiles}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js
new file mode 100644 (file)
index 0000000..911482e
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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 { restoreQualityProfile } from '../../../api/quality-profiles';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+type Props = {
+  onClose: () => 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 (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+
+        <form
+          id="restore-profile-form"
+          onSubmit={this.handleFormSubmit}
+          ref={node => (this.form = node)}>
+
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+
+          <div className="modal-body">
+            {profile != null && ruleSuccesses != null
+              ? ruleFailures
+                  ? <div className="alert alert-warning">
+                      {translateWithParameters(
+                        'quality_profiles.restore_profile.warning',
+                        profile.name,
+                        ruleSuccesses,
+                        ruleFailures
+                      )}
+                    </div>
+                  : <div className="alert alert-success">
+                      {translateWithParameters(
+                        'quality_profiles.restore_profile.success',
+                        profile.name,
+                        ruleSuccesses
+                      )}
+                    </div>
+              : <div className="modal-field">
+                  <label htmlFor="restore-profile-backup">
+                    {translate('backup')}<em className="mandatory">*</em>
+                  </label>
+                  <input id="restore-profile-backup" name="backup" required={true} type="file" />
+                </div>}
+          </div>
+
+          {ruleSuccesses == null
+            ? <div className="modal-foot">
+                {loading && <i className="spinner spacer-right" />}
+                <button disabled={loading} id="restore-profile-submit">
+                  {translate('restore')}
+                </button>
+                <a href="#" id="restore-profile-cancel" onClick={this.handleCancelClick}>
+                  {translate('cancel')}
+                </a>
+              </div>
+            : <div className="modal-foot">
+                <a href="#" onClick={this.handleCancelClick}>
+                  {translate('close')}
+                </a>
+              </div>}
+
+        </form>
+
+      </Modal>
+    );
+  }
+}
index f8438011916b9bb15e240df2698c982317e32c25..92af7067b7b92d471a092993201522baf06d28f6 100644 (file)
@@ -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 (file)
index 94c7e29..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<form id="change-profile-parent-form">
-  <div class="modal-head">
-    <h2>{{t 'quality_profiles.change_parent'}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    <div class="modal-field">
-      <label for="change-profile-parent">Parent: <em class="mandatory">*</em></label>
-      <select id="change-profile-parent" name="parentKey">
-        <option value="#none">{{t 'none'}}</option>
-        {{#each profiles}}
-          <option value="{{key}}">{{name}}</option>
-        {{/each}}
-      </select>
-    </div>
-  </div>
-  <div class="modal-foot">
-    <button id="change-profile-parent-submit">{{t 'change_verb'}}</button>
-    <a href="#" class="js-modal-close" id="change-profile-parent-cancel">{{t 'cancel'}}</a>
-  </div>
-</form>
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 (file)
index 09e5913..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<div class="modal-head">
-  <h2>{{t 'projects'}}</h2>
-</div>
-
-<div class="modal-body">
-  <div class="js-modal-messages"></div>
-  <div id="profile-projects"></div>
-</div>
-
-<div class="modal-foot">
-  <a href="#" class="js-modal-close">{{t 'close'}}</a>
-</div>
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 (file)
index 6cc034f..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<form id="copy-profile-form">
-  <div class="modal-head">
-    <h2>{{tp 'quality_profiles.copy_x_title' name languageName}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    <div class="modal-field">
-      <label for="copy-profile-name">{{t 'quality_profiles.copy_new_name'}}<em class="mandatory">*</em></label>
-      <input id="copy-profile-name" name="name" type="text" size="50" maxlength="100" required>
-    </div>
-  </div>
-  <div class="modal-foot">
-    <button id="copy-profile-submit">{{t 'copy'}}</button>
-    <a href="#" class="js-modal-close" id="copy-profile-cancel">{{t 'cancel'}}</a>
-  </div>
-</form>
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 (file)
index acdc7f9..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<form id="create-profile-form" action="{{link '/api/qualityprofiles/create'}}" enctype="multipart/form-data" method="POST">
-  <div class="modal-head">
-    <h2>{{t 'quality_profiles.new_profile'}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    {{#if organization}}
-      <input type="hidden" name="organization" value="{{organization}}">
-    {{/if}}
-    <div class="modal-field">
-      <label for="create-profile-name">{{t 'name'}}<em class="mandatory">*</em></label>
-      <input id="create-profile-name" name="name" type="text" size="50" maxlength="100" required>
-    </div>
-    <div class="modal-field spacer-bottom">
-      <label for="create-profile-language">{{t 'language'}}<em class="mandatory">*</em></label>
-      <select id="create-profile-language" name="language" required>
-        {{#each languages}}
-          <option value="{{key}}">{{name}}</option>
-        {{/each}}
-      </select>
-    </div>
-    {{#each importers}}
-    <div class="modal-field spacer-bottom js-importer" data-key="{{key}}">
-      <label for="create-profile-form-backup-{{key}}">{{name}}</label>
-      <input id="create-profile-form-backup-{{key}}" name="backup_{{key}}" type="file">
-      <p class="note">{{t 'quality_profiles.optional_configuration_file'}}</p>
-    </div>
-    {{/each}}
-  </div>
-  <div class="modal-foot">
-    <button id="create-profile-submit">{{t 'create'}}</button>
-    <a href="#" class="js-modal-close" id="create-profile-cancel">{{t 'cancel'}}</a>
-  </div>
-</form>
\ 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 (file)
index 1743852..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<form id="delete-profile-form">
-  <div class="modal-head">
-    <h2>{{t 'quality_profiles.delete_confirm_title'}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    {{#if childrenCount}}
-      <div class="alert alert-warning">{{t 'quality_profiles.this_profile_has_descendants'}}</div>
-      <p>{{tp 'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants' name languageName}}</p>
-    {{else}}
-      <p>{{tp 'quality_profiles.are_you_sure_want_delete_profile_x' name languageName}}</p>
-    {{/if}}
-  </div>
-  <div class="modal-foot">
-    <button id="delete-profile-submit">{{t 'delete'}}</button>
-    <a href="#" class="js-modal-close" id="delete-profile-cancel">{{t 'cancel'}}</a>
-  </div>
-</form>
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 (file)
index 3da132f..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<form id="rename-profile-form">
-  <div class="modal-head">
-    <h2>{{tp 'quality_profiles.rename_x_title' name languageName}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    <div class="modal-field">
-      <label for="rename-profile-name">{{t 'quality_profiles.new_name'}} <em class="mandatory">*</em></label>
-      <input id="rename-profile-name" name="name" type="text" size="50" maxlength="100" value="{{name}}" required>
-    </div>
-  </div>
-  <div class="modal-foot">
-    <button id="rename-profile-submit">{{t 'rename'}}</button>
-    <a href="#" class="js-modal-close" id="rename-profile-cancel">{{t 'cancel'}}</a>
-  </div>
-</form>
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 (file)
index 7c22c83..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<form id="restore-profile-form">
-  <div class="modal-head">
-    <h2>{{t 'quality_profiles.restore_profile'}}</h2>
-  </div>
-
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    {{#if organization}}
-      <input type="hidden" name="organization" value="{{organization}}">
-    {{/if}}
-    {{#if profile}}
-      {{#if ruleFailures}}
-        <div class="alert alert-warning">
-          {{tp 'quality_profiles.restore_profile.warning' profile.name ruleSuccesses ruleFailures}}
-        </div>
-      {{else}}
-        <div class="alert alert-success">
-          {{tp 'quality_profiles.restore_profile.success' profile.name ruleSuccesses}}
-        </div>
-      {{/if}}
-    {{else}}
-      <div class="modal-field">
-        <label for="restore-profile-backup">{{t 'backup'}}<em class="mandatory">*</em></label>
-        <input type="file" id="restore-profile-backup" name="backup" required>
-      </div>
-    {{/if}}
-  </div>
-
-  <div class="modal-foot">
-    {{#notNull ruleSuccesses}}
-      <a href="#" class="js-modal-close">{{t 'close'}}</a>
-    {{else}}
-      <button id="restore-profile-submit">{{t 'restore'}}</button>
-      <a href="#" class="js-modal-close" id="restore-profile-cancel">{{t 'cancel'}}</a>
-    {{/notNull}}
-  </div>
-</form>
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 (file)
index 912aecb..0000000
+++ /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/ChangeProjectsView.js b/server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js
deleted file mode 100644 (file)
index b9601e3..0000000
+++ /dev/null
@@ -1,73 +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 escapeHtml from 'escape-html';
-import ModalFormView from '../../../components/common/modal-form';
-import Template from '../templates/quality-profiles-change-projects.hbs';
-import { translate } from '../../../helpers/l10n';
-import '../../../components/SelectList';
-
-export default ModalFormView.extend({
-  template: Template,
-
-  onRender() {
-    // TODO remove uuid usage
-
-    ModalFormView.prototype.onRender.apply(this, arguments);
-
-    const { key } = this.options.profile;
-
-    const searchUrl =
-      window.baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key);
-
-    new window.SelectList({
-      searchUrl,
-      el: this.$('#profile-projects'),
-      width: '100%',
-      readOnly: false,
-      focusSearch: false,
-      dangerouslyUnescapedHtmlFormat(item) {
-        return escapeHtml(item.name);
-      },
-      selectUrl: window.baseUrl + '/api/qualityprofiles/add_project',
-      deselectUrl: window.baseUrl + '/api/qualityprofiles/remove_project',
-      extra: {
-        profileKey: key
-      },
-      selectParameter: 'projectUuid',
-      selectParameterValue: 'uuid',
-      labels: {
-        selected: translate('quality_gates.projects.with'),
-        deselected: translate('quality_gates.projects.without'),
-        all: translate('quality_gates.projects.all'),
-        noResults: translate('quality_gates.projects.noResults')
-      },
-      tooltips: {
-        select: translate('quality_profiles.projects.select_hint'),
-        deselect: translate('quality_profiles.projects.deselect_hint')
-      }
-    });
-  },
-
-  onDestroy() {
-    this.options.loadProjects();
-    ModalFormView.prototype.onDestroy.apply(this, arguments);
-  }
-});
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 (file)
index 711946b..0000000
+++ /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 (file)
index fb74811..0000000
+++ /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('<form>').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 (file)
index ec4c5f8..0000000
+++ /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 (file)
index 57e3e5c..0000000
+++ /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 (file)
index d2393e5..0000000
+++ /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
-    };
-  }
-});
index aba765f0df23029ba05db1f02f41c900f97f7634..f6e665b65ee4910f8f064983821036acabe66ef6 100644 (file)
@@ -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})