]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10081 Add Explore space for SonarCloud
authorStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 28 Nov 2017 13:32:23 +0000 (14:32 +0100)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 11 Dec 2017 17:00:33 +0000 (18:00 +0100)
86 files changed:
server/sonar-web/src/main/js/app/components/Landing.js [deleted file]
server/sonar-web/src/main/js/app/components/Landing.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/help/GlobalHelp.js
server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavExplore.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/explore/Explore.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/explore/ExploreIssues.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/IssuesPageSelector.tsx
server/sonar-web/src/main/js/apps/issues/components/App.js
server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
server/sonar-web/src/main/js/apps/issues/components/NoMyIssues.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx
server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.tsx
server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.tsx
server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/DuplicationsFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/MaintainabilityFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewCoverageFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewDuplicationsFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewLinesFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewMaintainabilityFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewReliabilityFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewSecurityFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/ReliabilityFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SecurityFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SizeFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/Filter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/MaintainabilityFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewCoverageFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewDuplicationsFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewMaintainabilityFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewReliabilityFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewSecurityFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/ReliabilityFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/styles.css
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/app/components/Landing.js b/server/sonar-web/src/main/js/app/components/Landing.js
deleted file mode 100644 (file)
index f5851ac..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withRouter } from 'react-router';
-import { connect } from 'react-redux';
-import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
-
-class Landing extends React.PureComponent {
-  static propTypes = {
-    currentUser: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).isRequired
-  };
-
-  componentDidMount() {
-    const { currentUser, router, onSonarCloud } = this.props;
-    if (currentUser.isLoggedIn) {
-      router.replace('/projects');
-    } else if (onSonarCloud && onSonarCloud.value === 'true') {
-      window.location = 'https://about.sonarcloud.io';
-    } else {
-      router.replace('/about');
-    }
-  }
-
-  render() {
-    return null;
-  }
-}
-
-const mapStateToProps = state => ({
-  currentUser: getCurrentUser(state),
-  onSonarCloud: getGlobalSettingValue(state, 'sonar.sonarcloud.enabled')
-});
-
-export default connect(mapStateToProps)(withRouter(Landing));
diff --git a/server/sonar-web/src/main/js/app/components/Landing.tsx b/server/sonar-web/src/main/js/app/components/Landing.tsx
new file mode 100644 (file)
index 0000000..792826f
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
+import { CurrentUser, isLoggedIn } from '../types';
+
+interface Props {
+  currentUser: CurrentUser;
+  onSonarCloud: boolean;
+}
+
+class Landing extends React.PureComponent<Props> {
+  static contextTypes = {
+    router: PropTypes.object.isRequired
+  };
+
+  componentDidMount() {
+    const { currentUser, onSonarCloud } = this.props;
+    if (isLoggedIn(currentUser)) {
+      this.context.router.replace('/projects');
+    } else if (onSonarCloud) {
+      window.location.href = 'https://about.sonarcloud.io';
+    } else {
+      this.context.router.replace('/about');
+    }
+  }
+
+  render() {
+    return null;
+  }
+}
+
+const mapStateToProps = (state: any) => {
+  const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
+  return {
+    currentUser: getCurrentUser(state),
+    onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true')
+  };
+};
+
+export default connect<Props>(mapStateToProps)(Landing);
index e6c60c0e8bff476b8ef0ee7e675ae2229ed68a0b..a4ec0a4c324875d1fa47b8d508c3d685c2abd20e 100644 (file)
@@ -32,7 +32,7 @@ type Props = {
   currentUser: { isLoggedIn: boolean },
   onClose: () => void,
   onTutorialSelect: () => void,
-  sonarCloud?: boolean
+  onSonarCloud?: boolean
 };
 */
 
