]> source.dussan.org Git - sonarqube.git/commitdiff
rewrite remaining backbone modals in react
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 21 Feb 2018 13:24:05 +0000 (14:24 +0100)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 27 Feb 2018 12:14:54 +0000 (13:14 +0100)
37 files changed:
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/ProjectKeyPage.java
server/sonar-web/src/main/js/api/projectLinks.ts
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/app/utils/exposeLibraries.ts
server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx
server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx
server/sonar-web/src/main/js/apps/project-admin/key/Key.js
server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs [deleted file]
server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/links/Header.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/links/Links.js
server/sonar-web/src/main/js/apps/project-admin/links/Table.js
server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModal.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModalTemplate.hbs [deleted file]
server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModal.js [deleted file]
server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModalTemplate.hbs [deleted file]
server/sonar-web/src/main/js/apps/project-admin/store/actions.js
server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js
server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js [deleted file]
server/sonar-web/src/main/js/components/RestartModal/index.js [deleted file]
server/sonar-web/src/main/js/components/RestartModal/templates/restarting.hbs [deleted file]
server/sonar-web/src/main/js/components/RestartModal/templates/template.hbs [deleted file]
server/sonar-web/src/main/js/components/common/modal-form.js [deleted file]
server/sonar-web/src/main/js/components/common/modals.js [deleted file]
server/sonar-web/src/main/js/components/common/selectable-collection-view.js [deleted file]
server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx
tests/src/test/java/org/sonarqube/tests/project/ProjectKeyUpdatePageTest.java
tests/src/test/java/org/sonarqube/tests/project/ProjectLinksTest.java

index 2c3fcf48893b8ab8843cac7584c70f5325b0d860..e8b5492b3e7a534c6f73df5b92be596b9cdd1122 100644 (file)
 package org.sonarqube.qa.util.pageobjects;
 
 import com.codeborne.selenide.Condition;
