]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6834 add search form
authorStas Vilchik <vilchiks@gmail.com>
Wed, 30 Sep 2015 15:03:30 +0000 (17:03 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Fri, 2 Oct 2015 08:57:25 +0000 (10:57 +0200)
server/sonar-web/src/main/js/apps/background-tasks/constants.js
server/sonar-web/src/main/js/apps/background-tasks/main.js
server/sonar-web/src/main/js/apps/background-tasks/search.js
server/sonar-web/tests/apps/background-tasks-test.js

index 68c14edb5b00fae8d674ded784ca0978778738e5..9988ee344fdf718d79912b78adb433fc182201d7 100644 (file)
@@ -22,3 +22,6 @@ export const DATE = {
 
 
 export const DATE_FORMAT = 'YYYY-MM-DD';
+
+
+export const DEBOUNCE_DELAY = 250;
index ddffd8d8831fb6f8c66d4524c60ec822c737f1d0..b9b7ced51381144c879339ba11e4d2a14ec05a0b 100644 (file)
@@ -19,7 +19,8 @@ export default React.createClass({
       activityPage: 1,
       statusFilter: STATUSES.ALL,
       currentsFilter: CURRENTS.ALL,
-      dateFilter: DATE.ANY
+      dateFilter: DATE.ANY,
+      searchQuery: ''
     };
   },
 
@@ -77,6 +78,9 @@ export default React.createClass({
     if (this.state.dateFilter !== DATE.ANY) {
       _.extend(filters, this.getDateFilter());
     }
+    if (this.state.searchQuery) {
+      _.extend(filters, { componentQuery: this.state.searchQuery });
+    }
     return filters;
   },
 
@@ -158,6 +162,10 @@ export default React.createClass({
     }, this.requestData);
   },
 
+  onSearch(query) {
+    this.setState({ searchQuery: query }, this.requestData);
+  },
+
   loadMore() {
     this.setState({ activityPage: this.state.activityPage + 1 }, this.requestActivity);
   },
@@ -186,9 +194,17 @@ export default React.createClass({
     return (
         <div className="page">
           <Header/>
+
           <Stats {...this.state} cancelPending={this.cancelPending} showFailures={this.showFailures}/>
-          <Search {...this.state} onStatusChange={this.onStatusChange} onCurrentsChange={this.onCurrentsChange} onDateChange={this.onDateChange}/>
+
+          <Search {...this.props} {...this.state}
+              onStatusChange={this.onStatusChange}
+              onCurrentsChange={this.onCurrentsChange}
+              onDateChange={this.onDateChange}
+              onSearch={this.onSearch}/>
+
           <Tasks tasks={[].concat(this.state.queue, this.state.activity)} onTaskCanceled={this.onTaskCanceled}/>
+
           <ListFooter count={this.state.queue.length + this.state.activity.length}
                       total={this.state.queue.length + this.state.activityTotal}
                       loadMore={this.loadMore}/>
index 994a035a9be872351564a6b2672091ee5ca34503..d70ab5e1337c5b06187131a009ca08fa67ac45e4 100644 (file)
@@ -1,7 +1,8 @@
 import $ from 'jquery';
+import _ from 'underscore';
 import React from 'react';
 import RadioToggle from '../../components/shared/radio-toggle';
-import {STATUSES, CURRENTS, DATE, DATE_FORMAT} from './constants';
+import {STATUSES, CURRENTS, DATE, DATE_FORMAT, DEBOUNCE_DELAY} from './constants';
 
 export default React.createClass({
   componentDidUpdate() {
@@ -12,6 +13,10 @@ export default React.createClass({
     this.attachDatePicker();
   },
 
+  componentWillMount() {
+    this.onSearch = _.debounce(this.onSearch, DEBOUNCE_DELAY);
+  },
+
   getCurrentsOptions() {
     return [
       { value: CURRENTS.ALL, label: 'All' },
@@ -36,17 +41,6 @@ export default React.createClass({
     ];
   },
 
-  attachDatePicker() {
-    let opts = {
-      dateFormat: 'yy-mm-dd',
-      changeMonth: true,
-      changeYear: true,
-      onSelect: this.onDateInputChange
-    };
-    $(React.findDOMNode(this.refs.minDate)).datepicker(opts);
-    $(React.findDOMNode(this.refs.maxDate)).datepicker(opts);
-  },
-
   onDateChange(newDate) {
     if (newDate === DATE.CUSTOM) {
       let minDateRaw = React.findDOMNode(this.refs.minDate).value,
@@ -65,6 +59,19 @@ export default React.createClass({
     this.onDateChange(DATE.CUSTOM);
   },
 
+  attachDatePicker() {
+    let opts = {
+      dateFormat: 'yy-mm-dd',
+      changeMonth: true,
+      changeYear: true,
+      onSelect: this.onDateInputChange
+    };
+    if ($.fn.datepicker) {
+      $(React.findDOMNode(this.refs.minDate)).datepicker(opts);
+      $(React.findDOMNode(this.refs.maxDate)).datepicker(opts);
+    }
+  },
+
   renderCustomDateInput() {
     let shouldBeVisible = this.props.dateFilter === DATE.CUSTOM,
         className = shouldBeVisible ? 'spacer-top' : 'spacer-top hidden';
@@ -78,6 +85,33 @@ export default React.createClass({
     );
   },
 
+  onSearchFormSubmit(e) {
+    e.preventDefault();
+    this.onSearch();
+  },
+
+  onSearch() {
+    let searchInput = React.findDOMNode(this.refs.searchInput),
+        query = searchInput.value;
+    this.props.onSearch(query);
+  },
+
+  renderSearchBox() {
+    if (this.props.options.componentId) {
+      // do not render search form on the project-level page
+      return null;
+    }
+    return (
+        <form onSubmit={this.onSearchFormSubmit} className="search-box">
+          <button className="search-box-submit button-clean">
+            <i className="icon-search"></i>
+          </button>
+          <input onChange={this.onSearch} ref="searchInput" className="search-box-input" type="search"
+                 placeholder="Search"/>
+        </form>
+    );
+  },
+
   render() {
     return (
         <section className="big-spacer-top big-spacer-bottom">
@@ -95,6 +129,7 @@ export default React.createClass({
                            name="background-task-date" onCheck={this.onDateChange}/>
               {this.renderCustomDateInput()}
             </li>
+            <li>{this.renderSearchBox()}</li>
           </ul>
         </section>
     );
index d9588cc26abbae3e0aa337ca7b63d548edeeec60..a5501a5ec0ad36a0da9ae3bdaf78a63468635f75 100644 (file)
@@ -1,10 +1,14 @@
 import React from 'react/addons';
 import App from '../../src/main/js/apps/background-tasks/app';
 import Header from '../../src/main/js/apps/background-tasks/header';
-import {STATUSES, CURRENTS} from '../../src/main/js/apps/background-tasks/constants';
+import Search from '../../src/main/js/apps/background-tasks/search';
+import {STATUSES, CURRENTS, DEBOUNCE_DELAY} from '../../src/main/js/apps/background-tasks/constants';
 
 let TestUtils = React.addons.TestUtils;
-let expect = require('chai').expect;
+let chai = require('chai');
+let expect = chai.expect;
+let sinon = require('sinon');
+chai.use(require('sinon-chai'));
 
 describe('Background Tasks', function () {
   describe('App', () => {
@@ -32,4 +36,43 @@ describe('Background Tasks', function () {
       expect(header.length).to.equal(1);
     });
   });
+
+  describe('Search', () => {
+    it('should render search form', () => {
+      let spy = sinon.spy();
+      let component = TestUtils.renderIntoDocument(<Search options={{}}
+                                                           onStatusChange={spy}
+                                                           onCurrentsChange={spy}
+                                                           onDateChange={spy}/>),
+          searchBox = TestUtils.scryRenderedDOMComponentsWithClass(component, 'search-box');
+      expect(searchBox).to.have.length(1);
+    });
+
+    it('should not render search form', () => {
+      let spy = sinon.spy();
+      let component = TestUtils.renderIntoDocument(<Search options={{ componentId: 'ABCD' }}
+                                                           onStatusChange={spy}
+                                                           onCurrentsChange={spy}
+                                                           onDateChange={spy}/>),
+          searchBox = TestUtils.scryRenderedDOMComponentsWithClass(component, 'search-box');
+      expect(searchBox).to.be.empty;
+    });
+
+    it('should search', (done) => {
+      let spy = sinon.spy(),
+          searchSpy = sinon.spy();
+      let component = TestUtils.renderIntoDocument(<Search options={{}}
+                                                           onStatusChange={spy}
+                                                           onCurrentsChange={spy}
+                                                           onDateChange={spy}
+                                                           onSearch={searchSpy}/>);
+      let searchInput = React.findDOMNode(TestUtils.findRenderedDOMComponentWithClass(component, 'search-box-input'));
+      searchInput.value = 'some search query';
+      TestUtils.Simulate.change(searchInput);
+      setTimeout(() => {
+        expect(searchSpy).to.have.been.calledWith('some search query');
+        done();
+      }, DEBOUNCE_DELAY);
+    });
+  });
 });