]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8452 open the last state of projects page (#1760)
authorStas Vilchik <stas-vilchik@users.noreply.github.com>
Thu, 9 Mar 2017 16:31:40 +0000 (17:31 +0100)
committerGitHub <noreply@github.com>
Thu, 9 Mar 2017 16:31:40 +0000 (17:31 +0100)
15 files changed:
it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java
it/it-tests/src/test/java/it/uiExtension/UiExtensionsTest.java
it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html
server/sonar-web/src/main/js/app/components/Landing.js
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.js
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
server/sonar-web/src/main/js/apps/projects/routes.js
server/sonar-web/src/main/js/apps/projects/utils.js [new file with mode: 0644]

index c2eb20d497c5c110652498bfa3ac5ed701622231..70c9f708f4a24ce23cbb8e6730cd6764d6ecfe60 100644 (file)
@@ -78,4 +78,27 @@ public class ProjectsPageTest {
     page.shouldHaveTotal(1);
   }
 
+  @Test
+  public void should_open_default_page() {
+    // default page can be "All Projects" or "Favorite Projects" depending on your last choice
+    ProjectsPage page = nav.openProjects();
+
+      // all projects for anonymous user
+    page.shouldHaveTotal(2).shouldDisplayAllProjects();
+
+    // all projects by default for logged in user
+    page = nav.logIn().asAdmin().openProjects();
+    page.shouldHaveTotal(2).shouldDisplayAllProjects();
+
+    // select favorite
+    page.selectFavoriteProjects();
+    page = nav.openProjects();
+    page.shouldHaveTotal(0).shouldDisplayFavoriteProjects();
+
+    // select all
+    page.selectAllProjects();
+    page = nav.openProjects();
+    page.shouldHaveTotal(2).shouldDisplayAllProjects();
+  }
+
 }
