Repository: couchdb-fauxton Updated Branches: refs/heads/master 5d1cb4378 -> b4e5f5353
Add loading lines to active tasks - Add tabs for filtering - Add source sequence tray - garrensmith helped me out a whole bunch with this Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/b4e5f535 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/b4e5f535 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/b4e5f535 Branch: refs/heads/master Commit: b4e5f535309d6a807d1628d9cbf5eef91d105190 Parents: 5d1cb43 Author: Michelle Phung <michelleph...@gmail.com> Authored: Wed Jun 3 09:50:28 2015 +0200 Committer: michelleph...@gmail.com <michelleph...@gmail.com> Committed: Wed Jun 3 14:09:56 2015 -0400 ---------------------------------------------------------------------- app/addons/activetasks/actions.js | 18 +- app/addons/activetasks/actiontypes.js | 3 +- .../activetasks/assets/less/activetasks.less | 171 +++++++---- app/addons/activetasks/components.react.jsx | 294 +++++++++---------- app/addons/activetasks/routes.js | 4 +- app/addons/activetasks/stores.js | 36 ++- .../tests/activetasks.componentsSpec.react.jsx | 25 +- .../activetasks/tests/activetasks.storesSpec.js | 45 +-- app/addons/fauxton/components.react.jsx | 35 +++ 9 files changed, 376 insertions(+), 255 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/actions.js b/app/addons/activetasks/actions.js index 74fae94..55469ab 100644 --- a/app/addons/activetasks/actions.js +++ b/app/addons/activetasks/actions.js @@ -16,17 +16,17 @@ define([ ], function (FauxtonAPI, ActionTypes, Resources) { return { - fetchAndSetActiveTasks: function (options) { - var activeTasks = options; - + init: function (activeTasks) { + this.fetchAndSetActiveTasks(activeTasks.table, activeTasks); FauxtonAPI.when(activeTasks.fetch()).then(function () { - this.init(activeTasks.table, activeTasks); + this.fetchAndSetActiveTasks(activeTasks.table, activeTasks); + this.setActiveTaskIsLoading(false); }.bind(this)); }, - init: function (collection, backboneCollection) { + fetchAndSetActiveTasks: function (collection, backboneCollection) { FauxtonAPI.dispatch({ - type: ActionTypes.ACTIVE_TASKS_INIT, + type: ActionTypes.ACTIVE_TASKS_FETCH_AND_SET, options: { collectionTable: collection, backboneCollection: backboneCollection @@ -64,6 +64,12 @@ function (FauxtonAPI, ActionTypes, Resources) { columnName: columnName } }); + }, + setActiveTaskIsLoading: function (boolean) { + FauxtonAPI.dispatch({ + type: ActionTypes.ACTIVE_TASKS_SET_IS_LOADING, + options: boolean + }); } }; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/actiontypes.js b/app/addons/activetasks/actiontypes.js index 8cd2838..cdba87d 100644 --- a/app/addons/activetasks/actiontypes.js +++ b/app/addons/activetasks/actiontypes.js @@ -16,6 +16,7 @@ define([], function () { ACTIVE_TASKS_SET_COLLECTION: 'ACTIVE_TASKS_SET_COLLECTION', ACTIVE_TASKS_SET_SEARCH_TERM: 'ACTIVE_TASKS_SET_SEARCH_TERM', ACTIVE_TASKS_SORT_BY_COLUMN_HEADER: 'ACTIVE_TASKS_SORT_BY_COLUMN_HEADER', - ACTIVE_TASKS_INIT: 'ACTIVE_TASKS_INIT' + ACTIVE_TASKS_FETCH_AND_SET: 'ACTIVE_TASKS_FETCH_AND_SET', + ACTIVE_TASKS_SET_IS_LOADING: 'ACTIVE_TASKS_SET_IS_LOADING' }; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/assets/less/activetasks.less ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/assets/less/activetasks.less b/app/addons/activetasks/assets/less/activetasks.less index 1116671..e11c0a8 100644 --- a/app/addons/activetasks/assets/less/activetasks.less +++ b/app/addons/activetasks/assets/less/activetasks.less @@ -10,6 +10,10 @@ // License for the specific language governing permissions and limitations under // the License. +@import "../../../../../assets/less/bootstrap/variables.less"; +@import "../../../../../assets/less/variables.less"; +@import "../../../../../assets/less/bootstrap/mixins.less"; + .task-tabs li, .active-tasks th { cursor: pointer; @@ -65,7 +69,7 @@ } .no-matching-database-on-search { - color: #e33f3b; + color: #af2d24; } p.multiline-active-tasks-message { @@ -88,67 +92,63 @@ p.multiline-active-tasks-message { } } -.dashboard-lower-menu { - padding-top: 90px; - padding-left: 20px; -} +#active-tasks-filter-tabs { -input[type="text"].searchbox { - width: 200px; - height: 40px; - float: right; - margin:0px; -} + height: 100px; + background-color: #CBCBCB; + padding-left: 20px; + min-width: 550px; + overflow: scroll; -#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; -} + input { + display:none; + } -.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; -} + li { + background-color: #eee; + margin-top: 65px; + margin-left: 3px; + &.active-tasks-checked { + background-color: #af2d24; + + label { + color: #fff; + } + }; + + &:hover { + border: 1px solid #af2d24; + color: #af2d24; + }; + + label { + height: 35px; + width: 100%; + line-height: 35px; + text-align: center; + } + } -.filter-checkboxes { - float: left; - height: auto; - width: auto; - margin-top: 10px; -} + #active-tasks-search-box { + display: inline; + font-size: 14px; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + height: 36px; + background-color: #eee; + width: 200px; + margin: 0px; -.active-tasks-one-checkbox { - input { - vertical-align: middle; - margin-right: 5px; - margin-bottom: 3px; + &:focus { + background-color: #fff; + outline-color: #fff; + }; } - margin-right: 10px; - display: inline-block; } -.active-tasks-checkbox-label { - display: inline-block; - color: #666; +.dashboard-lower-menu { + padding-top: 90px; + padding-left: 20px; } @media (max-width: 940px) { @@ -159,16 +159,13 @@ input[type="text"].searchbox { } } -.filter-checkboxes-form { - margin:0px; -} - .polling-interval-widget { width: 250px; margin-right: 20px; margin-top: 10px; color: #666; + float:right; li { list-style-type: none; @@ -208,4 +205,64 @@ input[type="text"].searchbox { padding: 0px; margin: 0px; } + + .active-tasks-header { + background-color: #ccc; + } + + .icon-caret-up, .icon-caret-down { + color: #af2d24; + } +} + +.active-tasks-loading-lines { + padding-top: 15px; + float: left; + margin-left: -40px; + + #line1, #line2, #line3, #line4 { + background-color: #bbb; + } +} + +.view-source-sequence-btn { // "View" Button + background-color: #666; + display: inline; + border-radius: 3px; + padding: 5px; + color: #fff !important; +} + +.view_source_sequence_tray { + padding: 16px 20px 28px; + + position: relative; + min-width: 360px; + top: 15px; + float: right; + + &:before { + right: 20px; + } + input.input-xxlarge { + margin-bottom: 0px; + width: 250px; + .border-radius(5px 0 0 5px); + } + + a.btn { + color: white; + background-color: #af2d24; + margin-left: 0; + line-height: 1.5em; + border: 0px; + padding: 10px 10px 9px; + font-size: 14px; + .border-radius(0 5px 5px 0); + + &:hover, &.copy-button.zeroclipboard-is-hover { + background-color: #cbcbcb; + color: white; + } + } } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/components.react.jsx b/app/addons/activetasks/components.react.jsx index 0110580..5f146ab 100644 --- a/app/addons/activetasks/components.react.jsx +++ b/app/addons/activetasks/components.react.jsx @@ -16,8 +16,10 @@ define([ 'react', 'addons/activetasks/stores', 'addons/activetasks/resources', - 'addons/activetasks/actions' -], function (app, FauxtonAPI, React, Stores, Resources, Actions) { + 'addons/activetasks/actions', + 'addons/components/react-components.react', + 'addons/fauxton/components.react' +], function (app, FauxtonAPI, React, Stores, Resources, Actions, Components, ComponentsReact) { var activeTasksStore = Stores.activeTasksStore; var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; @@ -34,7 +36,7 @@ define([ headerIsAscending: activeTasksStore.getHeaderIsAscending(), setPolling: activeTasksStore.setPolling, - clearPolling: activeTasksStore.clearPolling, + clearPolling: activeTasksStore.clearPolling }; }, @@ -60,7 +62,7 @@ define([ Actions.setSearchTerm(searchTerm); }, - switchTab: function (newRadioButton) { //radio buttons + switchTab: function (newRadioButton) { //tabs buttons Actions.switchTab(newRadioButton); }, @@ -84,14 +86,14 @@ define([ return ( <div className="scrollable"> <div className="inner"> - <ActiveTasksFilter - searchTerm={searchTerm} - selectedRadio={selectedRadio} - onSearch={setSearchTerm} + <ActiveTasksFilterTabs + searchTerm={searchTerm} + selectedRadio={selectedRadio} + onSearch={setSearchTerm} onRadioClick={this.switchTab}/> - <ActiveTaskTable - collection={collection} - searchTerm={searchTerm} + <ActiveTaskTable + collection={collection} + searchTerm={searchTerm} selectedRadio={selectedRadio} onTableHeaderClick={onTableHeaderClick} sortByHeader={sortByHeader} @@ -102,93 +104,7 @@ define([ } }); - 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({ - searchTermChange: function (e) { - var searchTerm = e.target.value; - this.props.onSearch(searchTerm); - }, - - 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.searchTermChange} /> - </div> - ); - } - }); - - var ActiveTasksFilterTrayCheckBoxes = React.createClass({ + var ActiveTasksFilterTabs = React.createClass({ getDefaultProps: function () { return { radioNames : [ @@ -210,21 +126,24 @@ define([ this.props.onRadioClick(radioName); }, - createCheckboxes: function () { + createFilterTabs: function () { return ( this.props.radioNames.map(function (radioName) { var checked = this.checked(radioName); var id = radioName.replace(' ', '-'); var radioClassName = "radio-" + id; var radioClick = this.onRadioClick; + var checkedClassName = checked ? 'active-tasks-checked' : ''; return ( - <li className="active-tasks-one-checkbox" key={radioName + "li"}> + <li className={"active-tasks-one-checkbox " + checkedClassName} key={radioName + "li"}> <input + className="toggle-filter-tab" + data-bypass="true" id={id} type="radio" - key ={radioName} - name="radio-button-active-task-filter-tray" + key ={radioName} + name="radio-button-active-task-filter-tray" value={radioName} checked={checked} onChange={radioClick} /> @@ -237,15 +156,27 @@ define([ ); }, + searchTermChange: function (e) { + var searchTerm = e.target.value; + this.props.onSearch(searchTerm); + }, + render: function () { - var filterCheckboxes = this.createCheckboxes(); + var filterTabs = this.createFilterTabs(); return ( - <ul className="filter-checkboxes"> - <form className="filter-checkboxes-form"> - {filterCheckboxes} - </form> - </ul> - ); + <ul className="nav nav-tabs" id="active-tasks-filter-tabs"> + {filterTabs} + <li> + <input + id="active-tasks-search-box" + className="searchbox" + type="text" + name="search" + placeholder="Search for databases..." + value={this.props.searchTerm} + onChange={this.searchTermChange} /> + </li> + </ul>); } }); @@ -261,13 +192,13 @@ define([ return ( <div id="dashboard-lower-content"> <table className="table table-bordered table-striped active-tasks"> - <ActiveTasksTableHeader + <ActiveTasksTableHeader onTableHeaderClick={onTableHeaderClick} sortByHeader={sortByHeader} headerIsAscending={headerIsAscending}/> - <ActiveTasksTableBody - collection={collection} - selectedRadio={selectedRadio} + <ActiveTasksTableBody + collection={collection} + selectedRadio={selectedRadio} searchTerm={searchTerm}/> </table> </div> @@ -293,26 +224,21 @@ define([ var onTableHeaderClick = this.props.onTableHeaderClick; var sortByHeader = this.props.sortByHeader; var headerIsAscending = this.props.headerIsAscending; - return ( - this.props.headerNames.map(function (header) { - return ( - <TableHeader - headerName={header[0]} - displayName={header[1]} - key={header[0]} - onTableHeaderClick={onTableHeaderClick} - sortByHeader={sortByHeader} - headerIsAscending={headerIsAscending} /> - ); - }) - ); + return this.props.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> + <tr>{this.createTableHeadingFields()}</tr> </thead> ); } @@ -348,8 +274,8 @@ define([ className="header-field radio" onChange={this.onTableHeaderClick}> <th className={th_class} value={this.props.headerName}> - <label - className="header-field label-text" + <label + className="header-field label-text active-tasks-header" htmlFor={this.props.headerName}> {this.props.displayName} {arrow} </label> @@ -384,8 +310,8 @@ define([ return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter(); } - return _.map(this.state.filteredTable, function (item, iteration) { - return <ActiveTaskTableBodyContents key={Math.random()} item={item} />; + return _.map(this.state.filteredTable, function (item, key) { + return <ActiveTaskTableBodyContents key={key} item={item} />; }); }, @@ -406,10 +332,9 @@ define([ }, render: function () { - var tableBody = this.createRows(); return ( <tbody className="js-tasks-go-here"> - {tableBody} + {this.createRows()} </tbody> ); } @@ -453,17 +378,66 @@ define([ <td>{startedOnMsg}</td> <td>{updatedOnMsg}</td> <td>{rowData.pid}</td> - <td>{progressMsg}</td> + <td>{progressMsg}<ActiveTasksViewSourceSequence item={this.props.item}/></td> </tr> ); } }); + var ActiveTasksViewSourceSequence = React.createClass({ + onTrayToggle: function (e) { + e.preventDefault(); + this.refs.view_source_sequence_btn.toggle(function (shown) { + if (shown) { + this.refs.view_source_sequence_btn.getDOMNode().focus(); + } + }.bind(this)); + }, + + sequences: function (item) { + if (_.isNumber(item) || _.isString(item)) { + return <ComponentsReact.ClipboardWithTextField textToCopy={item} uniqueKey={item}/>; + } + + if (_.isArray(item)) { + return _.map(item, function (seq, i) { + return <ComponentsReact.ClipboardWithTextField textToCopy={seq} uniqueKey={i + Math.random(100)} key={i}/>; + }); + } + + return <ComponentsReact.ClipboardWithTextField textToCopy="???" uniqueKey='unknownRevision'/>; + }, + + render: function () { + + if (_.has(this.props.item, 'source_seq')) { + var sequences = this.sequences(this.props.item.source_seq); + return ( + <div> + Current source sequence: + <a href="#" + className="view-source-sequence-btn" + onClick={this.onTrayToggle} + data-bypass="true"> + View + </a> + <ComponentsReact.Tray ref="view_source_sequence_btn" className="view_source_sequence_tray"> + <span className="add-on">Source Sequence</span> + {sequences} + </ComponentsReact.Tray> + </div> + ); + } + return null; + } + }); + var ActiveTasksPollingWidgetController = React.createClass({ getStoreState: function () { return { - pollingInterval: activeTasksStore.getPollingInterval() + pollingInterval: activeTasksStore.getPollingInterval(), + isLoading: false//activeTasksStore.isLoading() }; }, @@ -497,18 +471,18 @@ define([ 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 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} + <input + id="pollingRange" + type="range" + min="1" + max="30" + step="1" + value={pollingInterval} onChange={onChangeHandle}/> </li> </ul> @@ -517,8 +491,22 @@ define([ render: function () { var pollingWidget = this.createPollingWidget(); + var loadLines = null; + + if (this.state.isLoading || this.state.pollingInterval === "1") { + // show loading lines constantly if the polling interval is + // 1 second, so that the lines aren't choppy + loadLines = <Components.LoadLines />; + } - return <div>{pollingWidget}</div>; + return ( + <div> + <span className="active-tasks-loading-lines"> + {loadLines} + </span> + {pollingWidget} + </div> + ); } }); @@ -568,29 +556,29 @@ define([ } } - if (_.has(item, 'source_seq')) { - progressMessage.push('Current source sequence: ' + item.source_seq + '. '); - } - if (_.has(item, 'changes_done')) { progressMessage.push(item.changes_done + ' Changes done.'); } return progressMessage; + }, + + getSourceSequence: function (item) { + return item.source_seq; } + }; return { ActiveTasksController: ActiveTasksController, - ActiveTasksFilter: ActiveTasksFilter, - ActiveTasksFilterTab: ActiveTasksFilterTab, - ActiveTasksFilterTray: ActiveTasksFilterTray, + ActiveTasksFilterTabs: ActiveTasksFilterTabs, ActiveTaskTable: ActiveTaskTable, ActiveTasksTableHeader: ActiveTasksTableHeader, TableHeader: TableHeader, ActiveTasksTableBody: ActiveTasksTableBody, ActiveTaskTableBodyContents: ActiveTaskTableBodyContents, + ActiveTasksViewSourceSequence: ActiveTasksViewSourceSequence, ActiveTasksPollingWidgetController: ActiveTasksPollingWidgetController }; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/routes.js ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/routes.js b/app/addons/activetasks/routes.js index cb9f673..b03d54a 100644 --- a/app/addons/activetasks/routes.js +++ b/app/addons/activetasks/routes.js @@ -40,9 +40,7 @@ function (app, FauxtonAPI, ActiveTasksResources, ActiveTasksComponents, Actions) this.allTasks = new ActiveTasksResources.AllTasks(); }, showActiveTasks: function () { - Actions.fetchAndSetActiveTasks(this.allTasks); - Actions.changePollingInterval(1); - + Actions.init(this.allTasks); this.setComponent('#dashboard-content', ActiveTasksComponents.ActiveTasksController); this.setComponent('#right-header', ActiveTasksComponents.ActiveTasksPollingWidgetController); } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/stores.js b/app/addons/activetasks/stores.js index 4c61a52..baf5607 100644 --- a/app/addons/activetasks/stores.js +++ b/app/addons/activetasks/stores.js @@ -18,7 +18,7 @@ define([ var ActiveTasksStore = FauxtonAPI.Store.extend({ - init: function (collectionTable, backboneCollection) { + initAfterFetching: function (collectionTable, backboneCollection) { this._prevSortbyHeader = 'started_on'; this._headerIsAscending = true; this._selectedRadio = 'All Tasks'; @@ -28,6 +28,29 @@ define([ this._pollingIntervalSeconds = 5; this.sortCollectionByColumnHeader(this._sortByHeader); this._backboneCollection = backboneCollection; + this.setIsLoading(true, new Date()); + }, + + isLoading: function () { + return this._isLoading; + }, + + setIsLoading: function (bool, time) { + if (bool) { + this._startTimeForLoading = time; + this._isLoading = true; + } else { + var stoptime = time; + var responseTime = stoptime - this._startTimeForLoading; + if (responseTime < 800) { + setTimeout(function () { + this._isLoading = false; + this.triggerChange(); + }.bind(this), 800); //stop after 800ms for smooth animation + } else { + this._isLoading = false; + } + } }, getSelectedRadio: function () { @@ -47,7 +70,7 @@ define([ }, setPolling: function () { - clearInterval(this.getIntervalID()); + this.clearPolling(); var id = setInterval(function () { this._backboneCollection.pollingFetch(); this.setCollection(this._backboneCollection.table); @@ -170,8 +193,8 @@ define([ dispatch: function (action) { switch (action.type) { - case ActionTypes.ACTIVE_TASKS_INIT: - this.init(action.options.collectionTable, action.options.backboneCollection); + case ActionTypes.ACTIVE_TASKS_FETCH_AND_SET: + this.initAfterFetching(action.options.collectionTable, action.options.backboneCollection); break; case ActionTypes.ACTIVE_TASKS_CHANGE_POLLING_INTERVAL: @@ -202,6 +225,11 @@ define([ this.triggerChange(); break; + case ActionTypes.ACTIVE_TASKS_SET_IS_LOADING: + this.setIsLoading(action.options, new Date()); + this.triggerChange(); + break; + default: return; } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx b/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx index 4c22c3d..a147abd 100644 --- a/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx +++ b/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx @@ -18,8 +18,9 @@ define([ 'react', 'addons/activetasks/actions', 'testUtils' -], function (FauxtonAPI, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) { - var assert = testUtils.assert; +], function (FauxtonAPI, ActiveTasks, Components, Stores, fakedResponse, React, Actions, utils) { + var assert = utils.assert; + var restore = utils.restore; var TestUtils = React.addons.TestUtils; var activeTasksStore = Stores.activeTasksStore; var activeTasksCollection = new ActiveTasks.AllTasks({}); @@ -33,12 +34,13 @@ define([ beforeEach(function () { pollingWidgetDiv = document.createElement('div'); pollingWidget = TestUtils.renderIntoDocument( - React.createElement(Components.ActiveTasksPollingWidgetController, null), pollingWidgetDiv + <Components.ActiveTasksPollingWidgetController />, pollingWidgetDiv ); }); afterEach(function () { React.unmountComponentAtNode(pollingWidgetDiv); + restore(Actions.changePollingInterval); }); it('should trigger update polling interval', function () { @@ -56,17 +58,17 @@ define([ beforeEach(function () { tableDiv = document.createElement('div'); - activeTasksStore.init(activeTasksCollection.table, activeTasksCollection); - table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv); + activeTasksStore.initAfterFetching(activeTasksCollection.table, activeTasksCollection); + table = TestUtils.renderIntoDocument(<Components.ActiveTasksController />, tableDiv); // open filter tray - filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab'); - TestUtils.Simulate.click(filterTab); + //filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab'); + //TestUtils.Simulate.click(filterTab); }); afterEach(function () { React.unmountComponentAtNode(tableDiv); - window.confirm.restore && window.confirm.restore(); + restore(window.confirm); }); it('it displays a message instead of an empty table, if there are undefined active tasks', function () { @@ -78,7 +80,8 @@ define([ describe('Active Tasks Filter tray', function () { afterEach(function () { - spy.restore(); + restore(Actions.switchTab); + restore(Actions.setSearchTerm); }); var radioIDs = [ @@ -114,6 +117,10 @@ define([ 'progress' ]; + afterEach(function () { + restore(Actions.sortByColumnHeader); + }); + it('should trigger change to which header to sort by', function () { _.each(headerNames, function (header) { spy = sinon.spy(Actions, 'sortByColumnHeader'); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/tests/activetasks.storesSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/activetasks/tests/activetasks.storesSpec.js b/app/addons/activetasks/tests/activetasks.storesSpec.js index 462d94d..c059280 100644 --- a/app/addons/activetasks/tests/activetasks.storesSpec.js +++ b/app/addons/activetasks/tests/activetasks.storesSpec.js @@ -12,13 +12,13 @@ define([ 'api', 'addons/activetasks/resources', - 'addons/activetasks/components.react', 'addons/activetasks/stores', 'addons/activetasks/tests/fakeActiveTaskResponse', 'react', 'testUtils' -], function (FauxtonAPI, ActiveTasks, Components, Stores, fakedResponse, React, testUtils) { - var assert = testUtils.assert; +], function (FauxtonAPI, ActiveTasks, Stores, fakedResponse, React, utils) { + var assert = utils.assert; + var restore = utils.restore; var TestUtils = React.addons.TestUtils; var activeTasksStore = Stores.activeTasksStore; @@ -26,29 +26,30 @@ define([ activeTasksCollection.parse(fakedResponse); describe('Active Tasks -- Stores', function () { - var spy; + var spy, clock; beforeEach(function () { - activeTasksStore.init(activeTasksCollection.table, activeTasksCollection); - this.clock = sinon.useFakeTimers(); + activeTasksStore.initAfterFetching(activeTasksCollection.table, activeTasksCollection); + clock = sinon.useFakeTimers(); }); afterEach(function () { - testUtils.restore(spy); - testUtils.restore(this.clock); + restore(spy); + restore(clock); }); describe('Active Task Stores - Polling', function () { var pollingWidgetDiv, pollingWidget; beforeEach(function () { - activeTasksStore.init(activeTasksCollection.table, activeTasksCollection); - this.clock = sinon.useFakeTimers(); + activeTasksStore.initAfterFetching(activeTasksCollection.table, activeTasksCollection); + clock = sinon.useFakeTimers(); }); afterEach(function () { - testUtils.restore(spy); - testUtils.restore(this.clock); + restore(activeTasksStore.getPollingInterval); + restore(window.clearInterval); + restore(clock); }); it('should poll at the min time', function () { @@ -59,10 +60,10 @@ define([ assert.ok(spy.calledOnce); setInterval(spy, minTime * 1000); - this.clock.tick(minTime * 1000); + clock.tick(minTime * 1000); assert.ok(spy.calledTwice); - this.clock.tick(minTime * 1000); + clock.tick(minTime * 1000); assert.ok(spy.calledThrice); }); @@ -75,10 +76,10 @@ define([ assert.ok(spy.calledOnce); setInterval(spy, maxTime * 1000); - this.clock.tick(maxTime * 1000); + clock.tick(maxTime * 1000); assert.ok(spy.calledTwice); - this.clock.tick(maxTime * 1000); + clock.tick(maxTime * 1000); assert.ok(spy.calledThrice); }); @@ -91,10 +92,10 @@ define([ assert.ok(spy.calledOnce); setInterval(spy, midtime * 1000); - this.clock.tick(midtime * 1000); + clock.tick(midtime * 1000); assert.ok(spy.calledTwice); - this.clock.tick(midtime * 1000); + clock.tick(midtime * 1000); assert.ok(spy.calledThrice); }); @@ -132,7 +133,7 @@ define([ //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()); + assert.deepEqual(activeTask.type, activeTasksStore.getSelectedRadio()); }); }); @@ -158,20 +159,20 @@ define([ it('should set header as ascending, on default', function () { activeTasksStore.setSelectedRadio('all_tasks'); activeTasksStore._headerIsAscending = true; - assert.ok(activeTasksStore.getHeaderIsAscending() === true); + assert.ok(activeTasksStore.getHeaderIsAscending()); }); it('should set header as descending, if same header is selected again', function () { activeTasksStore._prevSortbyHeader = 'sameHeader'; activeTasksStore._sortByHeader = 'sameHeader'; activeTasksStore.toggleHeaderIsAscending(); - assert.ok(activeTasksStore.getHeaderIsAscending() === false); + assert.notOk(activeTasksStore.getHeaderIsAscending()); }); it('should set header as ascending, if different header is selected', function () { activeTasksStore._sortByHeader = 'differentHeader'; activeTasksStore.toggleHeaderIsAscending(); - assert.ok(activeTasksStore.getHeaderIsAscending() === true); + assert.ok(activeTasksStore.getHeaderIsAscending()); }); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/fauxton/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/fauxton/components.react.jsx b/app/addons/fauxton/components.react.jsx index 5e7e891..a08f8a6 100644 --- a/app/addons/fauxton/components.react.jsx +++ b/app/addons/fauxton/components.react.jsx @@ -46,6 +46,40 @@ function (app, FauxtonAPI, React, ZeroClipboard) { } }); + // use like this: + // <ComponentsReact.ClipboardWithTextField textToCopy={yourText} uniqueKey={someUniqueValue}> + // </ComponentsReact.ClipboardWithTextField> + // pass in the text and a unique key, the key has to be unique or you'll get a warning + var ClipboardWithTextField = React.createClass({ + componentWillMount: function () { + ZeroClipboard.config({ moviePath: app.zeroClipboardPath }); + }, + + componentDidUpdate: function () { + this.clipboard = new ZeroClipboard(document.getElementById("copy-text-" + this.props.uniqueKey)); + }, + + render: function () { + return ( + <p key={this.props.uniqueKey}> + <input + type="text" + className="input-xxlarge text-field-to-copy" + readOnly + value={this.props.textToCopy} /> + <a + id={"copy-text-" + this.props.uniqueKey} + className="fonticon-clipboard icon btn copy-button" + data-clipboard-text={this.props.textToCopy} + data-bypass="true" + title="Copy to clipboard" > + Copy + </a> + </p> + ); + } + }); + // formats a block of code and pretty-prints it in the page. Currently uses the prettyPrint plugin var CodeFormat = React.createClass({ getDefaultProps: function () { @@ -223,6 +257,7 @@ function (app, FauxtonAPI, React, ZeroClipboard) { return { Clipboard: Clipboard, + ClipboardWithTextField: ClipboardWithTextField, CodeFormat: CodeFormat, Tray: Tray, Pagination: Pagination