garrensmith closed pull request #1141: Convert activetasks to redux
URL: https://github.com/apache/couchdb-fauxton/pull/1141
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/app/addons/activetasks/__tests__/components.test.js
b/app/addons/activetasks/__tests__/components.test.js
index b2994a1ec..e871eb835 100644
--- a/app/addons/activetasks/__tests__/components.test.js
+++ b/app/addons/activetasks/__tests__/components.test.js
@@ -9,43 +9,20 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
under
// the License.
-import ActiveTasks from "../resources";
-import Components from "../components";
-import Stores from "../stores";
-import fakedResponse from "./fakeActiveTaskResponse";
+import TableHeader from '../components/tableheader';
+import FilterTabs from '../components/filtertabs';
import React from "react";
import ReactDOM from "react-dom";
-import Actions from "../actions";
import utils from "../../../../test/mocha/testUtils";
import {mount} from 'enzyme';
import sinon from "sinon";
const assert = utils.assert;
-var restore = utils.restore;
-var activeTasksStore = Stores.activeTasksStore;
-var activeTasksCollection = new ActiveTasks.AllTasks({});
-activeTasksCollection.parse(fakedResponse);
describe('Active Tasks -- Components', () => {
describe('Active Tasks Table (Components)', () => {
- let table;
-
- beforeEach(() => {
- activeTasksStore.initAfterFetching(activeTasksCollection.table,
activeTasksCollection);
- table = mount(<Components.ActiveTasksController />);
- });
-
- afterEach(() => {
- restore(window.confirm);
- });
-
describe('Active Tasks Filter tray', () => {
- afterEach(() => {
- restore(Actions.switchTab);
- restore(Actions.setSearchTerm);
- });
-
const radioTexts = [
'Replication',
'Database Compaction',
@@ -54,44 +31,57 @@ describe('Active Tasks -- Components', () => {
];
it('should trigger change to radio buttons', () => {
-
radioTexts.forEach((text) => {
- let spy = sinon.spy(Actions, 'switchTab');
+ let spy = sinon.spy();
+ const tabs = mount(
+ <FilterTabs
+ onRadioClick={spy}
+ selectedRadio={"All Tasks"}
+ radioNames={radioTexts}
+ />
+ );
- table.find(`input[value="${text}"]`).simulate('change');
+ tabs.find(`input[value="${text}"]`).simulate('change');
assert.ok(spy.calledOnce);
-
- spy.restore();
});
});
it('should trigger change to search term', () => {
- const spy = sinon.spy(Actions, 'setSearchTerm');
- table.find('.searchbox').simulate('change', {target: {value:
'searching'}});
+ const spy = sinon.spy();
+ const tabs = mount(
+ <FilterTabs
+ onSearch={spy}
+ selectedRadio={"All Tasks"}
+ />
+ );
+ tabs.find('.searchbox').simulate('change', {target: {value:
'searching'}});
assert.ok(spy.calledOnce);
});
});
describe('Active Tasks Table Headers', () => {
- var headerNames = [
+ const tableTexts = [
'type',
'database',
- 'started-on',
- 'updated-on',
- 'pid',
- 'progress'
+ 'started-on'
];
- afterEach(() => {
- restore(Actions.sortByColumnHeader);
- });
+ it('should trigger change to radio buttons', () => {
+
+ tableTexts.forEach((text) => {
+ let spy = sinon.spy();
+ const table = mount(
+ <table>
+ <TableHeader
+ onTableHeaderClick={spy}
+ headerIsAscending={true}
+ sortByHeader={"All Tasks"}
+ />
+ </table>
+ );
- it('should trigger change to which header to sort by', () => {
- headerNames.forEach(header => {
- let spy = sinon.spy(Actions, 'sortByColumnHeader');
- table.find('#' + header).simulate('change');
+ table.find(`.${text}`).simulate('click');
assert.ok(spy.calledOnce);
- spy.restore();
});
});
});
diff --git a/app/addons/activetasks/__tests__/fakeActiveTaskResponse.js
b/app/addons/activetasks/__tests__/fakeActiveTaskResponse.js
index 823b7f240..bf60a473d 100644
--- a/app/addons/activetasks/__tests__/fakeActiveTaskResponse.js
+++ b/app/addons/activetasks/__tests__/fakeActiveTaskResponse.js
@@ -9,7 +9,7 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
under
// the License.
-var fakeData = [
+export default [
{
"user": "okra",
"updated_on": 10,
@@ -116,4 +116,3 @@ var fakeData = [
"started_on": 1426602505
},
];
-export default fakeData;
diff --git a/app/addons/activetasks/__tests__/reducer.test.js
b/app/addons/activetasks/__tests__/reducer.test.js
new file mode 100644
index 000000000..28aba0c48
--- /dev/null
+++ b/app/addons/activetasks/__tests__/reducer.test.js
@@ -0,0 +1,106 @@
+// 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.
+import reducer from '../reducers';
+import ActionTypes from '../actiontypes';
+import fakedResponse from "./fakeActiveTaskResponse";
+import utils from "../../../../test/mocha/testUtils";
+const assert = utils.assert;
+
+describe('Active Tasks -- Stores', () => {
+ let initState;
+
+ beforeEach(() => {
+ initState = reducer(undefined, {
+ type: ActionTypes.ACTIVE_TASKS_FETCH_AND_SET,
+ options: fakedResponse
+ });
+ });
+
+ describe('Active Task Stores - Filter Tab Tray', () => {
+
+ it('should filter the table correctly, by radio -- All Tasks', () => {
+ const state = reducer(initState, {
+ type: ActionTypes.ACTIVE_TASKS_SWITCH_TAB,
+ options: 'all_tasks'
+ });
+ assert.ok(state.tasks.length > 0);
+ assert.deepEqual(state.tasks.length, state.filteredTasks.length);
+ });
+
+ it('should filter the table correctly, by radio', () => {
+ const state = reducer(initState, {
+ type: ActionTypes.ACTIVE_TASKS_SWITCH_TAB,
+ options: 'replication'
+ });
+
+ //parse table and check that it only contains objects with type:
Replication
+ assert.ok(state.filteredTasks.length > 0);
+ state.filteredTasks.forEach(task => {
+ assert.deepEqual(task.type, 'replication');
+ assert.deepEqual(task.type, state.selectedRadio);
+ });
+ });
+
+ it.only('should search the table correctly', () => {
+ var searchTerm = 'base';
+ const state = reducer(initState, {
+ type: ActionTypes.ACTIVE_TASKS_SET_SEARCH_TERM,
+ options: searchTerm
+ });
+
+ const fakeFilteredTable = [
+ { user: 'information'},
+ { user: 'ooo'}
+ ];
+
+ assert.equal(fakeFilteredTable[0].user, state.filteredTasks[0].user);
+ assert.equal(fakeFilteredTable[1].user, state.filteredTasks[1].user);
+ });
+ });
+
+ describe('Active Task Stores - Table Header Sort - Select
Ascending/Descending', () => {
+
+ it('should set header as ascending, on default', () => {
+ const state = reducer(initState, {
+ type: ActionTypes.ACTIVE_TASKS_SWITCH_TAB,
+ options: 'all_tasks'
+ });
+ assert.ok(state.headerIsAscending);
+ });
+
+ it('should set header as descending, if same header is selected again', ()
=> {
+ const state = reducer(initState, {
+ type: ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER,
+ options: 'sameHeader'
+ });
+ assert.ok(state.headerIsAscending);
+
+ const state2 = reducer(state, {
+ type: ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER,
+ options: 'sameHeader'
+ });
+ assert.notOk(state2.headerIsAscending);
+
+ const state3 = reducer(state2, {
+ type: ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER,
+ options: 'sameHeader'
+ });
+ assert.ok(state3.headerIsAscending);
+
+ const state4 = reducer(state3, {
+ type: ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER,
+ options: 'differentHeader'
+ });
+ assert.ok(state4.headerIsAscending);
+ });
+ });
+});
diff --git a/app/addons/activetasks/__tests__/stores.test.js
b/app/addons/activetasks/__tests__/stores.test.js
deleted file mode 100644
index c6f67d1ee..000000000
--- a/app/addons/activetasks/__tests__/stores.test.js
+++ /dev/null
@@ -1,90 +0,0 @@
-// 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.
-import ActiveTasks from "../resources";
-import Stores from "../stores";
-import fakedResponse from "./fakeActiveTaskResponse";
-import utils from "../../../../test/mocha/testUtils";
-const assert = utils.assert;
-
-var activeTasksStore = Stores.activeTasksStore;
-var activeTasksCollection = new ActiveTasks.AllTasks();
-activeTasksCollection.parse(fakedResponse);
-
-describe('Active Tasks -- Stores', function () {
- beforeEach(function () {
- activeTasksStore.initAfterFetching(activeTasksCollection.table,
activeTasksCollection);
- });
-
- describe('Active Task Stores - Filter Tab Tray', function () {
- var fakeFilteredTable;
-
- 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', function () {
- activeTasksStore.setSelectedRadio('replication');
- var storeFilteredtable =
activeTasksStore.getFilteredTable(activeTasksStore._collection);
-
- //parse table and check that it only contains objects with type:
Replication
- _.each(storeFilteredtable, (activeTask) => {
- assert.ok(activeTasksStore.passesRadioFilter(activeTask));
- assert.deepEqual(activeTask.type, activeTasksStore.getSelectedRadio());
- });
- });
-
- it('should search the table correctly', function () {
- activeTasksStore.setSelectedRadio('all_tasks');
- var searchTerm = 'base';
- activeTasksStore.setSearchTerm(searchTerm);
- var storeGeneratedTable =
activeTasksStore.getFilteredTable(activeTasksStore._collection);
-
- fakeFilteredTable = [
- { user: 'information'},
- { user: 'ooo'}
- ];
-
- assert.equal(fakeFilteredTable[0].user, storeGeneratedTable[0].user);
- assert.equal(fakeFilteredTable[1].user, storeGeneratedTable[1].user);
- });
- });
-
- describe('Active Task Stores - Table Header Sort - Select
Ascending/Descending', function () {
-
- it('should set header as ascending, on default', function () {
- activeTasksStore.setSelectedRadio('all-tasks');
- activeTasksStore._headerIsAscending = 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.notOk(activeTasksStore.getHeaderIsAscending());
- });
-
- it('should set header as ascending, if different header is selected',
function () {
- activeTasksStore._sortByHeader = 'differentHeader';
- activeTasksStore._prevSortbyHeader = 'sameHeader';
- activeTasksStore.toggleHeaderIsAscending();
- assert.ok(activeTasksStore.getHeaderIsAscending());
- });
- });
-});
diff --git a/app/addons/activetasks/actions.js
b/app/addons/activetasks/actions.js
index e6213cd44..60d6488a4 100644
--- a/app/addons/activetasks/actions.js
+++ b/app/addons/activetasks/actions.js
@@ -9,72 +9,69 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
under
// the License.
-import FauxtonAPI from "../../core/api";
+import FauxtonAPI from '../../core/api';
import ActionTypes from "./actiontypes";
+import fetchActiveTasks from './api';
-export default {
+export const setActiveTaskIsLoading = (boolean) => {
+ return {
+ type: ActionTypes.ACTIVE_TASKS_SET_IS_LOADING,
+ options: boolean
+ };
+};
- init: function (activeTasks) {
- this.fetchAndSetActiveTasks(activeTasks.table, activeTasks);
- FauxtonAPI.when(activeTasks.fetch()).then(function () {
- this.fetchAndSetActiveTasks(activeTasks.table, activeTasks);
- this.setActiveTaskIsLoading(false);
- }.bind(this));
- },
+export const setActiveTasks = (tasks) => {
+ return {
+ type: ActionTypes.ACTIVE_TASKS_FETCH_AND_SET,
+ options: tasks
+ };
+};
- fetchAndSetActiveTasks: function (collection, backboneCollection) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_FETCH_AND_SET,
- options: {
- collectionTable: collection,
- backboneCollection: backboneCollection
- }
- });
- },
- changePollingInterval: function (interval) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_CHANGE_POLLING_INTERVAL,
- options: interval
- });
- },
- switchTab: function (tab) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_SWITCH_TAB,
- options: tab
- });
- },
- setCollection: function (collection) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_SET_COLLECTION,
- options: collection
- });
- },
- setSearchTerm: function (searchTerm) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_SET_SEARCH_TERM,
- options: searchTerm
- });
- },
- sortByColumnHeader: function (columnName) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER,
- options: {
- columnName: columnName
- }
- });
- },
- setActiveTaskIsLoading: function (boolean) {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_SET_IS_LOADING,
- options: boolean
+export const init = () => (dispatch) => {
+ dispatch(setActiveTaskIsLoading(true));
+ fetchActiveTasks()
+ .then(tasks => {
+ dispatch(setActiveTaskIsLoading(false));
+ dispatch(setActiveTasks(tasks));
+ })
+ .catch(error => {
+ FauxtonAPI.addNotification({
+ msg: `Fetching active tasks failed: ${error}`,
+ type: 'error'
+ });
});
- },
- runPollingUpdate (collection) {
- collection.pollingFetch().then(() => {
- FauxtonAPI.dispatch({
- type: ActionTypes.ACTIVE_TASKS_POLLING_COLLECTION,
- options: collection.table
+};
+
+export const switchTab = (tab) => {
+ return {
+ type: ActionTypes.ACTIVE_TASKS_SWITCH_TAB,
+ options: tab
+ };
+};
+
+export const setSearchTerm = (searchTerm) => {
+ return {
+ type: ActionTypes.ACTIVE_TASKS_SET_SEARCH_TERM,
+ options: searchTerm
+ };
+};
+
+export const sortByColumnHeader = (columnName) => {
+ return {
+ type: ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER,
+ options: columnName
+ };
+};
+
+export const runPollingUpdate = () => (dispatch) => {
+ fetchActiveTasks()
+ .then(tasks => {
+ dispatch(setActiveTasks(tasks));
+ })
+ .catch(error => {
+ FauxtonAPI.addNotification({
+ msg: `Fetching active tasks failed: ${error}`,
+ type: 'error'
});
});
- }
};
diff --git a/app/addons/activetasks/api.js b/app/addons/activetasks/api.js
new file mode 100644
index 000000000..161c34163
--- /dev/null
+++ b/app/addons/activetasks/api.js
@@ -0,0 +1,25 @@
+// 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.
+
+import Helpers from "../../helpers";
+import { get } from '../../core/ajax';
+
+export default () => {
+ return get(Helpers.getServerUrl('/_active_tasks'))
+ .then(tasks => {
+ if (tasks.error) {
+ throw new Error(tasks.reason);
+ }
+ return tasks;
+ });
+};
+
diff --git a/app/addons/activetasks/base.js b/app/addons/activetasks/base.js
index 62a2728e1..f51cf89a4 100644
--- a/app/addons/activetasks/base.js
+++ b/app/addons/activetasks/base.js
@@ -13,9 +13,14 @@
import FauxtonAPI from "../../core/api";
import Activetasks from "./routes";
import "./assets/less/activetasks.less";
+import reducers from './reducers';
Activetasks.initialize = function () {
FauxtonAPI.addHeaderLink({title: 'Active Tasks', icon:
'fonticon-activetasks', href: '#/activetasks'});
};
+FauxtonAPI.addReducers({
+ activetasks: reducers
+});
+
export default Activetasks;
diff --git a/app/addons/activetasks/components.js
b/app/addons/activetasks/components.js
deleted file mode 100644
index fa02ebba0..000000000
--- a/app/addons/activetasks/components.js
+++ /dev/null
@@ -1,481 +0,0 @@
-// 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.
-
-import app from "../../app";
-import React from "react";
-import ReactDOM from "react-dom";
-import Stores from "./stores";
-import Resources from "./resources";
-import Actions from "./actions";
-import Components from "../components/react-components";
-import Helpers from '../../helpers';
-
-const {TabElement, TabElementWrapper, Polling} = Components;
-
-const activeTasksStore = Stores.activeTasksStore;
-
-export class ActiveTasksController extends React.Component {
- getStoreState = () => {
- return {
- collection: activeTasksStore.getCollection(),
- searchTerm: activeTasksStore.getSearchTerm(),
- selectedRadio: activeTasksStore.getSelectedRadio(),
-
- sortByHeader: activeTasksStore.getSortByHeader(),
- headerIsAscending: activeTasksStore.getHeaderIsAscending()
-
- };
- };
-
- componentDidMount() {
- Actions.init(new Resources.AllTasks());
- activeTasksStore.on('change', this.onChange, this);
- }
-
- componentWillUnmount() {
- activeTasksStore.off('change', this.onChange, this);
- }
-
- onChange = () => {
- this.setState(this.getStoreState());
- };
-
- setNewSearchTerm = (searchTerm) => {
- Actions.setSearchTerm(searchTerm);
- };
-
- switchTab = (newRadioButton) => { //tabs buttons
- Actions.switchTab(newRadioButton);
- };
-
- tableHeaderOnClick = (headerClicked) => {
- Actions.sortByColumnHeader(headerClicked);
- };
-
- state = this.getStoreState();
-
- render() {
- const {collection, searchTerm, selectedRadio, sortByHeader,
headerIsAscending} = this.state;
-
- const setSearchTerm = this.setNewSearchTerm;
- const onTableHeaderClick = this.tableHeaderOnClick;
-
- return (
- <div id="active-tasks-page" className="scrollable">
- <div className="inner">
- <ActiveTasksFilterTabs
- searchTerm={searchTerm}
- selectedRadio={selectedRadio}
- onSearch={setSearchTerm}
- onRadioClick={this.switchTab}/>
- <ActiveTaskTable
- collection={collection}
- searchTerm={searchTerm}
- selectedRadio={selectedRadio}
- onTableHeaderClick={onTableHeaderClick}
- sortByHeader={sortByHeader}
- headerIsAscending={headerIsAscending} />
- </div>
- </div>
- );
- }
-}
-
-class ActiveTasksFilterTabs extends React.Component {
- static defaultProps = {
- radioNames : [
- 'All Tasks',
- 'Replication',
- 'Database Compaction',
- 'Indexer',
- 'View Compaction'
- ]
- };
-
- checked = (radioName) => {
- return this.props.selectedRadio === radioName;
- };
-
- onRadioClick = (e) => {
- var radioName = e.target.value;
- this.props.onRadioClick(radioName);
- };
-
- createFilterTabs = () => {
- return (
- this.props.radioNames.map((radioName, i) => {
- const checked = this.checked(radioName);
-
- return (
- <TabElement
- key={i}
- selected={checked}
- text={radioName}
- onChange={this.onRadioClick} />
- );
- })
- );
- };
-
- searchTermChange = (e) => {
- var searchTerm = e.target.value;
- this.props.onSearch(searchTerm);
- };
-
- render() {
- const filterTabs = this.createFilterTabs();
- return (
- <TabElementWrapper>
- {filterTabs}
- <li className="component-tab-list-element">
- <input
- id="active-tasks-search-box"
- className="searchbox"
- type="text"
- name="search"
- placeholder="Search for databases..."
- value={this.props.searchTerm}
- onChange={this.searchTermChange} />
- </li>
- </TabElementWrapper>
- );
- }
-}
-
-class ActiveTaskTable extends React.Component {
- render() {
- 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 id="active-tasks-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>
- );
- }
-}
-
-class ActiveTasksTableHeader extends React.Component {
- static defaultProps = {
- headerNames : [
- ['type', 'Type'],
- ['database', 'Database'],
- ['started-on', 'Started on'],
- ['updated-on', 'Updated on'],
- ['pid', 'PID'],
- ['progress', 'Status']
- ]
- };
-
- createTableHeadingFields = () => {
- 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} />;
- });
- };
-
- render() {
- return (
- <thead>
- <tr>{this.createTableHeadingFields()}</tr>
- </thead>
- );
- }
-}
-
-class TableHeader extends React.Component {
- arrow = () => {
- 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>;
- }
- };
-
- onTableHeaderClick = (e) => {
- var headerSelected = e.target.value;
- this.props.onTableHeaderClick(headerSelected);
- };
-
- render() {
- var arrow = this.arrow();
- var th_class = 'header-field ' + this.props.headerName;
-
- return (
- <td className={th_class + " tableheader"} value={this.props.headerName}>
- <input
- type="radio"
- name="header-field"
- id={this.props.headerName}
- value={this.props.headerName}
- className="header-field radio"
- onChange={this.onTableHeaderClick} />
- <label
- className="header-field label-text active-tasks-header noselect"
- htmlFor={this.props.headerName}>
- {this.props.displayName} {arrow}
- </label>
- </td>
- );
- }
-}
-
-class ActiveTasksTableBody extends React.Component {
- getStoreState = () => {
- return {
- filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
- };
- };
-
- UNSAFE_componentWillReceiveProps() {
- this.setState({
- filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
- });
- }
-
- createRows = () => {
- var isThereASearchTerm = this.props.searchTerm.trim() === "";
-
- if (this.state.filteredTable.length === 0) {
- return isThereASearchTerm ? this.noActiveTasks() :
this.noActiveTasksMatchFilter();
- }
-
- return _.map(this.state.filteredTable, (item, key) => {
- return <ActiveTaskTableBodyContents key={key} item={item} />;
- });
- };
-
- noActiveTasks = () => {
- var type = this.props.selectedRadio;
- if (type === "All Tasks") {
- type = "";
- }
-
- return (
- <tr className="no-matching-database-on-search">
- <td colSpan="6">No active {type} tasks.</td>
- </tr>
- );
- };
-
- noActiveTasksMatchFilter = () => {
- var type = this.props.selectedRadio;
- if (type === "All Tasks") {
- type = "";
- }
-
- return (
- <tr className="no-matching-database-on-search">
- <td colSpan="6">No active {type} tasks match with filter:
"{this.props.searchTerm}"</td>
- </tr>
- );
- };
-
- state = this.getStoreState();
-
- render() {
- return (
- <tbody className="js-tasks-go-here">
- {this.createRows()}
- </tbody>
- );
- }
-}
-
-class ActiveTaskTableBodyContents extends React.Component {
- getInfo = (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 = (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() {
- var rowData = this.getInfo(this.props.item);
- var objectFieldMsg = this.multilineMessage(rowData.objectField,
'to-from-database');
- 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>
- );
- }
-}
-
-export class ActiveTasksPollingWidgetController extends React.Component {
- getStoreState = () => {
- return {
- collection: activeTasksStore.getBackboneCollection()
- };
- };
-
- componentDidMount() {
- activeTasksStore.on('change', this.onChange, this);
- }
-
- componentWillUnmount() {
- activeTasksStore.off('change', this.onChange, this);
- }
-
- onChange = () => {
- this.setState(this.getStoreState());
- };
-
- runPollingUpdate = () => {
- Actions.runPollingUpdate(this.state.collection);
- };
-
- getPluralForLabel = () => {
- return this.state.pollingInterval === "1" ? '' : 's';
- };
-
- state = this.getStoreState();
-
- render() {
- let activePollingClass = "active-tasks__polling-wrapper";
- if (Helpers.isIE1X()) {
- activePollingClass += " " + activePollingClass + "--ie1X";
- }
- return (
- <div className={activePollingClass}>
- <Polling
- min={1}
- max={30}
- stepSize={1}
- startValue={15}
- valueUnits={"second"}
- onPoll={this.runPollingUpdate}
- />
- </div>
- );
- }
-}
-
-var activeTasksHelpers = {
- getTimeInfo (timeStamp) {
- var timeMessage = [
- app.helpers.formatDate(timeStamp),
- app.helpers.getDateFromNow(timeStamp)
- ];
- return timeMessage;
- },
-
- getDatabaseFieldMessage (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 (item) {
- var progressMessage = [];
- var type = item.type;
-
- if (_.has(item, '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 (_.has(item, 'changes_pending')) {
- progressMessage.push(item.changes_pending + ' pending changes.');
- }
- }
-
- if (_.has(item, 'changes_done')) {
- progressMessage.push(item.changes_done + ' Changes done.');
- }
-
- return progressMessage;
- },
-
- getSourceSequence (item) {
- return item.source_seq;
- }
-
-};
-
-export default {
- ActiveTasksController: ActiveTasksController,
- ActiveTasksFilterTabs: ActiveTasksFilterTabs,
-
- ActiveTaskTable: ActiveTaskTable,
- ActiveTasksTableHeader: ActiveTasksTableHeader,
- TableHeader: TableHeader,
- ActiveTasksTableBody: ActiveTasksTableBody,
- ActiveTaskTableBodyContents: ActiveTaskTableBodyContents,
-
- ActiveTasksPollingWidgetController: ActiveTasksPollingWidgetController
-};
diff --git a/app/addons/activetasks/components/controller.js
b/app/addons/activetasks/components/controller.js
new file mode 100644
index 000000000..ec8a84140
--- /dev/null
+++ b/app/addons/activetasks/components/controller.js
@@ -0,0 +1,59 @@
+// 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.
+import React from 'react';
+import ActiveTasksFilterTabs from './filtertabs';
+import ActiveTaskTable from './table';
+
+export default class ActiveTasksController extends React.Component {
+ componentDidMount() {
+ this.props.init();
+ }
+
+ setNewSearchTerm = (searchTerm) => {
+ this.props.setSearchTerm(searchTerm);
+ };
+
+ switchTab = (newRadioButton) => { //tabs buttons
+ this.props.switchTab(newRadioButton);
+ };
+
+ tableHeaderOnClick = (headerClicked) => {
+ this.props.sortByColumnHeader(headerClicked);
+ };
+
+ render() {
+ const {isLoading, tasks, searchTerm, selectedRadio, sortByHeader,
headerIsAscending} = this.props;
+
+ const setSearchTerm = this.setNewSearchTerm;
+ const onTableHeaderClick = this.tableHeaderOnClick;
+
+ return (
+ <div id="active-tasks-page" className="scrollable">
+ <div className="inner">
+ <ActiveTasksFilterTabs
+ searchTerm={searchTerm}
+ selectedRadio={selectedRadio}
+ onSearch={setSearchTerm}
+ onRadioClick={this.switchTab}/>
+ <ActiveTaskTable
+ isLoading={isLoading}
+ tasks={tasks}
+ searchTerm={searchTerm}
+ selectedRadio={selectedRadio}
+ onTableHeaderClick={onTableHeaderClick}
+ sortByHeader={sortByHeader}
+ headerIsAscending={headerIsAscending} />
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/app/addons/activetasks/components/filtertabs.js
b/app/addons/activetasks/components/filtertabs.js
new file mode 100644
index 000000000..8d5410c75
--- /dev/null
+++ b/app/addons/activetasks/components/filtertabs.js
@@ -0,0 +1,75 @@
+// 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.
+import React from 'react';
+import Components from "../../components/react-components";
+const {TabElement, TabElementWrapper} = Components;
+
+export default class ActiveTasksFilterTabs extends React.Component {
+ static defaultProps = {
+ radioNames : [
+ 'All Tasks',
+ 'Replication',
+ 'Database Compaction',
+ 'Indexer',
+ 'View Compaction'
+ ]
+ };
+
+ checked = (radioName) => {
+ return this.props.selectedRadio === radioName;
+ };
+
+ onRadioClick = (e) => {
+ var radioName = e.target.value;
+ this.props.onRadioClick(radioName);
+ };
+
+ createFilterTabs = () => {
+ return (
+ this.props.radioNames.map((radioName, i) => {
+ const checked = this.checked(radioName);
+
+ return (
+ <TabElement
+ key={i}
+ selected={checked}
+ text={radioName}
+ onChange={this.onRadioClick} />
+ );
+ })
+ );
+ };
+
+ searchTermChange = (e) => {
+ var searchTerm = e.target.value;
+ this.props.onSearch(searchTerm);
+ };
+
+ render() {
+ const filterTabs = this.createFilterTabs();
+ return (
+ <TabElementWrapper>
+ {filterTabs}
+ <li className="component-tab-list-element">
+ <input
+ id="active-tasks-search-box"
+ className="searchbox"
+ type="text"
+ name="search"
+ placeholder="Search for databases..."
+ value={this.props.searchTerm}
+ onChange={this.searchTermChange} />
+ </li>
+ </TabElementWrapper>
+ );
+ }
+}
diff --git a/app/addons/activetasks/components/polling.js
b/app/addons/activetasks/components/polling.js
new file mode 100644
index 000000000..6e3d52e9b
--- /dev/null
+++ b/app/addons/activetasks/components/polling.js
@@ -0,0 +1,48 @@
+// 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.
+
+import React from 'react';
+import Helpers from '../../../helpers';
+import Components from "../../components/react-components";
+const { Polling } = Components;
+
+export default class ActiveTasksPollingWidgetController extends
React.Component {
+
+ runPollingUpdate = () => {
+ this.props.runPollingUpdate();
+ };
+
+ getPluralForLabel = () => {
+ return this.state.pollingInterval === "1" ? '' : 's';
+ };
+
+
+ render() {
+ let activePollingClass = "active-tasks__polling-wrapper";
+ if (Helpers.isIE1X()) {
+ activePollingClass += " " + activePollingClass + "--ie1X";
+ }
+ return (
+ <div className={activePollingClass}>
+ <Polling
+ min={1}
+ max={30}
+ stepSize={1}
+ startValue={15}
+ valueUnits={"second"}
+ onPoll={this.runPollingUpdate}
+ />
+ </div>
+ );
+ }
+}
+
diff --git a/app/addons/activetasks/components/table.js
b/app/addons/activetasks/components/table.js
new file mode 100644
index 000000000..b00609020
--- /dev/null
+++ b/app/addons/activetasks/components/table.js
@@ -0,0 +1,44 @@
+// 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.
+import React from 'react';
+import ActiveTasksTableBody from './tablebody';
+import ActiveTasksTableHeader from './tableheader';
+
+export default class ActiveTaskTable extends React.Component {
+ render() {
+ const {
+ tasks,
+ selectedRadio,
+ searchTerm,
+ sortByHeader,
+ onTableHeaderClick,
+ headerIsAscending,
+ isLoading
+ } = this.props;
+
+ return (
+ <div id="dashboard-lower-content">
+ <table id="active-tasks-table" className="table table-bordered
table-striped active-tasks">
+ <ActiveTasksTableHeader
+ onTableHeaderClick={onTableHeaderClick}
+ sortByHeader={sortByHeader}
+ headerIsAscending={headerIsAscending}/>
+ <ActiveTasksTableBody
+ tasks={tasks}
+ selectedRadio={selectedRadio}
+ isLoading={isLoading}
+ searchTerm={searchTerm}/>
+ </table>
+ </div>
+ );
+ }
+}
diff --git a/app/addons/activetasks/components/tablebody.js
b/app/addons/activetasks/components/tablebody.js
new file mode 100644
index 000000000..bb738807a
--- /dev/null
+++ b/app/addons/activetasks/components/tablebody.js
@@ -0,0 +1,70 @@
+// 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.
+
+import React from 'react';
+import ActiveTaskTableBodyContents from './tablebodycontents';
+
+export default class ActiveTasksTableBody extends React.Component {
+ createRows = () => {
+ var isThereASearchTerm = this.props.searchTerm.trim() === "";
+
+ if (this.props.tasks.length === 0 || this.props.isLoading) {
+ return isThereASearchTerm ? this.noActiveTasks() :
this.noActiveTasksMatchFilter();
+ }
+
+ return this.props.tasks.map((item, key) => {
+ return <ActiveTaskTableBodyContents key={key} item={item} />;
+ });
+ };
+
+ getType = () => {
+ const type = this.props.selectedRadio;
+ if (type === "All Tasks") {
+ return "";
+ }
+
+ return type;
+ }
+
+ noActiveTasks = () => {
+ const type = this.getType();
+ let msg = <td colSpan="6">No active {type} tasks.</td>;
+
+ if (this.props.isLoading) {
+ msg = <td colSpan="6">Loading tasks.</td>;
+ }
+
+ return (
+ <tr className="no-matching-database-on-search">
+ {msg}
+ </tr>
+ );
+ };
+
+ noActiveTasksMatchFilter = () => {
+ const type = this.getType();
+
+ return (
+ <tr className="no-matching-database-on-search">
+ <td colSpan="6">No active {type} tasks match with filter:
"{this.props.searchTerm}"</td>
+ </tr>
+ );
+ };
+
+ render() {
+ return (
+ <tbody className="js-tasks-go-here">
+ {this.createRows()}
+ </tbody>
+ );
+ }
+}
diff --git a/app/addons/activetasks/components/tablebodycontents.js
b/app/addons/activetasks/components/tablebodycontents.js
new file mode 100644
index 000000000..e8c6e2b5d
--- /dev/null
+++ b/app/addons/activetasks/components/tablebodycontents.js
@@ -0,0 +1,117 @@
+// 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.
+
+import React from 'react';
+import app from '../../../app';
+
+const activeTasksHelpers = {
+ getTimeInfo (timeStamp) {
+ var timeMessage = [
+ app.helpers.formatDate(timeStamp),
+ app.helpers.getDateFromNow(timeStamp)
+ ];
+ return timeMessage;
+ },
+
+ getDatabaseFieldMessage (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 (item) {
+ var progressMessage = [];
+ var type = item.type;
+
+ if (_.has(item, '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 (_.has(item, 'changes_pending')) {
+ progressMessage.push(item.changes_pending + ' pending changes.');
+ }
+ }
+
+ if (_.has(item, 'changes_done')) {
+ progressMessage.push(item.changes_done + ' Changes done.');
+ }
+
+ return progressMessage;
+ },
+
+ getSourceSequence (item) {
+ return item.source_seq;
+ }
+
+};
+
+export default class ActiveTaskTableBodyContents extends React.Component {
+ getInfo = (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 = (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() {
+ var rowData = this.getInfo(this.props.item);
+ var objectFieldMsg = this.multilineMessage(rowData.objectField,
'to-from-database');
+ 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>
+ );
+ }
+}
diff --git a/app/addons/activetasks/components/tableheader.js
b/app/addons/activetasks/components/tableheader.js
new file mode 100644
index 000000000..2b85cc520
--- /dev/null
+++ b/app/addons/activetasks/components/tableheader.js
@@ -0,0 +1,86 @@
+// 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.
+
+import React from 'react';
+
+export class TableHeader extends React.Component {
+
+ constructor (props) {
+ super(props);
+ this.onTableHeaderClick = this.onTableHeaderClick.bind(this);
+ }
+
+
+ arrow = () => {
+ const sortBy = this.props.sortByHeader;
+ const currentName = this.props.headerName;
+ const headerIsAscending = this.props.headerIsAscending;
+ const arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon
icon-caret-down';
+
+ if (sortBy === currentName) {
+ return <i className={arrow}></i>;
+ }
+ };
+
+ onTableHeaderClick = () => {
+ this.props.onTableHeaderClick(this.props.headerName);
+ };
+
+ render() {
+ return (
+ <td className={`header-field ${this.props.headerName} tableheader`}
onClick={this.onTableHeaderClick}>
+ <label className="header-field label-text active-tasks-header
noselect">
+ {this.props.displayName} {this.arrow()}
+ </label>
+ </td>
+ );
+ }
+}
+
+export default class ActiveTasksTableHeader extends React.Component {
+ static defaultProps = {
+ headerNames : [
+ ['type', 'Type'],
+ ['database', 'Database'],
+ ['started-on', 'Started on'],
+ ['updated-on', 'Updated on'],
+ ['pid', 'PID'],
+ ['progress', 'Status']
+ ]
+ };
+
+ createTableHeadingFields = () => {
+ const {
+ onTableHeaderClick,
+ sortByHeader,
+ headerIsAscending
+ } = this.props;
+
+ 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() {
+ return (
+ <thead>
+ <tr>{this.createTableHeadingFields()}</tr>
+ </thead>
+ );
+ }
+}
diff --git a/app/addons/activetasks/container.js
b/app/addons/activetasks/container.js
new file mode 100644
index 000000000..5e2f1263d
--- /dev/null
+++ b/app/addons/activetasks/container.js
@@ -0,0 +1,57 @@
+// 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.
+
+import { connect } from 'react-redux';
+import Controller from './layout';
+import {
+ init,
+ changePollingInterval,
+ setSearchTerm,
+ sortByColumnHeader,
+ switchTab,
+ runPollingUpdate
+} from './actions';
+import {
+ getTasks,
+ getHeaderIsAscending,
+ getSelectedRadio,
+ getSortByHeader,
+ getSearchTerm,
+ getIsLoading
+} from './reducers';
+
+const mapStateToProps = ({activetasks}) => {
+ return {
+ tasks: getTasks(activetasks),
+ headerIsAscending: getHeaderIsAscending(activetasks),
+ selectedRadio: getSelectedRadio(activetasks),
+ sortByHeader: getSortByHeader(activetasks),
+ searchTerm: getSearchTerm(activetasks),
+ isLoading: getIsLoading(activetasks)
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ init: () => dispatch(init()),
+ changePollingInterval: (interval) =>
dispatch(changePollingInterval(interval)),
+ setSearchTerm: (term) => dispatch(setSearchTerm(term)),
+ sortByColumnHeader: (column) => dispatch(sortByColumnHeader(column)),
+ switchTab: (tab) => dispatch(switchTab(tab)),
+ runPollingUpdate: () => dispatch(runPollingUpdate())
+ };
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Controller);
diff --git a/app/addons/activetasks/layout.js b/app/addons/activetasks/layout.js
index b6b88d203..e126237e4 100644
--- a/app/addons/activetasks/layout.js
+++ b/app/addons/activetasks/layout.js
@@ -14,13 +14,14 @@ import React from 'react';
import Helpers from "../../helpers";
import FauxtonAPI from "../../core/api";
import {OnePane, OnePaneHeader, OnePaneContent} from '../components/layouts';
-import {ActiveTasksController, ActiveTasksPollingWidgetController} from
"./components";
+import ActiveTasksController from "./components/controller";
+import ActiveTasksPollingWidgetController from './components/polling';
const crumbs = [
{'name': 'Active Tasks'}
];
-export const ActiveTasksLayout = () => {
+export const ActiveTasksLayout = (props) => {
return (
<OnePane>
<OnePaneHeader
@@ -28,10 +29,10 @@ export const ActiveTasksLayout = () => {
docURL={FauxtonAPI.constants.DOC_URLS.ACTIVE_TASKS}
endpoint={Helpers.getApiUrl('/_active_tasks')}
>
- <ActiveTasksPollingWidgetController />
+ <ActiveTasksPollingWidgetController {...props} />
</OnePaneHeader>
<OnePaneContent>
- <ActiveTasksController />
+ <ActiveTasksController {...props} />
</OnePaneContent>
</OnePane>
);
diff --git a/app/addons/activetasks/reducers.js
b/app/addons/activetasks/reducers.js
new file mode 100644
index 000000000..bba4bf48f
--- /dev/null
+++ b/app/addons/activetasks/reducers.js
@@ -0,0 +1,141 @@
+// 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.
+import ActionTypes from "./actiontypes";
+import { has, sortBy, isUndefined } from 'lodash';
+
+const initialState = {
+ isLoading: false,
+ sortByHeader: 'started-on',
+ headerIsAscending: true,
+ selectedRadio: 'All Tasks',
+ searchTerm: '',
+ tasks: [],
+ filteredTasks: []
+};
+
+const sortTasksByColumnHeader = (colName, tasks, headerIsAscending) => {
+ var sorted = sortBy(tasks, (item) => {
+ var variable = colName;
+
+ if (isUndefined(item[variable])) {
+ variable = 'source';
+ }
+ return item[variable];
+ });
+
+ if (!headerIsAscending) {
+ return sorted.reverse();
+ }
+
+ return sorted;
+};
+
+
+const setSearchTerm = (state, searchTerm) => {
+ const filteredTasks = filterTasks(searchTerm, state.selectedRadio,
state.sortbyHeader, state.tasks, state.headerIsAscending);
+ return {
+ ...state,
+ filteredTasks,
+ searchTerm
+ };
+};
+
+const passesSearchFilter = (item, searchTerm) => {
+ const regex = new RegExp(searchTerm, 'g');
+ let itemDatabasesTerm = '';
+ if (has(item, 'database')) {
+ itemDatabasesTerm += item.database;
+ }
+ if (has(item, 'source')) {
+ itemDatabasesTerm += item.source;
+ }
+ if (has(item, 'target')) {
+ itemDatabasesTerm += item.target;
+ }
+
+ return regex.test(itemDatabasesTerm);
+};
+
+const passesRadioFilter = (item, selectedRadio) => {
+ const fixedSelectedRadio = selectedRadio.toLowerCase().replace(' ', '_');
+ return item.type === fixedSelectedRadio || fixedSelectedRadio ===
'all_tasks';
+};
+
+const filterTasks = (searchTerm, selectedRadio, sortByHeader, tasks,
headerIsAscending) => {
+ const filtered = tasks.filter(task => {
+ return passesRadioFilter(task, selectedRadio) && passesSearchFilter(task,
searchTerm);
+ });
+
+ return sortTasksByColumnHeader(sortByHeader, filtered, headerIsAscending);
+};
+
+const setHeaderIsAscending = (prevSortbyHeader, sortByHeader,
headerIsAscending) => {
+ if (prevSortbyHeader === sortByHeader) {
+ return !headerIsAscending;
+ }
+
+ return true;
+};
+
+export default (state = initialState, {type, options}) => {
+ switch (type) {
+ case ActionTypes.ACTIVE_TASKS_FETCH_AND_SET:
+ return {
+ ...state,
+ tasks: options,
+ filteredTasks: filterTasks(state.searchTerm, state.selectedRadio,
state.sortByHeader, options, state.headerIsAscending)
+ };
+
+ case ActionTypes.ACTIVE_TASKS_SWITCH_TAB:
+ const filteredTasks = filterTasks(state.searchTerm, options,
state.sortByHeader, state.tasks, headerIsAscending);
+ return {
+ ...state,
+ selectedRadio: options,
+ filteredTasks
+ };
+
+ case ActionTypes.ACTIVE_TASKS_CHANGE_POLLING_INTERVAL:
+ return {
+ ...state,
+ pollingIntervalSeconds: options
+ };
+
+ case ActionTypes.ACTIVE_TASKS_SET_SEARCH_TERM:
+ return setSearchTerm(state, options);
+
+ case ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER:
+ const prevSortbyHeader = state.sortByHeader;
+ const sortByHeader = options;
+ const headerIsAscending = setHeaderIsAscending(prevSortbyHeader,
sortByHeader, state.headerIsAscending);
+ const headerFilteredTasks = filterTasks(state.searchTerm,
state.selectedRadio, sortByHeader, state.tasks, headerIsAscending);
+ return {
+ ...state,
+ sortByHeader,
+ headerIsAscending,
+ filteredTasks: headerFilteredTasks
+ };
+
+ case ActionTypes.ACTIVE_TASKS_SET_IS_LOADING:
+ return {
+ ...state,
+ isLoading: options
+ };
+ }
+ return state;
+};
+
+export const getTasks = (state) => state.filteredTasks;
+export const getHeaderIsAscending = (state) => state.headerIsAscending;
+export const getIsLoading = (state) => state.isLoading;
+export const getSelectedRadio = (state) => state.selectedRadio;
+export const getSortByHeader = (state) => state.sortByHeader;
+export const getSearchTerm = (state) => state.searchTerm;
diff --git a/app/addons/activetasks/resources.js
b/app/addons/activetasks/resources.js
deleted file mode 100644
index 13ca5459b..000000000
--- a/app/addons/activetasks/resources.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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.
-
-import Helpers from "../../helpers";
-import Actions from "./actions";
-var Active = {};
-
-Active.AllTasks = Backbone.Collection.extend({
-
- url: function () {
- return Helpers.getServerUrl('/_active_tasks');
- },
-
- pollingFetch: function () { //still need this for the polling
- Actions.setActiveTaskIsLoading(true);
- return this.fetch({reset: true, parse: true});
- },
-
- parse: function (resp) {
- //no more backbone models, collection is converted into an array of objects
- Actions.setActiveTaskIsLoading(false);
- var collectionTable = [];
-
- _.each(resp, (item) => {
- collectionTable.push(item);
- });
-
- //collection is an array of objects
- this.table = collectionTable;
- return resp;
- },
-
- table: []
-
-});
-
-export default Active;
diff --git a/app/addons/activetasks/routes.js b/app/addons/activetasks/routes.js
index e45cc9b5d..2d51fc451 100644
--- a/app/addons/activetasks/routes.js
+++ b/app/addons/activetasks/routes.js
@@ -12,8 +12,7 @@
import React from 'react';
import FauxtonAPI from "../../core/api";
-import ActiveTasksResources from "./resources";
-import Layout from './layout';
+import Layout from './container';
var ActiveTasksRouteObject = FauxtonAPI.RouteObject.extend({
selectedHeader: 'Active Tasks',
@@ -30,6 +29,6 @@ var ActiveTasksRouteObject = FauxtonAPI.RouteObject.extend({
}
});
-ActiveTasksResources.RouteObjects = [ActiveTasksRouteObject];
-
-export default ActiveTasksResources;
+export default {
+ RouteObjects: [ActiveTasksRouteObject]
+};
diff --git a/app/addons/activetasks/stores.js b/app/addons/activetasks/stores.js
deleted file mode 100644
index 656ddc8ef..000000000
--- a/app/addons/activetasks/stores.js
+++ /dev/null
@@ -1,223 +0,0 @@
-// 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.
-
-import FauxtonAPI from "../../core/api";
-import ActionTypes from "./actiontypes";
-
-var ActiveTasksStore = FauxtonAPI.Store.extend({
- initialize () {
- this._prevSortbyHeader = 'started-on';
- this._headerIsAscending = true;
- this._selectedRadio = 'All Tasks';
- this._sortByHeader = 'started-on';
- this._searchTerm = '';
- this._pollingIntervalSeconds = 5;
-
- },
-
- initAfterFetching (collectionTable, backboneCollection) {
- this._collection = collectionTable;
- this.sortCollectionByColumnHeader(this._sortByHeader);
- this._backboneCollection = backboneCollection;
- this.setIsLoading(true, new Date());
- },
-
- isLoading () {
- return this._isLoading;
- },
-
- setIsLoading (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 () {
- return this._selectedRadio;
- },
-
- setSelectedRadio (selectedRadio) {
- this._selectedRadio = selectedRadio;
- },
-
- setCollectionFromPolling (collection) {
- this.setCollection(collection);
- this.sortCollectionByColumnHeader(this._prevSortbyHeader, false);
- },
-
- setCollection (collection) {
- this._collection = collection;
- },
-
- getCollection () {
- return this._collection;
- },
-
- getBackboneCollection () {
- return this._backboneCollection;
- },
-
- setSearchTerm (searchTerm) {
- this._searchTerm = searchTerm;
- },
-
- getSearchTerm () {
- return this._searchTerm;
- },
-
- getSortByHeader () {
- return this._sortByHeader;
- },
-
- setSortByHeader (header) {
- this._sortByHeader = header;
- },
-
- getHeaderIsAscending () {
- return this._headerIsAscending;
- },
-
- toggleHeaderIsAscending () {
- if (this._prevSortbyHeader === this._sortByHeader) {
- this._headerIsAscending = !this._headerIsAscending;
- return;
- }
-
- this._headerIsAscending = true;
- },
-
- sortCollectionByColumnHeader (colName) {
- var collectionTable = this._collection;
-
- var sorted = _.sortBy(collectionTable, (item) => {
- var variable = colName;
-
- if (_.isUndefined(item[variable])) {
- variable = 'source';
- }
- return item[variable];
- });
-
- this._prevSortbyHeader = colName;
- this._collection = sorted;
- },
-
- getFilteredTable () {
- var table = [];
-
- //sort the table here
- this.sortCollectionByColumnHeader(this._sortByHeader);
-
- //insert all matches into table
- this._collection.map(function (item) {
- var passesRadioFilter = this.passesRadioFilter(item);
- var passesSearchFilter = this.passesSearchFilter(item);
-
- if (passesRadioFilter && passesSearchFilter) {
- table.push(item);
- }
- }.bind(this));
-
- // reverse if descending
- if (!this._headerIsAscending) {
- table.reverse();
- }
-
- return table;
- },
-
- passesSearchFilter (item) {
- var searchTerm = this._searchTerm;
- var regex = new RegExp(searchTerm, 'g');
-
- var itemDatabasesTerm = '';
- if (_.has(item, 'database')) {
- itemDatabasesTerm += item.database;
- }
- if (_.has(item, 'source')) {
- itemDatabasesTerm += item.source;
- }
- if (_.has(item, 'target')) {
- itemDatabasesTerm += item.target;
- }
-
- return regex.test(itemDatabasesTerm);
- },
-
- passesRadioFilter (item) {
- var selectedRadio = this._selectedRadio.toLowerCase().replace(' ', '_');
- return item.type === selectedRadio || selectedRadio === 'all_tasks';
- },
-
- dispatch (action) {
- switch (action.type) {
-
- case ActionTypes.ACTIVE_TASKS_FETCH_AND_SET:
- this.initAfterFetching(action.options.collectionTable,
action.options.backboneCollection);
- break;
-
- case ActionTypes.ACTIVE_TASKS_SWITCH_TAB:
- this.setSelectedRadio(action.options);
- this.triggerChange();
- break;
-
- case ActionTypes.ACTIVE_TASKS_SET_COLLECTION:
- this.setCollection(action.options);
- this.triggerChange();
- break;
-
- case ActionTypes.ACTIVE_TASKS_SET_SEARCH_TERM:
- this.setSearchTerm(action.options);
- this.triggerChange();
- break;
-
- case ActionTypes.ACTIVE_TASKS_SORT_BY_COLUMN_HEADER:
- this.toggleHeaderIsAscending();
- this.setSortByHeader(action.options.columnName);
- this.sortCollectionByColumnHeader(action.options.columnName);
- this.triggerChange();
- break;
-
- case ActionTypes.ACTIVE_TASKS_SET_IS_LOADING:
- this.setIsLoading(action.options, new Date());
- this.triggerChange();
- break;
-
- case ActionTypes.ACTIVE_TASKS_POLLING_COLLECTION:
- this.setCollectionFromPolling(action.options);
- this.triggerChange();
- break;
-
- default:
- return;
- }
- }
-});
-
-var activeTasksStore = new ActiveTasksStore();
-activeTasksStore.dispatchToken =
FauxtonAPI.dispatcher.register(activeTasksStore.dispatch.bind(activeTasksStore));
-
-export default {
- activeTasksStore: activeTasksStore
-};
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services