]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9254 Move the projects search box on top of the projects cards
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 29 May 2017 14:51:25 +0000 (16:51 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 9 Jun 2017 06:26:48 +0000 (08:26 +0200)
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap
server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.js
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilter-test.js.snap
server/sonar-web/src/main/js/apps/projects/styles.css

index 8b172222b75f2c2efc2eec007fe2987d6271979a..46d752be2db8a391d126802253bdd52ede656885 100644 (file)
@@ -120,6 +120,7 @@ export default class AllProjects extends React.PureComponent {
   };
 
   render() {
+    const { isFavorite, organization } = this.props;
     const { query, optionBarOpen } = this.state;
     const isFiltered = Object.keys(query).some(key => query[key] != null);
 
@@ -127,7 +128,7 @@ export default class AllProjects extends React.PureComponent {
     const visualization = query.visualization || 'risk';
     const selectedSort = query.sort || 'name';
 
-    const top = (this.props.organization ? 95 : 30) + (optionBarOpen ? 45 : 0);
+    const top = (organization ? 95 : 30) + (optionBarOpen ? 45 : 0);
 
     return (
       <div>
@@ -149,8 +150,8 @@ export default class AllProjects extends React.PureComponent {
               <div className="layout-page-side-inner">
                 <div className="layout-page-filters">
                   <PageSidebar
-                    isFavorite={this.props.isFavorite}
-                    organization={this.props.organization}
+                    isFavorite={isFavorite}
+                    organization={organization}
                     query={query}
                     view={view}
                     visualization={visualization}
@@ -162,19 +163,24 @@ export default class AllProjects extends React.PureComponent {
 
           <div className="layout-page-main">
             <div className="layout-page-main-inner">
-              <PageHeaderContainer onOpenOptionBar={this.openOptionBar} />
+              <PageHeaderContainer
+                query={query}
+                isFavorite={isFavorite}
+                organization={organization}
+                onOpenOptionBar={this.openOptionBar}
+              />
               {view !== 'visualizations' &&
                 <ProjectsListContainer
-                  isFavorite={this.props.isFavorite}
+                  isFavorite={isFavorite}
                   isFiltered={isFiltered}
-                  organization={this.props.organization}
+                  organization={organization}
                   cardType={view}
                 />}
               {view !== 'visualizations' &&
                 <ProjectsListFooterContainer
                   query={query}
-                  isFavorite={this.props.isFavorite}
-                  organization={this.props.organization}
+                  isFavorite={isFavorite}
+                  organization={organization}
                 />}
               {view === 'visualizations' &&
                 <VisualizationsContainer sort={query.sort} visualization={visualization} />}
index f3866ee393520ec535ce53818687c429ac66ba78..00022b1954595f208bde5a5789ec3fd0e8872157 100644 (file)
  */
 // @flow
 import React from 'react';
+import SearchFilterContainer from '../filters/SearchFilterContainer';
 import { translate } from '../../../helpers/l10n';
 
 type Props = {
+  isFavorite?: boolean,
   loading: boolean,
   onOpenOptionBar: () => void,
+  organization?: { key: string },
+  query: { [string]: string },
   total?: number
 };
 
 export default function PageHeader(props: Props) {
   return (
     <header className="page-header">
+      <SearchFilterContainer
+        isFavorite={props.isFavorite}
+        organization={props.organization}
+        query={props.query}
+      />
       <div className="page-actions projects-page-actions text-right">
         <div className="spacer-bottom">
           <a className="button js-projects-topbar-open" href="#" onClick={props.onOpenOptionBar}>
index 3e7ca9e459193267caf356cdb8e05d8145052e82..302ee1941ff62b93767c8f96e1b201a939af13d3 100644 (file)
@@ -34,7 +34,6 @@ import NewLinesFilter from '../filters/NewLinesFilter';
 import QualityGateFilter from '../filters/QualityGateFilter';
 import ReliabilityFilter from '../filters/ReliabilityFilter';
 import SecurityFilter from '../filters/SecurityFilter';
-import SearchFilterContainer from '../filters/SearchFilterContainer';
 import SizeFilter from '../filters/SizeFilter';
 import TagsFilterContainer from '../filters/TagsFilterContainer';
 import { translate } from '../../../helpers/l10n';
@@ -84,7 +83,6 @@ export default function PageSidebar({
           </div>}
 
         <h3>{translate('filters')}</h3>
-        <SearchFilterContainer {...facetProps} />
       </div>
       <QualityGateFilter {...facetProps} />
       {!isLeakView && [
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.js
new file mode 100644 (file)
index 0000000..ef93443
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 React from 'react';
+import { shallow } from 'enzyme';
+import PageHeader from '../PageHeader';
+
+it('should render correctly', () => {
+  expect(shallow(<PageHeader query={{ search: 'test' }} total="12" />)).toMatchSnapshot();
+});
+
+it('should render correctly while loading', () => {
+  expect(
+    shallow(<PageHeader query={{ search: '' }} loading={true} isFavorite={true} total="2" />)
+  ).toMatchSnapshot();
+});
+
+it('should not render projects total', () => {
+  expect(
+    shallow(<PageHeader query={{ search: '' }} />).find('#projects-total').exists()
+  ).toBeFalsy();
+});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.js.snap
new file mode 100644 (file)
index 0000000..9783b6a
--- /dev/null
@@ -0,0 +1,79 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<header
+  className="page-header"
+>
+  <withRouter(SearchFilterContainer)
+    query={
+      Object {
+        "search": "test",
+      }
+    }
+  />
+  <div
+    className="page-actions projects-page-actions text-right"
+  >
+    <div
+      className="spacer-bottom"
+    >
+      <a
+        className="button js-projects-topbar-open"
+        href="#"
+      >
+        projects.view_settings
+      </a>
+    </div>
+    <span>
+      <strong
+        id="projects-total"
+      >
+        12
+      </strong>
+       
+      projects._projects
+    </span>
+  </div>
+</header>
+`;
+
+exports[`should render correctly while loading 1`] = `
+<header
+  className="page-header"
+>
+  <withRouter(SearchFilterContainer)
+    isFavorite={true}
+    query={
+      Object {
+        "search": "",
+      }
+    }
+  />
+  <div
+    className="page-actions projects-page-actions text-right"
+  >
+    <div
+      className="spacer-bottom"
+    >
+      <a
+        className="button js-projects-topbar-open"
+        href="#"
+      >
+        projects.view_settings
+      </a>
+    </div>
+    <i
+      className="spinner spacer-right"
+    />
+    <span>
+      <strong
+        id="projects-total"
+      >
+        2
+      </strong>
+       
+      projects._projects
+    </span>
+  </div>
+</header>
+`;
index 5460d9a0e3da4069149bcdd004437c83ba8d2d08..d48dbb01e6d422a2c29afbf60a2e67ce96240199 100644 (file)
@@ -40,14 +40,6 @@ exports[`should render \`leak\` view correctly 1`] = `
     <h3>
       filters
     </h3>
-    <withRouter(SearchFilterContainer)
-      isFavorite={false}
-      query={
-        Object {
-          "view": "leak",
-        }
-      }
-    />
   </div>
   <QualityGateFilter
     isFavorite={false}
@@ -151,14 +143,6 @@ exports[`should render correctly 1`] = `
     <h3>
       filters
     </h3>
-    <withRouter(SearchFilterContainer)
-      isFavorite={true}
-      query={
-        Object {
-          "size": "3",
-        }
-      }
-    />
   </div>
   <QualityGateFilter
     isFavorite={true}
index 1ce1141fa1184be64430e9f325fa003022f1a78f..e6bcf8259230f5038a77a3bfd40cc24890a9f1c4 100644 (file)
@@ -19,7 +19,6 @@
  */
 // @flow
 import React from 'react';
-import classNames from 'classnames';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 
 type Props = {
@@ -62,23 +61,21 @@ export default class SearchFilter extends React.PureComponent {
 
   render() {
     const { userQuery } = this.state;
-    const inputClassName = classNames('input-super-large', {
-      touched: userQuery != null && userQuery.length === 1
-    });
-
+    const shortQuery = userQuery != null && userQuery.length === 1;
     return (
       <div className="projects-facet-search" data-key="search">
         <input
           type="search"
           value={userQuery || ''}
-          className={inputClassName}
+          className="input-super-large"
           placeholder={translate('projects.search')}
           onChange={this.handleQueryChange}
           autoComplete="off"
         />
-        <span className="note spacer-left">
-          {translateWithParameters('select2.tooShort', 2)}
-        </span>
+        {shortQuery &&
+          <span className="note spacer-left">
+            {translateWithParameters('select2.tooShort', 2)}
+          </span>}
       </div>
     );
   }
index 0fd5b0f6cb426a88ed467f9d2d722438ca6fb89c..a48f32ac99c89d70e3df49eb1fa4c4cab3179199 100644 (file)
@@ -7,7 +7,7 @@ exports[`should display a help message when there is less than 2 characters 1`]
 >
   <input
     autoComplete="off"
-    className="input-super-large touched"
+    className="input-super-large"
     onChange={[Function]}
     placeholder="projects.search"
     type="search"
@@ -34,11 +34,6 @@ exports[`should display a help message when there is less than 2 characters 2`]
     type="search"
     value="foo"
   />
-  <span
-    className="note spacer-left"
-  >
-    select2.tooShort.2
-  </span>
 </div>
 `;
 
@@ -55,11 +50,6 @@ exports[`should render correctly without any search query 1`] = `
     type="search"
     value=""
   />
-  <span
-    className="note spacer-left"
-  >
-    select2.tooShort.2
-  </span>
 </div>
 `;
 
@@ -76,10 +66,5 @@ exports[`should render with a search query 1`] = `
     type="search"
     value="foo"
   />
-  <span
-    className="note spacer-left"
-  >
-    select2.tooShort.2
-  </span>
 </div>
 `;
index a6c471bef8c7d6fa348c5810d1b6ce99436a4cca..afe6b79b614ba8db3d4a26c0fed5a67486abbded 100644 (file)
 }
 
 .projects-facet-search {
-  position: relative;
-  padding-top: 10px;
-  padding-bottom: 10px;
-}
-
-.projects-facet-search .note {
   position: absolute;
-  opacity: 0;
+  bottom: 0;
   left: 0;
-  bottom: -7px;
-  transition: opacity 0.3s ease;
+  width: 300px;
 }
 
-.projects-facet-search input.touched ~ .note {
-  opacity: 1;
+.projects-facet-search .note {
+  position: absolute;
+  top: 1px;
+  right: 30px;
+  line-height: 24px;
+  pointer-events: none;
 }
 
 .projects-facets-reset {