]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7149 display a list of sub-components when start searching
authorStas Vilchik <vilchiks@gmail.com>
Thu, 7 Jan 2016 16:11:07 +0000 (17:11 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Thu, 7 Jan 2016 16:11:07 +0000 (17:11 +0100)
server/sonar-web/src/main/js/apps/code/actions/index.js
server/sonar-web/src/main/js/apps/code/components/Code.js
server/sonar-web/src/main/js/apps/code/components/Search.js
server/sonar-web/src/main/js/apps/code/reducers/index.js
server/sonar-web/src/main/js/main/nav/templates/nav-shortcuts-help.hbs
server/sonar-web/tests/apps/code/store-test.js

index 1d3dfd7356131e5dbe3f818690ff796e2b6c84a4..98526d002877d25fdd1a2344710e3e83cf277fa1 100644 (file)
@@ -148,7 +148,14 @@ function retrieveComponent (componentKey, bucket) {
 
 let requestTree = (query, baseComponent, dispatch) => {
   dispatch(startFetching());
-  return getTree(baseComponent.key, { q: query, s: 'qualifier,name' })
+
+  const params = { s: 'qualifier,name', qualifiers: 'BRC,FIL,UTS' };
+
+  if (query) {
+    params.q = query;
+  }
+
+  return getTree(baseComponent.key, params)
       .then(r => dispatch(searchAction(r.components)))
       .then(() => dispatch(stopFetching()));
 };
@@ -199,7 +206,7 @@ export function browse (componentKey) {
 export function search (query, baseComponent) {
   return dispatch => {
     dispatch(updateQueryAction(query));
-    if (query) {
+    if (query != null) {
       requestTree(query, baseComponent, dispatch);
     } else {
       dispatch(searchAction(null));
index f2c61b51ec73ec02c3737c3e6378879d82a3891f..f7120d283df52773abf0653c4ba73f667d39238c 100644 (file)
@@ -25,7 +25,7 @@ import Components from './Components';
 import Breadcrumbs from './Breadcrumbs';
 import SourceViewer from './SourceViewer';
 import Search from './Search';
-import { initComponent, browse } from '../actions';
+import { initComponent, browse, search } from '../actions';
 import { translate } from '../../../helpers/l10n';
 
 
@@ -33,8 +33,12 @@ class Code extends Component {
   componentDidMount () {
     const { dispatch, component, routing } = this.props;
     const selectedKey = (routing.path && decodeURIComponent(routing.path.substr(1))) || component.key;
+
     dispatch(initComponent(component.key, component.breadcrumbs))
         .then(() => dispatch(browse(selectedKey)));
+
+    this.handleKeyDown = this.handleKeyDown.bind(this);
+    this.attachShortcuts();
   }
 
   componentWillReceiveProps (nextProps) {
@@ -47,11 +51,32 @@ class Code extends Component {
     }
   }
 
+  componentWillUnmount () {
+    this.removeShortcuts();
+  }
+
+  attachShortcuts () {
+    window.addEventListener('keyup', this.handleKeyDown);
+  }
+
+  removeShortcuts () {
+    window.removeEventListener('keyup', this.handleKeyDown);
+  }
+
   handleBrowse (component) {
     const { dispatch } = this.props;
     dispatch(browse(component.key));
   }
 
+  handleKeyDown (e) {
+    const { dispatch, component, searchQuery } = this.props;
+
+    // "t" key
+    if (e.keyCode === 84 && searchQuery == null) {
+      dispatch(search('', component));
+    }
+  }
+
   render () {
     const {
         fetching,
@@ -65,7 +90,7 @@ class Code extends Component {
     const shouldShowSearchResults = !!searchResults;
     const shouldShowSourceViewer = !!sourceViewer;
     const shouldShowComponents = !shouldShowSearchResults && !shouldShowSourceViewer && components;
-    const shouldShowBreadcrumbs = !shouldShowSearchResults &&  Array.isArray(breadcrumbs) && breadcrumbs.length > 1;
+    const shouldShowBreadcrumbs = !shouldShowSearchResults && Array.isArray(breadcrumbs) && breadcrumbs.length > 1;
 
     const componentsClassName = classNames('spacer-top', { 'new-loading': fetching });
 
@@ -80,7 +105,9 @@ class Code extends Component {
               <i className="spinner"/>
             </div>
 
-            <Search component={this.props.component}/>
+            <div className="page-actions">
+              <Search component={this.props.component}/>
+            </div>
           </header>
 
           {errorMessage && (
index 2a6c602926fd428674769614997cd2a4f127b0a6..f71d5f4e424e25b6e33a70c8ce5db40eb750c6c1 100644 (file)
@@ -21,40 +21,80 @@ import React, { Component } from 'react';
 import { connect } from 'react-redux';
 
 import { search } from '../actions';
+import { translate } from '../../../helpers/l10n';
 
 
 class Search extends Component {
   componentDidMount () {
-    this.refs.input.focus();
+    this.focusSearchInput();
+  }
+
+  componentDidUpdate () {
+    this.focusSearchInput();
+  }
+
+  focusSearchInput () {
+    if (this.refs.input) {
+      this.refs.input.focus();
+    }
   }
 
   handleSearch (e) {
     e.preventDefault();
     const { dispatch, component } = this.props;
-    const query = this.refs.input.value;
+    const query = this.refs.input ? this.refs.input.value : '';
     dispatch(search(query, component));
   }
 
+  handleStopSearch (e) {
+    e.preventDefault();
+    const { dispatch } = this.props;
+    dispatch(search(null));
+  }
+
+  handleKeyDown (e) {
+    const { dispatch } = this.props;
+
+    // "escape" key
+    if (e.keyCode === 27) {
+      dispatch(search(null));
+    }
+  }
+
   render () {
     const { query } = this.props;
+    const hasQuery = query != null;
 
     return (
         <form
             onSubmit={this.handleSearch.bind(this)}
             className="search-box code-search-box">
-          <button className="search-box-submit button-clean">
-            <i className="icon-search"></i>
-          </button>
-          <input
-              ref="input"
-              onChange={this.handleSearch.bind(this)}
-              value={query}
-              className="search-box-input"
-              type="search"
-              name="q"
-              placeholder="Search"
-              maxLength="100"
-              autoComplete="off"/>
+          {hasQuery && (
+              <input
+                  ref="input"
+                  onChange={this.handleSearch.bind(this)}
+                  onKeyDown={this.handleKeyDown.bind(this)}
+                  value={query}
+                  className="search-box-input"
+                  type="search"
+                  name="q"
+                  placeholder="Search"
+                  maxLength="100"
+                  autoComplete="off"
+                  style={{ visibility: hasQuery ? 'visible': 'hidden' }}/>
+          )}
+          {!hasQuery && (
+              <button className="search-box-submit">
+                {translate('search_verb')}
+              </button>
+          )}
+          {hasQuery && (
+              <button
+                  className="search-box-submit"
+                  onClick={this.handleStopSearch.bind(this)}>
+                {translate('cancel')}
+              </button>
+          )}
         </form>
     );
   }
index 2c8b31a1ebbb0f7b75d2562857b20a553351d620..596d92b91ea6dd41b1b9a94444bf555e96cd86e5 100644 (file)
@@ -76,7 +76,7 @@ export const initialState = {
   breadcrumbs: null,
   sourceViewer: null,
   searchResults: null,
-  searchQuery: '',
+  searchQuery: null,
   coverageMetric: null,
   baseBreadcrumbs: [],
   errorMessage: null
@@ -104,7 +104,7 @@ export function current (state = initialState, action) {
         breadcrumbs,
         sourceViewer,
         searchResults: null,
-        searchQuery: '',
+        searchQuery: null,
         errorMessage: null
       };
     case SEARCH:
index 2ef8a865b8b90dd99e2e35486d999f42e268628a..bfa98bc449534bd5d1b696365b92f7825fac5404 100644 (file)
         </ul>
       </div>
 
-      <h3 class="shortcuts-section-title">{{t 'shortcuts.section.rules'}}</h3>
+      <div class="spacer-bottom">
+        <h3 class="shortcuts-section-title">{{t 'shortcuts.section.rules'}}</h3>
+        <ul class="shortcuts-list">
+          <li><span class="shortcut-button">&uarr;</span> <span
+              class="shortcut-button">&darr;</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.navigate_between_rules'}}</li>
+          <li><span class="shortcut-button">&rarr;</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.open_details'}}</li>
+          <li><span class="shortcut-button">&larr;</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.return_to_list'}}</li>
+          <li><span class="shortcut-button">a</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.activate'}}</li>
+          <li><span class="shortcut-button">d</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.deactivate'}}</li>
+        </ul>
+      </div>
+
+      <h3 class="shortcuts-section-title">{{t 'shortcuts.section.code'}}</h3>
       <ul class="shortcuts-list">
-        <li><span class="shortcut-button">&uarr;</span> <span
-            class="shortcut-button">&darr;</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.navigate_between_rules'}}</li>
-        <li><span class="shortcut-button">&rarr;</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.open_details'}}</li>
-        <li><span class="shortcut-button">&larr;</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.return_to_list'}}</li>
-        <li><span class="shortcut-button">a</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.activate'}}</li>
-        <li><span class="shortcut-button">d</span> &nbsp;&nbsp; {{t 'shortcuts.section.rules.deactivate'}}</li>
+        <li><span class="shortcut-button">t</span> &nbsp;&nbsp; {{t 'shortcuts.section.code.search'}}</li>
       </ul>
     </div>
 
index e8b36a58e4b4406cf63661a32feef727de7b4321..fb54c02d32db2b16322b24d6cc34413ff6a902f1 100644 (file)
@@ -225,7 +225,7 @@ describe('Code :: Store', () => {
         it('should be reset', () => {
           const stateBefore = Object.assign({}, initialState, { searchQuery: 'query' });
           expect(current(stateBefore, browseAction(exampleComponent)).searchQuery)
-              .to.equal('');
+              .to.be.null;
         });
       });
       describe('errorMessage', () => {