export const DATE_FORMAT = 'YYYY-MM-DD';
+
+
+export const DEBOUNCE_DELAY = 250;
activityPage: 1,
statusFilter: STATUSES.ALL,
currentsFilter: CURRENTS.ALL,
- dateFilter: DATE.ANY
+ dateFilter: DATE.ANY,
+ searchQuery: ''
};
},
if (this.state.dateFilter !== DATE.ANY) {
_.extend(filters, this.getDateFilter());
}
+ if (this.state.searchQuery) {
+ _.extend(filters, { componentQuery: this.state.searchQuery });
+ }
return filters;
},
}, this.requestData);
},
+ onSearch(query) {
+ this.setState({ searchQuery: query }, this.requestData);
+ },
+
loadMore() {
this.setState({ activityPage: this.state.activityPage + 1 }, this.requestActivity);
},
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}/>
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() {
this.attachDatePicker();
},
+ componentWillMount() {
+ this.onSearch = _.debounce(this.onSearch, DEBOUNCE_DELAY);
+ },
+
getCurrentsOptions() {
return [
{ value: CURRENTS.ALL, label: 'All' },
];
},
- 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,
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';
);
},
+ 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">
name="background-task-date" onCheck={this.onDateChange}/>
{this.renderCustomDateInput()}
</li>
+ <li>{this.renderSearchBox()}</li>
</ul>
</section>
);
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', () => {
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);
+ });
+ });
});