-import com.codeborne.selenide.Selenide;
 import com.codeborne.selenide.SelenideElement;
 
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+
 public class ProjectKeyPage {
 
   public ProjectKeyPage() {
-    Selenide.$("#project-key").should(Condition.exist);
+    $("#project-key").should(Condition.exist);
   }
 
   public ProjectKeyPage assertSimpleUpdate() {
-    Selenide.$("#update-key-new-key").shouldBe(Condition.visible);
-    Selenide.$("#update-key-submit").shouldBe(Condition.visible);
+    $("#update-key-new-key").shouldBe(visible);
+    $("#update-key-submit").shouldBe(visible);
     return this;
   }
 
   public ProjectKeyPage trySimpleUpdate(String newKey) {
-    Selenide.$("#update-key-new-key").val(newKey);
-    Selenide.$("#update-key-submit").click();
-    Selenide.$("#update-key-confirm").click();
+    $("#update-key-new-key").val(newKey);
+    $("#update-key-submit").click();
+    $(".modal").shouldBe(visible);
+    $(".modal button[type=\"submit\"]").click();
     return this;
   }
 
   public ProjectKeyPage openFineGrainedUpdate() {
-    Selenide.$("#update-key-tab-fine").click();
-    Selenide.$("#project-key-fine-grained-update").shouldBe(Condition.visible);
+    $("#update-key-tab-fine").click();
+    $("#project-key-fine-grained-update").shouldBe(visible);
     return this;
   }
 
   public ProjectKeyPage tryFineGrainedUpdate(String key, String newKey) {
-    SelenideElement form = Selenide.$(".js-fine-grained-update[data-key=\"" + key + "\"]");
-    form.shouldBe(Condition.visible);
+    SelenideElement form = $(".js-fine-grained-update[data-key=\"" + key + "\"]");
+    form.shouldBe(visible);
 
     form.$("input").val(newKey);
     form.$("button").click();
 
-    Selenide.$("#update-key-confirm").click();
+    $(".modal").shouldBe(visible);
+    $(".modal button[type=\"submit\"]").click();
     return this;
   }
 
   public ProjectKeyPage assertBulkChange() {
-    Selenide.$("#bulk-update-replace").shouldBe(Condition.visible);
-    Selenide.$("#bulk-update-by").shouldBe(Condition.visible);
-    Selenide.$("#bulk-update-see-results").shouldBe(Condition.visible);
+    $("#bulk-update-replace").shouldBe(visible);
+    $("#bulk-update-by").shouldBe(visible);
+    $("#bulk-update-see-results").shouldBe(visible);
     return this;
   }
 
   public ProjectKeyPage simulateBulkChange(String replace, String by) {
-    Selenide.$("#bulk-update-replace").val(replace);
-    Selenide.$("#bulk-update-by").val(by);
-    Selenide.$("#bulk-update-see-results").click();
+    $("#bulk-update-replace").val(replace);
+    $("#bulk-update-by").val(by);
+    $("#bulk-update-see-results").click();
 
-    Selenide.$("#bulk-update-simulation").shouldBe(Condition.visible);
+    $("#bulk-update-simulation").shouldBe(visible);
     return this;
   }
 
   public ProjectKeyPage assertBulkChangeSimulationResult(String oldKey, String newKey) {
-    SelenideElement row = Selenide.$("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]");
+    SelenideElement row = $("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]");
     row.$(".js-old-key").should(Condition.text(oldKey));
     row.$(".js-new-key").should(Condition.text(newKey));
     return this;
   }
 
   public ProjectKeyPage assertDuplicated(String oldKey) {
-    SelenideElement row = Selenide.$("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]");
-    row.$(".js-new-key").$(".badge-danger").shouldBe(Condition.visible);
+    SelenideElement row = $("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]");
+    row.$(".js-new-key").$(".badge-danger").shouldBe(visible);
     return this;
   }
 
   public ProjectKeyPage confirmBulkUpdate() {
-    Selenide.$("#bulk-update-confirm").click();
+    $("#bulk-update-confirm").click();
     return this;
   }
 
   public ProjectKeyPage assertSuccessfulBulkUpdate() {
-    Selenide.$(".process-spinner")
-      .shouldBe(Condition.visible)
+    $(".process-spinner")
+      .shouldBe(visible)
       .shouldHave(Condition.text("The key has successfully been updated for all required resources"));
     return this;
   }
index e91b200c4fec107c66f5a091dfd5506c841e3bd8..22c5fba17fdc90c8869828ddb07b8d222cabc400 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { getJSON, post, postJSON } from '../helpers/request';
+import { ProjectLink } from '../app/types';
 import throwGlobalError from '../app/utils/throwGlobalError';
-
-export interface ProjectLink {
-  id: string;
-  name: string;
-  type: string;
-  url: string;
-}
+import { getJSON, post, postJSON } from '../helpers/request';
 
 export function getProjectLinks(projectKey: string): Promise<ProjectLink[]> {
-  const url = '/api/project_links/search';
-  const data = { projectKey };
-  return getJSON(url, data).then(r => r.links, throwGlobalError);
+  return getJSON('/api/project_links/search', { projectKey }).then(r => r.links, throwGlobalError);
 }
 
-export function deleteLink(linkId: string): Promise<void> {
-  const url = '/api/project_links/delete';
-  const data = { id: linkId };
-  return post(url, data);
+export function deleteLink(linkId: string) {
+  return post('/api/project_links/delete', { id: linkId }).catch(throwGlobalError);
 }
 
-export function createLink(projectKey: string, name: string, url: string): Promise<any> {
-  const apiURL = '/api/project_links/create';
-  const data = { projectKey, name, url };
-  return postJSON(apiURL, data).then(r => r.link);
+export function createLink(projectKey: string, name: string, url: string): Promise<ProjectLink> {
+  return postJSON('/api/project_links/create', { projectKey, name, url }).then(
+    r => r.link,
+    throwGlobalError
+  );
 }
index dddeec8424e82ff442db440c069bf83399b36ff6..13b8bac9f661deaf7171f7b478ef233d55b45b75 100644 (file)
@@ -245,6 +245,13 @@ export interface PermissionTemplate {
   }>;
 }
 
+export interface ProjectLink {
+  id: string;
+  name: string;
+  type: string;
+  url: string;
+}
+
 export interface Rule {
   isTemplate?: boolean;
   key: string;
index b19336572b06775973f1ae9a44d3ea3ac7adaa9b..4e853881db2949e65e1c5204545322e0e9928499 100644 (file)
@@ -33,7 +33,6 @@ import Modal from '../../components/controls/Modal';
 import SearchBox from '../../components/controls/SearchBox';
 import Select from '../../components/controls/Select';
 import Tooltip from '../../components/controls/Tooltip';
-import ModalForm from '../../components/common/modal-form';
 import SelectList from '../../components/SelectList';
 import CoverageRating from '../../components/ui/CoverageRating';
 import DuplicationsRating from '../../components/ui/DuplicationsRating';
@@ -63,9 +62,7 @@ const exposeLibraries = () => {
     Tooltip,
     Select,
     SelectList,
-    SearchBox,
-    // deprecated, used in Governance
-    ModalForm_deprecated: ModalForm
+    SearchBox
   };
 };
 
index e4c9a697009d4c57762c0e2fa172239daea089c5..d909bc0faffde752b6ae7d1cf32a7770a375fac0 100644 (file)
@@ -19,8 +19,8 @@
  */
 import * as React from 'react';
 import { isProvided, getLinkName } from '../../project-admin/links/utils';
+import { ProjectLink } from '../../../app/types';
 import BugTrackerIcon from '../../../components/ui/BugTrackerIcon';
-import { ProjectLink } from '../../../api/projectLinks';
 
 interface Props {
   link: ProjectLink;
index ed778b4cef73eeff551a9414031491a0995db7a9..d9db8d5b1509c1bb771df1209ac5cd1c3c23d467 100644 (file)
@@ -19,9 +19,9 @@
  */
 import * as React from 'react';
 import MetaLink from './MetaLink';
-import { getProjectLinks, ProjectLink } from '../../../api/projectLinks';
+import { getProjectLinks } from '../../../api/projectLinks';
 import { orderLinks } from '../../project-admin/links/utils';
-import { LightComponent } from '../../../app/types';
+import { LightComponent, ProjectLink } from '../../../app/types';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
index 52faac1bdc0cc385f658c78bc6929763eca2ca66..d1688ed106eb6af6008ca89f6061fb8bc5cf4cea 100644 (file)
@@ -26,14 +26,13 @@ import UpdateForm from './UpdateForm';
 import BulkUpdate from './BulkUpdate';
 import FineGrainedUpdate from './FineGrainedUpdate';
 import { reloadUpdateKeyPage } from './utils';
-import { fetchProjectModules, changeKey } from '../store/actions';
+import { changeKey, fetchProjectModules } from '../store/actions';
 import { translate } from '../../../helpers/l10n';
 import {
   addGlobalErrorMessage,
-  closeAllGlobalMessages,
-  addGlobalSuccessMessage
+  addGlobalSuccessMessage,
+  closeAllGlobalMessages
 } from '../../../store/globalMessages/duck';
-import { parseError } from '../../../helpers/request';
 import RecentHistory from '../../../app/components/RecentHistory';
 import { getProjectAdminProjectModules } from '../../../store/rootReducer';
 
@@ -55,29 +54,25 @@ class Key extends React.PureComponent {
     this.props.fetchProjectModules(this.props.component.key);
   }
 
-  handleChangeKey(key, newKey) {
-    return this.props
-      .changeKey(key, newKey)
-      .then(() => {
-        if (key === this.props.component.key) {
-          this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload'));
-          RecentHistory.remove(key);
-          reloadUpdateKeyPage(newKey);
-        } else {
-          this.props.addGlobalSuccessMessage(translate('update_key.key_updated'));
-        }
-      })
-      .catch(e => {
-        parseError(e).then(this.props.addGlobalErrorMessage);
-      });
-  }
+  handleChangeKey = (key, newKey) => {
+    return this.props.changeKey(key, newKey).then(() => {
+      if (key === this.props.component.key) {
+        this.props.addGlobalSuccessMessage(translate('update_key.key_updated.reload'));
+        RecentHistory.remove(key);
+        reloadUpdateKeyPage(newKey);
+      } else {
+        this.props.addGlobalSuccessMessage(translate('update_key.key_updated'));
+      }
+    });
+  };
 
-  handleChangeTab(tab, e) {
-    e.preventDefault();
-    e.target.blur();
+  handleChangeTab = event => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    const { tab } = event.currentTarget.dataset;
     this.setState({ tab });
     this.props.closeAllGlobalMessages();
-  }
+  };
 
   render() {
     const { component, modules } = this.props;
@@ -88,7 +83,7 @@ class Key extends React.PureComponent {
     const { tab } = this.state;
 
     return (
-      <div id="project-key" className="page page-limited">
+      <div className="page page-limited" id="project-key">
         <Helmet title={translate('update_key.page')} />
         <Header />
 
@@ -96,7 +91,7 @@ class Key extends React.PureComponent {
 
         {noModules && (
           <div>
-            <UpdateForm component={component} onKeyChange={this.handleChangeKey.bind(this)} />
+            <UpdateForm component={component} onKeyChange={this.handleChangeKey} />
           </div>
         )}
 
@@ -106,19 +101,21 @@ class Key extends React.PureComponent {
               <ul className="tabs">
                 <li>
                   <a
-                    id="update-key-tab-bulk"
                     className={tab === 'bulk' ? 'selected' : ''}
+                    data-tab="bulk"
                     href="#"
-                    onClick={this.handleChangeTab.bind(this, 'bulk')}>
+                    id="update-key-tab-bulk"
+                    onClick={this.handleChangeTab}>
                     {translate('update_key.bulk_update')}
                   </a>
                 </li>
                 <li>
                   <a
-                    id="update-key-tab-fine"
                     className={tab === 'fine' ? 'selected' : ''}
+                    data-tab="fine"
                     href="#"
-                    onClick={this.handleChangeTab.bind(this, 'fine')}>
+                    id="update-key-tab-fine"
+                    onClick={this.handleChangeTab}>
                     {translate('update_key.fine_grained_key_update')}
                   </a>
                 </li>
@@ -131,9 +128,9 @@ class Key extends React.PureComponent {
               <FineGrainedUpdate
                 component={component}
                 modules={modules}
-                onKeyChange={this.handleChangeKey.bind(this)}
-                onSuccess={this.props.closeAllGlobalMessages}
                 onError={this.props.addGlobalErrorMessage}
+                onKeyChange={this.handleChangeKey}
+                onSuccess={this.props.closeAllGlobalMessages}
               />
             )}
           </div>
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js
deleted file mode 100644 (file)
index 54564f8..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import UpdateKeyConfirmation from './views/UpdateKeyConfirmation';
-import { translate } from '../../../helpers/l10n';
-
-export default class UpdateForm extends React.PureComponent {
-  static propTypes = {
-    component: PropTypes.object.isRequired,
-    onKeyChange: PropTypes.func.isRequired
-  };
-
-  state = { newKey: null };
-
-  handleSubmit(e) {
-    e.preventDefault();
-
-    const newKey = this.refs.newKey.value;
-
-    new UpdateKeyConfirmation({
-      newKey,
-      component: this.props.component,
-      onChange: this.props.onKeyChange
-    }).render();
-  }
-
-  handleChange(e) {
-    const newKey = e.target.value;
-    this.setState({ newKey });
-  }
-
-  handleReset(e) {
-    e.preventDefault();
-    this.setState({ newKey: null });
-  }
-
-  render() {
-    const value = this.state.newKey != null ? this.state.newKey : this.props.component.key;
-
-    const hasChanged = value !== this.props.component.key;
-
-    return (
-      <form onSubmit={this.handleSubmit.bind(this)}>
-        <input
-          ref="newKey"
-          id="update-key-new-key"
-          className="input-super-large"
-          value={value}
-          type="text"
-          placeholder={translate('update_key.new_key')}
-          required={true}
-          onChange={this.handleChange.bind(this)}
-        />
-
-        <div className="spacer-top">
-          <button id="update-key-submit" disabled={!hasChanged}>
-            {translate('update_verb')}
-          </button>{' '}
-          <button
-            id="update-key-reset"
-            disabled={!hasChanged}
-            onClick={this.handleReset.bind(this)}>
-            {translate('reset_verb')}
-          </button>
-        </div>
-      </form>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.tsx
new file mode 100644 (file)
index 0000000..99dc58c
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import UpdateKeyConfirm from './UpdateKeyConfirm';
+import { Button, SubmitButton } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  component: { key: string; name: string };
+  onKeyChange: (oldKey: string, newKey: string) => Promise<void>;
+}
+
+interface State {
+  newKey?: string;
+}
+
+export default class UpdateForm extends React.PureComponent<Props, State> {
+  state: State = {};
+
+  handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    const newKey = event.currentTarget.value;
+    this.setState({ newKey });
+  };
+
+  handleReset = () => {
+    this.setState({ newKey: undefined });
+  };
+
+  render() {
+    const { component } = this.props;
+    const { newKey } = this.state;
+    const value = newKey != null ? newKey : component.key;
+    const hasChanged = value !== component.key;
+
+    return (
+      <UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}>
+        {({ onFormSubmit }) => (
+          <form onSubmit={onFormSubmit}>
+            <input
+              className="input-super-large"
+              id="update-key-new-key"
+              onChange={this.handleChange}
+              placeholder={translate('update_key.new_key')}
+              required={true}
+              type="text"
+              value={value}
+            />
+
+            <div className="spacer-top">
+              <SubmitButton disabled={!hasChanged} id="update-key-submit">
+                {translate('update_verb')}
+              </SubmitButton>
+
+              <Button
+                className="spacer-left"
+                disabled={!hasChanged}
+                id="update-key-reset"
+                onClick={this.handleReset}
+                type="reset">
+                {translate('reset_verb')}
+              </Button>
+            </div>
+          </form>
+        )}
+      </UpdateKeyConfirm>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyConfirm.tsx
new file mode 100644 (file)
index 0000000..6bb2bcc
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import ConfirmButton, { ChildrenProps } from '../../../components/controls/ConfirmButton';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+  children: (props: ChildrenProps) => React.ReactNode;
+  component: { key: string; name: string };
+  newKey: string | undefined;
+  onConfirm: (oldKey: string, newKey: string) => Promise<void>;
+}
+
+export default class UpdateKeyConfirm extends React.PureComponent<Props> {
+  handleConfirm = () => {
+    return this.props.newKey
+      ? this.props.onConfirm(this.props.component.key, this.props.newKey)
+      : Promise.reject(undefined);
+  };
+
+  render() {
+    const { children, component, newKey } = this.props;
+
+    return (
+      <ConfirmButton
+        confirmButtonText={translate('update_verb')}
+        modalBody={
+          <>
+            {translateWithParameters('update_key.are_you_sure_to_change_key', component.name)}
+            <div className="spacer-top">
+              {translate('update_key.old_key')}
+              {': '}
+              <strong>{component.key}</strong>
+            </div>
+            <div className="spacer-top">
+              {translate('update_key.new_key')}
+              {': '}
+              <strong>{newKey}</strong>
+            </div>
+          </>
+        }
+        modalHeader={translate('update_key.page')}
+        onConfirm={this.handleConfirm}>
+        {children}
+      </ConfirmButton>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js
deleted file mode 100644 (file)
index 4f4cf8e..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import UpdateKeyConfirmation from './views/UpdateKeyConfirmation';
-import { translate } from '../../../helpers/l10n';
-
-export default class UpdateKeyForm extends React.PureComponent {
-  static propTypes = {
-    component: PropTypes.object.isRequired
-  };
-
-  state = {};
-
-  componentWillMount() {
-    this.handleInputChange = this.handleInputChange.bind(this);
-    this.handleUpdateClick = this.handleUpdateClick.bind(this);
-    this.handleResetClick = this.handleResetClick.bind(this);
-  }
-
-  handleInputChange(e) {
-    const key = e.target.value;
-    this.setState({ key });
-  }
-
-  handleUpdateClick(e) {
-    e.preventDefault();
-    e.target.blur();
-
-    const newKey = this.refs.newKey.value;
-
-    new UpdateKeyConfirmation({
-      newKey,
-      component: this.props.component,
-      onChange: this.props.onKeyChange
-    }).render();
-  }
-
-  handleResetClick(e) {
-    e.preventDefault();
-    e.target.blur();
-    this.setState({ key: null });
-  }
-
-  render() {
-    const { component } = this.props;
-
-    const value = this.state.key != null ? this.state.key : component.key;
-
-    const hasChanged = this.state.key != null && this.state.key !== component.key;
-
-    return (
-      <div className="js-fine-grained-update" data-key={component.key}>
-        <input
-          ref="newKey"
-          className="input-super-large big-spacer-right"
-          type="text"
-          value={value}
-          onChange={this.handleInputChange}
-        />
-
-        <button disabled={!hasChanged} onClick={this.handleUpdateClick}>
-          {translate('update_verb')}
-        </button>
-
-        <button className="spacer-left" disabled={!hasChanged} onClick={this.handleResetClick}>
-          {translate('reset_verb')}
-        </button>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.tsx
new file mode 100644 (file)
index 0000000..48345df
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import UpdateKeyConfirm from './UpdateKeyConfirm';
+import { Button } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  component: { key: string; name: string };
+  onKeyChange: (oldKey: string, newKey: string) => Promise<void>;
+}
+
+interface State {
+  newKey?: string;
+}
+
+export default class UpdateKeyForm extends React.PureComponent<Props, State> {
+  state: State = {};
+
+  handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    const newKey = event.currentTarget.value;
+    this.setState({ newKey });
+  };
+
+  handleResetClick = () => {
+    this.setState({ newKey: undefined });
+  };
+
+  render() {
+    const { component } = this.props;
+    const { newKey } = this.state;
+    const value = newKey !== undefined ? newKey : component.key;
+    const hasChanged = newKey !== undefined && newKey !== component.key;
+
+    return (
+      <div className="js-fine-grained-update" data-key={component.key}>
+        <input
+          className="input-super-large big-spacer-right"
+          onChange={this.handleInputChange}
+          type="text"
+          value={value}
+        />
+
+        <UpdateKeyConfirm component={component} newKey={newKey} onConfirm={this.props.onKeyChange}>
+          {({ onClick }) => (
+            <Button disabled={!hasChanged} onClick={onClick}>
+              {translate('update_verb')}
+            </Button>
+          )}
+        </UpdateKeyConfirm>
+
+        <Button className="spacer-left" disabled={!hasChanged} onClick={this.handleResetClick}>
+          {translate('reset_verb')}
+        </Button>
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs
deleted file mode 100644 (file)
index bec83b7..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<form id="update-key-confirmation-form" autocomplete="off">
-  <div class="modal-head">
-    <h2>{{t 'update_key.page'}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    {{tp 'update_key.are_you_sure_to_change_key' component.name}}
-    <div class="spacer-top">
-      <div class="display-inline-block text-right" style="width: 80px;">
-        {{t 'update_key.old_key'}}:
-      </div>
-      <div class="display-inline-block">
-        {{component.key}}
-      </div>
-    </div>
-    <div class="spacer-top">
-      <div class="display-inline-block text-right" style="width: 80px;">
-        {{t 'update_key.new_key'}}:
-      </div>
-      <div class="display-inline-block">
-        {{newKey}}
-      </div>
-    </div>
-  </div>
-  <div class="modal-foot">
-    <i class="js-modal-spinner spinner spacer-right hidden"></i>
-    <button id="update-key-confirm">{{t 'update_verb'}}</button>
-    <a href="#" class="js-modal-close">{{t 'cancel'}}</a>
-  </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js
deleted file mode 100644 (file)
index afdc1cc..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import Template from './UpdateKeyConfirmation.hbs';
-import ModalForm from '../../../../components/common/modal-form';
-
-export default ModalForm.extend({
-  template: Template,
-
-  onFormSubmit() {
-    ModalForm.prototype.onFormSubmit.apply(this, arguments);
-    this.disableForm();
-    this.showSpinner();
-
-    this.options.onChange(this.options.component.key, this.options.newKey);
-    this.destroy();
-  },
-
-  serializeData() {
-    return {
-      component: this.options.component,
-      newKey: this.options.newKey
-    };
-  }
-});
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx b/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx
new file mode 100644 (file)
index 0000000..c87ae12
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import SimpleModal from '../../../components/controls/SimpleModal';
+import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  onClose: () => void;
+  onSubmit: (name: string, url: string) => Promise<void>;
+}
+
+interface State {
+  name: string;
+  url: string;
+}
+
+export default class CreationModal extends React.PureComponent<Props, State> {
+  state: State = { name: '', url: '' };
+
+  handleSubmit = () => {
+    return this.props.onSubmit(this.state.name, this.state.url).then(this.props.onClose);
+  };
+
+  handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ name: event.currentTarget.value });
+  };
+
+  handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ url: event.currentTarget.value });
+  };
+
+  render() {
+    const header = translate('project_links.create_new_project_link');
+
+    return (
+      <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}>
+        {({ onCloseClick, onFormSubmit, submitting }) => (
+          <form onSubmit={onFormSubmit}>
+            <header className="modal-head">
+              <h2>{header}</h2>
+            </header>
+
+            <div className="modal-body">
+              <div className="modal-field">
+                <label htmlFor="create-link-name">
+                  {translate('project_links.name')}
+                  <em className="mandatory">*</em>
+                </label>
+                <input
+                  autoFocus={true}
+                  id="create-link-name"
+                  maxLength={128}
+                  name="name"
+                  onChange={this.handleNameChange}
+                  required={true}
+                  type="text"
+                  value={this.state.name}
+                />
+              </div>
+
+              <div className="modal-field">
+                <label htmlFor="create-link-url">
+                  {translate('project_links.url')}
+                  <em className="mandatory">*</em>
+                </label>
+                <input
+                  id="create-link-url"
+                  maxLength={128}
+                  name="url"
+                  onChange={this.handleUrlChange}
+                  required={true}
+                  type="text"
+                  value={this.state.url}
+                />
+              </div>
+            </div>
+
+            <footer className="modal-foot">
+              <DeferredSpinner className="spacer-right" loading={submitting} />
+              <SubmitButton disabled={submitting} id="create-link-confirm">
+                {translate('create')}
+              </SubmitButton>
+              <ResetButtonLink disabled={submitting} onClick={onCloseClick}>
+                {translate('cancel')}
+              </ResetButtonLink>
+            </footer>
+          </form>
+        )}
+      </SimpleModal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Header.js b/server/sonar-web/src/main/js/apps/project-admin/links/Header.js
deleted file mode 100644 (file)
index 4ed55a4..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import CreationModal from './views/CreationModal';
-import { translate } from '../../../helpers/l10n';
-
-export default class Header extends React.PureComponent {
-  static propTypes = {
-    onCreate: PropTypes.func.isRequired
-  };
-
-  handleCreateClick(e) {
-    e.preventDefault();
-    e.target.blur();
-    new CreationModal({
-      onCreate: this.props.onCreate
-    }).render();
-  }
-
-  render() {
-    return (
-      <header className="page-header">
-        <h1 className="page-title">{translate('project_links.page')}</h1>
-        <div className="page-actions">
-          <button id="create-project-link" onClick={this.handleCreateClick.bind(this)}>
-            {translate('create')}
-          </button>
-        </div>
-        <div className="page-description">{translate('project_links.page.description')}</div>
-      </header>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx b/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx
new file mode 100644 (file)
index 0000000..b8e07c5
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import CreationModal from './CreationModal';
+import { Button } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  onCreate: (name: string, url: string) => Promise<void>;
+}
+
+interface State {
+  creationModal: boolean;
+}
+
+export default class Header extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = { creationModal: false };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCreateClick = () => {
+    this.setState({ creationModal: true });
+  };
+
+  handleCreationModalClose = () => {
+    if (this.mounted) {
+      this.setState({ creationModal: false });
+    }
+  };
+
+  render() {
+    return (
+      <>
+        <header className="page-header">
+          <h1 className="page-title">{translate('project_links.page')}</h1>
+          <div className="page-actions">
+            <Button id="create-project-link" onClick={this.handleCreateClick}>
+              {translate('create')}
+            </Button>
+          </div>
+          <div className="page-description">{translate('project_links.page.description')}</div>
+        </header>
+        {this.state.creationModal && (
+          <CreationModal onClose={this.handleCreationModalClose} onSubmit={this.props.onCreate} />
+        )}
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.js b/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.js
deleted file mode 100644 (file)
index 3124fb9..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { isProvided, getLinkName } from './utils';
-import { translate } from '../../../helpers/l10n';
-import BugTrackerIcon from '../../../components/ui/BugTrackerIcon';
-
-export default class LinkRow extends React.PureComponent {
-  static propTypes = {
-    link: PropTypes.object.isRequired,
-    onDelete: PropTypes.func.isRequired
-  };
-
-  handleDeleteClick(e) {
-    e.preventDefault();
-    e.target.blur();
-    this.props.onDelete();
-  }
-
-  renderIcon(iconClassName) {
-    if (iconClassName === 'icon-issue') {
-      return (
-        <div className="display-inline-block text-top spacer-right">
-          <BugTrackerIcon />
-        </div>
-      );
-    }
-
-    return (
-      <div className="display-inline-block text-top spacer-right">
-        <i className={iconClassName} />
-      </div>
-    );
-  }
-
-  renderNameForProvided(link) {
-    return (
-      <div>
-        {this.renderIcon(`icon-${link.type}`)}
-        <div className="display-inline-block text-top">
-          <div>
-            <span className="js-name">{getLinkName(link)}</span>
-          </div>
-          <div className="note little-spacer-top">
-            <span className="js-type">{`sonar.links.${link.type}`}</span>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  renderName(link) {
-    if (isProvided(link)) {
-      return this.renderNameForProvided(link);
-    }
-
-    return (
-      <div>
-        {this.renderIcon('icon-detach')}
-        <div className="display-inline-block text-top">
-          <span className="js-name">{link.name}</span>
-        </div>
-      </div>
-    );
-  }
-
-  renderDeleteButton(link) {
-    if (isProvided(link)) {
-      return null;
-    }
-
-    return (
-      <button className="button-red js-delete-button" onClick={this.handleDeleteClick.bind(this)}>
-        {translate('delete')}
-      </button>
-    );
-  }
-
-  render() {
-    const { link } = this.props;
-
-    return (
-      <tr data-name={link.name}>
-        <td className="nowrap">{this.renderName(link)}</td>
-        <td className="nowrap js-url">
-          <a href={link.url} rel="nofollow" target="_blank">
-            {link.url}
-          </a>
-        </td>
-        <td className="thin nowrap">{this.renderDeleteButton(link)}</td>
-      </tr>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx b/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx
new file mode 100644 (file)
index 0000000..74ea4df
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { isProvided, getLinkName } from './utils';
+import { ProjectLink } from '../../../app/types';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import BugTrackerIcon from '../../../components/ui/BugTrackerIcon';
+import { Button } from '../../../components/ui/buttons';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+  link: ProjectLink;
+  onDelete: (linkId: string) => Promise<void>;
+}
+
+export default class LinkRow extends React.PureComponent<Props> {
+  renderIcon = (iconClassName: string) => {
+    if (iconClassName === 'icon-issue') {
+      return (
+        <div className="display-inline-block text-top spacer-right">
+          <BugTrackerIcon />
+        </div>
+      );
+    }
+
+    return (
+      <div className="display-inline-block text-top spacer-right">
+        <i className={iconClassName} />
+      </div>
+    );
+  };
+
+  renderNameForProvided = (link: ProjectLink) => {
+    return (
+      <div>
+        {this.renderIcon(`icon-${link.type}`)}
+        <div className="display-inline-block text-top">
+          <div>
+            <span className="js-name">{getLinkName(link)}</span>
+          </div>
+          <div className="note little-spacer-top">
+            <span className="js-type">{`sonar.links.${link.type}`}</span>
+          </div>
+        </div>
+      </div>
+    );
+  };
+
+  renderName = (link: ProjectLink) => {
+    if (isProvided(link)) {
+      return this.renderNameForProvided(link);
+    }
+
+    return (
+      <div>
+        {this.renderIcon('icon-detach')}
+        <div className="display-inline-block text-top">
+          <span className="js-name">{link.name}</span>
+        </div>
+      </div>
+    );
+  };
+
+  renderDeleteButton = (link: ProjectLink) => {
+    if (isProvided(link)) {
+      return null;
+    }
+
+    return (
+      <ConfirmButton
+        confirmButtonText={translate('delete')}
+        confirmData={link.id}
+        isDestructive={true}
+        modalBody={translateWithParameters(
+          'project_links.are_you_sure_to_delete_x_link',
+          link.name
+        )}
+        modalHeader={translate('project_links.delete_project_link')}
+        onConfirm={this.props.onDelete}>
+        {({ onClick }) => (
+          <Button className="button-red js-delete-button" onClick={onClick}>
+            {translate('delete')}
+          </Button>
+        )}
+      </ConfirmButton>
+    );
+  };
+
+  render() {
+    const { link } = this.props;
+
+    return (
+      <tr data-name={link.name}>
+        <td className="nowrap">{this.renderName(link)}</td>
+        <td className="nowrap js-url">
+          <a href={link.url} rel="nofollow" target="_blank">
+            {link.url}
+          </a>
+        </td>
+        <td className="thin nowrap">{this.renderDeleteButton(link)}</td>
+      </tr>
+    );
+  }
+}
index e8a5318ba99cef2cee5169ac4b2274698c05fa14..fa77e03c6b8bd3bd5b68eda3764baad5663c474e 100644 (file)
@@ -23,7 +23,6 @@ import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Header from './Header';
 import Table from './Table';
-import DeletionModal from './views/DeletionModal';
 import { fetchProjectLinks, deleteProjectLink, createProjectLink } from '../store/actions';
 import { getProjectAdminProjectLinks } from '../../../store/rootReducer';
 import { translate } from '../../../helpers/l10n';
@@ -34,26 +33,17 @@ class Links extends React.PureComponent {
     links: PropTypes.array
   };
 
-  componentWillMount() {
-    this.handleCreateLink = this.handleCreateLink.bind(this);
-    this.handleDeleteLink = this.handleDeleteLink.bind(this);
-  }
-
   componentDidMount() {
     this.props.fetchProjectLinks(this.props.component.key);
   }
 
-  handleCreateLink(name, url) {
+  handleCreateLink = (name, url) => {
     return this.props.createProjectLink(this.props.component.key, name, url);
-  }
+  };
 
-  handleDeleteLink(link) {
-    new DeletionModal({ link })
-      .on('done', () => {
-        this.props.deleteProjectLink(this.props.component.key, link.id);
-      })
-      .render();
-  }
+  handleDeleteLink = linkId => {
+    return this.props.deleteProjectLink(this.props.component.key, linkId);
+  };
 
   render() {
     return (
index ad5ea09f9ba410550f507be9f871e546098fe3b1..8c8c80788fbde0c4e174420f817ab3db38168c6d 100644 (file)
@@ -29,10 +29,6 @@ export default class Table extends React.PureComponent {
     onDelete: PropTypes.func.isRequired
   };
 
-  handleDeleteLink(link) {
-    this.props.onDelete(link);
-  }
-
   renderHeader() {
     // keep empty cell for actions
     return (
@@ -50,12 +46,12 @@ export default class Table extends React.PureComponent {
     const orderedLinks = orderLinks(this.props.links);
 
     const linkRows = orderedLinks.map(link => (
-      <LinkRow key={link.id} link={link} onDelete={this.handleDeleteLink.bind(this, link)} />
+      <LinkRow key={link.id} link={link} onDelete={this.props.onDelete} />
     ));
 
     return (
       <div className="boxed-group boxed-group-inner">
-        <table id="project-links" className="data zebra">
+        <table className="data zebra" id="project-links">
           {this.renderHeader()}
           <tbody>{linkRows}</tbody>
         </table>
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModal.js b/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModal.js
deleted file mode 100644 (file)
index d00cb68..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import Template from './CreationModalTemplate.hbs';
-import ModalForm from '../../../../components/common/modal-form';
-import { parseError } from '../../../../helpers/request';
-
-export default ModalForm.extend({
-  template: Template,
-
-  onFormSubmit() {
-    ModalForm.prototype.onFormSubmit.apply(this, arguments);
-    this.disableForm();
-
-    const name = this.$('#create-link-name').val();
-    const url = this.$('#create-link-url').val();
-
-    this.options
-      .onCreate(name, url)
-      .then(() => this.destroy())
-      .catch(e => {
-        parseError(e).then(msg => this.showSingleError(msg));
-        this.enableForm();
-      });
-  }
-});
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModalTemplate.hbs b/server/sonar-web/src/main/js/apps/project-admin/links/views/CreationModalTemplate.hbs
deleted file mode 100644 (file)
index 6d7f250..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<form>
-  <div class="modal-head">
-    <h2>{{t 'project_links.create_new_project_link'}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-
-    <div class="modal-field">
-      <label for="create-link-name">{{t 'project_links.name'}}<em class="mandatory">*</em></label>
-      <input id="create-link-name" name="name" type="text" maxlength="128" required>
-    </div>
-
-    <div class="modal-field">
-      <label for="create-link-url">{{t 'project_links.url'}}<em class="mandatory">*</em></label>
-      <input id="create-link-url" name="url" type="text" maxlength="2048" required>
-    </div>
-  </div>
-  <div class="modal-foot">
-    <button id="create-link-confirm">{{t 'create'}}</button>
-    <a href="#" class="js-modal-close">{{t 'cancel'}}</a>
-  </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModal.js b/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModal.js
deleted file mode 100644 (file)
index 7b12ab0..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import Template from './DeletionModalTemplate.hbs';
-import ModalForm from '../../../../components/common/modal-form';
-import { deleteLink } from '../../../../api/projectLinks';
-import { parseError } from '../../../../helpers/request';
-
-export default ModalForm.extend({
-  template: Template,
-
-  onFormSubmit() {
-    ModalForm.prototype.onFormSubmit.apply(this, arguments);
-    this.disableForm();
-
-    deleteLink(this.options.link.id)
-      .then(() => {
-        this.trigger('done');
-        this.destroy();
-      })
-      .catch(e => {
-        parseError(e).then(msg => this.showSingleError(msg));
-        this.enableForm();
-      });
-  },
-
-  serializeData() {
-    return { link: this.options.link };
-  }
-});
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModalTemplate.hbs b/server/sonar-web/src/main/js/apps/project-admin/links/views/DeletionModalTemplate.hbs
deleted file mode 100644 (file)
index b8d744c..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<form>
-  <div class="modal-head">
-    <h2>{{t 'project_links.delete_project_link'}}</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    {{tp 'project_links.are_you_sure_to_delete_x_link' link.name}}
-  </div>
-  <div class="modal-foot">
-    <button id="delete-link-confirm" class="button-red">{{t 'delete'}}</button>
-    <a href="#" class="js-modal-close">{{t 'cancel'}}</a>
-  </div>
-</form>
index 11c64002edc9580f6df97e57310b323b836cc4b4..189832011e1de0fec5a113c1929aeaf0890b596c 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { getProjectLinks, createLink } from '../../../api/projectLinks';
+import { getProjectLinks, createLink, deleteLink } from '../../../api/projectLinks';
 import { getTree, changeKey as changeKeyApi } from '../../../api/components';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
 
 export const RECEIVE_PROJECT_LINKS = 'projectAdmin/RECEIVE_PROJECT_LINKS';
 export const receiveProjectLinks = (projectKey, links) => ({
@@ -28,9 +29,12 @@ export const receiveProjectLinks = (projectKey, links) => ({
 });
 
 export const fetchProjectLinks = projectKey => dispatch => {
-  getProjectLinks(projectKey).then(links => {
-    dispatch(receiveProjectLinks(projectKey, links));
-  });
+  getProjectLinks(projectKey).then(
+    links => {
+      dispatch(receiveProjectLinks(projectKey, links));
+    },
+    () => {}
+  );
 };
 
 export const ADD_PROJECT_LINK = 'projectAdmin/ADD_PROJECT_LINK';
@@ -47,12 +51,19 @@ export const createProjectLink = (projectKey, name, url) => dispatch => {
 };
 
 export const DELETE_PROJECT_LINK = 'projectAdmin/DELETE_PROJECT_LINK';
-export const deleteProjectLink = (projectKey, linkId) => ({
+export const deleteProjectLinkAction = (projectKey, linkId) => ({
   type: DELETE_PROJECT_LINK,
   projectKey,
   linkId
 });
 
+export function deleteProjectLink(projectKey, linkId) {
+  return dispatch =>
+    deleteLink(linkId).then(() => {
+      dispatch(deleteProjectLinkAction(projectKey, linkId));
+    });
+}
+
 export const RECEIVE_PROJECT_MODULES = 'projectAdmin/RECEIVE_PROJECT_MODULES';
 const receiveProjectModules = (projectKey, modules) => ({
   type: RECEIVE_PROJECT_MODULES,
@@ -62,9 +73,12 @@ const receiveProjectModules = (projectKey, modules) => ({
 
 export const fetchProjectModules = projectKey => dispatch => {
   const options = { qualifiers: 'BRC', s: 'name', ps: 500 };
-  getTree(projectKey, options).then(r => {
-    dispatch(receiveProjectModules(projectKey, r.components));
-  });
+  getTree(projectKey, options).then(
+    r => {
+      dispatch(receiveProjectModules(projectKey, r.components));
+    },
+    () => {}
+  );
 };
 
 export const CHANGE_KEY = 'projectAdmin/CHANGE_KEY';
@@ -75,5 +89,8 @@ const changeKeyAction = (key, newKey) => ({
 });
 
 export const changeKey = (key, newKey) => dispatch => {
-  return changeKeyApi(key, newKey).then(() => dispatch(changeKeyAction(key, newKey)));
+  return changeKeyApi(key, newKey).then(
+    () => dispatch(changeKeyAction(key, newKey)),
+    throwGlobalError
+  );
 };
index de38009e1285440214f6d6119752d814ffea35f6..09d8baf401d3a24f9d0f1ed5549a08e3601839fc 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
-import ProjectsView from '../views/gate-projects-view';
+import escapeHtml from 'escape-html';
+import SelectList from '../../../components/SelectList';
+import { translate } from '../../../helpers/l10n';
+import { getBaseUrl } from '../../../helpers/urls';
 
 export default class Projects extends React.PureComponent {
   componentDidMount() {
-    this.renderView();
+    this.renderSelectList();
   }
 
-  componentWillUpdate() {
-    this.destroyView();
-  }
-
-  componentDidUpdate() {
-    this.renderView();
-  }
+  renderSelectList = () => {
+    if (!this.container) return;
 
-  componentWillUnmount() {
-    this.destroyView();
-  }
+    const { qualityGate, edit, organization } = this.props;
 
-  destroyView() {
-    if (this.projectsView) {
-      this.projectsView.destroy();
+    const extra = { gateId: qualityGate.id };
+    let orgQuery = '';
+    if (organization) {
+      extra.organization = organization;
+      orgQuery = '&organization=' + organization;
     }
-  }
-
-  renderView() {
-    const { qualityGate, edit, organization } = this.props;
 
-    this.projectsView = new ProjectsView({
-      qualityGate,
-      edit,
-      container: this.refs.container,
-      organization
+    // eslint-disable-next-line no-new
+    new SelectList({
+      el: this.container,
+      width: '100%',
+      readOnly: !edit,
+      focusSearch: false,
+      dangerouslyUnescapedHtmlFormat: item => escapeHtml(item.name),
+      searchUrl: getBaseUrl() + `/api/qualitygates/search?gateId=${qualityGate.id}${orgQuery}`,
+      selectUrl: getBaseUrl() + '/api/qualitygates/select',
+      deselectUrl: getBaseUrl() + '/api/qualitygates/deselect',
+      extra,
+      selectParameter: 'projectId',
+      selectParameterValue: 'id',
+      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_gates.projects.select_hint'),
+        deselect: translate('quality_gates.projects.deselect_hint')
+      }
     });
-    this.projectsView.render();
-  }
+  };
 
   render() {
-    return <div ref="container" />;
+    return <div ref={node => (this.container = node)} />;
   }
 }
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js
deleted file mode 100644 (file)
index 85ab920..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import escapeHtml from 'escape-html';
-import SelectList from '../../../components/SelectList';
-import { translate } from '../../../helpers/l10n';
-
-export default Marionette.ItemView.extend({
-  template: () => {},
-
-  onRender() {
-    const { qualityGate, organization } = this.options;
-
-    const extra = {
-      gateId: qualityGate.id
-    };
-    let orgQuery = '';
-    if (organization) {
-      extra.organization = organization;
-      orgQuery = '&organization=' + organization;
-    }
-
-    new SelectList({
-      el: this.options.container,
-      width: '100%',
-      readOnly: !this.options.edit,
-      focusSearch: false,
-      dangerouslyUnescapedHtmlFormat(item) {
-        return escapeHtml(item.name);
-      },
-      searchUrl: `${window.baseUrl}/api/qualitygates/search?gateId=${qualityGate.id}${orgQuery}`,
-      selectUrl: window.baseUrl + '/api/qualitygates/select',
-      deselectUrl: window.baseUrl + '/api/qualitygates/deselect',
-      extra,
-      selectParameter: 'projectId',
-      selectParameterValue: 'id',
-      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_gates.projects.select_hint'),
-        deselect: translate('quality_gates.projects.deselect_hint')
-      }
-    });
-  },
-
-  serializeData() {
-    return {
-      ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
-      canEdit: this.options.edit
-    };
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/RestartModal/index.js b/server/sonar-web/src/main/js/components/RestartModal/index.js
deleted file mode 100644 (file)
index c3d3f26..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import Template from './templates/template.hbs';
-import RestartingTemplate from './templates/restarting.hbs';
-import ModalForm from '../common/modal-form';
-import { restartAndWait } from '../../api/system';
-
-const RestartModal = ModalForm.extend({
-  template: Template,
-  restartingTemplate: RestartingTemplate,
-
-  initialize() {
-    this.restarting = false;
-  },
-
-  getTemplate() {
-    return this.restarting ? this.restartingTemplate : this.template;
-  },
-
-  onFormSubmit() {
-    ModalForm.prototype.onFormSubmit.apply(this, arguments);
-    this.restarting = true;
-    this.render();
-    restartAndWait().then(() => {
-      document.location.reload();
-    });
-  }
-});
-
-export default RestartModal;
diff --git a/server/sonar-web/src/main/js/components/RestartModal/templates/restarting.hbs b/server/sonar-web/src/main/js/components/RestartModal/templates/restarting.hbs
deleted file mode 100644 (file)
index bab7b50..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<form id="restart-server-form">
-  <div class="modal-head">
-    <h2>Restart Server</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    <p class="spacer-top spacer-bottom text-center">
-      Server is restarting. This page will be automatically refreshed.
-    </p>
-    <p class="big-spacer-top spacer-bottom text-center">
-      <i class="spinner"></i>
-    </p>
-  </div>
-</form>
diff --git a/server/sonar-web/src/main/js/components/RestartModal/templates/template.hbs b/server/sonar-web/src/main/js/components/RestartModal/templates/template.hbs
deleted file mode 100644 (file)
index 6058532..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<form id="restart-server-form">
-  <div class="modal-head">
-    <h2>Restart Server</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    <p class="spacer-top spacer-bottom">
-      Are you sure you want to restart the server?
-    </p>
-  </div>
-  <div class="modal-foot">
-    <button id="restart-server-submit">Restart</button>
-    <a href="#" class="js-modal-close" id="restart-server-cancel">Cancel</a>
-  </div>
-</form>
diff --git a/server/sonar-web/src/main/js/components/common/modal-form.js b/server/sonar-web/src/main/js/components/common/modal-form.js
deleted file mode 100644 (file)
index b201fbf..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import ModalView from './modals';
-
-export default ModalView.extend({
-  ui() {
-    return {
-      messagesContainer: '.js-modal-messages'
-    };
-  },
-
-  events() {
-    return {
-      ...ModalView.prototype.events.apply(this, arguments),
-      'keydown input,textarea,select': 'onInputKeydown',
-      'submit form': 'onFormSubmit'
-    };
-  },
-
-  onRender() {
-    ModalView.prototype.onRender.apply(this, arguments);
-    const that = this;
-    setTimeout(() => {
-      that
-        .$(':tabbable')
-        .first()
-        .focus();
-    }, 0);
-  },
-
-  onInputKeydown(e) {
-    if (e.keyCode === 27) {
-      // escape
-      this.destroy();
-    }
-  },
-
-  onFormSubmit(e) {
-    e.preventDefault();
-  },
-
-  showErrors(errors, warnings) {
-    const container = this.ui.messagesContainer.empty();
-    if (Array.isArray(errors)) {
-      errors.forEach(error => {
-        const html = `<div class="alert alert-danger">${error.msg}</div>`;
-        container.append(html);
-      });
-    }
-    if (Array.isArray(warnings)) {
-      warnings.forEach(warn => {
-        const html = `<div class="alert alert-warning">${warn.msg}</div>`;
-        container.append(html);
-      });
-    }
-    this.ui.messagesContainer.scrollParent().scrollTop(0);
-  },
-
-  showSingleError(msg) {
-    this.showErrors([{ msg }], []);
-  },
-
-  disableForm() {
-    const form = this.$('form');
-    this.disabledFields = form.find(':input:not(:disabled)');
-    this.disabledFields.prop('disabled', true);
-  },
-
-  enableForm() {
-    if (this.disabledFields != null) {
-      this.disabledFields.prop('disabled', false);
-    }
-  },
-
-  showSpinner() {
-    this.$('.js-modal-spinner').removeClass('hidden');
-  },
-
-  hideSpinner() {
-    this.$('.js-modal-spinner').addClass('hidden');
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/common/modals.js b/server/sonar-web/src/main/js/components/common/modals.js
deleted file mode 100644 (file)
index 6251b77..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import $ from 'jquery';
-import Marionette from 'backbone.marionette';
-import key from 'keymaster';
-
-const EVENT_SCOPE = 'modal';
-
-export default Marionette.ItemView.extend({
-  className: 'modal',
-  overlayClassName: 'modal-overlay',
-  htmlClassName: 'modal-open',
-
-  events() {
-    return {
-      'click .js-modal-close': 'onCloseClick'
-    };
-  },
-
-  onRender() {
-    const that = this;
-    this.$el.detach().appendTo($('body'));
-    $('html').addClass(this.htmlClassName);
-    this.renderOverlay();
-    this.keyScope = key.getScope();
-    key.setScope('modal');
-    key('escape', 'modal', () => {
-      that.destroy();
-      return false;
-    });
-    this.show();
-    if (this.options.large) {
-      this.$el.addClass('modal-large');
-    }
-  },
-
-  show() {
-    const that = this;
-    setTimeout(() => {
-      that.$el.addClass('in');
-      $('.' + that.overlayClassName).addClass('in');
-    }, 0);
-  },
-
-  onDestroy() {
-    $('html').removeClass(this.htmlClassName);
-    this.removeOverlay();
-    key.deleteScope('modal');
-    key.setScope(this.keyScope);
-  },
-
-  onCloseClick(e) {
-    e.preventDefault();
-    this.destroy();
-  },
-
-  renderOverlay() {
-    const overlay = $('.' + this.overlayClassName);
-    if (overlay.length === 0) {
-      $(`<div class="${this.overlayClassName}"></div>`).appendTo($('body'));
-    }
-  },
-
-  removeOverlay() {
-    $('.' + this.overlayClassName).remove();
-  },
-
-  attachCloseEvents() {
-    const that = this;
-    $('body').on('click.' + EVENT_SCOPE, () => {
-      $('body').off('click.' + EVENT_SCOPE);
-      that.destroy();
-    });
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/common/selectable-collection-view.js b/server/sonar-web/src/main/js/components/common/selectable-collection-view.js
deleted file mode 100644 (file)
index 41ce5d2..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-
-export default Marionette.CollectionView.extend({
-  initialize() {
-    this.resetSelectedIndex();
-    this.listenTo(this.collection, 'reset', this.resetSelectedIndex);
-  },
-
-  childViewOptions(model, index) {
-    return { index };
-  },
-
-  resetSelectedIndex() {
-    this.selectedIndex = 0;
-  },
-
-  onRender() {
-    this.selectCurrent();
-  },
-
-  submitCurrent() {
-    const view = this.children.findByIndex(this.selectedIndex);
-    if (view != null) {
-      view.submit();
-    }
-  },
-
-  selectCurrent() {
-    this.selectItem(this.selectedIndex);
-  },
-
-  selectNext() {
-    if (this.selectedIndex < this.collection.length - 1) {
-      this.deselectItem(this.selectedIndex);
-      this.selectedIndex++;
-      this.selectItem(this.selectedIndex);
-    }
-  },
-
-  selectPrev() {
-    if (this.selectedIndex > 0) {
-      this.deselectItem(this.selectedIndex);
-      this.selectedIndex--;
-      this.selectItem(this.selectedIndex);
-    }
-  },
-
-  selectItem(index) {
-    if (index >= 0 && index < this.collection.length) {
-      const view = this.children.findByIndex(index);
-      if (view != null) {
-        view.select();
-      }
-    }
-  },
-
-  deselectItem(index) {
-    const view = this.children.findByIndex(index);
-    if (view != null) {
-      view.deselect();
-    }
-  }
-});
index 0f05db2ac90a8ef937f2f9a1b7e9fbb2a29f4a67..ab766a9eed6719ecbf06f0b976348e1a0699cf42 100644 (file)
@@ -23,8 +23,13 @@ import DeferredSpinner from '../common/DeferredSpinner';
 import { translate } from '../../helpers/l10n';
 import { SubmitButton, ResetButtonLink } from '../ui/buttons';
 
+export interface ChildrenProps {
+  onClick: () => void;
+  onFormSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
+}
+
 interface Props {
-  children: (props: { onClick: () => void }) => React.ReactNode;
+  children: (props: ChildrenProps) => React.ReactNode;
   confirmButtonText: string;
   confirmData?: string;
   isDestructive?: boolean;
@@ -53,6 +58,13 @@ export default class ConfirmButton extends React.PureComponent<Props, State> {
     this.setState({ modal: true });
   };
 
+  handleFormSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
+    if (event) {
+      event.preventDefault();
+    }
+    this.setState({ modal: true });
+  };
+
   handleSubmit = () => {
     const result = this.props.onConfirm(this.props.confirmData);
     if (result) {
@@ -74,7 +86,10 @@ export default class ConfirmButton extends React.PureComponent<Props, State> {
 
     return (
       <>
-        {this.props.children({ onClick: this.handleButtonClick })}
+        {this.props.children({
+          onClick: this.handleButtonClick,
+          onFormSubmit: this.handleFormSubmit
+        })}
         {this.state.modal && (
           <SimpleModal
             header={modalHeader}
index 40d7abf27ed5a7908a8f429f8a8a436fede3eca2..28abde7863b6e17a2d0664eb3e76345d5655611f 100644 (file)
@@ -100,7 +100,7 @@ public class ProjectKeyUpdatePageTest {
     ProjectKeyPage page = openPage("sample");
     page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
 
-    $("#update-key-confirmation-form").shouldNotBe(visible);
+    $(".modal").shouldNotBe(visible);
 
     tester.openBrowser().openProjectKey("another");
     assertThat(url()).endsWith("/project/key?id=another");
index 1cbb95dd0e051ba122665d369321f4ed5397381d..ffaef59432281b2fbfaff401c3cc34933606955e 100644 (file)
@@ -37,6 +37,7 @@ import org.sonarqube.ws.client.projectlinks.CreateRequest;
 import org.sonarqube.ws.client.projectlinks.DeleteRequest;
 
 import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Condition.visible;
 import static com.codeborne.selenide.Selenide.$;
 import static util.ItUtils.projectDir;
 
@@ -88,7 +89,7 @@ public class ProjectLinksTest {
     customLink.getName().should(text("Custom"));
     customLink.getType().shouldNot(Condition.exist);
     customLink.getUrl().should(text("http://example.org/custom"));
-    customLink.getDeleteButton().shouldBe(Condition.visible);
+    customLink.getDeleteButton().shouldBe(visible);
   }
 
   @Test
@@ -109,7 +110,7 @@ public class ProjectLinksTest {
     testLink.getName().should(text("Test"));
     testLink.getType().shouldNot(Condition.exist);
     testLink.getUrl().should(text("http://example.com/test"));
-    testLink.getDeleteButton().shouldBe(Condition.visible);
+    testLink.getDeleteButton().shouldBe(visible);
   }
 
   @Test
@@ -122,9 +123,8 @@ public class ProjectLinksTest {
     ProjectLinkItem customLink = links.get(1);
 
     customLink.getDeleteButton().click();
-    $("#delete-link-confirm")
-      .shouldBe(Condition.visible)
-      .click();
+    $(".modal").shouldBe(visible);
+    $(".modal button[type=\"submit\"]").click();
 
     page.getLinks().shouldHaveSize(1);
   }