@@ -62,7 +62,7 @@ export default class GlobalHelp extends React.PureComponent {
       case 'shortcuts':
         return <ShortcutsHelp />;
       case 'links':
-        return this.props.sonarCloud ? (
+        return this.props.onSonarCloud ? (
           <LinksHelpSonarCloud onClose={this.props.onClose} />
         ) : (
           <LinksHelp onClose={this.props.onClose} />
index 772448f1c3de76035eba088740f61765f2410c09..571b34fdc0a521fec1138b158ca20fc1a0a29cf0 100644 (file)
@@ -58,7 +58,7 @@ it('display special links page for SonarCloud', () => {
       currentUser={{ isLoggedIn: false }}
       onClose={jest.fn()}
       onTutorialSelect={jest.fn()}
-      sonarCloud={true}
+      onSonarCloud={true}
     />
   );
   clickOnSection(wrapper, 'links');
index 0284617f18a8a2902ed3721023a22b4c1cf57e03..8823ecd05336bdaa23e3ac0d9f5c67dd972029b6 100644 (file)
@@ -22,6 +22,7 @@ import React from 'react';
 import { connect } from 'react-redux';
 import GlobalNavBranding from './GlobalNavBranding';
 import GlobalNavMenu from './GlobalNavMenu';
+import GlobalNavExplore from './GlobalNavExplore';
 import GlobalNavUserContainer from './GlobalNavUserContainer';
 import Search from '../../search/Search';
 import GlobalHelp from '../../help/GlobalHelp';
@@ -38,8 +39,9 @@ import './GlobalNav.css';
 type Props = {
   appState: { organizationsEnabled: boolean },
   currentUser: { isLoggedIn: boolean, showOnboardingTutorial: boolean },
+  location: { pathname: string },
   skipOnboarding: () => void,
-  sonarCloud: boolean
+  onSonarCloud: boolean
 };
 */
 
@@ -129,12 +131,14 @@ class GlobalNav extends React.PureComponent {
           <GlobalNavUserContainer {...this.props} />
         </ul>
 
+        <GlobalNavExplore location={this.props.location} onSonarCloud={this.props.onSonarCloud} />
+
         {this.state.helpOpen && (
           <GlobalHelp
             currentUser={this.props.currentUser}
             onClose={this.closeHelp}
             onTutorialSelect={this.openOnboardingTutorial}
-            sonarCloud={this.props.sonarCloud}
+            onSonarCloud={this.props.onSonarCloud}
           />
         )}
 
@@ -152,7 +156,7 @@ const mapStateToProps = state => {
   return {
     currentUser: getCurrentUser(state),
     appState: getAppState(state),
-    sonarCloud: sonarCloudSetting != null && sonarCloudSetting.value === 'true'
+    onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
   };
 };
 
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavExplore.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavExplore.tsx
new file mode 100644 (file)
index 0000000..ea1495c
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 * as React from 'react';
+import { Link } from 'react-router';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+  location: { pathname: string };
+  onSonarCloud: boolean;
+}
+
+export default function GlobalNavExplore({ location, onSonarCloud }: Props) {
+  if (!onSonarCloud) {
+    return null;
+  }
+
+  const active = location.pathname.startsWith('explore');
+
+  return (
+    <ul className="global-navbar-menu spacer-right pull-right">
+      <li>
+        <Link to="/explore/projects" className={active ? 'active' : undefined}>
+          {translate('explore')}
+        </Link>
+      </li>
+    </ul>
+  );
+}
index 27702bf87f77678a4a755352b3e1abeefd72a98f..7cea4c635b2a2395b040470d8a4f667d706ad055 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Link } from 'react-router';
+import { isLoggedIn } from '../../../../app/types';
 import { translate } from '../../../../helpers/l10n';
 import { getQualityGatesUrl } from '../../../../helpers/urls';
 import { isMySet } from '../../../../apps/issues/utils';
@@ -31,7 +32,7 @@ export default class GlobalNavMenu extends React.PureComponent {
     location: PropTypes.shape({
       pathname: PropTypes.string.isRequired
     }).isRequired,
-    sonarCloud: PropTypes.bool
+    onSonarCloud: PropTypes.bool
   };
 
   static defaultProps = {
@@ -44,10 +45,14 @@ export default class GlobalNavMenu extends React.PureComponent {
   }
 
   renderProjects() {
+    if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) {
+      return null;
+    }
+
     return (
       <li>
         <Link to="/projects" activeClassName="active">
-          {this.props.sonarCloud ? translate('my_projects') : translate('projects.page')}
+          {this.props.onSonarCloud ? translate('my_projects') : translate('projects.page')}
         </Link>
       </li>
     );
@@ -64,9 +69,13 @@ export default class GlobalNavMenu extends React.PureComponent {
   }
 
   renderIssuesLink() {
+    if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) {
+      return null;
+    }
+
     const active = this.props.location.pathname === 'issues';
 
-    if (this.props.sonarCloud) {
+    if (this.props.onSonarCloud) {
       return (
         <li>
           <Link
index 3488208f5084bfd8a9e362b6426dcd23c509a698..0040ae22fa87dec5f8c2070fcf247715a2b8927c 100644 (file)
@@ -48,6 +48,9 @@ import componentMeasuresRoutes from '../../apps/component-measures/routes';
 import customMeasuresRoutes from '../../apps/custom-measures/routes';
 import groupsRoutes from '../../apps/groups/routes';
 import Issues from '../../apps/issues/components/AppContainer';
+import Explore from '../../apps/explore/Explore';
+import ExploreIssues from '../../apps/explore/ExploreIssues';
+import ExploreProjects from '../../apps/explore/ExploreProjects';
 import IssuesPageSelector from '../../apps/issues/IssuesPageSelector';
 import marketplaceRoutes from '../../apps/marketplace/routes';
 import metricsRoutes from '../../apps/metrics/routes';
@@ -164,6 +167,10 @@ const startReactApp = () => {
                   <Route path="account" childRoutes={accountRoutes} />
                   <Route path="coding_rules" childRoutes={codingRulesRoutes} />
                   <Route path="component" childRoutes={componentRoutes} />
+                  <Route path="explore" component={Explore}>
+                    <Route path="issues" component={ExploreIssues} />
+                    <Route path="projects" component={ExploreProjects} />
+                  </Route>
                   <Route
                     path="extension/:pluginKey/:extensionKey"
                     component={GlobalPageExtension}
diff --git a/server/sonar-web/src/main/js/apps/explore/Explore.tsx b/server/sonar-web/src/main/js/apps/explore/Explore.tsx
new file mode 100644 (file)
index 0000000..7cb0c77
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 * as React from 'react';
+import { Link } from 'react-router';
+import ContextNavBar from '../../components/nav/ContextNavBar';
+import NavBarTabs from '../../components/nav/NavBarTabs';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+  children: JSX.Element;
+}
+
+export default function Explore(props: Props) {
+  return (
+    <div id="explore">
+      <ContextNavBar id="explore-navigation" height={65}>
+        <div className="navbar-context-header">
+          <h1 className="display-inline-block">{translate('explore')}</h1>
+        </div>
+
+        <NavBarTabs>
+          <li>
+            <Link to="/explore/projects" activeClassName="active">
+              {translate('projects.page')}
+            </Link>
+          </li>
+          <li>
+            <Link
+              to={{ pathname: '/explore/issues', query: { resolved: 'false' } }}
+              activeClassName="active">
+              {translate('issues.page')}
+            </Link>
+          </li>
+        </NavBarTabs>
+      </ContextNavBar>
+
+      {props.children}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/explore/ExploreIssues.tsx b/server/sonar-web/src/main/js/apps/explore/ExploreIssues.tsx
new file mode 100644 (file)
index 0000000..9e8b31e
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 * as React from 'react';
+import AppContainer from '../issues/components/AppContainer';
+import { RawQuery } from '../../helpers/query';
+
+interface Props {
+  location: { pathname: string; query: RawQuery };
+}
+
+export default function ExploreIssues(props: Props) {
+  return <AppContainer myIssues={false} {...props} />;
+}
diff --git a/server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx b/server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx
new file mode 100644 (file)
index 0000000..c490e7d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 * as React from 'react';
+import AllProjectsContainer from '../projects/components/AllProjectsContainer';
+import { RawQuery } from '../../helpers/query';
+
+interface Props {
+  location: { pathname: string; query: RawQuery };
+}
+
+export default function ExploreProjects(props: Props) {
+  return <AllProjectsContainer isFavorite={false} {...props} />;
+}
index 4ff236be004e674c7a91ad1ed8c1b20324229cab..4208187fe97e86d20a5c971d87b622de12ab5fdf 100644 (file)
@@ -22,15 +22,20 @@ import { connect } from 'react-redux';
 import AppContainer from './components/AppContainer';
 import { CurrentUser, isLoggedIn } from '../../app/types';
 import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
+import { RawQuery } from '../../helpers/query';
 
 interface StateProps {
   currentUser: CurrentUser;
   onSonarCloud: boolean;
 }
 
-function IssuesPage({ currentUser, onSonarCloud, ...props }: StateProps) {
+interface Props extends StateProps {
+  location: { pathname: string; query: RawQuery };
+}
+
+function IssuesPage({ currentUser, location, onSonarCloud }: Props) {
   const myIssues = (isLoggedIn(currentUser) && onSonarCloud) || undefined;
-  return <AppContainer myIssues={myIssues} {...props} />;
+  return <AppContainer location={location} myIssues={myIssues} />;
 }
 
 const stateToProps = (state: any) => {
index 46cb1d239524464d08263a3a7a258ffc645e0f15..a7121ced22de51a1a78b95682ea995197e10d86e 100644 (file)
@@ -31,6 +31,7 @@ import IssuesList from './IssuesList';
 import ComponentBreadcrumbs from './ComponentBreadcrumbs';
 import IssuesSourceViewer from './IssuesSourceViewer';
 import BulkChangeModal from './BulkChangeModal';
+import NoMyIssues from './NoMyIssues';
 import ConciseIssuesList from '../conciseIssuesList/ConciseIssuesList';
 import ConciseIssuesListHeader from '../conciseIssuesList/ConciseIssuesListHeader';
 import * as actions from '../actions';
@@ -868,7 +869,8 @@ export default class App extends React.PureComponent {
           <ListFooter total={paging.total} count={issues.length} loadMore={this.fetchMoreIssues} />
         )}
 
-        {paging.total === 0 && <EmptySearch />}
+        {paging.total === 0 &&
+          (this.state.myIssues && !this.isFiltered() ? <NoMyIssues /> : <EmptySearch />)}
       </div>
     );
   }
index 7747e8aa148cc2a473e8954691e4ddb873d3735f..5bcb4a8f4e997fe7a0a9587e1a0b0a1079d1d196 100644 (file)
@@ -88,6 +88,7 @@ interface DispatchProps {
 const mapDispatchToProps = { fetchIssues: fetchIssues as any } as DispatchProps;
 
 interface OwnProps {
+  location: { pathname: string; query: RawQuery };
   myIssues?: boolean;
 }
 
diff --git a/server/sonar-web/src/main/js/apps/issues/components/NoMyIssues.tsx b/server/sonar-web/src/main/js/apps/issues/components/NoMyIssues.tsx
new file mode 100644 (file)
index 0000000..a16a1f5
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import '../../../components/common/EmptySearch.css';
+
+export default function NoMyIssues() {
+  return (
+    <div className="empty-search">
+      <h3>{translate('issues.no_my_issues')}</h3>
+    </div>
+  );
+}
index fb4e108e3dbca7627b098019b1ecb441e72326b7..daa3ffa6fa4c798777fce3eb1ce5f5101cec20bf 100644 (file)
 import * as React from 'react';
 import * as PropTypes from 'prop-types';
 import Helmet from 'react-helmet';
+import { omitBy } from 'lodash';
 import PageHeader from './PageHeader';
 import ProjectsList from './ProjectsList';
 import PageSidebar from './PageSidebar';
 import Visualizations from '../visualizations/Visualizations';
 import { CurrentUser, isLoggedIn } from '../../../app/types';
 import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import ListFooter from '../../../components/controls/ListFooter';
 import { translate } from '../../../helpers/l10n';
 import * as storage from '../../../helpers/storage';
@@ -38,7 +40,7 @@ import { parseUrlQuery, Query } from '../query';
 export interface Props {
   currentUser: CurrentUser;
   isFavorite: boolean;
-  location: { pathname: string; query: { [x: string]: string } };
+  location: { pathname: string; query: RawQuery };
   onSonarCloud: boolean;
   organization?: { key: string };
   organizationsEnabled: boolean;
@@ -224,35 +226,35 @@ export default class AllProjects extends React.PureComponent<Props, State> {
   }
 
   updateLocationQuery = (newQuery: RawQuery) => {
-    this.context.router.push({
-      pathname: this.props.location.pathname,
-      query: {
-        ...this.props.location.query,
-        ...newQuery
-      }
-    });
+    const query = omitBy({ ...this.props.location.query, ...newQuery }, x => !x);
+    this.context.router.push({ pathname: this.props.location.pathname, query });
+  };
+
+  handleClearAll = () => {
+    this.context.router.push({ pathname: this.props.location.pathname });
   };
 
   renderSide = () => (
-    <div className="layout-page-side-outer">
-      <div
-        className="layout-page-side projects-page-side"
-        style={{ top: this.props.organization ? 95 : 30 }}>
-        <div className="layout-page-side-inner">
-          <div className="layout-page-filters">
-            <PageSidebar
-              facets={this.state.facets}
-              isFavorite={this.props.isFavorite}
-              organization={this.props.organization}
-              query={this.state.query}
-              showFavoriteFilter={!this.props.onSonarCloud}
-              view={this.getView()}
-              visualization={this.getVisualization()}
-            />
+    <ScreenPositionHelper className="layout-page-side-outer">
+      {({ top }) => (
+        <div className="layout-page-side projects-page-side" style={{ top }}>
+          <div className="layout-page-side-inner">
+            <div className="layout-page-filters">
+              <PageSidebar
+                facets={this.state.facets}
+                onClearAll={this.handleClearAll}
+                onQueryChange={this.updateLocationQuery}
+                organization={this.props.organization}
+                query={this.state.query}
+                showFavoriteFilter={!this.props.onSonarCloud}
+                view={this.getView()}
+                visualization={this.getVisualization()}
+              />
+            </div>
           </div>
         </div>
-      </div>
-    </div>
+      )}
+    </ScreenPositionHelper>
   );
 
   renderHeader = () => (
@@ -261,9 +263,9 @@ export default class AllProjects extends React.PureComponent<Props, State> {
         <div className="layout-page-main-inner">
           <PageHeader
             currentUser={this.props.currentUser}
-            isFavorite={this.props.isFavorite}
             loading={this.state.loading}
             onPerspectiveChange={this.handlePerspectiveChange}
+            onQueryChange={this.updateLocationQuery}
             onSortChange={this.handleSortChange}
             organization={this.props.organization}
             projects={this.state.projects}
@@ -298,6 +300,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
             cardType={this.getView()}
             isFavorite={this.props.isFavorite}
             isFiltered={this.isFiltered()}
+            onSonarCloud={this.props.onSonarCloud}
             organization={this.props.organization}
             projects={this.state.projects}
             query={this.state.query}
@@ -319,7 +322,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
 
         {this.renderSide()}
 
-        <div className="layout-page-main projects-page-content">
+        <div className="layout-page-main">
           {this.renderHeader()}
           {this.renderMain()}
         </div>
index 5eb37454226dbdc42731f85ce90e6faa0ef80a6e..93b794181e0dff72428edcbcfd69d39f9563b069 100644 (file)
@@ -25,6 +25,7 @@ import {
   areThereCustomOrganizations,
   getGlobalSettingValue
 } from '../../../store/rootReducer';
+import { RawQuery } from '../../../helpers/query';
 
 interface StateProps {
   currentUser: CurrentUser;
@@ -32,6 +33,12 @@ interface StateProps {
   organizationsEnabled: boolean;
 }
 
+interface OwnProps {
+  isFavorite: boolean;
+  location: { pathname: string; query: RawQuery };
+  organization?: { key: string };
+}
+
 const stateToProps = (state: any) => {
   const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
   return {
@@ -41,4 +48,6 @@ const stateToProps = (state: any) => {
   };
 };
 
-export default connect<StateProps, any, any>(stateToProps)(lazyLoad(() => import('./AllProjects')));
+export default connect<StateProps, {}, OwnProps>(stateToProps)(
+  lazyLoad(() => import('./AllProjects'))
+);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx b/server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx
new file mode 100644 (file)
index 0000000..0a449dc
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  onClearAll: () => void;
+}
+
+export default class ClearAll extends React.PureComponent<Props> {
+  handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    this.props.onClearAll();
+  };
+
+  render() {
+    return (
+      <div className="projects-facets-reset">
+        <button className="button-red" onClick={this.handleClick}>
+          {translate('clear_all_filters')}
+        </button>
+      </div>
+    );
+  }
+}
index 56d148f82b2f51cd55721a68e0400b0a389b9ef1..80061b3335fa61b13a109d1d8d5b2c1f183ee265 100644 (file)
@@ -46,6 +46,10 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
   }
 
   componentDidMount() {
+    if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) {
+      this.context.router.replace('/explore/projects');
+    }
+
     if (!this.props.onSonarCloud) {
       this.defineIfShouldBeRedirected();
     }
@@ -98,15 +102,20 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
   }
 
   render() {
-    if (this.props.onSonarCloud) {
+    if (this.props.onSonarCloud && isLoggedIn(this.props.currentUser)) {
       return <AllProjectsContainer isFavorite={true} location={this.props.location} />;
     }
 
     const { shouldBeRedirected, shouldForceSorting } = this.state;
-    if (shouldBeRedirected == null || shouldBeRedirected === true || shouldForceSorting != null) {
-      return null;
-    } else {
+
+    if (
+      shouldBeRedirected !== undefined &&
+      shouldBeRedirected !== true &&
+      shouldForceSorting === undefined
+    ) {
       return <AllProjectsContainer isFavorite={false} location={this.props.location} />;
     }
+
+    return null;
   }
 }
index effc6ad0dbbfc13f89a4cca75e8fc1f34f121b09..b562b7d46bf490722eb98b84d37ef80a52ba3201 100644 (file)
@@ -21,13 +21,17 @@ import * as React from 'react';
 import { Link } from 'react-router';
 import { translate } from '../../../helpers/l10n';
 
-export default function NoFavoriteProjects() {
+interface Props {
+  onSonarCloud: boolean;
+}
+
+export default function NoFavoriteProjects({ onSonarCloud }: Props) {
   return (
     <div className="projects-empty-list">
       <h3>{translate('projects.no_favorite_projects')}</h3>
       <p className="big-spacer-top">{translate('projects.no_favorite_projects.engagement')}</p>
       <p className="big-spacer-top">
-        <Link to="/projects/all" className="button">
+        <Link to={onSonarCloud ? '/explore/projects' : '/projects/all'} className="button">
           {translate('projects.explore_projects')}
         </Link>
       </p>
index 8b401d2a6272fe21781077cf21ea2d4ddb21040b..d8de26017fea1dfdf8294412d0d60fa7fadde7a1 100644 (file)
@@ -30,9 +30,9 @@ import { Project } from '../types';
 
 interface Props {
   currentUser: CurrentUser;
-  isFavorite?: boolean;
   loading: boolean;
   onPerspectiveChange: (x: { view: string; visualization?: string }) => void;
+  onQueryChange: (change: RawQuery) => void;
   onSortChange: (sort: string, desc: boolean) => void;
   organization?: { key: string };
   projects?: Project[];
@@ -80,7 +80,7 @@ export default function PageHeader(props: Props) {
       )}
 
       <SearchFilterContainer
-        isFavorite={props.isFavorite}
+        onQueryChange={props.onQueryChange}
         organization={props.organization}
         query={props.query}
       />
index 52315fbefa5cbeae1e90527f067e48c87b4c1a23..19c51033dacc2b4b8b6a01c50a175c1fb93334b0 100644 (file)
@@ -18,9 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Link } from 'react-router';
 import { flatMap } from 'lodash';
 import FavoriteFilterContainer from './FavoriteFilterContainer';
+import ClearAll from './ClearAll';
 import LanguagesFilterContainer from '../filters/LanguagesFilterContainer';
 import CoverageFilter from '../filters/CoverageFilter';
 import DuplicationsFilter from '../filters/DuplicationsFilter';
@@ -42,7 +42,8 @@ import { Facets } from '../types';
 
 interface Props {
   facets?: Facets;
-  isFavorite: boolean;
+  onClearAll: () => void;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: RawQuery;
   showFavoriteFilter: boolean;
@@ -51,15 +52,13 @@ interface Props {
 }
 
 export default function PageSidebar(props: Props) {
-  const { facets, query, isFavorite, organization, view, visualization } = props;
+  const { facets, onQueryChange, query, organization, view, visualization } = props;
   const isFiltered = Object.keys(query)
     .filter(key => !['view', 'visualization', 'sort'].includes(key))
     .some(key => query[key] != null);
   const isLeakView = view === 'leak';
-  const basePathName = organization ? `/organizations/${organization.key}/projects` : '/projects';
-  const pathname = basePathName + (isFavorite ? '/favorite' : '');
   const maxFacetValue = getMaxFacetValue(facets);
-  const facetProps = { isFavorite, maxFacetValue, organization, query };
+  const facetProps = { onQueryChange, maxFacetValue, organization, query };
 
   let linkQuery: RawQuery | undefined = undefined;
   if (view !== 'overall') {
@@ -77,13 +76,7 @@ export default function PageSidebar(props: Props) {
       )}
 
       <div className="projects-facets-header clearfix">
-        {isFiltered && (
-          <div className="projects-facets-reset">
-            <Link to={{ pathname, query: linkQuery }} className="button button-red">
-              {translate('clear_all_filters')}
-            </Link>
-          </div>
-        )}
+        {isFiltered && <ClearAll onClearAll={props.onClearAll} />}
 
         <h3>{translate('filters')}</h3>
       </div>
index 2110166718756f0bd75ba2dc288a7c3cffe49d59..10800381384f1657c9f6681747ec1509e1349ea6 100644 (file)
@@ -30,6 +30,7 @@ interface Props {
   cardType?: string;
   isFavorite: boolean;
   isFiltered: boolean;
+  onSonarCloud: boolean;
   organization?: { key: string };
   projects: Project[];
   query: Query;
@@ -41,7 +42,11 @@ export default class ProjectsList extends React.PureComponent<Props> {
     if (isFiltered) {
       return isFavorite ? <EmptyFavoriteSearch query={query} /> : <EmptySearch />;
     }
-    return isFavorite ? <NoFavoriteProjects /> : <EmptyInstance />;
+    return isFavorite ? (
+      <NoFavoriteProjects onSonarCloud={this.props.onSonarCloud} />
+    ) : (
+      <EmptyInstance />
+    );
   }
 
   render() {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx
new file mode 100644 (file)
index 0000000..ea46863
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ClearAll from '../ClearAll';
+import { click } from '../../../../helpers/testUtils';
+
+it('renders', () => {
+  expect(shallow(<ClearAll onClearAll={jest.fn()} />)).toMatchSnapshot();
+});
+
+it('clears all', () => {
+  const onClearAll = jest.fn();
+  const wrapper = shallow(<ClearAll onClearAll={onClearAll} />);
+  click(wrapper.find('button'));
+  expect(onClearAll).toBeCalled();
+});
index a11013b31d78466ecfacc3cbc94c8f88f7585cde..3635c476f77f293ac18376e93cebd8b4c8f1c76d 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import NoFavoriteProjects from '../NoFavoriteProjects';
 
 it('renders', () => {
-  expect(shallow(<NoFavoriteProjects />)).toMatchSnapshot();
+  expect(shallow(<NoFavoriteProjects onSonarCloud={false} />)).toMatchSnapshot();
 });
index 279297daf13a2a1083c3f69f670a1f7fddcae0b9..ec11f7414104aeed5a159166009732ff6fe10382 100644 (file)
@@ -73,6 +73,7 @@ function shallowRender(props?: {}) {
       currentUser={{ isLoggedIn: false }}
       loading={false}
       onPerspectiveChange={jest.fn()}
+      onQueryChange={jest.fn()}
       onSortChange={jest.fn()}
       projects={[]}
       query={{ search: 'test' }}
index e19492bd40e6c7cf0b9f985c064cbdd7836e5e81..70b54d55d9e5da27b9b66efcac0c727f9023563e 100644 (file)
@@ -24,7 +24,8 @@ import PageSidebar from '../PageSidebar';
 it('should render correctly', () => {
   const sidebar = shallow(
     <PageSidebar
-      isFavorite={true}
+      onClearAll={jest.fn()}
+      onQueryChange={jest.fn()}
       query={{ size: '3' }}
       showFavoriteFilter={true}
       view="overall"
@@ -37,7 +38,8 @@ it('should render correctly', () => {
 it('should render `leak` view correctly', () => {
   const sidebar = shallow(
     <PageSidebar
-      isFavorite={false}
+      onClearAll={jest.fn()}
+      onQueryChange={jest.fn()}
       query={{ view: 'leak' }}
       showFavoriteFilter={true}
       view="leak"
@@ -50,14 +52,15 @@ it('should render `leak` view correctly', () => {
 it('reset function should work correctly with view and visualizations', () => {
   const sidebar = shallow(
     <PageSidebar
-      isFavorite={false}
+      onClearAll={jest.fn()}
+      onQueryChange={jest.fn()}
       query={{ view: 'visualizations', visualization: 'bugs' }}
       showFavoriteFilter={true}
       view="visualizations"
       visualization="bugs"
     />
   );
-  expect(sidebar.find('.projects-facets-reset').exists()).toBeFalsy();
+  expect(sidebar.find('ClearAll').exists()).toBeFalsy();
   sidebar.setProps({ query: { size: '3' } });
-  expect(sidebar.find('.projects-facets-reset')).toMatchSnapshot();
+  expect(sidebar.find('ClearAll').exists()).toBeTruthy();
 });
index 14e6af9b4e5d6aacd6329e91ea2d1b14490c59bd..40a5ea5b64724f783d0560e5cdaf915ea39abd35 100644 (file)
@@ -10,58 +10,11 @@ exports[`renders 1`] = `
     encodeSpecialCharacters={true}
     title="projects.page"
   />
-  <div
+  <ScreenPositionHelper
     className="layout-page-side-outer"
-  >
-    <div
-      className="layout-page-side projects-page-side"
-      style={
-        Object {
-          "top": 30,
-        }
-      }
-    >
-      <div
-        className="layout-page-side-inner"
-      >
-        <div
-          className="layout-page-filters"
-        >
-          <PageSidebar
-            isFavorite={false}
-            query={
-              Object {
-                "coverage": undefined,
-                "duplications": undefined,
-                "gate": undefined,
-                "languages": undefined,
-                "maintainability": undefined,
-                "new_coverage": undefined,
-                "new_duplications": undefined,
-                "new_lines": undefined,
-                "new_maintainability": undefined,
-                "new_reliability": undefined,
-                "new_security": undefined,
-                "reliability": undefined,
-                "search": undefined,
-                "security": undefined,
-                "size": undefined,
-                "sort": undefined,
-                "tags": undefined,
-                "view": undefined,
-                "visualization": undefined,
-              }
-            }
-            showFavoriteFilter={true}
-            view="overall"
-            visualization="risk"
-          />
-        </div>
-      </div>
-    </div>
-  </div>
+  />
   <div
-    className="layout-page-main projects-page-content"
+    className="layout-page-main"
   >
     <div
       className="layout-page-header-panel layout-page-main-header"
@@ -78,9 +31,9 @@ exports[`renders 1`] = `
                 "isLoggedIn": true,
               }
             }
-            isFavorite={false}
             loading={false}
             onPerspectiveChange={[Function]}
+            onQueryChange={[Function]}
             onSortChange={[Function]}
             projects={
               Array [
@@ -129,6 +82,7 @@ exports[`renders 1`] = `
         cardType="overall"
         isFavorite={false}
         isFiltered={false}
+        onSonarCloud={false}
         projects={
           Array [
             Object {
@@ -183,40 +137,11 @@ exports[`renders 2`] = `
     encodeSpecialCharacters={true}
     title="projects.page"
   />
-  <div
+  <ScreenPositionHelper
     className="layout-page-side-outer"
-  >
-    <div
-      className="layout-page-side projects-page-side"
-      style={
-        Object {
-          "top": 30,
-        }
-      }
-    >
-      <div
-        className="layout-page-side-inner"
-      >
-        <div
-          className="layout-page-filters"
-        >
-          <PageSidebar
-            isFavorite={false}
-            query={
-              Object {
-                "view": "visualizations",
-              }
-            }
-            showFavoriteFilter={true}
-            view="visualizations"
-            visualization="risk"
-          />
-        </div>
-      </div>
-    </div>
-  </div>
+  />
   <div
-    className="layout-page-main projects-page-content"
+    className="layout-page-main"
   >
     <div
       className="layout-page-header-panel layout-page-main-header"
@@ -233,9 +158,9 @@ exports[`renders 2`] = `
                 "isLoggedIn": true,
               }
             }
-            isFavorite={false}
             loading={false}
             onPerspectiveChange={[Function]}
+            onQueryChange={[Function]}
             onSortChange={[Function]}
             projects={
               Array [
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap
new file mode 100644 (file)
index 0000000..5f61d82
--- /dev/null
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+  className="projects-facets-reset"
+>
+  <button
+    className="button-red"
+    onClick={[Function]}
+  >
+    clear_all_filters
+  </button>
+</div>
+`;
index 71fc671f68ddaf54b2cef78f0d386b5bc51d037a..75cede0fa1524a78620b6c0a25e004e0b42110b7 100644 (file)
@@ -17,6 +17,7 @@ exports[`should render correctly 1`] = `
     view="overall"
   />
   <SearchFilterContainer
+    onQueryChange={[Function]}
     query={
       Object {
         "search": "test",
@@ -56,6 +57,7 @@ exports[`should render correctly while loading 1`] = `
     view="overall"
   />
   <SearchFilterContainer
+    onQueryChange={[Function]}
     query={
       Object {
         "search": "test",
@@ -108,6 +110,7 @@ exports[`should render disabled sorting options for visualizations 1`] = `
     </div>
   </Tooltip>
   <SearchFilterContainer
+    onQueryChange={[Function]}
     query={
       Object {
         "search": "test",
index cabe4555b867d71c43f2d096c857fc9036546fdc..1d2b3ef1b038153259cfaf93b6db6f14e8e5cc2b 100644 (file)
@@ -1,28 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`reset function should work correctly with view and visualizations 1`] = `
-<div
-  className="projects-facets-reset"
->
-  <Link
-    className="button button-red"
-    onlyActiveOnIndex={false}
-    style={Object {}}
-    to={
-      Object {
-        "pathname": "/projects",
-        "query": Object {
-          "view": "visualizations",
-          "visualization": "bugs",
-        },
-      }
-    }
-  >
-    clear_all_filters
-  </Link>
-</div>
-`;
-
 exports[`should render \`leak\` view correctly 1`] = `
 <div>
   <Connect(FavoriteFilter)
@@ -40,7 +17,7 @@ exports[`should render \`leak\` view correctly 1`] = `
     </h3>
   </div>
   <QualityGateFilter
-    isFavorite={false}
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -48,8 +25,8 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <NewReliabilityFilter
-    isFavorite={false}
     key="new_reliability"
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -57,8 +34,8 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <NewSecurityFilter
-    isFavorite={false}
     key="new_security"
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -66,8 +43,8 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <NewMaintainabilityFilter
-    isFavorite={false}
     key="new_maintainability"
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -75,8 +52,8 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <NewCoverageFilter
-    isFavorite={false}
     key="new_coverage"
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -84,8 +61,8 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <NewDuplicationsFilter
-    isFavorite={false}
     key="new_duplications"
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -93,8 +70,8 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <NewLinesFilter
-    isFavorite={false}
     key="new_lines"
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -102,7 +79,7 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <Connect(LanguagesFilter)
-    isFavorite={false}
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -110,7 +87,7 @@ exports[`should render \`leak\` view correctly 1`] = `
     }
   />
   <TagsFilter
-    isFavorite={false}
+    onQueryChange={[Function]}
     query={
       Object {
         "view": "leak",
@@ -126,29 +103,15 @@ exports[`should render correctly 1`] = `
   <div
     className="projects-facets-header clearfix"
   >
-    <div
-      className="projects-facets-reset"
-    >
-      <Link
-        className="button button-red"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/projects/favorite",
-            "query": undefined,
-          }
-        }
-      >
-        clear_all_filters
-      </Link>
-    </div>
+    <ClearAll
+      onClearAll={[Function]}
+    />
     <h3>
       filters
     </h3>
   </div>
   <QualityGateFilter
-    isFavorite={true}
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -156,8 +119,8 @@ exports[`should render correctly 1`] = `
     }
   />
   <ReliabilityFilter
-    isFavorite={true}
     key="reliability"
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -165,8 +128,8 @@ exports[`should render correctly 1`] = `
     }
   />
   <SecurityFilter
-    isFavorite={true}
     key="security"
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -174,8 +137,8 @@ exports[`should render correctly 1`] = `
     }
   />
   <MaintainabilityFilter
-    isFavorite={true}
     key="maintainability"
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -183,8 +146,8 @@ exports[`should render correctly 1`] = `
     }
   />
   <CoverageFilter
-    isFavorite={true}
     key="coverage"
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -192,8 +155,8 @@ exports[`should render correctly 1`] = `
     }
   />
   <DuplicationsFilter
-    isFavorite={true}
     key="duplications"
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -201,8 +164,8 @@ exports[`should render correctly 1`] = `
     }
   />
   <SizeFilter
-    isFavorite={true}
     key="size"
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -211,7 +174,7 @@ exports[`should render correctly 1`] = `
     value="3"
   />
   <Connect(LanguagesFilter)
-    isFavorite={true}
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
@@ -219,7 +182,7 @@ exports[`should render correctly 1`] = `
     }
   />
   <TagsFilter
-    isFavorite={true}
+    onQueryChange={[Function]}
     query={
       Object {
         "size": "3",
index 9f6b3bca427cafeda192124e31423ea6d14b77a2..db67bf360ea91ab02cbe352aa4fc07f22c9c3a3f 100644 (file)
@@ -24,12 +24,13 @@ import CoverageRating from '../../../components/ui/CoverageRating';
 import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 export interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property?: string;
   query: { [x: string]: any };
@@ -43,13 +44,13 @@ export default function CoverageFilter(props: Props) {
     <Filter
       facet={props.facet}
       maxFacetValue={props.maxFacetValue}
+      onQueryChange={props.onQueryChange}
       value={props.value}
       property={property}
       className={props.className}
       options={[1, 2, 3, 4, 5, 6]}
       query={props.query}
       renderOption={renderOption}
-      isFavorite={props.isFavorite}
       organization={props.organization}
       getFacetValueForOption={getFacetValueForOption}
       highlightUnder={1}
index f51f1072bc2534b6ab9ba310d73b6fada1bffc03..bb2c2c0f508856806a1860dc7b95152b3174b809 100644 (file)
@@ -27,12 +27,13 @@ import {
 } from '../../../helpers/ratings';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 export interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property?: string;
   query: { [x: string]: any };
@@ -45,13 +46,13 @@ export default function DuplicationsFilter(props: Props) {
     <Filter
       facet={props.facet}
       maxFacetValue={props.maxFacetValue}
+      onQueryChange={props.onQueryChange}
       value={props.value}
       property={property}
       className={props.className}
       options={[1, 2, 3, 4, 5, 6]}
       query={props.query}
       renderOption={renderOption}
-      isFavorite={props.isFavorite}
       organization={props.organization}
       getFacetValueForOption={getFacetValueForOption}
       highlightUnder={1}
index 655dd4ce491eb42bc2adfbc786e8259f3c52576b..37ee175913dc40b69030ec027402c07b856071a3 100644 (file)
  */
 import * as React from 'react';
 import * as classNames from 'classnames';
-import { Link } from 'react-router';
-import { getFilterUrl } from './utils';
 import { formatMeasure } from '../../../helpers/measures';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 export type Option = string | number;
 
 interface Props {
   property: string;
   className?: string;
+  onQueryChange: (change: RawQuery) => void;
   options: Option[];
   query: { [x: string]: any };
   renderOption: (option: Option, isSelected: boolean) => React.ReactNode;
@@ -38,7 +38,6 @@ interface Props {
   facet?: Facet;
   maxFacetValue?: number;
   optionClassName?: string;
-  isFavorite?: boolean;
   organization?: { key: string };
 
   getFacetValueForOption?: (facet: Facet, option: Option) => void;
@@ -54,7 +53,7 @@ interface Props {
 export default class Filter extends React.PureComponent<Props> {
   isSelected(option: Option): boolean {
     const { value } = this.props;
-    return Array.isArray(value) ? value.includes(option) : option === value;
+    return Array.isArray(value) ? value.includes(option) : String(option) === String(value);
   }
 
   highlightUnder(option?: Option): boolean {
@@ -66,23 +65,28 @@ export default class Filter extends React.PureComponent<Props> {
     );
   }
 
-  blurOnClick = (event: React.SyntheticEvent<HTMLElement>) => event.currentTarget.blur();
+  handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
 
-  getPath(option: Option) {
     const { property, value } = this.props;
+    const { key: option } = event.currentTarget.dataset;
     let urlOption;
 
-    if (Array.isArray(value)) {
-      if (this.isSelected(option)) {
-        urlOption = value.length > 1 ? value.filter(val => val !== option).join(',') : null;
+    if (option) {
+      if (Array.isArray(value)) {
+        if (this.isSelected(option)) {
+          urlOption = value.length > 1 ? value.filter(val => val !== option).join(',') : null;
+        } else {
+          urlOption = value.concat(option).join(',');
+        }
       } else {
-        urlOption = value.concat(option).join(',');
+        urlOption = this.isSelected(option) ? null : option;
       }
-    } else {
-      urlOption = this.isSelected(option) ? null : option;
+
+      this.props.onQueryChange({ [property]: urlOption });
     }
-    return getFilterUrl(this.props, { [property]: urlOption });
-  }
+  };
 
   renderOptionBar(facetValue: number | undefined) {
     if (facetValue === undefined || !this.props.maxFacetValue) {
@@ -111,7 +115,6 @@ export default class Filter extends React.PureComponent<Props> {
       this.props.optionClassName
     );
 
-    const path = this.getPath(option);
     const facetValue =
       facet && getFacetValueForOption ? getFacetValueForOption(facet, option) : undefined;
 
@@ -122,12 +125,7 @@ export default class Filter extends React.PureComponent<Props> {
       option > value;
 
     return (
-      <Link
-        key={option}
-        className={className}
-        to={path}
-        data-key={option}
-        onClick={this.blurOnClick}>
+      <a className={className} data-key={option} href="#" key={option} onClick={this.handleClick}>
         <span className="facet-name">
           {this.props.renderOption(option, this.isSelected(option) || isUnderSelectedOption)}
         </span>
@@ -137,7 +135,7 @@ export default class Filter extends React.PureComponent<Props> {
             {this.renderOptionBar(facetValue)}
           </span>
         )}
-      </Link>
+      </a>
     );
   }
 
index 65539aae09c62dc40db64f7f38a2690eef572242..47574759b73891480620003607a5f2e7a6c3b8e6 100644 (file)
@@ -23,14 +23,15 @@ import FilterHeader from './FilterHeader';
 import Rating from '../../../components/ui/Rating';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
   headerDetail?: React.ReactNode;
-  isFavorite?: boolean;
   maxFacetValue?: number;
   name: string;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property: string;
   query: { [x: string]: any };
@@ -42,13 +43,13 @@ export default function IssuesFilter(props: Props) {
     <Filter
       facet={props.facet}
       maxFacetValue={props.maxFacetValue}
+      onQueryChange={props.onQueryChange}
       value={props.value}
       property={props.property}
       className={props.className}
       options={[1, 2, 3, 4, 5]}
       query={props.query}
       renderOption={renderOption}
-      isFavorite={props.isFavorite}
       organization={props.organization}
       getFacetValueForOption={getFacetValueForOption}
       highlightUnder={1}
index fce64b5de4c323712450750c0e64746f46f162b0..b892731ce516d0709016494bd9076e7d6acf5ca9 100644 (file)
@@ -26,12 +26,13 @@ import SearchableFilterOption from './SearchableFilterOption';
 import { getLanguageByKey, Languages } from '../../../store/languages/reducer';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   facet?: Facet;
-  isFavorite?: boolean;
   languages: Languages;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property?: string;
   query: { [x: string]: any };
@@ -68,6 +69,7 @@ export default class LanguagesFilter extends React.Component<Props> {
 
     return (
       <Filter
+        onQueryChange={this.props.onQueryChange}
         property={property}
         options={this.getSortedOptions(this.props.facet)}
         query={this.props.query}
@@ -75,13 +77,12 @@ export default class LanguagesFilter extends React.Component<Props> {
         value={this.props.value}
         facet={this.props.facet}
         maxFacetValue={this.props.maxFacetValue}
-        isFavorite={this.props.isFavorite}
         organization={this.props.organization}
         getFacetValueForOption={this.getFacetValueForOption}
         header={<FilterHeader name={translate('projects.facets.languages')} />}
         footer={
           <SearchableFilterFooter
-            isFavorite={this.props.isFavorite}
+            onQueryChange={this.props.onQueryChange}
             organization={this.props.organization}
             options={this.getSearchOptions()}
             property={property}
index 0e3a09d8d9cfd8bf52e3ddf1dc56eb5d2aa06420..bb0d5e762c2ff10be42c6cef9c9c616c942a355e 100644 (file)
@@ -22,13 +22,14 @@ import IssuesFilter from './IssuesFilter';
 import { Facet } from '../types';
 import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
 import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
   headerDetail?: React.ReactNode;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
index 74d688c564dda5dab06cd119bee216b558cb644c..56072d54b0d3543afe92af189687a0da3e9be728 100644 (file)
@@ -23,12 +23,13 @@ import FilterHeader from './FilterHeader';
 import { translate } from '../../../helpers/l10n';
 import { getSizeRatingLabel } from '../../../helpers/ratings';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 export interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property?: string;
   query: { [x: string]: any };
@@ -42,13 +43,13 @@ export default function NewLinesFilter(props: Props) {
     <Filter
       facet={props.facet}
       maxFacetValue={props.maxFacetValue}
+      onQueryChange={props.onQueryChange}
       value={props.value}
       property={property}
       className="leak-facet-box"
       options={[1, 2, 3, 4, 5]}
       query={props.query}
       renderOption={renderOption}
-      isFavorite={props.isFavorite}
       organization={props.organization}
       getFacetValueForOption={getFacetValueForOption}
       highlightUnder={1}
index 6e3dd99d2edfc2ec7e9550dae692176b4859971a..baf85fa3ae22d7717ee0890bda63c877f06c5e83 100644 (file)
@@ -22,12 +22,13 @@ import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
 import IssuesFilter from './IssuesFilter';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
index 23eb35a733d19a1126676122c3fa42982b5cd808..3cb8daecb4c8ab63e0e63a753c450a91d0425af5 100644 (file)
@@ -22,12 +22,13 @@ import BugIcon from '../../../components/icons-components/BugIcon';
 import IssuesFilter from './IssuesFilter';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
index 8f09c50130d39c949ea77516708b9a0ec5156a50..36361b66104245108abe1071ada309e98f928dee 100644 (file)
@@ -22,12 +22,13 @@ import VulnerabilityIcon from '../../../components/icons-components/Vulnerabilit
 import IssuesFilter from './IssuesFilter';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
index 2daf2eb1755a6c2dc0a57181c67447b7545d5315..1425153c90bbc2c10fe44fbf23aa79ec00a7ea5e 100644 (file)
 import * as React from 'react';
 import Filter from './Filter';
 import FilterHeader from './FilterHeader';
+import { Facet } from '../types';
 import Level from '../../../components/ui/Level';
 import { translate } from '../../../helpers/l10n';
-import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 export interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
@@ -39,12 +40,12 @@ export default function QualityGateFilter(props: Props) {
     <Filter
       facet={props.facet}
       maxFacetValue={props.maxFacetValue}
+      onQueryChange={props.onQueryChange}
       value={props.value}
       property="gate"
       options={['OK', 'WARN', 'ERROR']}
       query={props.query}
       renderOption={renderOption}
-      isFavorite={props.isFavorite}
       organization={props.organization}
       getFacetValueForOption={getFacetValueForOption}
       header={<FilterHeader name={translate('projects.facets.quality_gate')} />}
index 56f71586862373aeaa6e80ce6ab7201e8f3c9cb8..9917b32817d267c1f5f25df92ab3e4432b76dfda 100644 (file)
@@ -22,13 +22,14 @@ import IssuesFilter from './IssuesFilter';
 import { Facet } from '../types';
 import BugIcon from '../../../components/icons-components/BugIcon';
 import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
   headerDetail?: React.ReactNode;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
index 4934f7ecaf31414a68136ee3293d939aa9c8c0dd..62083ec97a971a294aeed9ecb85bf59f0954c059 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import { getFilterUrl } from './utils';
 import SearchBox from '../../../components/controls/SearchBox';
 import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   query: { search?: string };
-  isFavorite?: boolean;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
 }
 
 export default class SearchFilterContainer extends React.PureComponent<Props> {
-  static contextTypes = {
-    router: PropTypes.object.isRequired
-  };
-
   handleSearch = (userQuery?: string) => {
-    const path = getFilterUrl(this.props, { search: userQuery });
-    this.context.router.push(path);
+    this.props.onQueryChange({ search: userQuery });
   };
 
   render() {
index c23ca53c3d370ed75c569a44f196b15281130530..aa09177baba2cc990bf8e555a919fd982807059a 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import { getFilterUrl } from './utils';
 import Select from '../../../components/controls/Select';
 import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
+  onQueryChange: (change: RawQuery) => void;
   property: string;
   query: { [x: string]: any };
   options: Array<{ label: string; value: string }>;
@@ -35,14 +35,9 @@ interface Props {
 }
 
 export default class SearchableFilterFooter extends React.PureComponent<Props> {
-  static contextTypes = {
-    router: PropTypes.object.isRequired
-  };
-
   handleOptionChange = ({ value }: { value: string }) => {
     const urlOptions = (this.props.query[this.props.property] || []).concat(value).join(',');
-    const path = getFilterUrl(this.props, { [this.props.property]: urlOptions });
-    this.context.router.push(path);
+    this.props.onQueryChange({ [this.props.property]: urlOptions });
   };
 
   render() {
index 4fbad543cf2a506436831a37413675b5f134ad5d..f2a93c4ebd8010a0421c0eb8b4cd3d3f2669921d 100644 (file)
@@ -22,13 +22,14 @@ import IssuesFilter from './IssuesFilter';
 import { Facet } from '../types';
 import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
 import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   className?: string;
   facet?: Facet;
   headerDetail?: React.ReactNode;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   query: { [x: string]: any };
   value?: any;
index ebf98c8bd877ea2761f11662241cf01ea7a22546..dc16b3110cc1f8ed203bf11a7c474b7f7d0692bc 100644 (file)
@@ -24,12 +24,13 @@ import SizeRating from '../../../components/ui/SizeRating';
 import { translate } from '../../../helpers/l10n';
 import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 export interface Props {
   className?: string;
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property?: string;
   query: { [x: string]: any };
@@ -43,13 +44,13 @@ export default function SizeFilter(props: Props) {
     <Filter
       facet={props.facet}
       maxFacetValue={props.maxFacetValue}
+      onQueryChange={props.onQueryChange}
       value={props.value}
       property={property}
       className={props.className}
       options={[1, 2, 3, 4, 5]}
       query={props.query}
       renderOption={renderOption}
-      isFavorite={props.isFavorite}
       organization={props.organization}
       getFacetValueForOption={getFacetValueForOption}
       highlightUnder={1}
index ee49080b8f05ec8f546516ba5a3d42124b3714e3..2aba6881e864fde6589780f1d736b584bf128134 100644 (file)
@@ -26,11 +26,12 @@ import SearchableFilterOption from './SearchableFilterOption';
 import { searchProjectTags } from '../../../api/components';
 import { translate } from '../../../helpers/l10n';
 import { Facet } from '../types';
+import { RawQuery } from '../../../helpers/query';
 
 interface Props {
   facet?: Facet;
-  isFavorite?: boolean;
   maxFacetValue?: number;
+  onQueryChange: (change: RawQuery) => void;
   organization?: { key: string };
   property?: string;
   query: { [x: string]: any };
@@ -101,6 +102,7 @@ export default class TagsFilter extends React.PureComponent<Props, State> {
 
     return (
       <Filter
+        onQueryChange={this.props.onQueryChange}
         property={property}
         options={this.getSortedOptions(this.props.facet)}
         query={this.props.query}
@@ -108,13 +110,12 @@ export default class TagsFilter extends React.PureComponent<Props, State> {
         value={this.props.value}
         facet={this.props.facet}
         maxFacetValue={this.props.maxFacetValue}
-        isFavorite={this.props.isFavorite}
         organization={this.props.organization}
         getFacetValueForOption={this.getFacetValueForOption}
         header={<FilterHeader name={translate('projects.facets.tags')} />}
         footer={
           <SearchableFilterFooter
-            isFavorite={this.props.isFavorite}
+            onQueryChange={this.props.onQueryChange}
             isLoading={this.state.isLoading}
             onInputChange={this.handleSearch}
             onOpen={this.handleSearch}
index 29f2121767dc17c66991230a1886838542305cc4..26493d83f782b843fac7e17f337c59c1a1d0928a 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import CoverageFilter from '../CoverageFilter';
 
 it('renders', () => {
-  const wrapper = shallow(<CoverageFilter query={{}} />);
+  const wrapper = shallow(<CoverageFilter onQueryChange={jest.fn()} query={{}} />);
   expect(wrapper).toMatchSnapshot();
 
   const renderOption = wrapper.prop('renderOption');
index 2a4588325dcf59e7d56832421894475d7b24b8ea..b036bf91fdef6a4b6fbbe5658ddd8fa83ff4e565 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import DuplicationsFilter from '../DuplicationsFilter';
 
 it('renders', () => {
-  const wrapper = shallow(<DuplicationsFilter query={{}} />);
+  const wrapper = shallow(<DuplicationsFilter onQueryChange={jest.fn()} query={{}} />);
   expect(wrapper).toMatchSnapshot();
 
   const renderOption = wrapper.prop('renderOption');
index 51c9f5b2b52e787acb749b10daa3c6cbfa28bdf1..d37b561651977fafee6e421ba81c1903a5c72bc0 100644 (file)
@@ -63,6 +63,7 @@ it('renders facet bar chart', () => {
 function shallowRender(props?: any) {
   return shallow(
     <Filter
+      onQueryChange={jest.fn()}
       options={[1, 2, 3]}
       property="foo"
       query={{}}
index a21a219fe68dc9feb9e4aaa4aef88c64526085fd..1dda1a58b54e6157afe6c9e6c3ed2007765a4a13 100644 (file)
@@ -22,7 +22,9 @@ import { shallow } from 'enzyme';
 import IssuesFilter from '../IssuesFilter';
 
 it('renders', () => {
-  const wrapper = shallow(<IssuesFilter name="bugs" property="bugs" query={{}} />);
+  const wrapper = shallow(
+    <IssuesFilter name="bugs" onQueryChange={jest.fn()} property="bugs" query={{}} />
+  );
   expect(wrapper).toMatchSnapshot();
 
   const renderOption = wrapper.prop('renderOption');
index ff0bf4f8b37cad8976dc4a79759706a1d215c558..d09423c82174d74ce9b9448527d9bab6c6044ecf 100644 (file)
@@ -34,7 +34,12 @@ const languagesFacet = { java: 39, cs: 4, js: 1 };
 
 it('should render the languages without the ones in the facet', () => {
   const wrapper = shallow(
-    <LanguagesFilter facet={languagesFacet} languages={languages} query={{ languages: null }} />
+    <LanguagesFilter
+      facet={languagesFacet}
+      languages={languages}
+      onQueryChange={jest.fn()}
+      query={{ languages: null }}
+    />
   );
   expect(wrapper).toMatchSnapshot();
 });
@@ -43,8 +48,8 @@ it('should render the languages facet with the selected languages', () => {
   const wrapper = shallow(
     <LanguagesFilter
       facet={languagesFacet}
-      isFavorite={true}
       languages={languages}
+      onQueryChange={jest.fn()}
       query={{ languages: ['java', 'cs'] }}
       value={['java', 'cs']}
     />
@@ -69,8 +74,8 @@ it('should render maximum 10 languages in the searchbox results', () => {
   const wrapper = shallow(
     <LanguagesFilter
       facet={{ ...languagesFacet, g: 1 }}
-      isFavorite={true}
       languages={manyLanguages}
+      onQueryChange={jest.fn()}
       query={{ languages: ['java', 'g'] }}
       value={['java', 'g']}
     />
index 59add14532b223a6c2f2746bd5e09f3f9412d1af..98b867073eba521a3663c557e8e440ea887acc41 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import MaintainabilityFilter from '../MaintainabilityFilter';
 
 it('renders', () => {
-  expect(shallow(<MaintainabilityFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<MaintainabilityFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index 69f0855f75ef1054c2d97e022a1abfbb133c0908..13426af67d19f748ae4d05e836da4a962ab34b3d 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import NewCoverageFilter from '../NewCoverageFilter';
 
 it('renders', () => {
-  expect(shallow(<NewCoverageFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<NewCoverageFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index 9420d8257be5dbb9fcf77b9b772b3958d4b9b7ac..8fe450221629b4e90b471a52902cb00df5ed06de 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import NewDuplicationsFilter from '../NewDuplicationsFilter';
 
 it('renders', () => {
-  expect(shallow(<NewDuplicationsFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<NewDuplicationsFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index 5760a50b1226483f54be62b1df43574daccaa280..a60432019f39db9f6b7914d0d72effcb32e260cd 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import NewLinesFilter from '../NewLinesFilter';
 
 it('renders', () => {
-  const wrapper = shallow(<NewLinesFilter query={{}} />);
+  const wrapper = shallow(<NewLinesFilter onQueryChange={jest.fn()} query={{}} />);
   expect(wrapper).toMatchSnapshot();
 
   const renderOption = wrapper.prop('renderOption');
index 9dc78541994030aa275ed4f453a657c4ccdbed46..26bf9cdb24f9612fb9ecc923eec55d32077c6aaf 100644 (file)
@@ -22,5 +22,7 @@ import { shallow } from 'enzyme';
 import NewMaintainabilityFilter from '../NewMaintainabilityFilter';
 
 it('renders', () => {
-  expect(shallow(<NewMaintainabilityFilter query={{}} />)).toMatchSnapshot();
+  expect(
+    shallow(<NewMaintainabilityFilter onQueryChange={jest.fn()} query={{}} />)
+  ).toMatchSnapshot();
 });
index 2f97df9e72e2f7ca261d32909dff15891f91bfc6..b1cb4f189de6900d79822495d601659933618436 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import NewReliabilityFilter from '../NewReliabilityFilter';
 
 it('renders', () => {
-  expect(shallow(<NewReliabilityFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<NewReliabilityFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index a3771637e9291b996c9fb68159d43a9ceb34252a..39c7b886a39a2609e499214097f1b621edea1c88 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import NewSecurityFilter from '../NewSecurityFilter';
 
 it('renders', () => {
-  expect(shallow(<NewSecurityFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<NewSecurityFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index 02316f2426b672596a97e0e07ac5b9bfd6dd15cd..d9c8607f804da7e5a09be73bb72fc8819bd1a881 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import QualityGateFilter from '../QualityGateFilter';
 
 it('renders', () => {
-  const wrapper = shallow(<QualityGateFilter query={{}} />);
+  const wrapper = shallow(<QualityGateFilter onQueryChange={jest.fn()} query={{}} />);
   expect(wrapper).toMatchSnapshot();
 
   const renderOption = wrapper.prop('renderOption');
index 8ca23ed4f5044118c86bd605af191e9466efa12e..6dafe35b73d0a01897ff296e9aed18041faddfd5 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import ReliabilityFilter from '../ReliabilityFilter';
 
 it('renders', () => {
-  expect(shallow(<ReliabilityFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<ReliabilityFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index 3154a9488fd288243d478e5143dfab0887998b3c..99d18dbd0d26b5929d2c512d1d1a9c8a29d05fbb 100644 (file)
@@ -22,9 +22,11 @@ import { shallow } from 'enzyme';
 import SearchFilterContainer from '../SearchFilterContainer';
 
 it('searches', () => {
-  const push = jest.fn();
-  const wrapper = shallow(<SearchFilterContainer query={{}} />, { context: { router: { push } } });
+  const onQueryChange = jest.fn();
+  const wrapper = shallow(<SearchFilterContainer onQueryChange={onQueryChange} query={{}} />, {
+    context: { router: { push: jest.fn() } }
+  });
   expect(wrapper).toMatchSnapshot();
   wrapper.find('SearchBox').prop<Function>('onChange')('foo');
-  expect(push).toBeCalledWith({ pathname: '/projects', query: { search: 'foo' } });
+  expect(onQueryChange).toBeCalledWith({ search: 'foo' });
 });
index 2d12621ec21c5d89583ff884819ada2b444fb5ce..5f5aee87e1f0824e93b208d8e65d367796ec2346 100644 (file)
@@ -30,6 +30,7 @@ const options = [
 it('should render items without the ones in the facet', () => {
   const wrapper = shallow(
     <SearchableFilterFooter
+      onQueryChange={jest.fn()}
       property="languages"
       query={{ languages: ['java'] }}
       options={options}
@@ -40,18 +41,16 @@ it('should render items without the ones in the facet', () => {
 });
 
 it('should render items without the ones in the facet', () => {
-  const push = jest.fn();
+  const onQueryChange = jest.fn();
   const wrapper = shallow(
     <SearchableFilterFooter
+      onQueryChange={onQueryChange}
       property="languages"
       query={{ languages: ['java'] }}
       options={options}
     />,
-    { context: { router: { push } } }
+    { context: { router: { push: jest.fn() } } }
   );
   (wrapper.find('Select').prop('onChange') as Function)({ value: 'js' });
-  expect(push).toBeCalledWith({
-    pathname: '/projects',
-    query: { languages: 'java,js' }
-  });
+  expect(onQueryChange).toBeCalledWith({ languages: 'java,js' });
 });
index 05c83d65c4111c6f3917ad295deb1af23f4c012d..1daf55d766fcf3161b6a10b9d598312aecfb71dd 100644 (file)
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme';
 import SecurityFilter from '../SecurityFilter';
 
 it('renders', () => {
-  expect(shallow(<SecurityFilter query={{}} />)).toMatchSnapshot();
+  expect(shallow(<SecurityFilter onQueryChange={jest.fn()} query={{}} />)).toMatchSnapshot();
 });
index 486594f766d72c2e2948fd01e71cd50213738e4c..ee5c83349bbae5242c13b919c538d1e8e1bd2727 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import SizeFilter from '../SizeFilter';
 
 it('renders', () => {
-  const wrapper = shallow(<SizeFilter query={{}} />);
+  const wrapper = shallow(<SizeFilter onQueryChange={jest.fn()} query={{}} />);
   expect(wrapper).toMatchSnapshot();
 
   const renderOption = wrapper.prop('renderOption');
index bfc0790d00cb41cd52b6370ff8eed7557f798c06..af3c0fcfe1adc95c86d6b1a81547eda9c5951762 100644 (file)
@@ -25,7 +25,9 @@ const tags = ['lang', 'sonar', 'csharp', 'dotnet', 'it', 'net'];
 const tagsFacet = { lang: 4, sonar: 3, csharp: 1 };
 
 it('should render the tags without the ones in the facet', () => {
-  const wrapper = shallow(<TagsFilter query={{ tags: null }} facet={tagsFacet} />);
+  const wrapper = shallow(
+    <TagsFilter onQueryChange={jest.fn()} query={{ tags: null }} facet={tagsFacet} />
+  );
   expect(wrapper).toMatchSnapshot();
   wrapper.setState({ tags });
   expect(wrapper).toMatchSnapshot();
@@ -34,10 +36,10 @@ it('should render the tags without the ones in the facet', () => {
 it('should render the tags facet with the selected tags', () => {
   const wrapper = shallow(
     <TagsFilter
+      onQueryChange={jest.fn()}
       query={{ tags: ['lang', 'sonar'] }}
       value={['lang', 'sonar']}
       facet={tagsFacet}
-      isFavorite={true}
     />
   );
   expect(wrapper).toMatchSnapshot();
@@ -47,10 +49,10 @@ it('should render the tags facet with the selected tags', () => {
 it('should render maximum 10 tags in the searchbox results', () => {
   const wrapper = shallow(
     <TagsFilter
+      onQueryChange={jest.fn()}
       query={{ languages: ['java', 'ad'] }}
       value={['java', 'ad']}
       facet={{ ...tagsFacet, ad: 1 }}
-      isFavorite={true}
     />
   );
   wrapper.setState({ tags: [...tags, 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'ag', 'ah', 'ai'] });
index da8e061961cac6b4f2b44b4d1849c124cfceec5f..49e49f3c84c7f6cf5a7a61d1fc651bb89bf57534 100644 (file)
@@ -10,6 +10,7 @@ exports[`renders 1`] = `
   }
   highlightUnder={1}
   highlightUnderMax={5}
+  onQueryChange={[Function]}
   options={
     Array [
       1,
index 86606f59ac7b585521a970126cdf5f1b4c356951..f70e42fab8f6e83834d8f4a5117de5b75761fb14 100644 (file)
@@ -10,6 +10,7 @@ exports[`renders 1`] = `
   }
   highlightUnder={1}
   highlightUnderMax={5}
+  onQueryChange={[Function]}
   options={
     Array [
       1,
index 3c6f588c1928afa560c038dcd39f621066d57b1a..d0d09c08b8c1954e995066b9192abab54d1a465d 100644 (file)
@@ -8,75 +8,48 @@ exports[`highlights under 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={1}
+      href="#"
       key="1"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 1,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </Link>
+    </a>
     <div
       className="search-navigator-facet-highlight-under-container"
     >
-      <Link
+      <a
         className="facet search-navigator-facet projects-facet"
         data-key={2}
+        href="#"
         key="2"
         onClick={[Function]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/projects",
-            "query": Object {
-              "foo": 2,
-            },
-          }
-        }
       >
         <span
           className="facet-name"
         >
           2
         </span>
-      </Link>
-      <Link
+      </a>
+      <a
         className="facet search-navigator-facet projects-facet"
         data-key={3}
+        href="#"
         key="3"
         onClick={[Function]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/projects",
-            "query": Object {
-              "foo": 3,
-            },
-          }
-        }
       >
         <span
           className="facet-name"
         >
           3
         </span>
-      </Link>
+      </a>
     </div>
   </div>
 </div>
@@ -90,73 +63,48 @@ exports[`hightlights under selected 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={1}
+      href="#"
       key="1"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 1,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </Link>
+    </a>
     <div
       className="search-navigator-facet-highlight-under-container"
     >
-      <Link
+      <a
         className="facet search-navigator-facet projects-facet active"
         data-key={2}
+        href="#"
         key="2"
         onClick={[Function]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/projects",
-            "query": Object {},
-          }
-        }
       >
         <span
           className="facet-name"
         >
           2
         </span>
-      </Link>
-      <Link
+      </a>
+      <a
         className="facet search-navigator-facet projects-facet"
         data-key={3}
+        href="#"
         key="3"
         onClick={[Function]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/projects",
-            "query": Object {
-              "foo": 3,
-            },
-          }
-        }
       >
         <span
           className="facet-name"
         >
           3
         </span>
-      </Link>
+      </a>
     </div>
   </div>
 </div>
@@ -170,72 +118,45 @@ exports[`renders 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={1}
+      href="#"
       key="1"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 1,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={2}
+      href="#"
       key="2"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 2,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={3}
+      href="#"
       key="3"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 3,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </Link>
+    </a>
   </div>
 </div>
 `;
@@ -248,21 +169,12 @@ exports[`renders facet bar chart 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key="a"
+      href="#"
       key="a"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": "a",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -286,22 +198,13 @@ exports[`renders facet bar chart 1`] = `
           />
         </div>
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key="b"
+      href="#"
       key="b"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": "b",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -325,22 +228,13 @@ exports[`renders facet bar chart 1`] = `
           />
         </div>
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key="c"
+      href="#"
       key="c"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": "c",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -364,7 +258,7 @@ exports[`renders facet bar chart 1`] = `
           />
         </div>
       </span>
-    </Link>
+    </a>
   </div>
 </div>
 `;
@@ -378,72 +272,45 @@ exports[`renders header and footer 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={1}
+      href="#"
       key="1"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 1,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={2}
+      href="#"
       key="2"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 2,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={3}
+      href="#"
       key="3"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 3,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </Link>
+    </a>
   </div>
   <footer />
 </div>
@@ -457,72 +324,45 @@ exports[`renders multiple selected 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key={1}
+      href="#"
       key="1"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": "2",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key={2}
+      href="#"
       key="2"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": "1",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={3}
+      href="#"
       key="3"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": "1,2,3",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </Link>
+    </a>
   </div>
 </div>
 `;
@@ -548,70 +388,45 @@ exports[`renders selected 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={1}
+      href="#"
       key="1"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 1,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key={2}
+      href="#"
       key="2"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {},
-        }
-      }
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key={3}
+      href="#"
       key="3"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects",
-          "query": Object {
-            "foo": 3,
-          },
-        }
-      }
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </Link>
+    </a>
   </div>
 </div>
 `;
index 3f1103ec20a0dd4f22c424f5f013c3f24f97f72f..6493966198433b371ee6171e4be9ac5873f78d0e 100644 (file)
@@ -9,6 +9,7 @@ exports[`renders 1`] = `
     />
   }
   highlightUnder={1}
+  onQueryChange={[Function]}
   options={
     Array [
       1,
index a9b3db73ed8ddc475c4dbd70613a25784ccca750..f599e813c5d5f0be838d4210e1d5547f41774c4f 100644 (file)
@@ -12,7 +12,7 @@ exports[`should render maximum 10 languages in the searchbox results 1`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={true}
+      onQueryChange={[Function]}
       options={
         Array [
           Object {
@@ -75,7 +75,7 @@ exports[`should render maximum 10 languages in the searchbox results 1`] = `
       name="projects.facets.languages"
     />
   }
-  isFavorite={true}
+  onQueryChange={[Function]}
   options={
     Array [
       "java",
@@ -114,7 +114,7 @@ exports[`should render the languages facet with the selected languages 1`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={true}
+      onQueryChange={[Function]}
       options={
         Array [
           Object {
@@ -149,7 +149,7 @@ exports[`should render the languages facet with the selected languages 1`] = `
       name="projects.facets.languages"
     />
   }
-  isFavorite={true}
+  onQueryChange={[Function]}
   options={
     Array [
       "java",
@@ -187,21 +187,12 @@ exports[`should render the languages facet with the selected languages 2`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key="java"
+      href="#"
       key="java"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects/favorite",
-          "query": Object {
-            "languages": "cs",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -221,22 +212,13 @@ exports[`should render the languages facet with the selected languages 2`] = `
       >
         39
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key="cs"
+      href="#"
       key="cs"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects/favorite",
-          "query": Object {
-            "languages": "java",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -256,22 +238,13 @@ exports[`should render the languages facet with the selected languages 2`] = `
       >
         4
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key="js"
+      href="#"
       key="js"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects/favorite",
-          "query": Object {
-            "languages": "java,cs,js",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -291,10 +264,10 @@ exports[`should render the languages facet with the selected languages 2`] = `
       >
         1
       </span>
-    </Link>
+    </a>
   </div>
   <SearchableFilterFooter
-    isFavorite={true}
+    onQueryChange={[Function]}
     options={
       Array [
         Object {
@@ -335,7 +308,7 @@ exports[`should render the languages without the ones in the facet 1`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={undefined}
+      onQueryChange={[Function]}
       options={
         Array [
           Object {
@@ -367,6 +340,7 @@ exports[`should render the languages without the ones in the facet 1`] = `
       name="projects.facets.languages"
     />
   }
+  onQueryChange={[Function]}
   options={
     Array [
       "java",
index bf53d3df2a02f188228da9f8197ce1b59f36045b..ffa5b740b7653f806c817a2b4a16774aba99bef8 100644 (file)
@@ -15,6 +15,7 @@ exports[`renders 1`] = `
     </span>
   }
   name="Maintainability"
+  onQueryChange={[Function]}
   property="maintainability"
   query={Object {}}
 />
index 08f6a0bcf1fd6ce1b35d6324a35199e7150dc2d2..b9972e1255e9a5b89a63da5ed99bece0b2f8034e 100644 (file)
@@ -3,6 +3,7 @@
 exports[`renders 1`] = `
 <CoverageFilter
   className="leak-facet-box"
+  onQueryChange={[Function]}
   property="new_coverage"
   query={Object {}}
 />
index a75df7d187ca2aec12b8989a802586a3350be37b..2f5c0bb6e712025c9afc83bc237247fb1ef5bc5c 100644 (file)
@@ -3,6 +3,7 @@
 exports[`renders 1`] = `
 <DuplicationsFilter
   className="leak-facet-box"
+  onQueryChange={[Function]}
   property="new_duplications"
   query={Object {}}
 />
index 2da8deedd2a6eeffa3b2d1ecc49ef5d4453aa97f..d2de9bc1ea401b6fb518a28a8295b11f2956a614 100644 (file)
@@ -10,6 +10,7 @@ exports[`renders 1`] = `
     />
   }
   highlightUnder={1}
+  onQueryChange={[Function]}
   options={
     Array [
       1,
index dc9057aa0221aa23a6fa03ea4a9aff217bf4c5ab..c1d5ebaf37547ca810228b103698d1ef817c4387 100644 (file)
@@ -16,6 +16,7 @@ exports[`renders 1`] = `
     </span>
   }
   name="Maintainability"
+  onQueryChange={[Function]}
   property="new_maintainability"
   query={Object {}}
 />
index b93c2e876b53dcf5963debac100b81e449ac82ab..0a10f640261b13a9ecdb1b034508c0c0c01965f2 100644 (file)
@@ -16,6 +16,7 @@ exports[`renders 1`] = `
     </span>
   }
   name="Reliability"
+  onQueryChange={[Function]}
   property="new_reliability"
   query={Object {}}
 />
index f0005022a9d278c527dd675c69cc52ef02637ad8..493fcad02e50044c2f24327e2fa91934ee8cf252 100644 (file)
@@ -16,6 +16,7 @@ exports[`renders 1`] = `
     </span>
   }
   name="Security"
+  onQueryChange={[Function]}
   property="new_security"
   query={Object {}}
 />
index 350e162a603ad8326bfa97ae5d89c4a020b1a7cf..3b75ce1ab21417e69ae061e7a70acea3afc32351 100644 (file)
@@ -8,6 +8,7 @@ exports[`renders 1`] = `
       name="projects.facets.quality_gate"
     />
   }
+  onQueryChange={[Function]}
   options={
     Array [
       "OK",
index 00c4db1716b2d3b823450a55029d45407be95877..a846594a324475c03490ebc430201b2d7842d8cd 100644 (file)
@@ -15,6 +15,7 @@ exports[`renders 1`] = `
     </span>
   }
   name="Reliability"
+  onQueryChange={[Function]}
   property="reliability"
   query={Object {}}
 />
index bc4c098e76ea0e73ac113d88951a839ee67bebec..693c67c08ab056d80d020e42ef6ee34cb4000bce 100644 (file)
@@ -15,6 +15,7 @@ exports[`renders 1`] = `
     </span>
   }
   name="Security"
+  onQueryChange={[Function]}
   property="security"
   query={Object {}}
 />
index 057d6b0e42e28b3b2600a2b16250ea0b096d9b6c..dd4378985d66143b6d33d2e72243825e125b9969 100644 (file)
@@ -9,6 +9,7 @@ exports[`renders 1`] = `
     />
   }
   highlightUnder={1}
+  onQueryChange={[Function]}
   options={
     Array [
       1,
index 0d669e7d8c8713a68cc8c36a4000b40beec21e0e..d28245d7c5913f38d3dfce527e6af8d2ca3da610 100644 (file)
@@ -12,10 +12,10 @@ exports[`should render maximum 10 tags in the searchbox results 1`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={true}
       isLoading={false}
       onInputChange={[Function]}
       onOpen={[Function]}
+      onQueryChange={[Function]}
       options={
         Array [
           Object {
@@ -78,7 +78,7 @@ exports[`should render maximum 10 tags in the searchbox results 1`] = `
       name="projects.facets.tags"
     />
   }
-  isFavorite={true}
+  onQueryChange={[Function]}
   options={
     Array [
       "lang",
@@ -117,10 +117,10 @@ exports[`should render the tags facet with the selected tags 1`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={true}
       isLoading={false}
       onInputChange={[Function]}
       onOpen={[Function]}
+      onQueryChange={[Function]}
       options={Array []}
       organization={undefined}
       property="tags"
@@ -140,7 +140,7 @@ exports[`should render the tags facet with the selected tags 1`] = `
       name="projects.facets.tags"
     />
   }
-  isFavorite={true}
+  onQueryChange={[Function]}
   options={
     Array [
       "lang",
@@ -178,21 +178,12 @@ exports[`should render the tags facet with the selected tags 2`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <Link
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key="lang"
+      href="#"
       key="lang"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects/favorite",
-          "query": Object {
-            "tags": "sonar",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -206,22 +197,13 @@ exports[`should render the tags facet with the selected tags 2`] = `
       >
         4
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet active"
       data-key="sonar"
+      href="#"
       key="sonar"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects/favorite",
-          "query": Object {
-            "tags": "lang",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -235,22 +217,13 @@ exports[`should render the tags facet with the selected tags 2`] = `
       >
         3
       </span>
-    </Link>
-    <Link
+    </a>
+    <a
       className="facet search-navigator-facet projects-facet"
       data-key="csharp"
+      href="#"
       key="csharp"
       onClick={[Function]}
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/projects/favorite",
-          "query": Object {
-            "tags": "lang,sonar,csharp",
-          },
-        }
-      }
     >
       <span
         className="facet-name"
@@ -264,13 +237,13 @@ exports[`should render the tags facet with the selected tags 2`] = `
       >
         1
       </span>
-    </Link>
+    </a>
   </div>
   <SearchableFilterFooter
-    isFavorite={true}
     isLoading={false}
     onInputChange={[Function]}
     onOpen={[Function]}
+    onQueryChange={[Function]}
     options={Array []}
     property="tags"
     query={
@@ -296,10 +269,10 @@ exports[`should render the tags without the ones in the facet 1`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={undefined}
       isLoading={false}
       onInputChange={[Function]}
       onOpen={[Function]}
+      onQueryChange={[Function]}
       options={Array []}
       organization={undefined}
       property="tags"
@@ -316,6 +289,7 @@ exports[`should render the tags without the ones in the facet 1`] = `
       name="projects.facets.tags"
     />
   }
+  onQueryChange={[Function]}
   options={
     Array [
       "lang",
@@ -344,10 +318,10 @@ exports[`should render the tags without the ones in the facet 2`] = `
   }
   footer={
     <SearchableFilterFooter
-      isFavorite={undefined}
       isLoading={false}
       onInputChange={[Function]}
       onOpen={[Function]}
+      onQueryChange={[Function]}
       options={
         Array [
           Object {
@@ -379,6 +353,7 @@ exports[`should render the tags without the ones in the facet 2`] = `
       name="projects.facets.tags"
     />
   }
+  onQueryChange={[Function]}
   options={
     Array [
       "lang",
index 1bd7e2865482214f11d0e0af5f5125c19817ab3f..707f0243994a0fd3b901a27a76d4e8230bda74c8 100644 (file)
@@ -1,11 +1,3 @@
-.projects-page-side {
-  transition: top 150ms ease-out;
-}
-
-.projects-page-content {
-  transition: padding-top 150ms ease-out;
-}
-
 .projects-topbar-items {
   display: flex;
   align-items: center;
index 5618a879e48b3671eb0c9d3d5aee618538051dbc..8b3a473149e2410f92b9d5aea8ec46208512fa9a 100644 (file)
@@ -60,6 +60,7 @@ download_verb=Download
 duplications=Duplications
 edit=Edit
 events=Events
+explore=Explore
 false=False
 favorite=Favorite
 file=File
@@ -605,6 +606,7 @@ issues.to_navigate_issue_locations=to navigate issue locations
 issues.to_switch_flows=to switch flows
 issues.leak_period=Leak Period
 issues.my_issues=My Issues
+issues.no_my_issues=There are no issues assigned to you.
 
 
 #------------------------------------------------------------------------------