index 2e7e2aa20ce6ce74bf66b74c7e14be4b3fcf827a..867f8d75afe7db1985b04e2efd2076500b05462c 100644 (file)
@@ -81,9 +81,8 @@ public class UiExtensionsTest {
 
   @Test
   public void project_page() {
-    nav.open("/projects");
+    nav.open("/dashboard?id=sample");
 
-    $(By.linkText("Sample")).click();
     $("#component-navigation-more").click();
     $(By.linkText("Project Page")).click();
 
@@ -93,9 +92,8 @@ public class UiExtensionsTest {
 
   @Test
   public void project_admin_page() {
-    nav.logIn().asAdmin().open("/projects");
+    nav.logIn().asAdmin().open("/dashboard?id=sample");
 
-    $(By.linkText("Sample")).click();
     $("#component-navigation-admin").click();
     $(By.linkText("Project Admin Page")).click();
 
index ac7aa7379574b2add41c10819baa4f92b4436acc..0cc1b638a117a137348c7ee5f4f684ac9561935e 100644 (file)
@@ -27,6 +27,8 @@ import static com.codeborne.selenide.Condition.text;
 import static com.codeborne.selenide.Condition.visible;
 import static com.codeborne.selenide.Selenide.$;
 import static com.codeborne.selenide.Selenide.$$;
+import static com.codeborne.selenide.WebDriverRunner.url;
+import static org.assertj.core.api.Assertions.assertThat;
 
 public class ProjectsPage {
 
@@ -57,4 +59,24 @@ public class ProjectsPage {
     $("#projects-total").shouldHave(text(String.valueOf(total)));
     return this;
   }
+
+  public ProjectsPage shouldDisplayAllProjects() {
+    assertThat(url()).endsWith("/projects");
+    return this;
+  }
+
+  public ProjectsPage shouldDisplayFavoriteProjects() {
+    assertThat(url()).endsWith("/projects/favorite");
+    return this;
+  }
+
+  public ProjectsPage selectAllProjects() {
+    $("#all-projects").click();
+    return shouldDisplayAllProjects();
+  }
+
+  public ProjectsPage selectFavoriteProjects() {
+    $("#favorite-projects").click();
+    return shouldDisplayFavoriteProjects();
+  }
 }
index 653ec010e239ade736757324088599bb0792cc03..9345f6e3f75a4c970e040084d8bafe72ddd03acc 100644 (file)
        <td>css=.js-user-authenticated</td>
        <td></td>
 </tr>
-<tr>
-       <td>open</td>
-       <td>/projects</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>css=.page-header</td>
-       <td>*1 projects*</td>
-</tr>
-<tr>
-       <td>assertTextPresent</td>
-       <td>Sample</td>
-       <td></td>
-</tr>
 <tr>
        <td>open</td>
        <td>/project/deletion?id=sample</td>
 </tr>
 <tr>
        <td>open</td>
-       <td>/projects</td>
+       <td>/dashboard?id=sample</td>
        <td></td>
 </tr>
 <tr>
-       <td>waitForText</td>
-       <td>css=.page-header</td>
-       <td>*0 projects*</td>
-</tr>
-<tr>
-       <td>assertTextNotPresent</td>
-       <td>content</td>
-       <td>*Sample*</td>
+       <td>waitForElementPresent</td>
+       <td>css=.process-spinner-failed</td>
+       <td></td>
 </tr>
 </tbody>
 </table>
index 2204c53b8292e43c04d508a83555563bc8f8e565..7b4ba1721dc62ef899d982d0934e41dddcd8f17a 100644 (file)
@@ -31,7 +31,7 @@ class Landing extends React.Component {
   componentDidMount () {
     const { currentUser, router } = this.props;
     if (currentUser.isLoggedIn) {
-      router.replace('/projects/favorite');
+      router.replace('/projects');
     } else {
       router.replace('/about');
     }
index bb099b964c10b2cad80e27663a4ebc68a223cbf4..a1f73443ea2a7afece21a69d38f219de373248e6 100644 (file)
@@ -40,7 +40,7 @@ class GlobalNavBranding extends React.Component {
   }
 
   render () {
-    const homeController = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/about';
+    const homeController = this.props.currentUser.isLoggedIn ? '/projects' : '/about';
     const homeLinkClassName = 'navbar-brand' + (this.props.customLogoUrl ? ' navbar-brand-custom' : '');
     return (
         <div className="navbar-header">
index 3c3272a339c635675c052f5a168cdff4c36db291..3693e493fcf3aa98c4758dcbe81f37c362e18f8c 100644 (file)
@@ -38,10 +38,9 @@ export default class GlobalNavMenu extends React.Component {
   }
 
   renderProjects () {
-    const pathname = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/projects';
     return (
         <li>
-          <Link to={{ pathname }} activeClassName="active">
+          <Link to="/projects" activeClassName="active">
             {translate('projects.page')}
           </Link>
         </li>
index f212db31949d7c02fc343c84a448586d20e9904b..2f2dc6dd0f3c304f95df50fcacb64bc6881307ba 100644 (file)
@@ -6,11 +6,7 @@ exports[`test should work with extensions 1`] = `
       activeClassName="active"
       onlyActiveOnIndex={false}
       style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-        }
-      }>
+      to="/projects">
       projects.page
     </Link>
   </li>
index d80c910db60317b5d1bcf34579d36f6743b1a4db..1b71ee0f02d0106846d65c73dde5fb07c69a174b 100644 (file)
@@ -49,7 +49,10 @@ class OrganizationProjects extends React.Component {
         <div id="projects-page" className="page page-limited">
           <Helmet title={translate('projects.page')} titleTemplate="%s - SonarQube"/>
           <PageHeaderContainer organization={this.props.organization}/>
-          <AllProjectsContainer location={this.props.location} organization={this.props.organization}/>
+          <AllProjectsContainer
+            isFavorite={false}
+            location={this.props.location}
+            organization={this.props.organization}/>
         </div>
     );
   }
index f7fbe17c899300ae12966b01d2f04cb8355bc985..b4d8d44a06c83ff70b4435a6350bd56f92316667 100644 (file)
@@ -22,6 +22,7 @@ import ProjectsListContainer from './ProjectsListContainer';
 import ProjectsListFooterContainer from './ProjectsListFooterContainer';
 import PageSidebar from './PageSidebar';
 import { parseUrlQuery } from '../store/utils';
+import { saveAll, saveFavorite } from '../utils';
 
 export default class AllProjects extends React.Component {
   static propTypes = {
@@ -35,6 +36,14 @@ export default class AllProjects extends React.Component {
   };
 
   componentDidMount () {
+    // do not touch organization-level page
+    if (!this.props.organization) {
+      if (this.props.isFavorite) {
+        saveFavorite();
+      } else {
+        saveAll();
+      }
+    }
     this.handleQueryChange();
   }
 
index 1230fca10d31757e3d689ebd5776c05382e4136e..52398e9b34031369306624cf5d079a7d13d3ebcf 100644 (file)
 import { connect } from 'react-redux';
 import AllProjects from './AllProjects';
 import { fetchProjects } from '../store/actions';
-import { getCurrentUser } from '../../../store/rootReducer';
 
-const mapStateToProps = state => ({
-  user: getCurrentUser(state),
-  isFavorite: false
-});
-
-export default connect(
-  mapStateToProps,
-    { fetchProjects }
-)(AllProjects);
+export default connect(null, { fetchProjects })(AllProjects);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js
new file mode 100644 (file)
index 0000000..eb487b5
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router';
+import AllProjectsContainer from './AllProjectsContainer';
+import { getCurrentUser } from '../../../store/rootReducer';
+import { shouldRedirectToFavorite } from '../utils';
+
+class DefaultPageSelector extends React.PureComponent {
+  props: {
+    currentUser: { isLoggedIn: boolean },
+    location: {},
+    router: { replace: (path: string) => void }
+  };
+
+  componentDidMount () {
+    if (shouldRedirectToFavorite(this.props.currentUser)) {
+      this.props.router.replace('/projects/favorite');
+    }
+  }
+
+  render () {
+    if (shouldRedirectToFavorite(this.props.currentUser)) {
+      return null;
+    } else {
+      return (
+        <AllProjectsContainer
+          isFavorite={false}
+          location={this.props.location}
+          user={this.props.currentUser}/>
+      );
+    }
+  }
+}
+
+const mapStateToProps = state => ({
+  currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(withRouter(DefaultPageSelector));
index 5bc0311bb23f4f473b3998c1f2a067a430c2265b..ebf2305ab13a2a8d2e0505f63dd78d220c4d49b0 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import { IndexLink, Link } from 'react-router';
 import { translate } from '../../../helpers/l10n';
+import { saveAll } from '../utils';
 
 export default class FavoriteFilter extends React.Component {
   render () {
@@ -27,25 +28,34 @@ export default class FavoriteFilter extends React.Component {
       return null;
     }
 
-    const pathnameForFavorite = this.props.organization ?
-        `/organizations/${this.props.organization.key}/projects/favorite` :
-        '/projects/favorite';
+    const pathnameForFavorite = this.props.organization
+      ? `/organizations/${this.props.organization.key}/projects/favorite`
+      : '/projects/favorite';
 
-    const pathnameForAll = this.props.organization ?
-        `/organizations/${this.props.organization.key}/projects` :
-        '/projects';
+    const pathnameForAll = this.props.organization
+      ? `/organizations/${this.props.organization.key}/projects`
+      : '/projects';
 
     return (
-        <div className="projects-sidebar pull-left text-center">
-          <div className="button-group">
-            <Link to={pathnameForFavorite} className="button" activeClassName="button-active">
-              {translate('my_favorites')}
-            </Link>
-            <IndexLink to={pathnameForAll} className="button" activeClassName="button-active">
-              {translate('all')}
-            </IndexLink>
-          </div>
+      <div className="projects-sidebar pull-left text-center">
+        <div className="button-group">
+          <Link
+            id="favorite-projects"
+            to={pathnameForFavorite}
+            className="button"
+            activeClassName="button-active">
+            {translate('my_favorites')}
+          </Link>
+          <IndexLink
+            id="all-projects"
+            to={pathnameForAll}
+            className="button"
+            activeClassName="button-active"
+            onClick={saveAll}>
+            {translate('all')}
+          </IndexLink>
         </div>
+      </div>
     );
   }
 }
index 4da139e7ad01a8822946871c80dd571e70843365..4ed8f72a50a12b15aa7ca939777f4e18364231b6 100644 (file)
 import React from 'react';
 import { Route, IndexRoute } from 'react-router';
 import App from './components/App';
-import AllProjectsContainer from './components/AllProjectsContainer';
+import DefaultPageSelector from './components/DefaultPageSelector';
 import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
 
 export default (
-    <Route component={App}>
-      <IndexRoute component={AllProjectsContainer}/>
-      <Route path="favorite" component={FavoriteProjectsContainer}/>
-    </Route>
+  <Route component={App}>
+    <IndexRoute component={DefaultPageSelector}/>
+    <Route path="favorite" component={FavoriteProjectsContainer}/>
+  </Route>
 );
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.js b/server/sonar-web/src/main/js/apps/projects/utils.js
new file mode 100644 (file)
index 0000000..598a8cf
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+// @flow
+const LOCALSTORAGE_KEY = 'sonarqube.projects.default';
+const LOCALSTORAGE_FAVORITE = 'favorite';
+const LOCALSTORAGE_ALL = 'all';
+
+const isFavoriteSet = (): boolean => {
+  const setting = window.localStorage.getItem(LOCALSTORAGE_KEY);
+  return setting === LOCALSTORAGE_FAVORITE;
+};
+
+export const shouldRedirectToFavorite = (currentUser: { isLoggedIn: boolean }) => {
+  return currentUser.isLoggedIn && isFavoriteSet();
+};
+
+const save = (value: string) => {
+  try {
+    window.localStorage.setItem(LOCALSTORAGE_KEY, value);
+  } catch (e) {
+    // usually that means the storage is full
+    // just do nothing in this case
+  }
+};
+
+export const saveAll = () => save(LOCALSTORAGE_ALL);
+
+export const saveFavorite = () => save(LOCALSTORAGE_FAVORITE);