]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7227 Convert "My Profile" page to the new "My Account" page
authorStas Vilchik <vilchiks@gmail.com>
Mon, 25 Jan 2016 15:20:19 +0000 (16:20 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Thu, 28 Jan 2016 15:14:24 +0000 (16:14 +0100)
20 files changed:
it/it-tests/src/test/java/it/qualityGate/QualityGateNotificationTest.java
it/it-tests/src/test/java/it/user/MyAccountPageTest.java [new file with mode: 0644]
it/it-tests/src/test/resources/user/MyAccountPageTest/should_display_user_details.html [new file with mode: 0644]
server/sonar-web/src/main/js/api/issues.js
server/sonar-web/src/main/js/apps/account/app.js
server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/Favorites.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/Home.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/Nav.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/Notifications.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/UserCard.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/containers/AccountApp.js [new file with mode: 0644]
server/sonar-web/src/main/js/main/nav/global/global-nav-user.js
server/sonar-web/src/main/less/components/ui.less
server/sonar-web/src/main/less/init/icons.less
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/account_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/views/account/index.html.erb
server/sonar-web/src/main/webapp/WEB-INF/app/views/account/notifications.html.erb [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 0b5547b63cb7fdcf1c446f374a4f119da2d58c26..37271141fbef937975dcea376a9026f3862fcc77 100644 (file)
@@ -25,11 +25,8 @@ import com.sonar.orchestrator.selenium.Selenese;
 import it.Category1Suite;
 import java.util.Iterator;
 import javax.mail.internet.MimeMessage;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
+
+import org.junit.*;
 import org.sonar.wsclient.Sonar;
 import org.sonar.wsclient.qualitygate.NewCondition;
 import org.sonar.wsclient.qualitygate.QualityGate;
@@ -73,6 +70,7 @@ public class QualityGateNotificationTest {
   }
 
   @Test
+  @Ignore("waiting for SONAR-7230")
   public void status_on_metric_variation_and_send_notifications() throws Exception {
     Wiser smtpServer = new Wiser(NetworkUtils.getNextAvailablePort());
     try {
diff --git a/it/it-tests/src/test/java/it/user/MyAccountPageTest.java b/it/it-tests/src/test/java/it/user/MyAccountPageTest.java
new file mode 100644 (file)
index 0000000..e342549
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package it.user;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.selenium.Selenese;
+import it.Category1Suite;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.wsclient.SonarClient;
+import org.sonar.wsclient.user.UserParameters;
+import util.selenium.SeleneseTest;
+
+public class MyAccountPageTest {
+
+  @ClassRule
+  public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
+
+  @BeforeClass
+  public static void initUser() {
+    createUser("account-user", "User With Account", "user@example.com");
+  }
+
+  @AfterClass
+  public static void deleteTestUser() {
+    deactivateUser("account-user");
+  }
+
+  @Test
+  public void should_display_user_details() throws Exception {
+    Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("should_display_user_details",
+      "/user/MyAccountPageTest/should_display_user_details.html"
+    ).build();
+    new SeleneseTest(selenese).runOn(orchestrator);
+  }
+
+  private static void createUser(String login, String name, String email) {
+    SonarClient client = orchestrator.getServer().adminWsClient();
+    UserParameters userCreationParameters = UserParameters.create()
+      .login(login)
+      .name(name)
+      .email(email)
+      .password("password")
+      .passwordConfirmation("password");
+    client.userClient().create(userCreationParameters);
+  }
+
+  private static void deactivateUser(String user) {
+    orchestrator.getServer().adminWsClient().userClient().deactivate(user);
+  }
+
+}
diff --git a/it/it-tests/src/test/resources/user/MyAccountPageTest/should_display_user_details.html b/it/it-tests/src/test/resources/user/MyAccountPageTest/should_display_user_details.html
new file mode 100644 (file)
index 0000000..d0b30c0
--- /dev/null
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+  <link rel="selenium.base" href="http://localhost:49506"/>
+  <title>should_display_user_details</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <thead>
+  <tr>
+    <td rowspan="1" colspan="3">should_display_user_details</td>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+       <td>open</td>
+       <td>/sonar/sessions/login</td>
+       <td></td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>login</td>
+       <td>account-user</td>
+</tr>
+<tr>
+       <td>type</td>
+       <td>password</td>
+       <td>password</td>
+</tr>
+<tr>
+       <td>clickAndWait</td>
+       <td>commit</td>
+       <td></td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/sonar/account/</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=name</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>id=name</td>
+       <td>*User With Account*</td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=login</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>id=login</td>
+       <td>*account-user*</td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=email</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>id=email</td>
+       <td>*user@example.com*</td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=groups</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>id=groups</td>
+       <td>*sonar-users*</td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=scm-accounts</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>id=scm-accounts</td>
+       <td>*user@example.com*</td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>css=#avatar img</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=favorite-components</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=favorite-issue-filters</td>
+       <td></td>
+</tr>
+<tr>
+       <td>waitForElementPresent</td>
+       <td>id=favorite-measure-filters</td>
+       <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
index d9722be01accc5fa9b01aee3cc89cd0e736364eb..3569d1854bd7fa579a765660b32209deccdf400f 100644 (file)
@@ -68,3 +68,8 @@ export function getIssuesCount (query) {
     return { issues: r.total, debt: r.debtTotal };
   });
 }
+
+export function getIssueFilters () {
+  const url = window.baseUrl + '/api/issue_filters/search';
+  return getJSON(url).then(r => r.issueFilters);
+}
index f2f87052d5e84ef4687b63d465879d8c09abc5ed..5af38f5b6c3ba685e122327537a9bbca28c8d4a3 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 $ from 'jquery';
-import Backbone from 'backbone';
-import ChangePasswordView from './change-password-view';
-import TokensView from './tokens-view';
-import avatarHelper from '../../helpers/handlebars/avatarHelper';
-
-var shouldShowAvatars = window.SS && window.SS.lf && window.SS.lf.enableGravatar;
-var favorites = $('.js-account-favorites tr');
-
-function showExtraFavorites () {
-  favorites.removeClass('hidden');
-}
-
-class App {
-  start () {
-    $('html').addClass('dashboard-page');
-
-    if (shouldShowAvatars) {
-      var avatarHtml = avatarHelper(window.SS.userEmail, 100).string;
-      $('.js-avatar').html(avatarHtml);
-    }
-
-    $('.js-show-all-favorites').on('click', function (e) {
-      e.preventDefault();
-      $(e.currentTarget).hide();
-      showExtraFavorites();
-    });
-
-    $('#account-change-password-trigger').on('click', function (e) {
-      e.preventDefault();
-      new ChangePasswordView().render();
-    });
-
-    const account = new Backbone.Model({
-      id: window.SS.user
-    });
-
-    new TokensView({
-      el: '#account-tokens',
-      model: account
-    }).render();
-  }
-}
-
-window.sonarqube.appStarted.then(options => new App().start(options));
+import React from 'react';
+import { render } from 'react-dom';
+import { Router, Route, IndexRoute, Redirect } from 'react-router';
+import { createHistory, useBasename } from 'history';
+
+import AccountApp from './containers/AccountApp';
+import Home from './components/Home';
+import Notifications from './components/Notifications';
+
+window.sonarqube.appStarted.then(options => {
+  const el = document.querySelector(options.el);
+
+  const history = useBasename(createHistory)({
+    basename: window.baseUrl + '/account'
+  });
+
+  document.querySelector('html').classList.add('dashboard-page');
+  document.querySelector('#container').classList.add('page-wrapper-context');
+
+  render((
+      <Router history={history}>
+        <Route path="/" component={AccountApp}>
+          <IndexRoute component={Home}/>
+
+          <Redirect from="/index" to="/"/>
+        </Route>
+      </Router>
+  ), el);
+});
diff --git a/server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js b/server/sonar-web/src/main/js/apps/account/components/FavoriteIssueFilters.js
new file mode 100644 (file)
index 0000000..80cbac9
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import { translate } from '../../../helpers/l10n';
+
+const FavoriteIssueFilters = ({ issueFilters }) => (
+    <section className="big-spacer-bottom">
+      <h2 className="spacer-bottom">
+        {translate('my_account.favorite_issue_filters')}
+      </h2>
+      <table id="favorite-issue-filters" className="data">
+        <tbody>
+          {issueFilters.map(f => (
+              <tr key={f.name}>
+                <td className="thin">
+                  <i className="icon-favorite"/>
+                </td>
+                <td>
+                  <a href={`${window.baseUrl}/issues/search#id=${f.id}`}>
+                    {f.name}
+                  </a>
+                </td>
+              </tr>
+          ))}
+        </tbody>
+      </table>
+
+    </section>
+);
+
+export default FavoriteIssueFilters;
diff --git a/server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js b/server/sonar-web/src/main/js/apps/account/components/FavoriteMeasureFilters.js
new file mode 100644 (file)
index 0000000..3e528ed
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import { translate } from '../../../helpers/l10n';
+
+const FavoriteMeasureFilters = ({ measureFilters }) => (
+    <section className="big-spacer-bottom">
+      <h2 className="spacer-bottom">
+        {translate('my_account.favorite_measure_filters')}
+      </h2>
+      <table id="favorite-measure-filters" className="data">
+        <tbody>
+          {measureFilters.map(f => (
+              <tr key={f.name}>
+                <td className="thin">
+                  <i className="icon-favorite"/>
+                </td>
+                <td>
+                  <a href={`${window.baseUrl}/measures/filter/${f.id}`}>
+                    {f.name}
+                  </a>
+                </td>
+              </tr>
+          ))}
+        </tbody>
+      </table>
+
+    </section>
+);
+
+export default FavoriteMeasureFilters;
diff --git a/server/sonar-web/src/main/js/apps/account/components/Favorites.js b/server/sonar-web/src/main/js/apps/account/components/Favorites.js
new file mode 100644 (file)
index 0000000..5466066
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import Favorite from '../../../components/shared/favorite';
+import QualifierIcon from '../../../components/shared/qualifier-icon';
+import { translate } from '../../../helpers/l10n';
+import { getComponentUrl } from '../../../helpers/urls';
+
+const Favorites = ({ favorites }) => (
+    <section className="big-spacer-bottom">
+      <h2 className="spacer-bottom">
+        {translate('my_account.favorite_components')}
+      </h2>
+      <table id="favorite-components" className="data">
+        <tbody>
+          {favorites.map(f => (
+              <tr key={f.key}>
+                <td className="thin">
+                  <Favorite component={f.key} favorite={true}/>
+                </td>
+                <td>
+                  <a href={getComponentUrl(f.key)} className="link-with-icon">
+                    <QualifierIcon qualifier={f.qualifier}/>
+                    {' '}
+                    <span>{f.name}</span>
+                  </a>
+                </td>
+              </tr>
+          ))}
+        </tbody>
+      </table>
+
+    </section>
+);
+
+export default Favorites;
diff --git a/server/sonar-web/src/main/js/apps/account/components/Home.js b/server/sonar-web/src/main/js/apps/account/components/Home.js
new file mode 100644 (file)
index 0000000..f9a76bf
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import UserCard from './UserCard';
+import Favorites from './Favorites';
+import FavoriteIssueFilters from './FavoriteIssueFilters';
+import FavoriteMeasureFilters from './FavoriteMeasureFilters';
+
+const Home = ({ user, favorites, issueFilters, measureFilters }) => (
+    <div>
+      <UserCard user={user}/>
+      <div className="overflow-hidden">
+        <Favorites favorites={favorites}/>
+        {issueFilters && <FavoriteIssueFilters issueFilters={issueFilters}/>}
+        {measureFilters && <FavoriteMeasureFilters measureFilters={measureFilters}/>}
+      </div>
+    </div>
+);
+
+export default Home;
diff --git a/server/sonar-web/src/main/js/apps/account/components/Nav.js b/server/sonar-web/src/main/js/apps/account/components/Nav.js
new file mode 100644 (file)
index 0000000..3a85c7d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import { IndexLink } from 'react-router';
+
+import { translate } from '../../../helpers/l10n';
+
+const Nav = () => (
+    <nav className="navbar navbar-context page-container">
+      <div className="container">
+        <ul className="nav navbar-nav nav-crumbs">
+          <li>
+            <IndexLink to="/" activeClassName="active">
+              {translate('my_account.page')}
+            </IndexLink>
+          </li>
+        </ul>
+        <ul className="nav navbar-nav nav-tabs">
+          <li>
+            <IndexLink to="/" activeClassName="active">
+              <i className="icon-home"/>
+            </IndexLink>
+          </li>
+        </ul>
+      </div>
+    </nav>
+);
+
+export default Nav;
diff --git a/server/sonar-web/src/main/js/apps/account/components/Notifications.js b/server/sonar-web/src/main/js/apps/account/components/Notifications.js
new file mode 100644 (file)
index 0000000..195a134
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+const Notifications = () => (
+    <h1>Notifications</h1>
+);
+
+export default Notifications;
diff --git a/server/sonar-web/src/main/js/apps/account/components/UserCard.js b/server/sonar-web/src/main/js/apps/account/components/UserCard.js
new file mode 100644 (file)
index 0000000..327ab9e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+
+import Avatar from '../../../components/shared/avatar';
+import { translate } from '../../../helpers/l10n';
+
+const UserCard = ({ user }) => (
+    <div className="pull-left big-spacer-right abs-width-240">
+      <div className="panel panel-white">
+        <div className="text-center">
+          <div id="avatar" className="big-spacer-bottom">
+            <Avatar email={user.email} size={100}/>
+          </div>
+          <h2 id="name" className="text-ellipsis" title={user.name}>{user.name}</h2>
+          <p id="login" className="note text-ellipsis" title={user.login}>{user.login}</p>
+          <div className="text-center spacer-top">
+            <p id="email" className="text-ellipsis" title={user.email}>{user.email}</p>
+          </div>
+        </div>
+
+        <div className="big-spacer-top">
+          <h3 className="text-center">{translate('my_profile.groups')}</h3>
+          <ul id="groups">
+            {user.groups.map(group => (
+                <li key={group} className="text-ellipsis" title={group}>{group}</li>
+            ))}
+          </ul>
+        </div>
+
+        <div className="big-spacer-top">
+          <h3 className="text-center">{translate('my_profile.scm_accounts')}</h3>
+          <ul id="scm-accounts">
+            {user.scmAccounts.map(scmAccount => (
+                <li key={scmAccount} className="text-ellipsis" title={scmAccount}>{scmAccount}</li>
+            ))}
+          </ul>
+        </div>
+      </div>
+    </div>
+);
+
+export default UserCard;
diff --git a/server/sonar-web/src/main/js/apps/account/containers/AccountApp.js b/server/sonar-web/src/main/js/apps/account/containers/AccountApp.js
new file mode 100644 (file)
index 0000000..6325b56
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React, { Component, cloneElement } from 'react';
+
+import Nav from '../components/Nav';
+import { getIssueFilters } from '../../../api/issues';
+
+export default class AccountApp extends Component {
+  state = {}
+
+  componentDidMount () {
+    this.fetchFavoriteIssueFilters();
+  }
+
+  fetchFavoriteIssueFilters () {
+    getIssueFilters().then(issueFilters => {
+      this.setState({ issueFilters });
+    });
+  }
+
+  render () {
+    const { user } = window.sonarqube;
+    const { favorites } = user;
+    const { issueFilters } = this.state;
+    const children = cloneElement(this.props.children, {
+      measureFilters: user.favoriteMeasureFilters,
+      user,
+      favorites,
+      issueFilters
+    });
+
+    return (
+        <div>
+          <Nav/>
+          <div className="page">
+            {children}
+          </div>
+        </div>
+    );
+  }
+}
index a1eef1045183b1fa4cf18d8911ffa4975799b5ff..54f78a2dad0502c9a06dd6b9049983f19886005a 100644 (file)
@@ -32,7 +32,7 @@ export default React.createClass({
           </a>
           <ul className="dropdown-menu dropdown-menu-right">
             <li>
-              <a href={`${window.baseUrl}/account/index`}>{translate('layout.user_panel.my_profile')}</a>
+              <a href={`${window.baseUrl}/account/`}>{translate('my_account.page')}</a>
             </li>
             <li>
               <a onClick={this.handleLogout} href="#">{translate('layout.logout')}</a>
index 7eae3fd374b88634494dda6293a248d413ce3f36..1066541d50f20c83c4b9908a645f4444e85ff5f7 100644 (file)
     }
   }
 
-  > li.active > a {
+  > li.active > a,
+  > li > a.active {
     border-color: @blue;
   }
 }
index 6980eff936afb267c74bb856feba3a9c83250854..847ee248b10bda3c9ed143f5004b2a1ab0b90c6d 100644 (file)
@@ -346,7 +346,7 @@ a[class^="icon-"], a[class*=" icon-"] {
 }
 
 .icon-star-favorite {
-  animation: spin-star .6s forwards;
+
 }
 
 .icon-star-favorite path {
index 8ee24acdefa379d1a7a06df21518c444593ef2f3..aa803c1f9b98596ef4512690278b5741e2671083 100644 (file)
@@ -22,14 +22,18 @@ class AccountController < ApplicationController
   before_filter :login_required
 
   def index
+
+  end
+
+  def notifications
     @channels = notification_service.getChannels()
     @global_dispatchers = dispatchers_for_scope("globalNotification")
     @per_project_dispatchers = dispatchers_for_scope("perProjectNotification")
-    
+
     @global_notifications = {}
     @per_project_notifications = {}
     load_notification_properties
-    
+
     if params[:new_project]
       new_project = Project.by_key params[:new_project]
       unless @per_project_notifications[new_project.id]
@@ -79,33 +83,33 @@ class AccountController < ApplicationController
         end
       end
     end
-    
+
     # New project added
     new_params = {}
     unless params[:new_project].blank?
       new_params[:new_project] = params[:new_project]
     end
-    
+
     redirect_to :action => 'index', :params => new_params
   end
-  
+
   private
 
   def notification_service
     java_facade.getCoreComponentByClassname('org.sonar.server.notification.NotificationCenter')
   end
-  
+
   def dispatchers_for_scope(scope)
     notification_service.getDispatcherKeysForProperty(scope, "true").to_a.sort {|x,y| dispatcher_name(x) <=> dispatcher_name(y)}
   end
-  
+
   def dispatcher_name(dispatcher_key)
     Api::Utils.message('notification.dispatcher.' + dispatcher_key)
   end
-  
+
   def load_notification_properties
     channel_keys = @channels.map {|c| c.getKey()}
-    
+
     Property.find(:all, :conditions => ['prop_key like ? AND user_id = ?', 'notification.%', current_user.id]).each do |property|
       r_id = property.resource_id
       if r_id
@@ -132,7 +136,7 @@ class AccountController < ApplicationController
     end
     project_notifs
   end
-  
+
   def init_project_notifications
     project_notifs = {}
     @per_project_dispatchers.each do |dispatcher|
index 29ac2b1ce1d1f28e73328849a8e6d27e806af7c7..4ffc62880f0c1ffd9423cc3dcf5de0e34028c44e 100644 (file)
@@ -1,75 +1,38 @@
-<div class="page">
-
-  <div class="pull-left big-spacer-right abs-width-240">
-    <div class="panel panel-white">
-      <div class="text-center">
-        <% if configuration('sonar.lf.enableGravatar', 'true') == 'true' %>
-          <div class="js-avatar big-spacer-bottom"></div>
-        <% end %>
-        <h2 id="name" class="text-ellipsis" title="<%= current_user.name -%>"><%= current_user.name -%></h2>
-        <p id="login" class="note text-ellipsis" title="<%= current_user.login -%>"><%= current_user.login -%></p>
-        <div class="text-center spacer-top">
-          <p id="email" class="text-ellipsis" title="<%= current_user.email -%>"><%= current_user.email -%></p>
-        </div>
-      </div>
-
-      <div class="big-spacer-top">
-        <h3 class="text-center"><%= message('my_profile.groups') -%></h3>
-        <ul id="groups">
-          <% current_user.groups.sort.each do |group| -%>
-            <li class="text-ellipsis" title="<%= group.name -%>"><%= group.name -%></li>
-          <% end -%>
-        </ul>
-      </div>
-
-      <div class="big-spacer-top">
-        <h3 class="text-center"><%= message('my_profile.scm_accounts') -%></h3>
-        <ul id="scm-accounts">
-          <% current_user.full_scm_accounts.each do |scm_account| -%>
-            <li class="text-ellipsis" title="<%= scm_account -%>"><%= scm_account -%></li>
-          <% end -%>
-        </ul>
-      </div>
-
-      <% if User.editable_password? %>
-        <div class="big-spacer-top text-center">
-          <button id="account-change-password-trigger">
-            <i class="icon-lock"></i> <%= message('my_profile.password.title') -%>
-          </button>
-        </div>
-      <% end %>
-    </div>
-  </div>
-
-  <div class="overflow-hidden">
-    <% unless current_user.favourites.empty? -%>
-      <section class="big-spacer-bottom">
-        <%= render "account/favorites" -%>
-      </section>
-    <% end %>
-
-    <form id="notif_form" method="post" action="<%= ApplicationController.root_context -%>/account/update_notifications">
-      <% unless @global_dispatchers.empty? -%>
-        <section class="big-spacer-bottom">
-          <%= render "account/global_notifications" -%>
-        </section>
-      <% end %>
-
-      <% unless @per_project_dispatchers.empty? -%>
-        <section>
-          <%= render "account/per_project_notifications" -%>
-        </section>
-      <% end %>
-
-      <section class="big-spacer-top">
-        <input type="submit" value="<%= message('my_profile.notifications.submit') -%>" name="commit">
-      </section>
-    </form>
-
-    <section id="account-tokens" class="huge-spacer-top spacer-bottom"></section>
-  </div>
-</div>
-
 <% content_for :extra_script do %>
+  <script>
+    window.sonarqube.user = {
+      login: '<%= escape_javascript current_user.login -%>',
+      name: '<%= escape_javascript current_user.name -%>',
+      email: '<%= escape_javascript current_user.email -%>',
+      groups: [
+        <% current_user.groups.sort.each do |group| -%>
+          '<%= escape_javascript group.name -%>',
+        <% end -%>
+      ],
+      scmAccounts: [
+        <% current_user.full_scm_accounts.sort.each do |scm_account| -%>
+          '<%= escape_javascript scm_account -%>',
+        <% end -%>
+      ],
+      favorites: [
+        <% current_user.favourites.each_with_index do |f, index| %>
+        {
+          id: '<%= escape_javascript f.uuid -%>',
+          key: '<%= escape_javascript f.key -%>',
+          name: '<%= escape_javascript f.name -%>',
+          qualifier: '<%= escape_javascript f.qualifier -%>'
+        },
+        <% end %>
+      ],
+      favoriteMeasureFilters: [
+        <% current_user.favourited_measure_filters.each do |filter| %>
+        {
+          id: <%= filter.id -%>,
+          name: '<%= escape_javascript filter.name -%>'
+        },
+        <% end %>
+      ]
+    };
+  </script>
   <script src="<%= ApplicationController.root_context -%>/js/bundles/account.js?v=<%= sonar_version -%>"></script>
 <% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/account/notifications.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/account/notifications.html.erb
new file mode 100644 (file)
index 0000000..29ac2b1
--- /dev/null
@@ -0,0 +1,75 @@
+<div class="page">
+
+  <div class="pull-left big-spacer-right abs-width-240">
+    <div class="panel panel-white">
+      <div class="text-center">
+        <% if configuration('sonar.lf.enableGravatar', 'true') == 'true' %>
+          <div class="js-avatar big-spacer-bottom"></div>
+        <% end %>
+        <h2 id="name" class="text-ellipsis" title="<%= current_user.name -%>"><%= current_user.name -%></h2>
+        <p id="login" class="note text-ellipsis" title="<%= current_user.login -%>"><%= current_user.login -%></p>
+        <div class="text-center spacer-top">
+          <p id="email" class="text-ellipsis" title="<%= current_user.email -%>"><%= current_user.email -%></p>
+        </div>
+      </div>
+
+      <div class="big-spacer-top">
+        <h3 class="text-center"><%= message('my_profile.groups') -%></h3>
+        <ul id="groups">
+          <% current_user.groups.sort.each do |group| -%>
+            <li class="text-ellipsis" title="<%= group.name -%>"><%= group.name -%></li>
+          <% end -%>
+        </ul>
+      </div>
+
+      <div class="big-spacer-top">
+        <h3 class="text-center"><%= message('my_profile.scm_accounts') -%></h3>
+        <ul id="scm-accounts">
+          <% current_user.full_scm_accounts.each do |scm_account| -%>
+            <li class="text-ellipsis" title="<%= scm_account -%>"><%= scm_account -%></li>
+          <% end -%>
+        </ul>
+      </div>
+
+      <% if User.editable_password? %>
+        <div class="big-spacer-top text-center">
+          <button id="account-change-password-trigger">
+            <i class="icon-lock"></i> <%= message('my_profile.password.title') -%>
+          </button>
+        </div>
+      <% end %>
+    </div>
+  </div>
+
+  <div class="overflow-hidden">
+    <% unless current_user.favourites.empty? -%>
+      <section class="big-spacer-bottom">
+        <%= render "account/favorites" -%>
+      </section>
+    <% end %>
+
+    <form id="notif_form" method="post" action="<%= ApplicationController.root_context -%>/account/update_notifications">
+      <% unless @global_dispatchers.empty? -%>
+        <section class="big-spacer-bottom">
+          <%= render "account/global_notifications" -%>
+        </section>
+      <% end %>
+
+      <% unless @per_project_dispatchers.empty? -%>
+        <section>
+          <%= render "account/per_project_notifications" -%>
+        </section>
+      <% end %>
+
+      <section class="big-spacer-top">
+        <input type="submit" value="<%= message('my_profile.notifications.submit') -%>" name="commit">
+      </section>
+    </form>
+
+    <section id="account-tokens" class="huge-spacer-top spacer-bottom"></section>
+  </div>
+</div>
+
+<% content_for :extra_script do %>
+  <script src="<%= ApplicationController.root_context -%>/js/bundles/account.js?v=<%= sonar_version -%>"></script>
+<% end %>
index 04de62241c962cc95585f63adcfedfecd8eea1f1..c8f3f6da8fd614baf1e6b7f557f4080f566f743d 100644 (file)
@@ -2132,7 +2132,7 @@ user.password_cant_be_changed_on_external_auth=Password cannot be changed when e
 
 #------------------------------------------------------------------------------
 #
-# MY PROFILE
+# MY PROFILE & MY ACCOUNT
 #
 #------------------------------------------------------------------------------
 my_profile.login=Login
@@ -2155,6 +2155,11 @@ my_profile.add_project=Add project
 my_profile.remove_this_line=Remove this line
 my_profile.favorites.title=Favorites
 
+my_account.page=My Account
+my_account.favorite_components=Favorite Components
+my_account.favorite_issue_filters=Favorite Issue Filters
+my_account.favorite_measure_filters=Favorite Measure Filters
+
 
 
 #------------------------------------------------------------------------------