You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@couchdb.apache.org by michellephung <gi...@git.apache.org> on 2015/03/15 02:24:45 UTC

[GitHub] couchdb-fauxton pull request: Active tasks in react

GitHub user michellephung opened a pull request:

    https://github.com/apache/couchdb-fauxton/pull/317

    Active tasks in react

    

You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/michellephung/couchdb-fauxton Active-Tasks-in-REACT

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/couchdb-fauxton/pull/317.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #317
    
----
commit 2b3f85020b6e445a27792f037aeaff454b72bf42
Author: michellephung@gmail.com <mi...@gmail.com>
Date:   2015-03-06T23:57:02Z

    Active Tasks in ReactJS

commit 68545e1967889851ea88f707078220bda207bbbc
Author: michellephung@gmail.com <mi...@gmail.com>
Date:   2015-03-15T01:20:31Z

    re-added test

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28230472
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks',
    +      'Replication',
    +      'Database Compaction',
    +      'Indexer',
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      return this.props.selectedRadio == radioName;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ', '-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +              {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +          {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader = this.props.sortByHeader;
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var headerIsAscending = this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function (nextProps) {
    +      this.setState({
    +        filteredTable:
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +        {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on),
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      if (!optionalClassName) {
    +        optionalClassName = '';
    +      }
    +      var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
    +
    +      return messageArray.map(function (msgLine, iterator) {
    +        return <p key={iterator} className={cssClasses}>{msgLine}</p>;
    +      });
    +    },
    +
    +    render: function () {
    +      var rowData =  this.getInfo(this.props.item);
    +      var objectFieldMsg = this.multilineMessage(rowData.objectField);
    +      var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
    +      var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
    +      var progressMsg = this.multilineMessage(rowData.progress);
    +
    +      return (
    +        <tr>
    +          <td>{rowData.type}</td>
    +          <td>{objectFieldMsg}</td>
    +          <td>{startedOnMsg}</td>
    +          <td>{updatedOnMsg}</td>
    +          <td>{rowData.pid}</td>
    +          <td>{progressMsg}</td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksPollingWidget = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    getPluralForLabel: function () {
    +      return this.state.pollingInterval === "1" ? '' : 's';
    +    },
    +
    +    createPollingWidget: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +      var s = this.getPluralForLabel();
    +      var onChangeHandle = this.pollingIntervalChange;
    +
    +      return (
    +        <ul className="polling-interval-widget">
    +          <li className="polling-interval-name">Polling interval
    +            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
    +              <span>{pollingInterval}</span> second{s} 
    +            </label>
    +          </li>
    +          <li>
    +            <input 
    +              id="pollingRange" 
    +              type="range" 
    +              min="1" 
    +              max="30" 
    +              step="1" 
    +              value={pollingInterval} 
    +              onChange={onChangeHandle}/>
    +          </li>
    +        </ul>
    +      );
    +    },
    +
    +    render: function () {
    +      var pollingWidget = this.createPollingWidget();
    +
    +      return  <div>{pollingWidget}</div>;
    +    }
    +  });
    +
    +  var activeTasksHelpers = {
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [app.helpers.formatDate(timeStamp)];
    +      timeMessage.push(app.helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +
    +    getDatabaseFieldMessage: function (item) {
    +      var type = item.type;
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + item.source);
    +        databaseFieldMessage.push('To: ' + item.target);
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(item.database);
    +        databaseFieldMessage.push('(View: ' + item.design_document + ')');
    +      } else {
    +        databaseFieldMessage.push(item.database);
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +
    +    getProgressMessage: function (item) {
    +      var progressMessage = [];
    +      var type = item.type;
    +
    +      if (item.hasOwnProperty('progress')) {
    +        progressMessage.push('Progress: ' + item.progress + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push(
    +          'Processed ' + item.changes_done + ' of ' + item.total_changes + ' changes.'
    +        );
    +      } else if (type === 'replication') {
    +        progressMessage.push(item.docs_written + ' docs written.');
    +
    +        if (item.hasOwnProperty('changes_pending')) {
    +          progressMessage.push(item.changes_pending + ' pending changes.');
    +        }
    +      }
    +
    +      if (item.hasOwnProperty('source_seq')) {
    +        progressMessage.push('Current source sequence: ' + item.source_seq + '. ');
    +      }
    +
    +      if (item.hasOwnProperty('changes_done')) {
    +        progressMessage.push(item.changes_done + ' Changes done.');
    +      }
    +
    +      return progressMessage;
    +    }
    +  };
    +
    +  return {
    +    renderActiveTasks: function (el) {
    +      React.render(<ActiveTasksController />, el);
    +    },
    +
    +    removeActiveTasks: function (el) {
    --- End diff --
    
    React support in RouteObjects are now in master. These can be removed and Active Tasks can be implemented similar to (this)[https://github.com/apache/couchdb-fauxton/blob/master/app/addons/documents/routes-index-editor.js#L106]


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27435168
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks',
    +      'Replication',
    +      'Database Compaction',
    +      'Indexer',
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      return this.props.selectedRadio == radioName;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ', '-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +              {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +          {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader = this.props.sortByHeader;
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var headerIsAscending = this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function (nextProps) {
    +      this.setState({
    +        filteredTable:
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +        {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on),
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      if (!optionalClassName) {
    +        optionalClassName = '';
    +      }
    +      var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
    +
    +      return messageArray.map(function (msgLine, iterator) {
    +        return <p key={iterator} className={cssClasses}>{msgLine}</p>;
    +      });
    +    },
    +
    +    render: function () {
    +      var rowData =  this.getInfo(this.props.item);
    +      var objectFieldMsg = this.multilineMessage(rowData.objectField);
    +      var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
    +      var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
    +      var progressMsg = this.multilineMessage(rowData.progress);
    +
    +      return (
    +        <tr>
    +          <td>{rowData.type}</td>
    +          <td>{objectFieldMsg}</td>
    +          <td>{startedOnMsg}</td>
    +          <td>{updatedOnMsg}</td>
    +          <td>{rowData.pid}</td>
    +          <td>{progressMsg}</td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksPollingWidget = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    getPluralForLabel: function () {
    +      return this.state.pollingInterval === "1" ? '' : 's';
    +    },
    +
    +    createPollingWidget: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +      var s = this.getPluralForLabel();
    +      var onChangeHandle = this.pollingIntervalChange;
    +
    +      return (
    +        <ul className="polling-interval-widget">
    +          <li className="polling-interval-name">Polling interval
    +            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
    +              <span>{pollingInterval}</span> second{s} 
    +            </label>
    +          </li>
    +          <li>
    +            <input 
    +              id="pollingRange" 
    +              type="range" 
    +              min="1" 
    +              max="30" 
    +              step="1" 
    +              value={pollingInterval} 
    +              onChange={onChangeHandle}/>
    +          </li>
    +        </ul>
    +      );
    +    },
    +
    +    render: function () {
    +      var pollingWidget = this.createPollingWidget();
    +
    +      return  <div>{pollingWidget}</div>;
    +    }
    +  });
    +
    +  var activeTasksHelpers = {
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [app.helpers.formatDate(timeStamp)];
    +      timeMessage.push(app.helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +
    +    getDatabaseFieldMessage: function (item) {
    +      var type = item.type;
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + item.source);
    +        databaseFieldMessage.push('To: ' + item.target);
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(item.database);
    +        databaseFieldMessage.push('(View: ' + item.design_document + ')');
    +      } else {
    +        databaseFieldMessage.push(item.database);
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +
    +    getProgressMessage: function (item) {
    +      var progressMessage = [];
    +      var type = item.type;
    +
    +      if (item.hasOwnProperty('progress')) {
    --- End diff --
    
    `_.has` is a little less verbose


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26449175
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    Yes, I mean about using Javascript property feature to define PollingInterval property (getter and setter) as like as the others here.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27435419
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,236 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (app, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({
    +
    +    init: function (collectionTable, backboneCollection) {
    +      this._prevSortbyHeader = 'started_on';
    +      this._headerIsAscending = true;
    +      this._selectedRadio = 'All Tasks';
    +      this._sortByHeader = 'started_on';
    +      this._searchTerm = '';
    +      this._collection = collectionTable;
    +      this._pollingInterval = 5;
    --- End diff --
    
    this in seconds? Be nice to add the unit to the var name just for clarity


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471806
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    +      $('#dashboard-content').scrollTop(0);
    +      $('#query').toggle('slow');
    +    },
    +    setNewSearchTerm: function (e) {
    +      this.setState({searchTerm: e.target.value});
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    render: function () {
    +      var searchTerm = this.state.searchTerm;
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu">
    +            <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +              <li>
    +                <a className="js-toggle-filter" href="#filter" data-bypass="true" data-toggle="tab" onClick={this.toggleFilter}>
    +                  <i className="fonticon fonticon-plus"></i>Filter
    +                </a>
    +              </li>
    +            </ul>
    +          </div>
    +
    +          <div className="tab-content">
    +            <div className="tab-pane" id="query">
    +              <div className="activetasks-header">
    +                <div className="pull-right">
    +                  <input  
    +                    className="task-search-database" 
    +                    type="text" name="search" 
    +                    placeholder="Search for databases..." 
    +                    value={searchTerm}
    +                    onChange={this.setNewSearchTerm}
    +                  />
    +                </div>
    +              </div>
    +            </div>
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.get('type'),
    +        objectField : this.getDatabaseFieldMessage(item) ,
    +        started_on : this.getTimeInfo(item.get('started_on')), 
    +        updated_on : this.getTimeInfo(item.get('updated_on')),
    +        pid : item.get('pid').replace(/[<>]/g, ''),
    +        progress : this.getProgressMessage(item),
    +      };
    +    },
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [Helpers.formatDate(timeStamp)];
    +      timeMessage.push(Helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +    getDatabaseFieldMessage: function (model) {
    +      var type = model.get('type');
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + model.get('source'));
    +        databaseFieldMessage.push('To: ' + model.get('target'));
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(model.get('database'));
    +        databaseFieldMessage.push('(View: ' + model.get('design_document') + ')');
    +      } else {
    +        databaseFieldMessage.push(model.get('database'));
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +    getProgressMessage: function (model) {
    +      var progressMessage = [];
    +      var type = model.get('type');
    +      if (!_.isUndefined(model.get('progress'))) {
    +        progressMessage.push('Progress: ' + model.get('progress') + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push('Processed ' + model.get('changes_done') + ' of ' + model.get('total_changes') + ' changes.');
    +      } else if (type === 'replication') {
    +        progressMessage.push(model.get('docs_written')+ ' docs written.');
    +        if (!_.isUndefined(model.get('changes_pending'))) {
    +          progressMessage.push(model.get('changes_pending') + ' pending changes.');
    +        }
    +      }
    +      if (!_.isUndefined(model.get('source_seq'))) {
    +        progressMessage.push('Current source sequence: ' + model.get('source_seq') + '. ');
    +      }
    +      if (!_.isUndefined(model.get('changes_done'))) {
    +        progressMessage.push(model.get('changes_done') + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    },
    +    passesFilter: function (item) {
    +      var searchTerm = this.props.searchTerm;
    +      var regex = new RegExp(searchTerm, 'g');
    +      
    +      var itemDatabasesTerm = '';
    +      if (item.has('database')) {
    +        itemDatabasesTerm += item.get('database'); 
    +      }
    +      if (item.has('source')) {
    +        itemDatabasesTerm += item.get('source'); 
    +      }
    +      if (item.has('target')) {
    +        itemDatabasesTerm += item.get('target'); 
    +      }
    +
    +      if (regex.test(itemDatabasesTerm)) {
    +        return this.passesTabFilter(item.get('type'));
    +      }
    +      return false;
    +    },
    +    passesTabFilter: function (type) {
    +      if (this.props.tab.toLowerCase() === type.replace('_', ' ') || this.props.tab ==='All Tasks') {
    +        return true;
    +      }
    +      return false;
    +    },
    +    sortByHeader: function (e) {
    +      var headerField = $(e.currentTarget),
    +      columnName = headerField.attr('data-type');
    +      Actions.sortByColumnHeader(columnName);
    +    },
    +    render: function () {
    +      var collection = this.props.collection;
    +      var resultsCount = 0;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <thead>
    +              <tr>
    +                <th className="type"      data-type="type"        onClick={this.sortByHeader}> Type</th>
    +                <th className="database"  data-type="database"    onClick={this.sortByHeader}> Database</th>
    +                <th className="started"   data-type="started_on"  onClick={this.sortByHeader}> Started on</th>
    +                <th className="updated"   data-type="updated_on"  onClick={this.sortByHeader}> Last updated on</th>
    +                <th className="pid"       data-type="pid"         onClick={this.sortByHeader}> PID</th>
    +                <th className="status"    data-type="progress"    onClick={this.sortByHeader}> Status</th>
    +              </tr>
    +            </thead>
    +            <tbody className="js-tasks-go-here">
    +              { collection.map(function (item, iteration) {
    --- End diff --
    
    This should be done in a a separate function. Here is an [example](https://github.com/apache/couchdb-fauxton/blob/master/app/addons/cors/components.react.jsx#L132)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-87361371
  
    Tests are written!


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27976201
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,610 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (searchTerm) {
    +      Actions.setSearchTerm(searchTerm);
    +    },
    +
    +    switchTab: function (newRadioButton) {  //radio buttons
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (headerClicked) {
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    --- End diff --
    
    Its a good point @robertkowalski but lets leave as is for now. Passing down 4 levels is a bit of a pain but it then allows us to keep all actions centralised to one controller component.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26449343
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    ok. I was thinking of defining the object inside the ActiveTaskStore object. 
    
    I'll wait for some more feedback though. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27434735
  
    --- Diff: app/addons/activetasks/assets/less/activetasks.less ---
    @@ -63,3 +64,148 @@
       }
     }
     
    +.no-matching-database-on-search {
    +  color: #e33f3b;
    +}
    +
    +p.multiline-active-tasks-message {
    +  margin: 0;
    +
    +  &.time:nth-child(2) {
    +    color: #888;
    +  }
    +}
    +
    +#dashboard-upper-content {
    +  padding-right: 20px;
    +}
    +
    +.active-tasks.dashboard-upper-menu {
    +  left: 220px;
    +
    +  .closeMenu & {
    +    left: 64px;
    +  }
    +}
    +
    +.dashboard-lower-menu {
    +  padding-top: 90px;
    +  padding-left: 20x;
    --- End diff --
    
    typo


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26445202
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    Pardon my curiosity, but all these `getFoo` / `setFoo` shouldn't be defined via [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) or [\_\_defineGetter\_\_ / \_\_defineSetter\_\_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__) instead for more nicer, java-less, usage?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28230520
  
    --- Diff: app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx ---
    @@ -0,0 +1,116 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'addons/activetasks/actions',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks({});
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Components', function () {
    +
    +    describe('Active Tasks Polling (Components)', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      beforeEach(function () {
    +        pollingWidgetDiv = document.createElement('div');
    +        pollingWidget = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksPollingWidget, null), pollingWidgetDiv);
    +      });
    +
    +      afterEach(function () {
    +        React.unmountComponentAtNode(pollingWidgetDiv);
    +      });
    +
    +      it('should trigger update polling interval', function () {
    +        var spy = sinon.spy(Actions, 'changePollingInterval');
    +        var rangeNode = TestUtils.findRenderedDOMComponentWithTag(pollingWidget, 'input');
    +        var time = '9';
    +
    +        TestUtils.Simulate.change(rangeNode, {target: {value: time}});
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Tasks Table (Components)', function () {
    +      var table, tableDiv, spy, filterTab;
    +
    +      beforeEach(function () {
    +        tableDiv = document.createElement('div');
    +        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +        table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv);
    +
    +        // open filter tray
    +        filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
    +        TestUtils.Simulate.click(filterTab);
    +      });
    +
    +      afterEach(function () {
    +        spy.restore();
    +        React.unmountComponentAtNode(tableDiv);
    +        window.confirm.restore && window.confirm.restore();
    +      });
    +
    +      describe('Active Tasks Filter tray', function () {
    +        var radioIDs = [
    +          'Replication',
    +          'Database-Compaction',
    +          'Indexer',
    +          'View-Compaction'
    +        ];
    +
    +        it('should trigger change to radio buttons', function () {
    +          _.each(radioIDs, function (radioID) {
    +            spy = sinon.spy(Actions, 'switchTab');
    +            TestUtils.Simulate.change($(table.getDOMNode()).find('#' + radioID)[0]);
    +            assert.ok(spy.calledOnce);
    +            spy.restore();
    --- End diff --
    
    @michellephung what @robertkowalski means here is that you call `spy.restore` in the test and the `afterEach` function. You only need to call it in the `afterEach` function.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27959178
  
    --- Diff: app/addons/activetasks/tests/activetasks.storesSpec.js ---
    @@ -0,0 +1,231 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks();
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Stores', function () {
    +    var spy;
    +
    +    beforeEach(function () {
    +      activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +      this.clock = sinon.useFakeTimers();
    +    });
    +
    +    afterEach(function () {
    +      spy.restore();
    +      this.clock.restore();
    +    });
    +
    +    describe('Active Task Stores - Polling', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      it('should poll at the min time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +        var minTime = 1;
    +        activeTasksStore.setPollingInterval(minTime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, minTime * 1000);
    +        this.clock.tick(minTime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(minTime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should poll at the max time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +
    +        var maxTime = 30;
    +        activeTasksStore.setPollingInterval(maxTime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, maxTime * 1000);
    +        this.clock.tick(maxTime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(maxTime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should poll at a mid time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +
    +        var midtime = 15;
    +        activeTasksStore.setPollingInterval(midtime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, midtime * 1000);
    +        this.clock.tick(midtime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(midtime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should clear interval each time', function () {
    +        var spy = sinon.spy(window, 'clearInterval');
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Task Stores - Filter Tab Tray', function () {
    +      var fakeFilteredTable, storeFilteredtable;
    +      function sort (a, b, sortBy) {  //sorts array by objects with key 'sortBy', with default started_on
    +        if (_.isUndefined(sortBy)) {
    +          sortBy = 'started_on';
    +        }
    +        return b[sortBy] - a[sortBy];
    +      }
    +
    +      afterEach(function () {
    +        fakeFilteredTable = [];
    +      });
    +
    +      it('should filter the table correctly, by radio -- All Tasks', function () {
    +        activeTasksStore.setSelectedRadio('all_tasks');
    +        //parse table and check that it only contains objects any type
    +        var table = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +        assert.ok(activeTasksStore._collection.length, table.length);
    +      });
    +
    +      it('should filter the table correctly, by radio -- Replication', function () {
    +        activeTasksStore.setSelectedRadio('replication');
    +        var storeFilteredtable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +
    +        //parse table and check that it only contains objects with type: Replication
    +        _.each(storeFilteredtable, function (activeTask) {
    +          assert.ok(activeTasksStore.passesRadioFilter(activeTask));
    +          assert.ok(activeTask.type === activeTasksStore.getSelectedRadio());
    +        });
    +
    +        //check that the other tasks from collection do not have type: Replication
    +        fakeFilteredTable = _.filter(activeTasksCollection.table, function (task) {
    --- End diff --
    
    I don't quite understand what you are testing here?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28253350
  
    --- Diff: app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx ---
    @@ -0,0 +1,116 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'addons/activetasks/actions',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks({});
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Components', function () {
    +
    +    describe('Active Tasks Polling (Components)', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      beforeEach(function () {
    +        pollingWidgetDiv = document.createElement('div');
    +        pollingWidget = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksPollingWidget, null), pollingWidgetDiv);
    +      });
    +
    +      afterEach(function () {
    +        React.unmountComponentAtNode(pollingWidgetDiv);
    +      });
    +
    +      it('should trigger update polling interval', function () {
    +        var spy = sinon.spy(Actions, 'changePollingInterval');
    +        var rangeNode = TestUtils.findRenderedDOMComponentWithTag(pollingWidget, 'input');
    +        var time = '9';
    +
    +        TestUtils.Simulate.change(rangeNode, {target: {value: time}});
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Tasks Table (Components)', function () {
    +      var table, tableDiv, spy, filterTab;
    +
    +      beforeEach(function () {
    +        tableDiv = document.createElement('div');
    +        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +        table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv);
    +
    +        // open filter tray
    +        filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
    +        TestUtils.Simulate.click(filterTab);
    +      });
    +
    +      afterEach(function () {
    +        spy.restore();
    +        React.unmountComponentAtNode(tableDiv);
    +        window.confirm.restore && window.confirm.restore();
    +      });
    +
    +      describe('Active Tasks Filter tray', function () {
    +        var radioIDs = [
    +          'Replication',
    +          'Database-Compaction',
    +          'Indexer',
    +          'View-Compaction'
    +        ];
    +
    +        it('should trigger change to radio buttons', function () {
    +          _.each(radioIDs, function (radioID) {
    +            spy = sinon.spy(Actions, 'switchTab');
    +            TestUtils.Simulate.change($(table.getDOMNode()).find('#' + radioID)[0]);
    +            assert.ok(spy.calledOnce);
    +            spy.restore();
    --- End diff --
    
    Oh, @michellephung you are right! Sorry Garren and me can't read code!


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26449314
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    @michellephung mmm...one moment! I'm not sure in success of such idea, just throw it because I'd read about properties in javascript recently (: Let's ask @garrensmith or @robertkowalski if is it ever applicable for Fauxton to not let your time be wasted.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-87826108
  
    rebased, and fixed code per suggestions


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27404996
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,588 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +  
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +       
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +        
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    
    +    //radio buttons
    +    switchTab: function (e) { 
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {  
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +    
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return ( 
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks', 
    +      'Replication',
    +      'Database Compaction', 
    +      'Indexer', 
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      if (this.props.selectedRadio == radioName) return true;
    +      else return false;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ','-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +                {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +            {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader= this.props.sortByHeader;
    +      var onTableHeaderClick= this.props.onTableHeaderClick;
    +      var headerIsAscending= this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function(nextProps) {
    +      this.setState({
    +        filteredTable: 
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (  
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +          {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on), 
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      optionalClassName ? optionalClassName = optionalClassName : optionalClassName = "";
    +      var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
    +
    +      return messageArray.map(function (msgLine, iterator) {
    +        return <p key={iterator} className={cssClasses}>{msgLine}</p>;
    +      });
    +    },
    +
    +    render: function () {
    +      var rowData =  this.getInfo(this.props.item);
    +      var objectFieldMsg = this.multilineMessage(rowData.objectField);
    +      var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
    +      var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
    +      var progressMsg = this.multilineMessage(rowData.progress);
    +
    +      return (
    +        <tr>
    +          <td>{rowData.type}</td>
    +          <td>{objectFieldMsg}</td>
    +          <td>{startedOnMsg}</td>
    +          <td>{updatedOnMsg}</td>
    +          <td>{rowData.pid}</td>
    +          <td>{progressMsg}</td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksPollingWidget = React.createClass({
    +    
    +    getStoreState: function () {
    +      return {  
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +       activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    onChange: function () {  
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    getPluralForLabel: function () {
    +      return this.state.pollingInterval === "1" ? '' : 's';
    +    },
    +
    +    createPollingWidget: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +      var s = this.getPluralForLabel();
    +      var onChangeHandle = this.pollingIntervalChange;
    +
    +      return (
    +        <ul className="polling-interval-widget">
    +          <li className="polling-interval-name">Polling interval
    +            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
    +              <span>{pollingInterval}</span> second{s} 
    +            </label>
    +          </li>
    +          <li>
    +            <input 
    +              id="pollingRange" 
    +              type="range" 
    +              min="1" 
    +              max="30" 
    +              step="1" 
    +              value={pollingInterval} 
    +              onChange={onChangeHandle}/>
    +          </li>
    +        </ul>
    +      );
    +    },
    +
    +    render: function () {
    +      var pollingWidget = this.createPollingWidget();
    +
    +      return  <div>{pollingWidget}</div>;
    +    }
    +  });
    +
    +  var activeTasksHelpers = {
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [app.helpers.formatDate(timeStamp)];
    +      timeMessage.push(app.helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +
    +    getDatabaseFieldMessage: function (item) {
    +      var type = item.type;
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + item.source);
    +        databaseFieldMessage.push('To: ' + item.target);
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(item.database);
    +        databaseFieldMessage.push('(View: ' + item.design_document + ')');
    +      } else {
    +        databaseFieldMessage.push(item.database);
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +
    +    getProgressMessage: function (item) {
    +      var progressMessage = [];
    +      var type = item.type;
    +
    +      if (item.hasOwnProperty('progress')) {  
    +        progressMessage.push('Progress: ' + item.progress + '%'); 
    +      }
    +      
    +      if (type === 'indexer') { 
    +        progressMessage.push(
    +          'Processed ' + item.changes_done + ' of ' + item.total_changes + ' changes.'
    +        );
    +      } else if (type === 'replication') {
    +        progressMessage.push(item.docs_written + ' docs written.');
    +        
    +        if (item.hasOwnProperty('changes_pending')) { 
    +          progressMessage.push(item.changes_pending + ' pending changes.'); 
    +        }
    +      }
    +
    +      if (item.hasOwnProperty('source_seq')) { 
    +        progressMessage.push('Current source sequence: ' + item.source_seq + '. '); 
    +      }
    +
    +      if (item.hasOwnProperty('changes_done')) {
    +        progressMessage.push(item.changes_done + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    }
    +  };
    +  
    +  return {
    +    renderActiveTasks: function (el) {
    +      React.render(<ActiveTasksController />, el);
    +    },
    +
    +    removeActiveTasks: function (el) {
    +      React.unmountComponentAtNode(el);
    +    },
    +
    +    renderActiveTasksPollingWidget: function (el) {
    +      React.render(<ActiveTasksPollingWidget />, el);
    +    },
    +
    +    ActiveTasksController: ActiveTasksController,
    +      ActiveTasksFilter: ActiveTasksFilter,
    +        ActiveTasksFilterTab: ActiveTasksFilterTab,
    +        ActiveTasksFilterTray: ActiveTasksFilterTray,
    +
    +      ActiveTaskTable: ActiveTaskTable,
    +        ActiveTasksTableHeader: ActiveTasksTableHeader,
    +        TableHeader: TableHeader,
    +        ActiveTasksTableBody: ActiveTasksTableBody,
    +        ActiveTaskTableBodyContents: ActiveTaskTableBodyContents,
    +
    +    ActiveTasksPollingWidget: ActiveTasksPollingWidget
    --- End diff --
    
    I was aiming for a kind of hierarchy :( 
    but if you think its confusing, I can change it. :)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471294
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    +      $('#dashboard-content').scrollTop(0);
    +      $('#query').toggle('slow');
    +    },
    +    setNewSearchTerm: function (e) {
    +      this.setState({searchTerm: e.target.value});
    --- End diff --
    
    If you are storing the searchTerm in the store then you don't need to store it in the state.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27404069
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,588 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +  
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +       
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +        
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    
    +    //radio buttons
    +    switchTab: function (e) { 
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {  
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +    
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return ( 
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks', 
    +      'Replication',
    +      'Database Compaction', 
    +      'Indexer', 
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      if (this.props.selectedRadio == radioName) return true;
    +      else return false;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ','-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +                {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +            {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader= this.props.sortByHeader;
    +      var onTableHeaderClick= this.props.onTableHeaderClick;
    +      var headerIsAscending= this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function(nextProps) {
    +      this.setState({
    +        filteredTable: 
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (  
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +          {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on), 
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      optionalClassName ? optionalClassName = optionalClassName : optionalClassName = "";
    --- End diff --
    
    this can be written easier:
    
    ```js
    if (!optionalClassName) {
      optionalClassName = '';
    }
    
    ```


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28010758
  
    --- Diff: app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx ---
    @@ -0,0 +1,116 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'addons/activetasks/actions',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks({});
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Components', function () {
    +
    +    describe('Active Tasks Polling (Components)', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      beforeEach(function () {
    +        pollingWidgetDiv = document.createElement('div');
    +        pollingWidget = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksPollingWidget, null), pollingWidgetDiv);
    +      });
    +
    +      afterEach(function () {
    +        React.unmountComponentAtNode(pollingWidgetDiv);
    +      });
    +
    +      it('should trigger update polling interval', function () {
    +        var spy = sinon.spy(Actions, 'changePollingInterval');
    +        var rangeNode = TestUtils.findRenderedDOMComponentWithTag(pollingWidget, 'input');
    +        var time = '9';
    +
    +        TestUtils.Simulate.change(rangeNode, {target: {value: time}});
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Tasks Table (Components)', function () {
    +      var table, tableDiv, spy, filterTab;
    +
    +      beforeEach(function () {
    +        tableDiv = document.createElement('div');
    +        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +        table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv);
    +
    +        // open filter tray
    +        filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
    +        TestUtils.Simulate.click(filterTab);
    +      });
    +
    +      afterEach(function () {
    +        spy.restore();
    +        React.unmountComponentAtNode(tableDiv);
    +        window.confirm.restore && window.confirm.restore();
    +      });
    +
    +      describe('Active Tasks Filter tray', function () {
    +        var radioIDs = [
    +          'Replication',
    +          'Database-Compaction',
    +          'Indexer',
    +          'View-Compaction'
    +        ];
    +
    +        it('should trigger change to radio buttons', function () {
    +          _.each(radioIDs, function (radioID) {
    +            spy = sinon.spy(Actions, 'switchTab');
    +            TestUtils.Simulate.change($(table.getDOMNode()).find('#' + radioID)[0]);
    +            assert.ok(spy.calledOnce);
    +            spy.restore();
    --- End diff --
    
    the spy is called/restored for each radio button clicked


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-87404115
  
    I don't know specifically your and JS practices, but you have a lot cases like:
    
    ```
    if (cond){ 
      return true;
    } 
    return false;
    ```
    
    Do you prefer this style, require `return cond;` instead or both are acceptable?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27548993
  
    --- Diff: app/addons/activetasks/tests/activetasks.storesSpec.js ---
    @@ -0,0 +1,91 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks({});
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Stores', function () {
    +
    +    beforeEach(function () {
    +      activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +      this.clock = sinon.useFakeTimers();
    +    });
    +
    +    afterEach(function () {
    +      this.clock.restore();
    +    });
    +
    +    describe('Active Task Stores - Polling', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      it('should poll at the correct time', function () {
    --- End diff --
    
    This test is good but you need to break it up into multiple tests. Its better to test each different time in a new test.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27959286
  
    --- Diff: app/addons/activetasks/tests/activetasks.storesSpec.js ---
    @@ -0,0 +1,231 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks();
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Stores', function () {
    +    var spy;
    +
    +    beforeEach(function () {
    +      activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +      this.clock = sinon.useFakeTimers();
    +    });
    +
    +    afterEach(function () {
    +      spy.restore();
    +      this.clock.restore();
    +    });
    +
    +    describe('Active Task Stores - Polling', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      it('should poll at the min time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +        var minTime = 1;
    +        activeTasksStore.setPollingInterval(minTime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, minTime * 1000);
    +        this.clock.tick(minTime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(minTime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should poll at the max time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +
    +        var maxTime = 30;
    +        activeTasksStore.setPollingInterval(maxTime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, maxTime * 1000);
    +        this.clock.tick(maxTime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(maxTime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should poll at a mid time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +
    +        var midtime = 15;
    +        activeTasksStore.setPollingInterval(midtime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, midtime * 1000);
    +        this.clock.tick(midtime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(midtime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should clear interval each time', function () {
    +        var spy = sinon.spy(window, 'clearInterval');
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Task Stores - Filter Tab Tray', function () {
    +      var fakeFilteredTable, storeFilteredtable;
    +      function sort (a, b, sortBy) {  //sorts array by objects with key 'sortBy', with default started_on
    +        if (_.isUndefined(sortBy)) {
    +          sortBy = 'started_on';
    +        }
    +        return b[sortBy] - a[sortBy];
    +      }
    +
    +      afterEach(function () {
    +        fakeFilteredTable = [];
    +      });
    +
    +      it('should filter the table correctly, by radio -- All Tasks', function () {
    +        activeTasksStore.setSelectedRadio('all_tasks');
    +        //parse table and check that it only contains objects any type
    +        var table = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +        assert.ok(activeTasksStore._collection.length, table.length);
    +      });
    +
    +      it('should filter the table correctly, by radio -- Replication', function () {
    +        activeTasksStore.setSelectedRadio('replication');
    +        var storeFilteredtable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +
    +        //parse table and check that it only contains objects with type: Replication
    +        _.each(storeFilteredtable, function (activeTask) {
    +          assert.ok(activeTasksStore.passesRadioFilter(activeTask));
    +          assert.ok(activeTask.type === activeTasksStore.getSelectedRadio());
    +        });
    +
    +        //check that the other tasks from collection do not have type: Replication
    +        fakeFilteredTable = _.filter(activeTasksCollection.table, function (task) {
    +          return task.type === 'replication';
    +        });
    +        assert.deepEqual(fakeFilteredTable.sort(sort), storeFilteredtable.sort(sort));
    +      });
    +
    +      it('should filter the table correctly, by radio -- Database Compaction', function () {
    +        activeTasksStore.setSelectedRadio('database_compaction');
    +        var storeFilteredtable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +
    +        //parse table and check that it only contains objects with type: database_compaction
    +        _.each(storeFilteredtable, function (activeTask) {
    +          assert.ok(activeTasksStore.passesRadioFilter(activeTask));
    +          assert.ok(activeTask.type === activeTasksStore.getSelectedRadio());
    +        });
    +
    +        //check that the other tasks from collection do not have type: database_compaction
    +        fakeFilteredTable = _.filter(activeTasksCollection.table, function (task) {
    +          return task.type === 'database_compaction';
    +        });
    +        assert.deepEqual(fakeFilteredTable.sort(sort), storeFilteredtable.sort(sort));
    +      });
    +
    +      it('should filter the table correctly, by radio -- Indexer', function () {
    +        activeTasksStore.setSelectedRadio('indexer');
    +        var storeFilteredtable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +
    +        //parse table and check that it only contains objects with type: indexer
    +        _.each(storeFilteredtable, function (activeTask) {
    +          assert.ok(activeTasksStore.passesRadioFilter(activeTask));
    +          assert.ok(activeTask.type === activeTasksStore.getSelectedRadio());
    +        });
    +
    +        //check that the other tasks from collection do not have type: indexer
    +        fakeFilteredTable = _.filter(activeTasksCollection.table, function (task) {
    +          return task.type === 'indexer';
    +        });
    +        assert.deepEqual(fakeFilteredTable.sort(sort), storeFilteredtable.sort(sort));
    +      });
    +
    +      it('should filter the table correctly, by radio -- View Compaction', function () {
    +        activeTasksStore.setSelectedRadio('view_compaction');
    +        var storeFilteredtable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +
    +        //parse table and check that it only contains objects with type: view_compaction
    +        _.each(storeFilteredtable, function (activeTask) {
    +          assert.ok(activeTasksStore.passesRadioFilter(activeTask));
    +          assert.ok(activeTask.type === activeTasksStore.getSelectedRadio());
    +        });
    +
    +        //check that the other tasks from collection do not have type: view_compaction
    +        fakeFilteredTable = _.filter(activeTasksCollection.table, function (task) {
    +          return task.type === 'view_compaction';
    +        });
    +        assert.deepEqual(fakeFilteredTable.sort(sort), storeFilteredtable.sort(sort));
    +      });
    +
    +      it('should search the table correctly', function () {
    +        activeTasksStore.setSelectedRadio('all_tasks');
    +        var searchTerm = 'base';
    +        activeTasksStore.setSearchTerm(searchTerm);
    +        var storeGeneratedTable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    --- End diff --
    
    Instead of generating a take filterered table. Rather just create a table with the required results. Then check that the store can generate the same results. It will greatly simplify the test


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27404239
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,588 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +  
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +       
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +        
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    
    +    //radio buttons
    +    switchTab: function (e) { 
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {  
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +    
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return ( 
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks', 
    +      'Replication',
    +      'Database Compaction', 
    +      'Indexer', 
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      if (this.props.selectedRadio == radioName) return true;
    +      else return false;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ','-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +                {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +            {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader= this.props.sortByHeader;
    +      var onTableHeaderClick= this.props.onTableHeaderClick;
    +      var headerIsAscending= this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function(nextProps) {
    +      this.setState({
    +        filteredTable: 
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (  
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +          {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on), 
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      optionalClassName ? optionalClassName = optionalClassName : optionalClassName = "";
    +      var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
    +
    +      return messageArray.map(function (msgLine, iterator) {
    +        return <p key={iterator} className={cssClasses}>{msgLine}</p>;
    +      });
    +    },
    +
    +    render: function () {
    +      var rowData =  this.getInfo(this.props.item);
    +      var objectFieldMsg = this.multilineMessage(rowData.objectField);
    +      var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
    +      var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
    +      var progressMsg = this.multilineMessage(rowData.progress);
    +
    +      return (
    +        <tr>
    +          <td>{rowData.type}</td>
    +          <td>{objectFieldMsg}</td>
    +          <td>{startedOnMsg}</td>
    +          <td>{updatedOnMsg}</td>
    +          <td>{rowData.pid}</td>
    +          <td>{progressMsg}</td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksPollingWidget = React.createClass({
    +    
    +    getStoreState: function () {
    +      return {  
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +       activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    onChange: function () {  
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    getPluralForLabel: function () {
    +      return this.state.pollingInterval === "1" ? '' : 's';
    +    },
    +
    +    createPollingWidget: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +      var s = this.getPluralForLabel();
    +      var onChangeHandle = this.pollingIntervalChange;
    +
    +      return (
    +        <ul className="polling-interval-widget">
    +          <li className="polling-interval-name">Polling interval
    +            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
    +              <span>{pollingInterval}</span> second{s} 
    +            </label>
    +          </li>
    +          <li>
    +            <input 
    +              id="pollingRange" 
    +              type="range" 
    +              min="1" 
    +              max="30" 
    +              step="1" 
    +              value={pollingInterval} 
    +              onChange={onChangeHandle}/>
    +          </li>
    +        </ul>
    +      );
    +    },
    +
    +    render: function () {
    +      var pollingWidget = this.createPollingWidget();
    +
    +      return  <div>{pollingWidget}</div>;
    +    }
    +  });
    +
    +  var activeTasksHelpers = {
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [app.helpers.formatDate(timeStamp)];
    +      timeMessage.push(app.helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +
    +    getDatabaseFieldMessage: function (item) {
    +      var type = item.type;
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + item.source);
    +        databaseFieldMessage.push('To: ' + item.target);
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(item.database);
    +        databaseFieldMessage.push('(View: ' + item.design_document + ')');
    +      } else {
    +        databaseFieldMessage.push(item.database);
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +
    +    getProgressMessage: function (item) {
    +      var progressMessage = [];
    +      var type = item.type;
    +
    +      if (item.hasOwnProperty('progress')) {  
    +        progressMessage.push('Progress: ' + item.progress + '%'); 
    +      }
    +      
    +      if (type === 'indexer') { 
    +        progressMessage.push(
    +          'Processed ' + item.changes_done + ' of ' + item.total_changes + ' changes.'
    +        );
    +      } else if (type === 'replication') {
    +        progressMessage.push(item.docs_written + ' docs written.');
    +        
    +        if (item.hasOwnProperty('changes_pending')) { 
    +          progressMessage.push(item.changes_pending + ' pending changes.'); 
    +        }
    +      }
    +
    +      if (item.hasOwnProperty('source_seq')) { 
    +        progressMessage.push('Current source sequence: ' + item.source_seq + '. '); 
    +      }
    +
    +      if (item.hasOwnProperty('changes_done')) {
    +        progressMessage.push(item.changes_done + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    }
    +  };
    +  
    +  return {
    +    renderActiveTasks: function (el) {
    +      React.render(<ActiveTasksController />, el);
    +    },
    +
    +    removeActiveTasks: function (el) {
    +      React.unmountComponentAtNode(el);
    +    },
    +
    +    renderActiveTasksPollingWidget: function (el) {
    +      React.render(<ActiveTasksPollingWidget />, el);
    +    },
    +
    +    ActiveTasksController: ActiveTasksController,
    +      ActiveTasksFilter: ActiveTasksFilter,
    +        ActiveTasksFilterTab: ActiveTasksFilterTab,
    +        ActiveTasksFilterTray: ActiveTasksFilterTray,
    +
    +      ActiveTaskTable: ActiveTaskTable,
    +        ActiveTasksTableHeader: ActiveTasksTableHeader,
    +        TableHeader: TableHeader,
    +        ActiveTasksTableBody: ActiveTasksTableBody,
    +        ActiveTaskTableBodyContents: ActiveTaskTableBodyContents,
    +
    +    ActiveTasksPollingWidget: ActiveTasksPollingWidget
    --- End diff --
    
    the formatting is a bit disturbing


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471908
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    +      $('#dashboard-content').scrollTop(0);
    +      $('#query').toggle('slow');
    +    },
    +    setNewSearchTerm: function (e) {
    +      this.setState({searchTerm: e.target.value});
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    render: function () {
    +      var searchTerm = this.state.searchTerm;
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu">
    +            <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +              <li>
    +                <a className="js-toggle-filter" href="#filter" data-bypass="true" data-toggle="tab" onClick={this.toggleFilter}>
    +                  <i className="fonticon fonticon-plus"></i>Filter
    +                </a>
    +              </li>
    +            </ul>
    +          </div>
    +
    +          <div className="tab-content">
    +            <div className="tab-pane" id="query">
    +              <div className="activetasks-header">
    +                <div className="pull-right">
    +                  <input  
    +                    className="task-search-database" 
    +                    type="text" name="search" 
    +                    placeholder="Search for databases..." 
    +                    value={searchTerm}
    +                    onChange={this.setNewSearchTerm}
    +                  />
    +                </div>
    +              </div>
    +            </div>
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.get('type'),
    +        objectField : this.getDatabaseFieldMessage(item) ,
    +        started_on : this.getTimeInfo(item.get('started_on')), 
    +        updated_on : this.getTimeInfo(item.get('updated_on')),
    +        pid : item.get('pid').replace(/[<>]/g, ''),
    +        progress : this.getProgressMessage(item),
    +      };
    +    },
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [Helpers.formatDate(timeStamp)];
    +      timeMessage.push(Helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +    getDatabaseFieldMessage: function (model) {
    +      var type = model.get('type');
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + model.get('source'));
    +        databaseFieldMessage.push('To: ' + model.get('target'));
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(model.get('database'));
    +        databaseFieldMessage.push('(View: ' + model.get('design_document') + ')');
    +      } else {
    +        databaseFieldMessage.push(model.get('database'));
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +    getProgressMessage: function (model) {
    +      var progressMessage = [];
    +      var type = model.get('type');
    +      if (!_.isUndefined(model.get('progress'))) {
    +        progressMessage.push('Progress: ' + model.get('progress') + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push('Processed ' + model.get('changes_done') + ' of ' + model.get('total_changes') + ' changes.');
    +      } else if (type === 'replication') {
    +        progressMessage.push(model.get('docs_written')+ ' docs written.');
    +        if (!_.isUndefined(model.get('changes_pending'))) {
    +          progressMessage.push(model.get('changes_pending') + ' pending changes.');
    +        }
    +      }
    +      if (!_.isUndefined(model.get('source_seq'))) {
    +        progressMessage.push('Current source sequence: ' + model.get('source_seq') + '. ');
    +      }
    +      if (!_.isUndefined(model.get('changes_done'))) {
    +        progressMessage.push(model.get('changes_done') + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    },
    +    passesFilter: function (item) {
    +      var searchTerm = this.props.searchTerm;
    +      var regex = new RegExp(searchTerm, 'g');
    +      
    +      var itemDatabasesTerm = '';
    +      if (item.has('database')) {
    +        itemDatabasesTerm += item.get('database'); 
    +      }
    +      if (item.has('source')) {
    +        itemDatabasesTerm += item.get('source'); 
    +      }
    +      if (item.has('target')) {
    +        itemDatabasesTerm += item.get('target'); 
    +      }
    +
    +      if (regex.test(itemDatabasesTerm)) {
    +        return this.passesTabFilter(item.get('type'));
    +      }
    +      return false;
    +    },
    +    passesTabFilter: function (type) {
    +      if (this.props.tab.toLowerCase() === type.replace('_', ' ') || this.props.tab ==='All Tasks') {
    +        return true;
    +      }
    +      return false;
    +    },
    +    sortByHeader: function (e) {
    +      var headerField = $(e.currentTarget),
    +      columnName = headerField.attr('data-type');
    +      Actions.sortByColumnHeader(columnName);
    +    },
    +    render: function () {
    +      var collection = this.props.collection;
    +      var resultsCount = 0;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <thead>
    +              <tr>
    +                <th className="type"      data-type="type"        onClick={this.sortByHeader}> Type</th>
    +                <th className="database"  data-type="database"    onClick={this.sortByHeader}> Database</th>
    +                <th className="started"   data-type="started_on"  onClick={this.sortByHeader}> Started on</th>
    +                <th className="updated"   data-type="updated_on"  onClick={this.sortByHeader}> Last updated on</th>
    +                <th className="pid"       data-type="pid"         onClick={this.sortByHeader}> PID</th>
    +                <th className="status"    data-type="progress"    onClick={this.sortByHeader}> Status</th>
    +              </tr>
    +            </thead>
    +            <tbody className="js-tasks-go-here">
    +              { collection.map(function (item, iteration) {
    +    
    +                  //'+ Filter' Tab , shows rows that match the search term, no search term shows all rows
    +                  if (this.passesFilter(item)) {
    +                    resultsCount++;
    +                    return <ActiveTaskTableBody key={item.cid} itemInfo={this.getInfo(item)} />;
    +                  }
    +
    +                  //Show a message if nothing is returned
    +                  if ((iteration === collection.length - 1) && resultsCount === 0) {  //if we looped through everything AND there is nothing
    +                    if (this.props.searchTerm ==="") {      //no search filter
    +                      return (
    +                        <tr className="no-matching-database-on-search" key={item.cid}>
    +                          <td  colSpan="6">No active {this.props.tab} tasks.</td>
    +                        </tr>
    +                      );
    +                    } else {  // with search filter
    +                      return (  
    +                        <tr className="no-matching-database-on-search" key={item.cid}>
    +                          <td  colSpan="6">No active {this.props.tab} tasks match with filter: "{this.props.searchTerm}".</td>
    +                        </tr>
    +                      );
    +                    }
    +                  }
    +
    +                }.bind(this))
    +              }
    +            </tbody>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBody = React.createClass({
    +    render: function () {
    +      var data =  this.props.itemInfo;
    --- End diff --
    
    `data` is not a great variable name. Its the same as saying `var stuff` :smile:. I have no idea what this is.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-92468298
  
    @garrensmith hey i just pushed these (https://github.com/apache/couchdb-fauxton/commit/280edd54ca522bfd10056a08229cd61dd8e5290a) changes to reflect the new RouteObject functions. I'd like it if you could take a look (please :) to make sure i'm doing everything correctly. The big thing I wasn't sure about is if I need to explicitly call removeComponents somewhere. I saw that you did that sometimes in your code, but wasn't sure where to put it in mines, if at all. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-92320201
  
    +1 this looks great. One small fix and then merge :ship: 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471215
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    --- End diff --
    
    This needs to be changed to use CSS animations instead of jQuery


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28011051
  
    --- Diff: test/mocha/mocha.css ---
    @@ -132,6 +132,7 @@ body {
       color: #c00;
       max-height: 300px;
       overflow: auto;
    +  white-space: pre-wrap;
    --- End diff --
    
    i didn't intend to patch mocha -- i just put this in there to help me debug :)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-87737355
  
    please rebase


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-87738399
  
    hi @kxepal @robertkowalski ! 
    
    thanks for reviewing, and for the suggestions!
    I will make those changes. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27480731
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks',
    +      'Replication',
    +      'Database Compaction',
    +      'Indexer',
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      return this.props.selectedRadio == radioName;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ', '-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +              {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +          {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader = this.props.sortByHeader;
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var headerIsAscending = this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function (nextProps) {
    +      this.setState({
    +        filteredTable:
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +        {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on),
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      if (!optionalClassName) {
    +        optionalClassName = '';
    +      }
    +      var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
    +
    +      return messageArray.map(function (msgLine, iterator) {
    +        return <p key={iterator} className={cssClasses}>{msgLine}</p>;
    +      });
    +    },
    +
    +    render: function () {
    +      var rowData =  this.getInfo(this.props.item);
    +      var objectFieldMsg = this.multilineMessage(rowData.objectField);
    +      var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
    +      var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
    +      var progressMsg = this.multilineMessage(rowData.progress);
    +
    +      return (
    +        <tr>
    +          <td>{rowData.type}</td>
    +          <td>{objectFieldMsg}</td>
    +          <td>{startedOnMsg}</td>
    +          <td>{updatedOnMsg}</td>
    +          <td>{rowData.pid}</td>
    +          <td>{progressMsg}</td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksPollingWidget = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    getPluralForLabel: function () {
    +      return this.state.pollingInterval === "1" ? '' : 's';
    +    },
    +
    +    createPollingWidget: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +      var s = this.getPluralForLabel();
    +      var onChangeHandle = this.pollingIntervalChange;
    +
    +      return (
    +        <ul className="polling-interval-widget">
    +          <li className="polling-interval-name">Polling interval
    +            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
    +              <span>{pollingInterval}</span> second{s} 
    +            </label>
    +          </li>
    +          <li>
    +            <input 
    +              id="pollingRange" 
    +              type="range" 
    +              min="1" 
    +              max="30" 
    +              step="1" 
    +              value={pollingInterval} 
    +              onChange={onChangeHandle}/>
    +          </li>
    +        </ul>
    +      );
    +    },
    +
    +    render: function () {
    +      var pollingWidget = this.createPollingWidget();
    +
    +      return  <div>{pollingWidget}</div>;
    +    }
    +  });
    +
    +  var activeTasksHelpers = {
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [app.helpers.formatDate(timeStamp)];
    +      timeMessage.push(app.helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +
    +    getDatabaseFieldMessage: function (item) {
    +      var type = item.type;
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + item.source);
    +        databaseFieldMessage.push('To: ' + item.target);
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(item.database);
    +        databaseFieldMessage.push('(View: ' + item.design_document + ')');
    +      } else {
    +        databaseFieldMessage.push(item.database);
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +
    +    getProgressMessage: function (item) {
    +      var progressMessage = [];
    +      var type = item.type;
    +
    +      if (item.hasOwnProperty('progress')) {
    +        progressMessage.push('Progress: ' + item.progress + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push(
    +          'Processed ' + item.changes_done + ' of ' + item.total_changes + ' changes.'
    +        );
    +      } else if (type === 'replication') {
    +        progressMessage.push(item.docs_written + ' docs written.');
    +
    +        if (item.hasOwnProperty('changes_pending')) {
    +          progressMessage.push(item.changes_pending + ' pending changes.');
    +        }
    +      }
    +
    +      if (item.hasOwnProperty('source_seq')) {
    +        progressMessage.push('Current source sequence: ' + item.source_seq + '. ');
    +      }
    +
    +      if (item.hasOwnProperty('changes_done')) {
    +        progressMessage.push(item.changes_done + ' Changes done.');
    +      }
    +
    +      return progressMessage;
    +    }
    +  };
    +
    +  return {
    +    renderActiveTasks: function (el) {
    +      React.render(<ActiveTasksController />, el);
    +    },
    +
    +    removeActiveTasks: function (el) {
    --- End diff --
    
    Good point @benkeen. Lets add it as a separate pull request.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471136
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    --- End diff --
    
    Do you mean `this.state.setPolling()`?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27480579
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks',
    +      'Replication',
    +      'Database Compaction',
    +      'Indexer',
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      return this.props.selectedRadio == radioName;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ', '-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +              {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +          {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader = this.props.sortByHeader;
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var headerIsAscending = this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    --- End diff --
    
    Can you also make this a default prop


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27435222
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,236 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app",
    --- End diff --
    
    single quotes for these guys


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-92786240
  
    This is looking good. +1 for merging.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27959207
  
    --- Diff: app/addons/activetasks/tests/activetasks.storesSpec.js ---
    @@ -0,0 +1,231 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks();
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Stores', function () {
    +    var spy;
    +
    +    beforeEach(function () {
    +      activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +      this.clock = sinon.useFakeTimers();
    +    });
    +
    +    afterEach(function () {
    +      spy.restore();
    +      this.clock.restore();
    +    });
    +
    +    describe('Active Task Stores - Polling', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      it('should poll at the min time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +        var minTime = 1;
    +        activeTasksStore.setPollingInterval(minTime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, minTime * 1000);
    +        this.clock.tick(minTime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(minTime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should poll at the max time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +
    +        var maxTime = 30;
    +        activeTasksStore.setPollingInterval(maxTime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, maxTime * 1000);
    +        this.clock.tick(maxTime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(maxTime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should poll at a mid time', function () {
    +        spy = sinon.spy(activeTasksStore, 'getPollingInterval');
    +
    +        var midtime = 15;
    +        activeTasksStore.setPollingInterval(midtime);
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +
    +        setInterval(spy, midtime * 1000);
    +        this.clock.tick(midtime * 1000);
    +        assert.ok(spy.calledTwice);
    +
    +        this.clock.tick(midtime * 1000);
    +        assert.ok(spy.calledThrice);
    +      });
    +
    +      it('should clear interval each time', function () {
    +        var spy = sinon.spy(window, 'clearInterval');
    +        activeTasksStore.setPolling();
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Task Stores - Filter Tab Tray', function () {
    +      var fakeFilteredTable, storeFilteredtable;
    +      function sort (a, b, sortBy) {  //sorts array by objects with key 'sortBy', with default started_on
    +        if (_.isUndefined(sortBy)) {
    +          sortBy = 'started_on';
    +        }
    +        return b[sortBy] - a[sortBy];
    +      }
    +
    +      afterEach(function () {
    +        fakeFilteredTable = [];
    +      });
    +
    +      it('should filter the table correctly, by radio -- All Tasks', function () {
    +        activeTasksStore.setSelectedRadio('all_tasks');
    +        //parse table and check that it only contains objects any type
    +        var table = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +        assert.ok(activeTasksStore._collection.length, table.length);
    +      });
    +
    +      it('should filter the table correctly, by radio -- Replication', function () {
    +        activeTasksStore.setSelectedRadio('replication');
    +        var storeFilteredtable = activeTasksStore.getFilteredTable(activeTasksStore._collection);
    +
    +        //parse table and check that it only contains objects with type: Replication
    +        _.each(storeFilteredtable, function (activeTask) {
    --- End diff --
    
    This is good and I think you could probably end the test here.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27434828
  
    --- Diff: app/addons/activetasks/assets/less/activetasks.less ---
    @@ -63,3 +64,148 @@
       }
     }
     
    +.no-matching-database-on-search {
    +  color: #e33f3b;
    +}
    +
    +p.multiline-active-tasks-message {
    +  margin: 0;
    +
    +  &.time:nth-child(2) {
    +    color: #888;
    +  }
    +}
    +
    +#dashboard-upper-content {
    +  padding-right: 20px;
    +}
    +
    +.active-tasks.dashboard-upper-menu {
    +  left: 220px;
    +
    +  .closeMenu & {
    +    left: 64px;
    +  }
    +}
    +
    +.dashboard-lower-menu {
    +  padding-top: 90px;
    +  padding-left: 20x;
    +}
    +
    +input[type="text"].searchbox {
    +  width: 200px;
    +  height: 40px;
    +  float: right;
    +  margin:0px;
    +}
    +
    +#toggle-filter-tab:hover {
    +  color: white;
    +  background-color: #e33f3b;
    +}
    +
    +.filter-tray.toggleFilterTray-enter {     /* starting css*/
    +  overflow: hidden;
    +  padding-top: 0px;
    +  max-height: 0px;
    +}
    +                                          /* animate opening */
    +.filter-tray.toggleFilterTray-enter.toggleFilterTray-enter-active {     
    +  max-height: 300px;
    +  height: auto;
    +  transition: max-height 0.5s linear;
    +}
    +
    +.filter-tray {                             /* final css */
    +  height: auto;
    +  max-height: 300px;                      
    +  overflow: hidden;
    +}
    +                                          /* animate closing */
    +.filter-tray.toggleFilterTray-leave.toggleFilterTray-leave-active{     
    +  max-height: 0;
    +  padding-top: 0px;
    +  transition: max-height 0.5s linear;
    +}
    +
    +.filter-checkboxes {
    +  float: left;
    +  height: auto;
    +  width: auto;
    +  margin-top: 10px;
    +}
    +
    +.active-tasks-one-checkbox {
    +  input {
    +    vertical-align: middle;
    +    margin-right: 5px;
    +    margin-bottom: 3px;
    +  }
    +  margin-right: 10px;
    +  display: inline-block;
    +}
    +
    +.active-tasks-checkbox-label {
    +  display: inline-block;
    +  color: #666;
    +}
    +
    +@media (max-width: 940px) {
    +  .filter-checkboxes li {
    +    display: inline-block;
    +    text-align: left;
    +    width: 175px;
    +  }
    +}
    +
    +.filter-checkboxes-form{
    +  margin:0px;
    +}
    +
    +.polling-interval-widget{
    +
    +  width: 250px;
    +  margin-right: 20px;
    +  margin-top: 10px;
    +  color: #666;
    +
    +  li {
    +    list-style-type: none;
    +  }
    +
    +  .polling-interval-time-label{
    +    display: inline-block;
    +    float: right;
    +    margin-right: 0px;
    +    cursor: default;
    +  }
    +  
    +  #pollingRange{
    +    width: 250px;
    +  }
    +}
    +
    +.header-field {
    +  &.radio{
    --- End diff --
    
    space


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471457
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    +      $('#dashboard-content').scrollTop(0);
    +      $('#query').toggle('slow');
    +    },
    +    setNewSearchTerm: function (e) {
    +      this.setState({searchTerm: e.target.value});
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    render: function () {
    +      var searchTerm = this.state.searchTerm;
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu">
    +            <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +              <li>
    +                <a className="js-toggle-filter" href="#filter" data-bypass="true" data-toggle="tab" onClick={this.toggleFilter}>
    +                  <i className="fonticon fonticon-plus"></i>Filter
    +                </a>
    +              </li>
    +            </ul>
    +          </div>
    +
    +          <div className="tab-content">
    +            <div className="tab-pane" id="query">
    +              <div className="activetasks-header">
    +                <div className="pull-right">
    +                  <input  
    +                    className="task-search-database" 
    +                    type="text" name="search" 
    +                    placeholder="Search for databases..." 
    +                    value={searchTerm}
    +                    onChange={this.setNewSearchTerm}
    +                  />
    +                </div>
    +              </div>
    +            </div>
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.get('type'),
    +        objectField : this.getDatabaseFieldMessage(item) ,
    +        started_on : this.getTimeInfo(item.get('started_on')), 
    +        updated_on : this.getTimeInfo(item.get('updated_on')),
    +        pid : item.get('pid').replace(/[<>]/g, ''),
    +        progress : this.getProgressMessage(item),
    +      };
    +    },
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [Helpers.formatDate(timeStamp)];
    +      timeMessage.push(Helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +    getDatabaseFieldMessage: function (model) {
    +      var type = model.get('type');
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + model.get('source'));
    +        databaseFieldMessage.push('To: ' + model.get('target'));
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(model.get('database'));
    +        databaseFieldMessage.push('(View: ' + model.get('design_document') + ')');
    +      } else {
    +        databaseFieldMessage.push(model.get('database'));
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +    getProgressMessage: function (model) {
    +      var progressMessage = [];
    +      var type = model.get('type');
    +      if (!_.isUndefined(model.get('progress'))) {
    +        progressMessage.push('Progress: ' + model.get('progress') + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push('Processed ' + model.get('changes_done') + ' of ' + model.get('total_changes') + ' changes.');
    +      } else if (type === 'replication') {
    +        progressMessage.push(model.get('docs_written')+ ' docs written.');
    +        if (!_.isUndefined(model.get('changes_pending'))) {
    +          progressMessage.push(model.get('changes_pending') + ' pending changes.');
    +        }
    +      }
    +      if (!_.isUndefined(model.get('source_seq'))) {
    +        progressMessage.push('Current source sequence: ' + model.get('source_seq') + '. ');
    +      }
    +      if (!_.isUndefined(model.get('changes_done'))) {
    +        progressMessage.push(model.get('changes_done') + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    },
    +    passesFilter: function (item) {
    +      var searchTerm = this.props.searchTerm;
    +      var regex = new RegExp(searchTerm, 'g');
    +      
    +      var itemDatabasesTerm = '';
    +      if (item.has('database')) {
    +        itemDatabasesTerm += item.get('database'); 
    +      }
    +      if (item.has('source')) {
    +        itemDatabasesTerm += item.get('source'); 
    +      }
    +      if (item.has('target')) {
    +        itemDatabasesTerm += item.get('target'); 
    +      }
    +
    +      if (regex.test(itemDatabasesTerm)) {
    +        return this.passesTabFilter(item.get('type'));
    +      }
    +      return false;
    +    },
    +    passesTabFilter: function (type) {
    +      if (this.props.tab.toLowerCase() === type.replace('_', ' ') || this.props.tab ==='All Tasks') {
    +        return true;
    +      }
    +      return false;
    +    },
    +    sortByHeader: function (e) {
    +      var headerField = $(e.currentTarget),
    --- End diff --
    
    YUCK!!!!


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27353963
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,588 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +  
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +       
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +        
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    
    +    //radio buttons
    +    switchTab: function (e) { 
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {  
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +    
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return ( 
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks', 
    +      'Replication',
    +      'Database Compaction', 
    +      'Indexer', 
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      if (this.props.selectedRadio == radioName) return true;
    --- End diff --
    
    Why not just `return this.props.selectedRadio === radioName`?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27434961
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks',
    +      'Replication',
    +      'Database Compaction',
    +      'Indexer',
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      return this.props.selectedRadio == radioName;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    --- End diff --
    
    could probably just drop this var


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28251108
  
    --- Diff: app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx ---
    @@ -0,0 +1,116 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'addons/activetasks/actions',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks({});
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Components', function () {
    +
    +    describe('Active Tasks Polling (Components)', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      beforeEach(function () {
    +        pollingWidgetDiv = document.createElement('div');
    +        pollingWidget = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksPollingWidget, null), pollingWidgetDiv);
    +      });
    +
    +      afterEach(function () {
    +        React.unmountComponentAtNode(pollingWidgetDiv);
    +      });
    +
    +      it('should trigger update polling interval', function () {
    +        var spy = sinon.spy(Actions, 'changePollingInterval');
    +        var rangeNode = TestUtils.findRenderedDOMComponentWithTag(pollingWidget, 'input');
    +        var time = '9';
    +
    +        TestUtils.Simulate.change(rangeNode, {target: {value: time}});
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Tasks Table (Components)', function () {
    +      var table, tableDiv, spy, filterTab;
    +
    +      beforeEach(function () {
    +        tableDiv = document.createElement('div');
    +        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +        table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv);
    +
    +        // open filter tray
    +        filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
    +        TestUtils.Simulate.click(filterTab);
    +      });
    +
    +      afterEach(function () {
    +        spy.restore();
    +        React.unmountComponentAtNode(tableDiv);
    +        window.confirm.restore && window.confirm.restore();
    +      });
    +
    +      describe('Active Tasks Filter tray', function () {
    +        var radioIDs = [
    +          'Replication',
    +          'Database-Compaction',
    +          'Indexer',
    +          'View-Compaction'
    +        ];
    +
    +        it('should trigger change to radio buttons', function () {
    +          _.each(radioIDs, function (radioID) {
    +            spy = sinon.spy(Actions, 'switchTab');
    +            TestUtils.Simulate.change($(table.getDOMNode()).find('#' + radioID)[0]);
    +            assert.ok(spy.calledOnce);
    +            spy.restore();
    --- End diff --
    
    OK thats fine then.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by sebastianrothbucher <gi...@git.apache.org>.
Github user sebastianrothbucher commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-92109747
  
    now that I finally understand enough to do meaningful review I'm +1 ;-)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27435599
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks',
    +      'Replication',
    +      'Database Compaction',
    +      'Indexer',
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      return this.props.selectedRadio == radioName;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ', '-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +              {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +          {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader = this.props.sortByHeader;
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var headerIsAscending = this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function (nextProps) {
    +      this.setState({
    +        filteredTable:
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +        {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on),
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      if (!optionalClassName) {
    +        optionalClassName = '';
    +      }
    +      var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
    +
    +      return messageArray.map(function (msgLine, iterator) {
    +        return <p key={iterator} className={cssClasses}>{msgLine}</p>;
    +      });
    +    },
    +
    +    render: function () {
    +      var rowData =  this.getInfo(this.props.item);
    +      var objectFieldMsg = this.multilineMessage(rowData.objectField);
    +      var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
    +      var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
    +      var progressMsg = this.multilineMessage(rowData.progress);
    +
    +      return (
    +        <tr>
    +          <td>{rowData.type}</td>
    +          <td>{objectFieldMsg}</td>
    +          <td>{startedOnMsg}</td>
    +          <td>{updatedOnMsg}</td>
    +          <td>{rowData.pid}</td>
    +          <td>{progressMsg}</td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksPollingWidget = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    getPluralForLabel: function () {
    +      return this.state.pollingInterval === "1" ? '' : 's';
    +    },
    +
    +    createPollingWidget: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +      var s = this.getPluralForLabel();
    +      var onChangeHandle = this.pollingIntervalChange;
    +
    +      return (
    +        <ul className="polling-interval-widget">
    +          <li className="polling-interval-name">Polling interval
    +            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
    +              <span>{pollingInterval}</span> second{s} 
    +            </label>
    +          </li>
    +          <li>
    +            <input 
    +              id="pollingRange" 
    +              type="range" 
    +              min="1" 
    +              max="30" 
    +              step="1" 
    +              value={pollingInterval} 
    +              onChange={onChangeHandle}/>
    +          </li>
    +        </ul>
    +      );
    +    },
    +
    +    render: function () {
    +      var pollingWidget = this.createPollingWidget();
    +
    +      return  <div>{pollingWidget}</div>;
    +    }
    +  });
    +
    +  var activeTasksHelpers = {
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [app.helpers.formatDate(timeStamp)];
    +      timeMessage.push(app.helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +
    +    getDatabaseFieldMessage: function (item) {
    +      var type = item.type;
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + item.source);
    +        databaseFieldMessage.push('To: ' + item.target);
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(item.database);
    +        databaseFieldMessage.push('(View: ' + item.design_document + ')');
    +      } else {
    +        databaseFieldMessage.push(item.database);
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +
    +    getProgressMessage: function (item) {
    +      var progressMessage = [];
    +      var type = item.type;
    +
    +      if (item.hasOwnProperty('progress')) {
    +        progressMessage.push('Progress: ' + item.progress + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push(
    +          'Processed ' + item.changes_done + ' of ' + item.total_changes + ' changes.'
    +        );
    +      } else if (type === 'replication') {
    +        progressMessage.push(item.docs_written + ' docs written.');
    +
    +        if (item.hasOwnProperty('changes_pending')) {
    +          progressMessage.push(item.changes_pending + ' pending changes.');
    +        }
    +      }
    +
    +      if (item.hasOwnProperty('source_seq')) {
    +        progressMessage.push('Current source sequence: ' + item.source_seq + '. ');
    +      }
    +
    +      if (item.hasOwnProperty('changes_done')) {
    +        progressMessage.push(item.changes_done + ' Changes done.');
    +      }
    +
    +      return progressMessage;
    +    }
    +  };
    +
    +  return {
    +    renderActiveTasks: function (el) {
    +      React.render(<ActiveTasksController />, el);
    +    },
    +
    +    removeActiveTasks: function (el) {
    --- End diff --
    
    Seems like we do this a lot - i.e. a custom function to remove a React element at a DOM node. Wonder if we should just add a generic `remove()` method in the main Fauxton React components section that removes an element at a node...?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by sebastianrothbucher <gi...@git.apache.org>.
Github user sebastianrothbucher commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28206625
  
    --- Diff: app/addons/activetasks/assets/less/activetasks.less ---
    @@ -63,3 +64,148 @@
       }
     }
     
    +.no-matching-database-on-search {
    +  color: #e33f3b;
    +}
    +
    +p.multiline-active-tasks-message {
    +  margin: 0;
    +
    +  &.time:nth-child(2) {
    +    color: #888;
    +  }
    +}
    +
    +#dashboard-upper-content {
    --- End diff --
    
    documents contains a def for this also - but there don't seem to be side effects. It's minor anyway


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471614
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    +      $('#dashboard-content').scrollTop(0);
    +      $('#query').toggle('slow');
    +    },
    +    setNewSearchTerm: function (e) {
    +      this.setState({searchTerm: e.target.value});
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    render: function () {
    +      var searchTerm = this.state.searchTerm;
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu">
    +            <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +              <li>
    +                <a className="js-toggle-filter" href="#filter" data-bypass="true" data-toggle="tab" onClick={this.toggleFilter}>
    +                  <i className="fonticon fonticon-plus"></i>Filter
    +                </a>
    +              </li>
    +            </ul>
    +          </div>
    +
    +          <div className="tab-content">
    +            <div className="tab-pane" id="query">
    +              <div className="activetasks-header">
    +                <div className="pull-right">
    +                  <input  
    +                    className="task-search-database" 
    +                    type="text" name="search" 
    +                    placeholder="Search for databases..." 
    +                    value={searchTerm}
    +                    onChange={this.setNewSearchTerm}
    +                  />
    +                </div>
    +              </div>
    +            </div>
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.get('type'),
    +        objectField : this.getDatabaseFieldMessage(item) ,
    +        started_on : this.getTimeInfo(item.get('started_on')), 
    +        updated_on : this.getTimeInfo(item.get('updated_on')),
    +        pid : item.get('pid').replace(/[<>]/g, ''),
    +        progress : this.getProgressMessage(item),
    +      };
    +    },
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [Helpers.formatDate(timeStamp)];
    +      timeMessage.push(Helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +    getDatabaseFieldMessage: function (model) {
    +      var type = model.get('type');
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + model.get('source'));
    +        databaseFieldMessage.push('To: ' + model.get('target'));
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(model.get('database'));
    +        databaseFieldMessage.push('(View: ' + model.get('design_document') + ')');
    +      } else {
    +        databaseFieldMessage.push(model.get('database'));
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +    getProgressMessage: function (model) {
    +      var progressMessage = [];
    +      var type = model.get('type');
    +      if (!_.isUndefined(model.get('progress'))) {
    +        progressMessage.push('Progress: ' + model.get('progress') + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push('Processed ' + model.get('changes_done') + ' of ' + model.get('total_changes') + ' changes.');
    +      } else if (type === 'replication') {
    +        progressMessage.push(model.get('docs_written')+ ' docs written.');
    +        if (!_.isUndefined(model.get('changes_pending'))) {
    +          progressMessage.push(model.get('changes_pending') + ' pending changes.');
    +        }
    +      }
    +      if (!_.isUndefined(model.get('source_seq'))) {
    +        progressMessage.push('Current source sequence: ' + model.get('source_seq') + '. ');
    +      }
    +      if (!_.isUndefined(model.get('changes_done'))) {
    +        progressMessage.push(model.get('changes_done') + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    },
    +    passesFilter: function (item) {
    +      var searchTerm = this.props.searchTerm;
    +      var regex = new RegExp(searchTerm, 'g');
    +      
    +      var itemDatabasesTerm = '';
    +      if (item.has('database')) {
    +        itemDatabasesTerm += item.get('database'); 
    +      }
    +      if (item.has('source')) {
    +        itemDatabasesTerm += item.get('source'); 
    +      }
    +      if (item.has('target')) {
    +        itemDatabasesTerm += item.get('target'); 
    +      }
    +
    +      if (regex.test(itemDatabasesTerm)) {
    +        return this.passesTabFilter(item.get('type'));
    +      }
    +      return false;
    +    },
    +    passesTabFilter: function (type) {
    +      if (this.props.tab.toLowerCase() === type.replace('_', ' ') || this.props.tab ==='All Tasks') {
    +        return true;
    +      }
    +      return false;
    +    },
    +    sortByHeader: function (e) {
    +      var headerField = $(e.currentTarget),
    +      columnName = headerField.attr('data-type');
    +      Actions.sortByColumnHeader(columnName);
    +    },
    +    render: function () {
    +      var collection = this.props.collection;
    +      var resultsCount = 0;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <thead>
    +              <tr>
    +                <th className="type"      data-type="type"        onClick={this.sortByHeader}> Type</th>
    --- End diff --
    
    Make theses their own components.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26471989
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,387 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "react",
    +  "addons/activetasks/stores",
    +  "addons/activetasks/resources",
    +  "addons/activetasks/actions"
    +
    +], function (Helpers, FauxtonAPI, React, Stores, Resources, Actions) {
    +  var activeTasksStore = Stores.activeTasksStore;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        setPolling: activeTasksStore.setPolling(),
    +        clearPolling: activeTasksStore.clearPolling
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling;
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedTab = this.state.selectedTab;
    +
    +      if (collection.length === 0 ) {
    +        return ( <div className="active-tasks"><tr><td><p>  No tasks. </p></td></tr></div> );
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilterHeader/>
    +              <ActiveTaskTable collection={collection} searchTerm={searchTerm} tab={selectedTab}/>
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilterHeader = React.createClass({ //This is for the little '+ Filter' Tab 
    +    getStoreState: function () {
    +      return {  
    +        searchTerm: activeTasksStore.getSearchTerm()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilter: function () {
    +      $('#dashboard-content').scrollTop(0);
    +      $('#query').toggle('slow');
    +    },
    +    setNewSearchTerm: function (e) {
    +      this.setState({searchTerm: e.target.value});
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    render: function () {
    +      var searchTerm = this.state.searchTerm;
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu">
    +            <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +              <li>
    +                <a className="js-toggle-filter" href="#filter" data-bypass="true" data-toggle="tab" onClick={this.toggleFilter}>
    +                  <i className="fonticon fonticon-plus"></i>Filter
    +                </a>
    +              </li>
    +            </ul>
    +          </div>
    +
    +          <div className="tab-content">
    +            <div className="tab-pane" id="query">
    +              <div className="activetasks-header">
    +                <div className="pull-right">
    +                  <input  
    +                    className="task-search-database" 
    +                    type="text" name="search" 
    +                    placeholder="Search for databases..." 
    +                    value={searchTerm}
    +                    onChange={this.setNewSearchTerm}
    +                  />
    +                </div>
    +              </div>
    +            </div>
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.get('type'),
    +        objectField : this.getDatabaseFieldMessage(item) ,
    +        started_on : this.getTimeInfo(item.get('started_on')), 
    +        updated_on : this.getTimeInfo(item.get('updated_on')),
    +        pid : item.get('pid').replace(/[<>]/g, ''),
    +        progress : this.getProgressMessage(item),
    +      };
    +    },
    +    getTimeInfo: function (timeStamp) {
    +      var timeMessage = [Helpers.formatDate(timeStamp)];
    +      timeMessage.push(Helpers.getDateFromNow(timeStamp));
    +      return timeMessage;
    +    },
    +    getDatabaseFieldMessage: function (model) {
    +      var type = model.get('type');
    +      var databaseFieldMessage = [];
    +
    +      if (type === 'replication') {
    +        databaseFieldMessage.push('From: ' + model.get('source'));
    +        databaseFieldMessage.push('To: ' + model.get('target'));
    +      } else if (type === 'indexer') {
    +        databaseFieldMessage.push(model.get('database'));
    +        databaseFieldMessage.push('(View: ' + model.get('design_document') + ')');
    +      } else {
    +        databaseFieldMessage.push(model.get('database'));
    +      }
    +
    +      return databaseFieldMessage;
    +    },
    +    getProgressMessage: function (model) {
    +      var progressMessage = [];
    +      var type = model.get('type');
    +      if (!_.isUndefined(model.get('progress'))) {
    +        progressMessage.push('Progress: ' + model.get('progress') + '%');
    +      }
    +
    +      if (type === 'indexer') {
    +        progressMessage.push('Processed ' + model.get('changes_done') + ' of ' + model.get('total_changes') + ' changes.');
    +      } else if (type === 'replication') {
    +        progressMessage.push(model.get('docs_written')+ ' docs written.');
    +        if (!_.isUndefined(model.get('changes_pending'))) {
    +          progressMessage.push(model.get('changes_pending') + ' pending changes.');
    +        }
    +      }
    +      if (!_.isUndefined(model.get('source_seq'))) {
    +        progressMessage.push('Current source sequence: ' + model.get('source_seq') + '. ');
    +      }
    +      if (!_.isUndefined(model.get('changes_done'))) {
    +        progressMessage.push(model.get('changes_done') + ' Changes done.');
    +      }
    +      
    +      return progressMessage;
    +    },
    +    passesFilter: function (item) {
    +      var searchTerm = this.props.searchTerm;
    +      var regex = new RegExp(searchTerm, 'g');
    +      
    +      var itemDatabasesTerm = '';
    +      if (item.has('database')) {
    +        itemDatabasesTerm += item.get('database'); 
    +      }
    +      if (item.has('source')) {
    +        itemDatabasesTerm += item.get('source'); 
    +      }
    +      if (item.has('target')) {
    +        itemDatabasesTerm += item.get('target'); 
    +      }
    +
    +      if (regex.test(itemDatabasesTerm)) {
    +        return this.passesTabFilter(item.get('type'));
    +      }
    +      return false;
    +    },
    +    passesTabFilter: function (type) {
    +      if (this.props.tab.toLowerCase() === type.replace('_', ' ') || this.props.tab ==='All Tasks') {
    +        return true;
    +      }
    +      return false;
    +    },
    +    sortByHeader: function (e) {
    +      var headerField = $(e.currentTarget),
    +      columnName = headerField.attr('data-type');
    +      Actions.sortByColumnHeader(columnName);
    +    },
    +    render: function () {
    +      var collection = this.props.collection;
    +      var resultsCount = 0;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <thead>
    +              <tr>
    +                <th className="type"      data-type="type"        onClick={this.sortByHeader}> Type</th>
    +                <th className="database"  data-type="database"    onClick={this.sortByHeader}> Database</th>
    +                <th className="started"   data-type="started_on"  onClick={this.sortByHeader}> Started on</th>
    +                <th className="updated"   data-type="updated_on"  onClick={this.sortByHeader}> Last updated on</th>
    +                <th className="pid"       data-type="pid"         onClick={this.sortByHeader}> PID</th>
    +                <th className="status"    data-type="progress"    onClick={this.sortByHeader}> Status</th>
    +              </tr>
    +            </thead>
    +            <tbody className="js-tasks-go-here">
    +              { collection.map(function (item, iteration) {
    +    
    +                  //'+ Filter' Tab , shows rows that match the search term, no search term shows all rows
    +                  if (this.passesFilter(item)) {
    +                    resultsCount++;
    +                    return <ActiveTaskTableBody key={item.cid} itemInfo={this.getInfo(item)} />;
    +                  }
    +
    +                  //Show a message if nothing is returned
    +                  if ((iteration === collection.length - 1) && resultsCount === 0) {  //if we looped through everything AND there is nothing
    +                    if (this.props.searchTerm ==="") {      //no search filter
    +                      return (
    +                        <tr className="no-matching-database-on-search" key={item.cid}>
    +                          <td  colSpan="6">No active {this.props.tab} tasks.</td>
    +                        </tr>
    +                      );
    +                    } else {  // with search filter
    +                      return (  
    +                        <tr className="no-matching-database-on-search" key={item.cid}>
    +                          <td  colSpan="6">No active {this.props.tab} tasks match with filter: "{this.props.searchTerm}".</td>
    +                        </tr>
    +                      );
    +                    }
    +                  }
    +
    +                }.bind(this))
    +              }
    +            </tbody>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBody = React.createClass({
    +    render: function () {
    +      var data =  this.props.itemInfo;
    +      return (
    +        <tr>
    +          <td>{data.type}            </td>
    +          <td>
    +            { data.objectField.map(function (msgLine, iterator) {
    +                return (
    +                  <p key={iterator} className="multiline-active-tasks-message">{msgLine}</p> 
    +                );
    +              })
    +            }
    +          </td>
    +          <td>
    +            <p className="multiline-active-tasks-message">{data.started_on[0]}</p>
    +            <p className="multiline-active-tasks-message gray">{data.started_on[1]}</p>
    +          </td>
    +          <td>
    +            <p className="multiline-active-tasks-message">{data.updated_on[0]}</p>
    +            <p className="multiline-active-tasks-message gray">{data.updated_on[1]}</p>
    +          </td>
    +          <td>{data.pid}             </td>
    +          <td>
    +            { data.progress.map(function (msgLine, iterator) {
    +                return (
    +                  <p key={iterator} className="multiline-active-tasks-message">{msgLine}</p> 
    +                );
    +              })
    +            }
    +          </td>
    +        </tr>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksSidebar = React.createClass({
    +    tabs : function () {
    +      return ['All Tasks', 
    +              'Replication',
    +              'Database Compaction', 
    +              'Indexer', 
    +              'View Compaction'
    +             ];
    +    },
    +    getStoreState: function () {
    +      return {  
    +        selectedTab: activeTasksStore.getSelectedTab(),
    +        pollingInterval:  activeTasksStore.getPollingInterval()
    +      };
    +    },
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +    componentDidMount: function () {
    +       activeTasksStore.on('change', this.onChange, this);      
    +    },
    +    switchTab: function (goToTab) {
    +      Actions.switchTab(goToTab);
    +    },
    +    onChange: function () {  
    +      if (this.isMounted()) {
    +        this.setState(this.getStoreState());
    +      }
    +    },
    +    isSelected: function (tabName) {
    +      if (tabName === activeTasksStore.getSelectedTab()) {
    +       return true;
    +      }
    +      return false;
    +    },
    +    pollingIntervalChange: function (event) {
    +      Actions.changePollingInterval(event.target.value);
    +    },
    +
    +    render: function () {
    +      var pollingInterval = this.state.pollingInterval;
    +
    +      return (
    +        <div>
    +        <ul className="task-tabs nav nav-list">
    +          { this.tabs().map(function (tabName) {
    +            return (
    +              <li key = {tabName} 
    +                  onClick = {this.switchTab.bind(this, tabName)} 
    +                  className = {this.isSelected(tabName) ? 'active': ''}
    +              >
    +                <a>{tabName}</a>
    +              </li>
    +            );
    +          }.bind(this))}
    +        </ul>
    +        <ul className="nav nav-list views polling-interval">
    +          <li className="nav-header">Polling interval</li>
    +          <li>
    +            <input id="pollingRange" type="range" min="1" max="30" step="1" value={pollingInterval} onChange={this.pollingIntervalChange}/>
    +            <label htmlFor="pollingRange"><span>{pollingInterval}</span> {pollingInterval === "1" ? 'second' : 'seconds'}</label>
    --- End diff --
    
    For neatness move the `pollingInterval` if statement into a function.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27354002
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,588 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +  
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +       
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +        
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);      
    +    },
    +
    +    componentWillUnmount: function() {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this); 
    +    },
    +    
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +    
    +    //radio buttons
    +    switchTab: function (e) { 
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection; 
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {  
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +    
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return ( 
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    +      'All Tasks', 
    +      'Replication',
    +      'Database Compaction', 
    +      'Indexer', 
    +      'View Compaction'
    +    ],
    +
    +    checked: function (radioName) {
    +      if (this.props.selectedRadio == radioName) return true;
    +      else return false;
    +    },
    +
    +    createCheckboxes: function () {
    +      var onRadioClick = this.props.onRadioClick;
    +      return (
    +        this.radioNames.map(function (radioName) {
    +          var checked = this.checked(radioName);
    +          var radioClassName = "radio-" + radioName.replace(' ','-');
    +          return (
    +            <li className="active-tasks-one-checkbox" key={radioName+"li"}>
    +              <input
    +                  id={radioName.replace(' ', '-')}
    +                  type="radio"
    +                  key ={radioName} 
    +                  name="radio-button-active-task-filter-tray" 
    +                  value={radioName}
    +                  checked={checked}
    +                  onChange={onRadioClick} />
    +              <label htmlFor={radioName} className="active-tasks-checkbox-label">
    +                {radioName}
    +              </label>
    +            </li>
    +          );
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      var filterCheckboxes = this.createCheckboxes();
    +      return (
    +        <ul className="filter-checkboxes">
    +          <form className="filter-checkboxes-form">
    +            {filterCheckboxes}
    +          </form>
    +        </ul>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTable = React.createClass({
    +    render: function () {
    +      var collection = this.props.collection;
    +      var selectedRadio = this.props.selectedRadio;
    +      var searchTerm = this.props.searchTerm;
    +      var sortByHeader= this.props.sortByHeader;
    +      var onTableHeaderClick= this.props.onTableHeaderClick;
    +      var headerIsAscending= this.props.headerIsAscending;
    +
    +      return (
    +        <div id="dashboard-lower-content">
    +          <table className="table table-bordered table-striped active-tasks">
    +            <ActiveTasksTableHeader 
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending}/>
    +            <ActiveTasksTableBody 
    +              collection={collection} 
    +              selectedRadio={selectedRadio} 
    +              searchTerm={searchTerm}/>
    +          </table>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableHeader = React.createClass({
    +    headerNames : [
    +      ['type', 'Type'],
    +      ['database', 'Database'],
    +      ['started_on', 'Started On'],
    +      ['updated_on', 'Updated On'],
    +      ['pid', 'PID'],
    +      ['progress', 'Status']
    +    ],
    +
    +    createTableHeadingFields: function () {
    +      var onTableHeaderClick = this.props.onTableHeaderClick;
    +      var sortByHeader = this.props.sortByHeader;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      return (
    +        this.headerNames.map(function (header) {
    +          return (
    +            <TableHeader 
    +              HeaderName={header[0]}
    +              DisplayName={header[1]}
    +              key={header[0]}
    +              onTableHeaderClick={onTableHeaderClick}
    +              sortByHeader={sortByHeader}
    +              headerIsAscending={headerIsAscending} />
    +          );
    +        })
    +      );
    +    },
    +
    +    render: function () {
    +      var tableHeadingFields = this.createTableHeadingFields();
    +      return (
    +        <thead>
    +          <tr>{tableHeadingFields}</tr>
    +        </thead>
    +      );
    +    }
    +  });
    +
    +  var TableHeader = React.createClass({
    +    arrow: function () {
    +      var sortBy = this.props.sortByHeader;
    +      var currentName = this.props.HeaderName;
    +      var headerIsAscending = this.props.headerIsAscending;
    +      var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
    +
    +      if (sortBy === currentName) {
    +        return <i className={arrow}></i>;
    +      }
    +    },
    +
    +    render: function () {
    +      var arrow = this.arrow();
    +      var th_class = 'header-field ' + this.props.HeaderName;
    +
    +      return (
    +        <input
    +          type="radio"
    +          name="header-field"
    +          id={this.props.HeaderName}
    +          value={this.props.HeaderName}
    +          className="header-field radio"
    +          onChange={this.props.onTableHeaderClick}>
    +          <th className={th_class} value={this.props.HeaderName}>
    +            <label 
    +              className="header-field label-text"
    +              htmlFor={this.props.HeaderName}>
    +              {this.props.DisplayName} {arrow}
    +            </label>
    +          </th>
    +        </input>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksTableBody = React.createClass({
    +
    +    getStoreState: function () {
    +      return {  
    +        filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentWillReceiveProps: function(nextProps) {
    +      this.setState({
    +        filteredTable: 
    +          activeTasksStore.getFilteredTable(this.props.collection)
    +      });
    +    },
    +
    +    createRows: function () {
    +      var isThereASearchTerm = this.props.searchTerm.trim() === "";
    +
    +      if (this.state.filteredTable.length === 0) {
    +        return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
    +      }
    +
    +      return _.map(this.state.filteredTable, function (item, iteration) {
    +        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
    +      });
    +    },
    +
    +    noActiveTasks: function () {
    +      return (
    +        <tr className="no-matching-database-on-search">
    +          <td  colSpan="6">No active {this.props.selectedRadio} tasks.</td>
    +        </tr>
    +      );
    +    },
    +
    +    noActiveTasksMatchFilter: function () {
    +      return (  
    +        <tr className="no-matching-database-on-search">
    +          <td colSpan="6">No active {this.props.selectedRadio} tasks match with filter: "{this.props.searchTerm}".</td>
    +        </tr>
    +      );
    +    },
    +
    +    render: function () {
    +      var tableBody = this.createRows();
    +      return (
    +        <tbody className="js-tasks-go-here">
    +          {tableBody}
    +        </tbody>
    +      );
    +    }
    +  });
    +
    +  var ActiveTaskTableBodyContents = React.createClass({
    +    getInfo: function (item) {
    +      return {
    +        type : item.type,
    +        objectField: activeTasksHelpers.getDatabaseFieldMessage(item) ,
    +        started_on: activeTasksHelpers.getTimeInfo(item.started_on), 
    +        updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
    +        pid: item.pid.replace(/[<>]/g, ''),
    +        progress: activeTasksHelpers.getProgressMessage(item),
    +      };
    +    },
    +
    +    multilineMessage: function (messageArray, optionalClassName) {
    +
    +      optionalClassName ? optionalClassName = optionalClassName : optionalClassName = "";
    --- End diff --
    
    Tricky (;


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#issuecomment-90116440
  
    I think this is ready. If you guys should check out how i wrote my tests https://github.com/michellephung/couchdb-fauxton/blob/Active-Tasks-in-REACT/app/addons/activetasks/tests/activetasks.storesSpec.js i'd appreciate the feedback


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Active tasks in React (don't loo...

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27353999
  
    --- Diff: app/addons/activetasks/tests/fakeActiveTaskResponse.js ---
    @@ -0,0 +1,110 @@
    +define([], function () {
    --- End diff --
    
    This file looks too lonely without ASF license header ):


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28250574
  
    --- Diff: app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx ---
    @@ -0,0 +1,116 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/activetasks/views',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/tests/fakeActiveTaskResponse',
    +  'react',
    +  'addons/activetasks/actions',
    +  'testUtils'
    +], function (FauxtonAPI, Views, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) {
    +  var assert = testUtils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var activeTasksCollection = new ActiveTasks.AllTasks({});
    +  activeTasksCollection.parse(fakedResponse);
    +
    +  describe('Active Tasks -- Components', function () {
    +
    +    describe('Active Tasks Polling (Components)', function () {
    +      var pollingWidgetDiv, pollingWidget;
    +
    +      beforeEach(function () {
    +        pollingWidgetDiv = document.createElement('div');
    +        pollingWidget = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksPollingWidget, null), pollingWidgetDiv);
    +      });
    +
    +      afterEach(function () {
    +        React.unmountComponentAtNode(pollingWidgetDiv);
    +      });
    +
    +      it('should trigger update polling interval', function () {
    +        var spy = sinon.spy(Actions, 'changePollingInterval');
    +        var rangeNode = TestUtils.findRenderedDOMComponentWithTag(pollingWidget, 'input');
    +        var time = '9';
    +
    +        TestUtils.Simulate.change(rangeNode, {target: {value: time}});
    +        assert.ok(spy.calledOnce);
    +      });
    +    });
    +
    +    describe('Active Tasks Table (Components)', function () {
    +      var table, tableDiv, spy, filterTab;
    +
    +      beforeEach(function () {
    +        tableDiv = document.createElement('div');
    +        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
    +        table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv);
    +
    +        // open filter tray
    +        filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
    +        TestUtils.Simulate.click(filterTab);
    +      });
    +
    +      afterEach(function () {
    +        spy.restore();
    +        React.unmountComponentAtNode(tableDiv);
    +        window.confirm.restore && window.confirm.restore();
    +      });
    +
    +      describe('Active Tasks Filter tray', function () {
    +        var radioIDs = [
    +          'Replication',
    +          'Database-Compaction',
    +          'Indexer',
    +          'View-Compaction'
    +        ];
    +
    +        it('should trigger change to radio buttons', function () {
    +          _.each(radioIDs, function (radioID) {
    +            spy = sinon.spy(Actions, 'switchTab');
    +            TestUtils.Simulate.change($(table.getDOMNode()).find('#' + radioID)[0]);
    +            assert.ok(spy.calledOnce);
    +            spy.restore();
    --- End diff --
    
    the test 'should trigger change to radio buttons' is in an each loop, so i have to restore it in the loop. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung closed the pull request at:

    https://github.com/apache/couchdb-fauxton/pull/317


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26449189
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    I can do that! 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r27480494
  
    --- Diff: app/addons/activetasks/components.react.jsx ---
    @@ -0,0 +1,589 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'react',
    +  'addons/activetasks/stores',
    +  'addons/activetasks/resources',
    +  'addons/activetasks/actions'
    +], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
    +
    +  var activeTasksStore = Stores.activeTasksStore;
    +  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    +
    +  var ActiveTasksController = React.createClass({
    +
    +    getStoreState: function () {
    +      return {
    +        collection: activeTasksStore.getCollection(),
    +        searchTerm: activeTasksStore.getSearchTerm(),
    +        selectedRadio: activeTasksStore.getSelectedRadio(),
    +
    +        sortByHeader: activeTasksStore.getSortByHeader(),
    +        headerIsAscending: activeTasksStore.getHeaderIsAscending(),
    +
    +        setPolling: activeTasksStore.setPolling,
    +        clearPolling: activeTasksStore.clearPolling,
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      this.state.setPolling();
    +      activeTasksStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      this.state.clearPolling();
    +      activeTasksStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    setNewSearchTerm: function (e) {
    +      Actions.setSearchTerm(e.target.value);
    +    },
    +
    +    //radio buttons
    +    switchTab: function (e) {
    +      var newRadioButton = e.target.value;
    +      Actions.switchTab(newRadioButton);
    +    },
    +
    +    tableHeaderOnClick: function (e) {
    +      var headerClicked = e.target.value;
    +      Actions.sortByColumnHeader(headerClicked);
    +    },
    +
    +    render: function () {
    +      var collection = this.state.collection;
    +      var searchTerm = this.state.searchTerm;
    +      var selectedRadio = this.state.selectedRadio;
    +      var sortByHeader = this.state.sortByHeader;
    +      var headerIsAscending = this.state.headerIsAscending;
    +
    +      var setSearchTerm = this.setNewSearchTerm;
    +      var onTableHeaderClick = this.tableHeaderOnClick;
    +
    +      if (collection.length === 0 ) {
    +        return (<div className="active-tasks"><tr><td><p>  No active tasks. </p></td></tr></div>);
    +      } else {
    +        return (
    +          <div className="scrollable">
    +            <div className="inner">
    +              <ActiveTasksFilter 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio} 
    +                onSearch={setSearchTerm} 
    +                onRadioClick={this.switchTab}/>
    +              <ActiveTaskTable 
    +                collection={collection} 
    +                searchTerm={searchTerm} 
    +                selectedRadio={selectedRadio}
    +                onTableHeaderClick={onTableHeaderClick}
    +                sortByHeader={sortByHeader}
    +                headerIsAscending={headerIsAscending} />
    +            </div>
    +          </div>
    +        );
    +      }
    +    }
    +  });
    +
    +  var ActiveTasksFilter = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isFilterTrayVisible: false
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    toggleFilterTray: function () {
    +      this.setState({
    +        isFilterTrayVisible : !this.state.isFilterTrayVisible
    +      });
    +    },
    +
    +    render: function () {
    +      var filterTray = '';
    +
    +      if (this.state.isFilterTrayVisible) {
    +        filterTray = <ActiveTasksFilterTray 
    +                        key="filter-tray" 
    +                        selectedRadio={this.props.selectedRadio}
    +                        onSearch={this.props.onSearch} 
    +                        onRadioClick={this.props.onRadioClick} />;
    +      }
    +
    +      return (
    +        <div id="dashboard-upper-content">
    +          <div className="dashboard-upper-menu active-tasks">
    +            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
    +          </div>
    +          <ReactCSSTransitionGroup 
    +            className="dashboard-lower-menu" 
    +            transitionName="toggleFilterTray" 
    +            component="div" >
    +            {filterTray}
    +          </ReactCSSTransitionGroup>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTab = React.createClass({
    +    render: function () {
    +      return (
    +        <ul className="nav nav-tabs" id="db-views-tabs-nav">
    +          <li>
    +            <a id="toggle-filter-tab"
    +               className="toggle-filter-tab"
    +               data-bypass="true" 
    +               data-toggle="button"
    +               onClick={this.props.onClick}>
    +              <i className="fonticon fonticon-plus"></i>
    +              Filter
    +            </a>
    +          </li>
    +        </ul>);
    +    }
    +  });
    +
    +  var ActiveTasksFilterTray = React.createClass({
    +    render: function () {
    +      return (
    +        <div className="filter-tray">
    +          <ActiveTasksFilterTrayCheckBoxes 
    +            onRadioClick={this.props.onRadioClick} 
    +            selectedRadio={this.props.selectedRadio} />
    +          <input  
    +            className="searchbox" 
    +            type="text" 
    +            name="search" 
    +            placeholder="Search for databases..." 
    +            value={this.props.searchTerm}
    +            onChange={this.props.onSearch} />  
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
    +
    +    radioNames : [
    --- End diff --
    
    This should be default props


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26449154
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    Hi @kxepal! 
    
    Not sure what you mean. Do you mean, for example, to create a `Polling` object, then define getters/setters on that object instead of attaching them to ActiveTaskStore object? Then likewise for the rest or the get/set functions here? that would make things a lot clearer :)



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in react

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r26449218
  
    --- Diff: app/addons/activetasks/stores.js ---
    @@ -0,0 +1,127 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  "app/helpers",
    +  "api",
    +  "addons/activetasks/actiontypes"
    +], function (Helpers, FauxtonAPI, ActionTypes) {
    +
    +  var ActiveTasksStore = FauxtonAPI.Store.extend({  
    +    //elements in here
    +
    +    getSelectedTab: function () {
    +      return this._selectedTab;
    +    },
    +    setSelectedTab: function (selectedTab) {
    +      this._selectedTab = selectedTab;
    +    },
    +    getPollingInterval: function () {
    +      return this._pollingInterval;
    +    },
    +    setPollingInterval: function (pollingInterval) {
    +      this._pollingInterval = pollingInterval;
    +    },
    --- End diff --
    
    Good idea! thanks @kxepal :)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Active tasks in React

Posted by garrensmith <gi...@git.apache.org>.
Github user garrensmith commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/317#discussion_r28320921
  
    --- Diff: app/addons/activetasks/routes.js ---
    @@ -14,60 +14,40 @@ define([
       'app',
       'api',
       'addons/activetasks/resources',
    -  'addons/activetasks/views'
    +  'addons/activetasks/components.react',
    +  'addons/activetasks/actions'
     ],
     
    -function (app, FauxtonAPI, Activetasks, Views) {
    +function (app, FauxtonAPI, ActiveTasksResources, ActiveTasksComponents, Actions) {
     
       var ActiveTasksRouteObject = FauxtonAPI.RouteObject.extend({
    -    layout: 'with_tabs_sidebar',
    -
    +    selectedHeader: 'Active Tasks',
    +    layout: 'one_pane',
         routes: {
    -      'activetasks/:id': 'defaultView',
    -      'activetasks': 'defaultView'
    -    },
    -
    -    events: {
    -      'route:changeFilter': 'changeFilter'
    +      'activetasks/:id': 'showActiveTasks',
    +      'activetasks': 'showActiveTasks'
         },
    -
    -    selectedHeader: 'Active Tasks',
    -
         crumbs: [
           {'name': 'Active tasks', 'link': 'activetasks'}
         ],
    -
         apiUrl: function () {
    -      return [this.allTasks.url('apiurl'), this.allTasks.documentation];
    +      var apiurl = window.location.origin + '/_active_tasks';
    +      return [ apiurl, FauxtonAPI.constants.DOC_URLS.ACTIVE_TASKS];
         },
    -
         roles: ['_admin'],
    -
         initialize: function () {
    -      this.allTasks = new Activetasks.AllTasks();
    -      this.search = new Activetasks.Search();
    -    },
    -
    -    defaultView: function () {
    -      this.setView('#dashboard-lower-content', new Views.View({
    -        collection: this.allTasks,
    -        currentView: 'all',
    -        searchModel: this.search
    -      }));
    -
    -      this.setView('#sidebar-content', new Views.TabMenu({}));
    -
    -      this.headerView = this.setView('#dashboard-upper-content', new Views.TabHeader({
    -        searchModel: this.search
    -      }));
    +      this.allTasks = new ActiveTasksResources.AllTasks();
         },
    +    showActiveTasks: function () {
    +      Actions.fetchAndSetActiveTasks(this.allTasks);
    +      Actions.changePollingInterval(5);
     
    -    changeFilter: function (filterType) {
    -      this.search.set('filterType', filterType);
    +      this.activeTasksSection = this.setComponent('#dashboard-content', ActiveTasksComponents.ActiveTasksController);
    --- End diff --
    
    This looks great. I don't think you need `this.pollingwidget` or `this.activeTasksSection`. The RouteObject will call `removeComponent` so you don't need to manually remove them.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---