You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by am...@apache.org on 2023/03/13 15:06:47 UTC

[couchdb-fauxton] branch main updated: Control amount of replications displayed (#1391)

This is an automated email from the ASF dual-hosted git repository.

amaranhao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/main by this push:
     new 034f5aaf Control amount of replications displayed (#1391)
034f5aaf is described below

commit 034f5aafcfd35757ab2781f8a97d8cb824b8da0c
Author: Margaret Harrigan <39...@users.noreply.github.com>
AuthorDate: Mon Mar 13 08:06:40 2023 -0700

    Control amount of replications displayed (#1391)
---
 app/addons/replication/__tests__/actions.test.js   | 38 +++++++++--
 app/addons/replication/__tests__/api.tests.js      | 10 +--
 .../__tests__/replication-footer.test.js           | 63 ++++++++++++++++++
 app/addons/replication/actions.js                  | 25 +++++---
 app/addons/replication/actiontypes.js              |  3 +-
 app/addons/replication/api.js                      | 14 ++--
 .../replication/assets/less/replication.less       |  4 +-
 app/addons/replication/components/activity.js      |  1 -
 .../replication/components/replication-footer.js   | 52 +++++++++++++++
 app/addons/replication/container.js                | 20 +++---
 app/addons/replication/controller.js               | 23 ++++++-
 app/addons/replication/reducers.js                 | 11 +++-
 .../tests/nightwatch/replicationactivity.js        | 75 ++++++++++++++++++++++
 assets/less/fauxton.less                           |  3 +-
 14 files changed, 301 insertions(+), 41 deletions(-)

diff --git a/app/addons/replication/__tests__/actions.test.js b/app/addons/replication/__tests__/actions.test.js
index b9cfa64f..50eaef10 100644
--- a/app/addons/replication/__tests__/actions.test.js
+++ b/app/addons/replication/__tests__/actions.test.js
@@ -10,7 +10,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import {replicate, getReplicationStateFrom, deleteDocs} from '../actions';
+import {replicate, getReplicationStateFrom, deleteDocs, setPageLimit} from '../actions';
 import ActionTypes from '../actiontypes';
 import fetchMock from 'fetch-mock';
 import FauxtonAPI from '../../../core/api';
@@ -33,6 +33,7 @@ describe("Replication Actions", () => {
 
     it('creates a new database if it does not exist', () => {
       const dispatch = () => {};
+      const pageLimit = 20;
       fetchMock.postOnce('./_replicator', {
         status: 404,
         body: {
@@ -66,7 +67,8 @@ describe("Replication Actions", () => {
         replicationTarget: "REPLICATION_TARGET_NEW_LOCAL_DATABASE",
         replicationType: "",
         username: "tester"
-      })(dispatch).then(() => {
+      }, pageLimit)(dispatch).then(() => {
+        finalPost.calls('./_replicator');
         expect(finalPost.calls('./_replicator').length).toBe(3);
 
         //fetchMock.done();
@@ -75,6 +77,7 @@ describe("Replication Actions", () => {
 
     it('does not try to create new database if it already exist', () => {
       const dispatch = () => {};
+      const pageLimit = 20;
       const mockPost = fetchMock.postOnce('./_replicator', {
         status: 200,
         body: {
@@ -93,7 +96,8 @@ describe("Replication Actions", () => {
         replicationTarget: "REPLICATION_TARGET_NEW_LOCAL_DATABASE",
         replicationType: "",
         username: "tester"
-      })(dispatch).then(() => {
+      }, pageLimit)(dispatch).then(() => {
+        mockPost.calls('./_replicator');
         expect(mockPost.calls('./_replicator').length).toBe(1);
         fetchMock.done();
       });
@@ -255,22 +259,44 @@ describe("Replication Actions", () => {
           }
         }
       ];
+      const pageLimit = 20;
 
       fetchMock.getOnce('./_scheduler/jobs', 404);
-      fetchMock.getOnce('./_replicator/_all_docs?include_docs=true&limit=100', {rows: []});
+      fetchMock.getOnce(`./_replicator/_all_docs?include_docs=true&limit=${pageLimit + 1}`, {rows: []});
       fetchMock.postOnce('./_replicator/_bulk_docs', {
         status: 200,
         body: resp
       });
 
-
       const dispatch = ({type}) => {
         if (ActionTypes.REPLICATION_CLEAR_SELECTED_DOCS === type) {
           done();
         }
       };
 
-      deleteDocs(docs)(dispatch);
+      deleteDocs(docs, pageLimit)(dispatch);
+    });
+  });
+
+  describe('setPageLimit', () => {
+    afterEach(() => {
+      fetchMock.reset();
+    });
+
+    it('sends request for new replications list', (done) => {
+      const pageLimit = 20;
+
+      fetchMock.getOnce('./_scheduler/jobs', 404);
+      fetchMock.getOnce(`./_replicator/_all_docs?include_docs=true&limit=${pageLimit + 1}`, {rows: []});
+
+      const dispatch = ({type, options}) => {
+        if (ActionTypes.REPLICATION_SET_PAGE_LIMIT === type) {
+          expect(options).toEqual(pageLimit);
+          done();
+        }
+      };
+
+      setPageLimit(pageLimit)(dispatch);
     });
   });
 });
diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js
index ff2db57e..537424ca 100644
--- a/app/addons/replication/__tests__/api.tests.js
+++ b/app/addons/replication/__tests__/api.tests.js
@@ -577,10 +577,11 @@ describe('Replication API', () => {
       });
 
       it("returns parsedReplicationDocs and ignores all design docs", () => {
+        const pageLimit = 20;
         fetchMock.getOnce('./_scheduler/jobs', 404);
-        fetchMock.get('./_replicator/_all_docs?include_docs=true&limit=100', _repDocs);
+        fetchMock.get(`./_replicator/_all_docs?include_docs=true&limit=${pageLimit + 1}`, _repDocs);
         return supportNewApi(true)
-          .then(fetchReplicationDocs)
+          .then(() => fetchReplicationDocs(pageLimit))
           .then(docs => {
             expect(docs.length).toBe(1);
             expect(docs[0]._id).toBe("c94d4839d1897105cb75e1251e0003ea");
@@ -594,11 +595,12 @@ describe('Replication API', () => {
       });
 
       it("returns parsedReplicationDocs", () => {
+        const pageLimit = 20;
         fetchMock.getOnce('./_scheduler/jobs', 200);
-        fetchMock.get('./_replicator/_all_docs?include_docs=true&limit=100', _repDocs);
+        fetchMock.get(`./_replicator/_all_docs?include_docs=true&limit=${pageLimit + 1}`, _repDocs);
         fetchMock.get('./_scheduler/docs?include_docs=true', _schedDocs);
         return supportNewApi(true)
-          .then(fetchReplicationDocs)
+          .then(() => fetchReplicationDocs(pageLimit))
           .then(docs => {
             expect(docs.length).toBe(1);
             expect(docs[0]._id).toBe("c94d4839d1897105cb75e1251e0003ea");
diff --git a/app/addons/replication/__tests__/replication-footer.test.js b/app/addons/replication/__tests__/replication-footer.test.js
new file mode 100644
index 00000000..7a3931d4
--- /dev/null
+++ b/app/addons/replication/__tests__/replication-footer.test.js
@@ -0,0 +1,63 @@
+// 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 { mount } from 'enzyme';
+import sinon from 'sinon';
+import { ReplicationFooter } from '../components/replication-footer';
+
+describe('Replication Footer', () =>{
+  it('no replications to display', () => {
+    const footer = mount(<ReplicationFooter
+      statusDocs={[]}
+      pageLimit={5}
+      setPageLimit={() => {}}
+    />);
+
+    expect(footer.find('.current-replications').text()).toBe('Showing 0 replications.');
+  });
+
+  it('display max # of replications', () => {
+    const footer = mount(<ReplicationFooter
+      statusDocs={[1, 2, 3, 4, 5]}
+      pageLimit={5}
+      setPageLimit={() => {}}
+    />);
+
+    expect(footer.find('.current-replications').text()).toBe('Showing replications 1 - 5');
+  });
+
+  it('display replications with less than max #', () => {
+    const footer = mount(<ReplicationFooter
+      statusDocs={[1, 2, 3, 4, 5, 6, 7, 8]}
+      pageLimit={10}
+      setPageLimit={() => {}}
+    />);
+
+    expect(footer.find('.current-replications').text()).toBe('Showing replications 1 - 8');
+  });
+
+  it('change max value with dropdown', () => {
+    const spy = sinon.spy();
+    const footer = mount(<ReplicationFooter
+      statusDocs={[1, 2, 3, 4, 5, 6, 7, 8]}
+      pageLimit={5}
+      setPageLimit={spy}
+    />);
+
+    expect(footer.find('.current-replications').text()).toBe('Showing replications 1 - 5');
+    footer.find('#select-per-page').simulate('change', {
+      target: {value: 10}
+    });
+    sinon.assert.calledOnce(spy);
+  });
+});
diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js
index 68ed9bc3..22a04ab8 100644
--- a/app/addons/replication/actions.js
+++ b/app/addons/replication/actions.js
@@ -51,7 +51,7 @@ export const getDatabasesList = () => dispatch => {
     });
 };
 
-export const replicate = (params) => dispatch => {
+export const replicate = (params, pageLimit) => dispatch => {
   const replicationDoc = createReplicationDoc(params);
   const url = MainHelper.getServerUrl("/_replicator");
   const promise = post(url, replicationDoc);
@@ -85,12 +85,12 @@ export const replicate = (params) => dispatch => {
         clear: true
       });
 
-      dispatch(getReplicationActivity());
+      dispatch(getReplicationActivity(pageLimit));
       FauxtonAPI.navigate('#/replication');
     }).catch(json => {
       if (json.error && json.error === "not_found") {
         return createReplicatorDB().then(() => {
-          return replicate(params)(dispatch);
+          return replicate(params, pageLimit)(dispatch);
         }).catch(handleError);
       }
       handleError(json);
@@ -111,15 +111,12 @@ export const clearReplicationForm = () => {
   return { type: ActionTypes.REPLICATION_CLEAR_FORM };
 };
 
-export const getReplicationActivity = () => dispatch => {
+export const getReplicationActivity = (pageLimit) => (dispatch) => {
   dispatch({
     type: ActionTypes.REPLICATION_FETCHING_STATUS,
   });
 
-  supportNewApi()
-    .then(supportNewApi => {
-      return fetchReplicationDocs(supportNewApi);
-    })
+  fetchReplicationDocs(pageLimit)
     .then(docs => {
       dispatch({
         type: ActionTypes.REPLICATION_STATUS,
@@ -201,7 +198,7 @@ export const clearSelectedReplicates = () => {
   };
 };
 
-export const deleteDocs = (docs) => dispatch => {
+export const deleteDocs = (docs, pageLimit) => dispatch => {
   const bulkDocs = docs.map(({raw: doc}) => {
     doc._deleted = true;
     return doc;
@@ -237,7 +234,7 @@ export const deleteDocs = (docs) => dispatch => {
       });
 
       dispatch(clearSelectedDocs());
-      dispatch(getReplicationActivity());
+      dispatch(getReplicationActivity(pageLimit));
     })
     .catch(resp => {
       resp.json()
@@ -420,3 +417,11 @@ export const checkForNewApi = () => dispatch => {
     });
   });
 };
+
+export const setPageLimit = (limit) => dispatch => {
+  dispatch({
+    type: ActionTypes.REPLICATION_SET_PAGE_LIMIT,
+    options: limit,
+  });
+  getReplicationActivity(limit)(dispatch);
+};
diff --git a/app/addons/replication/actiontypes.js b/app/addons/replication/actiontypes.js
index 71438686..8fdabd00 100644
--- a/app/addons/replication/actiontypes.js
+++ b/app/addons/replication/actiontypes.js
@@ -38,5 +38,6 @@ export default {
   REPLICATION_CLEAR_SELECTED_REPLICATES: 'REPLICATION_CLEAR_SELECTED_REPLICATES',
   REPLICATION_FETCHING_FORM_STATE: 'REPLICATION_FETCHING_FORM_STATE',
   REPLICATION_HIDE_PASSWORD_MODAL: 'REPLICATION_HIDE_PASSWORD_MODAL',
-  REPLICATION_SHOW_PASSWORD_MODAL: 'REPLICATION_SHOW_PASSWORD_MODAL'
+  REPLICATION_SHOW_PASSWORD_MODAL: 'REPLICATION_SHOW_PASSWORD_MODAL',
+  REPLICATION_SET_PAGE_LIMIT: 'REPLICATION_SET_PAGE_LIMIT'
 };
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index 75dee5f0..701b66c5 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -16,7 +16,6 @@ import FauxtonAPI from '../../core/api';
 import Helpers from '../../helpers';
 import {get, post, put} from '../../core/ajax';
 import base64 from 'base-64';
-import _ from 'lodash';
 
 let newApiPromise = null;
 export const supportNewApi = (forceCheck) => {
@@ -308,19 +307,24 @@ export const combineDocsAndScheduler = (docs, schedulerDocs) => {
   });
 };
 
-export const fetchReplicationDocs = () => {
+export const fetchReplicationDocs = (maxItems) => {
   return supportNewApi()
     .then(newApi => {
-      const url = Helpers.getServerUrl('/_replicator/_all_docs?include_docs=true&limit=100');
+      // Increase limit by 1 to account for the design doc in the DB
+      const url = Helpers.getServerUrl(`/_replicator/_all_docs?include_docs=true&limit=${maxItems + 1}`);
       const docsPromise = get(url)
         .then((res) => {
           if (res.error) {
             return [];
           }
-
-          return parseReplicationDocs(res.rows.filter(row => row.id.indexOf("_design/") === -1));
+          const listWithoutDDocs = res.rows.filter(row => row.id.indexOf("_design/") === -1);
+          if (listWithoutDDocs.length > maxItems) {
+            listWithoutDDocs.pop();
+          }
+          return parseReplicationDocs(listWithoutDDocs);
         });
 
+
       if (!newApi) {
         return docsPromise;
       }
diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less
index ec1c0ad6..076346bf 100644
--- a/app/addons/replication/assets/less/replication.less
+++ b/app/addons/replication/assets/less/replication.less
@@ -16,7 +16,7 @@
 @replication_input_field_width: 400px;
 
 div.replication__page {
-  padding-top: 25px !important;
+  padding: 25px 0 40px 0 !important;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -179,7 +179,7 @@ div.replication__page {
 }
 
 .replication__activity {
-  padding: 0 10px 0 10px !important;
+  padding: 0 10px 40px 10px !important;
   width:100%;
 }
 
diff --git a/app/addons/replication/components/activity.js b/app/addons/replication/components/activity.js
index b2fc0eeb..0d1ae981 100644
--- a/app/addons/replication/components/activity.js
+++ b/app/addons/replication/components/activity.js
@@ -74,7 +74,6 @@ export default class Activity extends React.Component {
       <div className="replication__activity">
         <p className="replication__activity-caveat">
           Replications must have a replication document to display in the following table.
-          Up to about 100 replications are displayed.
         </p>
         <ReplicationHeader
           filter={filter}
diff --git a/app/addons/replication/components/replication-footer.js b/app/addons/replication/components/replication-footer.js
new file mode 100644
index 00000000..d09f9f98
--- /dev/null
+++ b/app/addons/replication/components/replication-footer.js
@@ -0,0 +1,52 @@
+// 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 PropTypes from 'prop-types';
+
+import React from 'react';
+import PerPageSelector from '../../documents/index-results/components/pagination/PerPageSelector';
+
+export class ReplicationFooter extends React.Component {
+
+  getFooterText () {
+    const { statusDocs, pageLimit } = this.props;
+
+    if (statusDocs.length === 0) {
+      return <span>Showing 0 replications.</span>;
+    }
+
+    //either page limit or total # of replications, whichever is smaller
+    return <span>Showing replications 1 - {Math.min(pageLimit, statusDocs.length)}</span>;
+  }
+
+  perPageChange (limit) {
+    this.props.setPageLimit(limit);
+  }
+
+  render() {
+    const { pageLimit } = this.props;
+
+    return (
+      <footer className="pagination-footer">
+        <PerPageSelector label="Max replications displayed:" options={[5, 10, 25, 50, 100, 200, 300, 400, 500]} perPageChange={this.perPageChange.bind(this)} perPage={pageLimit} />
+        <div className="current-replications">
+          {this.getFooterText()}
+        </div>
+      </footer>
+    );
+  }
+}
+
+ReplicationFooter.propTypes = {
+  statusDocs: PropTypes.array.isRequired,
+  pageLimit: PropTypes.number.isRequired,
+  setPageLimit: PropTypes.func.isRequired
+};
diff --git a/app/addons/replication/container.js b/app/addons/replication/container.js
index 5be9079e..5ade93fa 100644
--- a/app/addons/replication/container.js
+++ b/app/addons/replication/container.js
@@ -21,7 +21,8 @@ import {
   changeActivitySort,
   deleteReplicates,
   selectAllReplicates,
-  selectReplicate
+  selectReplicate,
+  setPageLimit
 } from './actions';
 
 import {
@@ -53,7 +54,8 @@ import {
   isReplicateInfoLoading,
   getAllReplicateSelected,
   getReplicateInfo,
-  someReplicateSelected
+  someReplicateSelected,
+  getPageLimit
 } from './reducers';
 
 const mapStateToProps = ({replication, databases}, ownProps) => {
@@ -102,11 +104,12 @@ const mapStateToProps = ({replication, databases}, ownProps) => {
     replicateLoading: isReplicateInfoLoading(replication),
     replicateInfo: getReplicateInfo(replication),
     allReplicateSelected: getAllReplicateSelected(replication),
-    someReplicateSelected: someReplicateSelected(replication)
+    someReplicateSelected: someReplicateSelected(replication),
+    pageLimit: getPageLimit(replication)
   };
 };
 
-const mapDispatchToProps = (dispatch) => {
+const mapDispatchToProps = (dispatch, ownProps) => {
   return {
     checkForNewApi: () => dispatch(checkForNewApi()),
     updateFormField: (fieldName) => (value) => {
@@ -114,22 +117,23 @@ const mapDispatchToProps = (dispatch) => {
     },
     clearReplicationForm: () => dispatch(clearReplicationForm()),
     initReplicator: (localSource) => dispatch(initReplicator(localSource)),
-    getReplicationActivity: () => dispatch(getReplicationActivity()),
+    getReplicationActivity: (pageLimit) => dispatch(getReplicationActivity(pageLimit)),
     getReplicateActivity: () => dispatch(getReplicateActivity()),
     getReplicationStateFrom: (id) => dispatch(getReplicationStateFrom(id)),
     getDatabasesList: () => dispatch(getDatabasesList()),
-    replicate: (params) => dispatch(replicate(params)),
+    replicate: (params) => dispatch(replicate(params, ownProps.pageLimit)),
     showConflictModal: () => dispatch(showConflictModal()),
     hideConflictModal: () => dispatch(hideConflictModal()),
     filterReplicate: (filter) => dispatch(filterReplicate(filter)),
     filterDocs: (filter) => dispatch(filterDocs(filter)),
     selectDoc: (doc) => dispatch(selectDoc(doc)),
-    deleteDocs: (docs) => dispatch(deleteDocs(docs)),
+    deleteDocs: (docs) => dispatch(deleteDocs(docs, ownProps.pageLimit)),
     selectAllDocs: () => dispatch(selectAllDocs()),
     changeActivitySort: (sort) => dispatch(changeActivitySort(sort)),
     selectAllReplicates: () => dispatch(selectAllReplicates()),
     deleteReplicates: (replicates) => dispatch(deleteReplicates(replicates)),
-    selectReplicate: (replicate) => dispatch(selectReplicate(replicate))
+    selectReplicate: (replicate) => dispatch(selectReplicate(replicate)),
+    setPageLimit: (limit) => dispatch(setPageLimit(limit))
   };
 };
 
diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js
index 0c7cc00d..bf3f3235 100644
--- a/app/addons/replication/controller.js
+++ b/app/addons/replication/controller.js
@@ -16,9 +16,10 @@ import Components from '../components/react-components';
 import NewReplication from './components/newreplication';
 import Activity from './components/activity';
 import {checkReplicationDocID} from './api';
-import {OnePane, OnePaneHeader, OnePaneContent} from '../components/layouts';
+import {OnePane, OnePaneHeader, OnePaneContent, OnePaneFooter} from '../components/layouts';
 import {TabElementWrapper, TabElement} from '../components/components/tabelement';
 import ReplicateActivity from './components/replicate-activity';
+import { ReplicationFooter } from './components/replication-footer';
 
 const {LoadLines, Polling, RefreshBtn} = Components;
 
@@ -38,7 +39,7 @@ export default class ReplicationController extends React.Component {
   }
 
   getAllActivity () {
-    this.props.getReplicationActivity();
+    this.props.getReplicationActivity(this.props.pageLimit);
     this.props.getReplicateActivity();
   }
 
@@ -220,6 +221,21 @@ export default class ReplicationController extends React.Component {
     );
   }
 
+  getFooter () {
+    const { tabSection, statusDocs, pageLimit, setPageLimit } = this.props;
+
+    if (tabSection === 'activity') {
+      return (
+        <ReplicationFooter
+          statusDocs={statusDocs}
+          pageLimit={pageLimit}
+          setPageLimit={setPageLimit}
+        />
+      );
+    }
+    return null;
+  }
+
   render () {
     const { checkingAPI } = this.props;
 
@@ -240,6 +256,9 @@ export default class ReplicationController extends React.Component {
             </div>
           </div>
         </OnePaneContent>
+        <OnePaneFooter>
+          {this.getFooter()}
+        </OnePaneFooter>
       </OnePane>
     );
   }
diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js
index 40a276a1..45b3e62b 100644
--- a/app/addons/replication/reducers.js
+++ b/app/addons/replication/reducers.js
@@ -89,7 +89,8 @@ const initialState = {
   replicateInfo: [],
 
   checkingAPI: true,
-  activitySort: loadActivitySort()
+  activitySort: loadActivitySort(),
+  pageLimit: 100,
 };
 
 const clearForm = (state) => {
@@ -348,6 +349,12 @@ const replication = (state = initialState, {type, options}) => {
         allReplicateSelected: false
       };
 
+    case ActionTypes.REPLICATION_SET_PAGE_LIMIT:
+      return {
+        ...state,
+        pageLimit: options
+      };
+
     default:
       return state;
   }
@@ -406,4 +413,6 @@ export const getReplicateInfo = (state) => {
   });
 };
 
+export const getPageLimit = (state) => state.pageLimit;
+
 export default replication;
diff --git a/app/addons/replication/tests/nightwatch/replicationactivity.js b/app/addons/replication/tests/nightwatch/replicationactivity.js
index e32cfe8a..c5b8fa3d 100644
--- a/app/addons/replication/tests/nightwatch/replicationactivity.js
+++ b/app/addons/replication/tests/nightwatch/replicationactivity.js
@@ -92,6 +92,7 @@ module.exports = {
       .waitForElementPresent('a[href="#/database/_replicator/existing-doc-filter2"]', waitTime, true)
       .end();
   },
+
   "Action click doesn't change doc's order": client =>{
     const waitTime = client.globals.maxWaitTime;
     const baseUrl = client.options.launch_url;
@@ -129,5 +130,79 @@ module.exports = {
           'Checking if the order was reserved if no documents were sorted');
       })
       .end();
+  },
+
+  "Change number of replications displayed": client =>{
+    const waitTime = client.globals.maxWaitTime;
+    const baseUrl = client.options.launch_url;
+
+    const replicatorDoc1 = {
+      _id: 'existing-doc-id-display',
+      source: "http://source-db.com",
+      target: "http://target-db.com"
+    };
+
+    const replicatorDoc2 = {
+      _id: 'existing-doc-id-display2',
+      source: "http://source-db2.com",
+      target: "http://target-db.com"
+    };
+
+    const replicatorDoc3 = {
+      _id: 'existing-doc-id-display3',
+      source: "http://source-db3.com",
+      target: "http://target-db.com"
+    };
+
+    const replicatorDoc4 = {
+      _id: 'existing-doc-id-display4',
+      source: "http://source-db4.com",
+      target: "http://target-db.com"
+    };
+
+    const replicatorDoc5 = {
+      _id: 'existing-doc-id-display5',
+      source: "http://source-db5.com",
+      target: "http://target-db.com"
+    };
+
+    const replicatorDoc6 = {
+      _id: 'existing-doc-id-display6',
+      source: "http://source-db6.com",
+      target: "http://target-db.com"
+    };
+
+    client
+      .deleteDatabase('_replicator')
+      .createDatabase('_replicator')
+      .createDocument(replicatorDoc1._id, '_replicator', replicatorDoc1)
+      .createDocument(replicatorDoc2._id, '_replicator', replicatorDoc2)
+      .createDocument(replicatorDoc3._id, '_replicator', replicatorDoc3)
+      .createDocument(replicatorDoc4._id, '_replicator', replicatorDoc4)
+      .createDocument(replicatorDoc5._id, '_replicator', replicatorDoc5)
+      .createDocument(replicatorDoc6._id, '_replicator', replicatorDoc6)
+      .loginToGUI()
+      .url(baseUrl + '/#replication')
+      .waitForElementNotPresent('.load-lines', waitTime, true)
+      .waitForElementPresent('.replication__table-row', waitTime, true)
+      .getText('.current-replications', function(result) {
+        this.verify.ok(result.value === "Showing replications 1 - 6");
+      })
+      .assert.elementsCount('.replication__table-row', 6)
+      .clickWhenVisible('select[id="select-per-page"] option[value="5"]')
+      .waitForElementNotPresent('.load-lines', waitTime, true)
+      .waitForElementPresent('.replication__table-row', waitTime, true)
+      .getText('.current-replications', function(result) {
+        this.verify.ok(result.value === "Showing replications 1 - 5");
+      })
+      .assert.elementsCount('.replication__table-row', 5)
+      .clickWhenVisible('select[id="select-per-page"] option[value="25"]')
+      .waitForElementNotPresent('.load-lines', waitTime, true)
+      .waitForElementPresent('.replication__table-row', waitTime, true)
+      .getText('.current-replications', function(result) {
+        this.verify.ok(result.value === "Showing replications 1 - 6");
+      })
+      .assert.elementsCount('.replication__table-row', 6)
+      .end();
   }
 };
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index 96815248..db6b1dbe 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -437,7 +437,8 @@ footer.pagination-footer {
   }
 
   .current-databases,
-  .current-docs {
+  .current-docs,
+  .current-replications {
     float: right;
     margin: 17px 20px 17px 20px;
   }