From 224bb35b290de61a77613ad095bcb450ac98dd17 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Tue, 24 Apr 2018 16:03:09 +0200 Subject: [PATCH 1/9] paginate for activity page --- .../components/pagination/PaginationFooter.js | 1 + app/addons/replication/actions.js | 52 ++++++++++++--- app/addons/replication/actiontypes.js | 5 +- app/addons/replication/api.js | 38 +++++++++-- .../replication/assets/less/replication.less | 7 ++ app/addons/replication/components/activity.js | 29 +++++++- app/addons/replication/container.js | 27 ++++++-- app/addons/replication/controller.js | 22 +++++-- app/addons/replication/reducers.js | 66 ++++++++++++++++++- 9 files changed, 219 insertions(+), 28 deletions(-) diff --git a/app/addons/documents/index-results/components/pagination/PaginationFooter.js b/app/addons/documents/index-results/components/pagination/PaginationFooter.js index 5f1c663ba..8c9a117e7 100644 --- a/app/addons/documents/index-results/components/pagination/PaginationFooter.js +++ b/app/addons/documents/index-results/components/pagination/PaginationFooter.js @@ -40,6 +40,7 @@ export default class PaginationFooter extends React.Component { const { canShowNext, fetchParams, queryOptionsParams, paginateNext, perPage } = this.props; if (canShowNext) { + console.log('FOOTER NEXT', fetchParams, queryOptionsParams, perPage); paginateNext(fetchParams, queryOptionsParams, perPage); } } diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js index 745a36120..99cb652a4 100644 --- a/app/addons/replication/actions.js +++ b/app/addons/replication/actions.js @@ -26,7 +26,6 @@ import { createReplicatorDB } from './api'; - export const initReplicator = (routeLocalSource, localSource) => dispatch => { if (routeLocalSource && routeLocalSource !== localSource) { dispatch({ @@ -111,19 +110,16 @@ export const clearReplicationForm = () => { return { type: ActionTypes.REPLICATION_CLEAR_FORM }; }; -export const getReplicationActivity = () => dispatch => { +export const getReplicationActivity = (params) => dispatch => { dispatch({ type: ActionTypes.REPLICATION_FETCHING_STATUS, }); - supportNewApi() - .then(supportNewApi => { - return fetchReplicationDocs(supportNewApi); - }) - .then(docs => { + fetchReplicationDocs(params) + .then(docsInfo => { dispatch({ type: ActionTypes.REPLICATION_STATUS, - options: docs + options: docsInfo }); }); }; @@ -416,3 +412,43 @@ export const checkForNewApi = () => dispatch => { }); }); }; + +export const updatePerPageResults = (amount) => { + const newPaginate = { + page: 1, + docsPerPage: amount + }; + return (dispatch) => { + dispatch({ + type: ActionTypes.REPLICATION_UPDATE_PER_PAGE_RESULTS, + options: amount + }); + + dispatch(getReplicationActivity(newPaginate)); + }; +}; + +export const paginateNext = (paginate) => { + return dispatch => { + dispatch({ + type: ActionTypes.REPLICATION_NEXT_PAGE + }); + + paginate.page += 1; + dispatch(getReplicationActivity(paginate)); + }; +}; + +export const paginatePrevious = (paginate) => { + return dispatch => { + dispatch({ + type: ActionTypes.REPLICATION_PREVIOUS_PAGE + }); + + if (paginate.page > 1) { + paginate.page -= 1; + } + + dispatch(getReplicationActivity(paginate)); + }; +}; diff --git a/app/addons/replication/actiontypes.js b/app/addons/replication/actiontypes.js index 714386863..e5d56e94f 100644 --- a/app/addons/replication/actiontypes.js +++ b/app/addons/replication/actiontypes.js @@ -38,5 +38,8 @@ 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_UPDATE_PER_PAGE_RESULTS: 'REPLICATION_UPDATE_PER_PAGE_RESULTS', + REPLICATION_NEXT_PAGE: 'REPLICATION_NEXT_PAGE', + REPLICATION_PREVIOUS_PAGE: 'REPLICATION_PREVIOUS_PAGE' }; diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js index 75dee5f0e..d459e3dd1 100644 --- a/app/addons/replication/api.js +++ b/app/addons/replication/api.js @@ -13,10 +13,12 @@ import '@webcomponents/url'; import Constants from './constants'; import FauxtonAPI from '../../core/api'; +import app from '../../app'; import Helpers from '../../helpers'; import {get, post, put} from '../../core/ajax'; import base64 from 'base-64'; import _ from 'lodash'; +import { removeOverflowDocsAndCalculateHasNext } from '../documents/index-results/actions/fetch'; let newApiPromise = null; export const supportNewApi = (forceCheck) => { @@ -308,10 +310,16 @@ export const combineDocsAndScheduler = (docs, schedulerDocs) => { }); }; -export const fetchReplicationDocs = () => { +export const fetchReplicationDocs = ({docsPerPage, page}) => { + const params = { + limit: docsPerPage + 1, + skip: (page - 1) * docsPerPage, + include_docs: true + }; + return supportNewApi() .then(newApi => { - const url = Helpers.getServerUrl('/_replicator/_all_docs?include_docs=true&limit=100'); + const url = Helpers.getServerUrl(`/_replicator/_all_docs?${app.utils.queryParams(parent)}`); const docsPromise = get(url) .then((res) => { if (res.error) { @@ -322,12 +330,32 @@ export const fetchReplicationDocs = () => { }); if (!newApi) { - return docsPromise; + return docsPromise + .then(docs => { + const { + finalDocList, + canShowNext + } = removeOverflowDocsAndCalculateHasNext(docs, false, docsPerPage); + return { + docs: finalDocList, + canShowNext + }; + }); } - const schedulerPromise = fetchSchedulerDocs(); + const schedulerPromise = fetchSchedulerDocs(params); return FauxtonAPI.Promise.join(docsPromise, schedulerPromise, (docs, schedulerDocs) => { return combineDocsAndScheduler(docs, schedulerDocs); }) + .then(docs => { + const { + finalDocList, + canShowNext + } = removeOverflowDocsAndCalculateHasNext(docs, false, docsPerPage + 1); + return { + docs: finalDocList, + canShowNext + }; + }) .catch(() => { return []; }); @@ -335,7 +363,7 @@ export const fetchReplicationDocs = () => { }; export const fetchSchedulerDocs = () => { - const url = Helpers.getServerUrl('/_scheduler/docs?include_docs=true'); + const url = Helpers.getServerUrl(`/_scheduler/docs?${app.utils.queryParams(parent)}`); return get(url) .then((res) => { if (res.error) { diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less index ec1c0ad6b..d5c7ad658 100644 --- a/app/addons/replication/assets/less/replication.less +++ b/app/addons/replication/assets/less/replication.less @@ -374,3 +374,10 @@ td.replication__empty-row { .replication__activity-caveat { padding-left: 80px; } + +.replication__paginate-footer { + position: absolute; + bottom: 0px; + right: 0px; + width: 100%; +} diff --git a/app/addons/replication/components/activity.js b/app/addons/replication/components/activity.js index 0d1ae981a..0dad752e1 100644 --- a/app/addons/replication/components/activity.js +++ b/app/addons/replication/components/activity.js @@ -15,6 +15,7 @@ import React from 'react'; import {DeleteModal} from './modals'; import {ReplicationTable} from './common-table'; import {ReplicationHeader} from './common-activity'; +import PaginationFooter from '../../documents/index-results/components/pagination/paginationfooter'; export default class Activity extends React.Component { constructor (props) { @@ -66,7 +67,14 @@ export default class Activity extends React.Component { selectAllDocs, someDocsSelected, allDocsSelected, - selectDoc + selectDoc, + pageStart, + pageEnd, + docsPerPage, + updatePerPageResults, + paginateNext, + paginatePrevious, + pagination } = this.props; const {modalVisible} = this.state; @@ -90,6 +98,25 @@ export default class Activity extends React.Component { column={activitySort.column} changeSort={changeActivitySort} /> +
+ 1} + perPage={docsPerPage} + toggleShowAllColumns={false} + docs={this.props.docs} + pageStart={pageStart} + pageEnd={pageEnd} + updatePerPageResults={updatePerPageResults} + paginateNext={paginateNext} + paginatePrevious={paginatePrevious} + queryOptionsParams={{}} + fetchParams={pagination} + /> +
{ @@ -102,7 +110,13 @@ const mapStateToProps = ({replication, databases}, ownProps) => { replicateLoading: isReplicateInfoLoading(replication), replicateInfo: getReplicateInfo(replication), allReplicateSelected: getAllReplicateSelected(replication), - someReplicateSelected: someReplicateSelected(replication) + someReplicateSelected: someReplicateSelected(replication), + pagination: getPagination(replication), + pageStart: getPageStart(replication), + pageEnd: getPageEnd(replication), + docsPerPage: getDocsPerPage(replication), + canShowNext: canShowNext(replication) + }; }; @@ -114,7 +128,7 @@ const mapDispatchToProps = (dispatch) => { }, clearReplicationForm: () => dispatch(clearReplicationForm()), initReplicator: (localSource) => dispatch(initReplicator(localSource)), - getReplicationActivity: () => dispatch(getReplicationActivity()), + getReplicationActivity: (params) => dispatch(getReplicationActivity(params)), getReplicateActivity: () => dispatch(getReplicateActivity()), getReplicationStateFrom: (id) => dispatch(getReplicationStateFrom(id)), getDatabasesList: () => dispatch(getDatabasesList()), @@ -129,7 +143,10 @@ const mapDispatchToProps = (dispatch) => { changeActivitySort: (sort) => dispatch(changeActivitySort(sort)), selectAllReplicates: () => dispatch(selectAllReplicates()), deleteReplicates: (replicates) => dispatch(deleteReplicates(replicates)), - selectReplicate: (replicate) => dispatch(selectReplicate(replicate)) + selectReplicate: (replicate) => dispatch(selectReplicate(replicate)), + updatePerPageResults: (amount) => dispatch(updatePerPageResults(amount)), + paginateNext: (params) => dispatch(paginateNext(params)), + paginatePrevious: (params) => dispatch(paginatePrevious(params)) }; }; diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js index 0c7cc00dd..c71c04f94 100644 --- a/app/addons/replication/controller.js +++ b/app/addons/replication/controller.js @@ -26,7 +26,7 @@ export default class ReplicationController extends React.Component { loadReplicationInfo (props, oldProps) { this.props.initReplicator(props.routeLocalSource, props.localSource); - this.getAllActivity(); + this.getAllActivity(props.pagination); this.loadReplicationStateFrom(props, oldProps); } @@ -37,8 +37,8 @@ export default class ReplicationController extends React.Component { } } - getAllActivity () { - this.props.getReplicationActivity(); + getAllActivity (pagination) { + this.props.getReplicationActivity(pagination); this.props.getReplicateActivity(); } @@ -70,7 +70,8 @@ export default class ReplicationController extends React.Component { hideConflictModal, isConflictModalVisible, filterDocs, filterReplicate, replicate, clearReplicationForm, selectAllDocs, changeActivitySort, selectDoc, deleteDocs, deleteReplicates, selectAllReplicates, selectReplicate, - sourceAuthType, sourceAuth, targetAuthType, targetAuth, targetDatabasePartitioned, allowNewPartitionedLocalDbs + sourceAuthType, sourceAuth, targetAuthType, targetAuth, pageStart, pageEnd, docsPerPage, + updatePerPageResults, paginateNext, pagination, paginatePrevious } = this.props; if (tabSection === 'new replication') { @@ -125,6 +126,8 @@ export default class ReplicationController extends React.Component { activitySort={activitySort} changeActivitySort={changeActivitySort} deleteDocs={deleteReplicates} + pageStart={pageStart} + pageEnd={pageEnd} />; } @@ -143,6 +146,13 @@ export default class ReplicationController extends React.Component { deleteDocs={deleteDocs} activitySort={activitySort} changeActivitySort={changeActivitySort} + pageStart={pageStart} + pageEnd={pageEnd} + docsPerPage={docsPerPage} + updatePerPageResults={updatePerPageResults} + paginateNext={paginateNext} + paginatePrevious={paginatePrevious} + pagination={pagination} />; } @@ -162,10 +172,10 @@ export default class ReplicationController extends React.Component { max={600} startValue={300} stepSize={60} - onPoll={this.getAllActivity.bind(this)} + onPoll={this.getAllActivity.bind(this, this.props.pagination)} /> ); diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js index 40a276a1d..2a2a56a46 100644 --- a/app/addons/replication/reducers.js +++ b/app/addons/replication/reducers.js @@ -34,6 +34,20 @@ const loadActivitySort = () => { return sort; }; +const loadDocsPerPage = () => { + let docsPerPage = app.utils.localStorageGet('replication-docs-per-page'); + + if (!docsPerPage) { + docsPerPage = 10; + } + + return docsPerPage; +}; + +const saveDocsPerPage = (docsPerPage) => { + app.utils.localStorageSet('replication-docs-per-page', docsPerPage); +}; + const validFieldMap = { remoteSource: 'remoteSource', remoteTarget: 'remoteTarget', @@ -89,7 +103,12 @@ const initialState = { replicateInfo: [], checkingAPI: true, - activitySort: loadActivitySort() + activitySort: loadActivitySort(), + pagination: { + docsPerPage: loadDocsPerPage(), + page: 1, + canShowNext: false + } }; const clearForm = (state) => { @@ -258,10 +277,15 @@ const replication = (state = initialState, {type, options}) => { }; case ActionTypes.REPLICATION_STATUS: + console.log('STATUS', options); return { ...state, activityLoading: false, - statusDocs: options + statusDocs: options.docs, + pagination: { + ...state.pagination, + canShowNext: options.canShowNext + } }; case ActionTypes.REPLICATION_FILTER_DOCS: @@ -348,6 +372,38 @@ const replication = (state = initialState, {type, options}) => { allReplicateSelected: false }; + case ActionTypes.REPLICATION_UPDATE_PER_PAGE_RESULTS: + const newResultsState = { ...state }; + newResultsState.pagination.docsPerPage = options; + saveDocsPerPage(options); + return { + ...state, + pagination: { + ...state.pagination, + docsPerPage: options, + page: 1, + canShowNext: false + } + }; + + case ActionTypes.REPLICATION_NEXT_PAGE: + return { + ...state, + pagination: { + docsPerPage: state.pagination.docsPerPage, + page: state.pagination.page + 1 + } + }; + + case ActionTypes.REPLICATION_PREVIOUS_PAGE: + return { + ...state, + pagination: { + docsPerPage: state.pagination.docsPerPage, + page: state.pagination.page - 1 + } + }; + default: return state; } @@ -406,4 +462,10 @@ export const getReplicateInfo = (state) => { }); }; +export const getPagination = (state) => state.pagination; +export const getPageStart = (state) => 1 + (state.pagination.page - 1) * state.pagination.docsPerPage; +export const getPageEnd = (state) => state.pagination.docsPerPage + (state.pagination.page - 1) * state.pagination.docsPerPage; +export const getDocsPerPage = (state) => state.pagination.docsPerPage; +export const canShowNext = (state) => state.pagination.canShowNext; + export default replication; From 7ad938eacf2cbeb92a946c262fc5ea428b21e450 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 25 Apr 2018 11:11:04 +0200 Subject: [PATCH 2/9] fix css for footer --- app/addons/replication/assets/less/replication.less | 2 +- app/addons/replication/components/common-table.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less index d5c7ad658..4bea76658 100644 --- a/app/addons/replication/assets/less/replication.less +++ b/app/addons/replication/assets/less/replication.less @@ -376,7 +376,7 @@ td.replication__empty-row { } .replication__paginate-footer { - position: absolute; + position: fixed; bottom: 0px; right: 0px; width: 100%; diff --git a/app/addons/replication/components/common-table.js b/app/addons/replication/components/common-table.js index f12b3825e..02ab1b769 100644 --- a/app/addons/replication/components/common-table.js +++ b/app/addons/replication/components/common-table.js @@ -381,7 +381,7 @@ export class ReplicationTable extends React.Component { render () { return ( - +
-
From 60eaec5b33c754d5ad211b0f18601c16677548d6 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 25 Apr 2018 11:55:00 +0200 Subject: [PATCH 3/9] fix tests --- .../components/pagination/PaginationFooter.js | 1 - app/addons/replication/__tests__/api.tests.js | 28 +++++++++---------- app/addons/replication/reducers.js | 1 - 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/addons/documents/index-results/components/pagination/PaginationFooter.js b/app/addons/documents/index-results/components/pagination/PaginationFooter.js index 8c9a117e7..5f1c663ba 100644 --- a/app/addons/documents/index-results/components/pagination/PaginationFooter.js +++ b/app/addons/documents/index-results/components/pagination/PaginationFooter.js @@ -40,7 +40,6 @@ export default class PaginationFooter extends React.Component { const { canShowNext, fetchParams, queryOptionsParams, paginateNext, perPage } = this.props; if (canShowNext) { - console.log('FOOTER NEXT', fetchParams, queryOptionsParams, perPage); paginateNext(fetchParams, queryOptionsParams, perPage); } } diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js index ff2db57e6..1a5a1157b 100644 --- a/app/addons/replication/__tests__/api.tests.js +++ b/app/addons/replication/__tests__/api.tests.js @@ -577,13 +577,13 @@ describe('Replication API', () => { }); it("returns parsedReplicationDocs and ignores all design docs", () => { - fetchMock.getOnce('./_scheduler/jobs', 404); - fetchMock.get('./_replicator/_all_docs?include_docs=true&limit=100', _repDocs); + fetchMock.getOnce('/_scheduler/jobs', 404); + fetchMock.get('/_replicator/_all_docs?limit=6&skip=0&include_docs=true', _repDocs); return supportNewApi(true) - .then(fetchReplicationDocs) - .then(docs => { - expect(docs.length).toBe(1); - expect(docs[0]._id).toBe("c94d4839d1897105cb75e1251e0003ea"); + .then(() => fetchReplicationDocs({docsPerPage: 5, page: 1})) + .then(({docs}) => { + assert.deepEqual(docs.length, 1); + assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea"); }); }); }); @@ -594,15 +594,15 @@ describe('Replication API', () => { }); it("returns parsedReplicationDocs", () => { - fetchMock.getOnce('./_scheduler/jobs', 200); - fetchMock.get('./_replicator/_all_docs?include_docs=true&limit=100', _repDocs); - fetchMock.get('./_scheduler/docs?include_docs=true', _schedDocs); + fetchMock.getOnce('/_scheduler/jobs', 200); + fetchMock.get('/_replicator/_all_docs?limit=6&skip=0&include_docs=true', _repDocs); + fetchMock.get('/_scheduler/docs?limit=6&skip=0&include_docs=true', _schedDocs); return supportNewApi(true) - .then(fetchReplicationDocs) - .then(docs => { - expect(docs.length).toBe(1); - expect(docs[0]._id).toBe("c94d4839d1897105cb75e1251e0003ea"); - expect(docs[0].stateTime.toDateString()).toBe((new Date('2017-03-07T14:46:17')).toDateString()); + .then(() => fetchReplicationDocs({docsPerPage: 5, page: 1})) + .then(({docs}) => { + assert.deepEqual(docs.length, 1); + assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea"); + assert.deepEqual(docs[0].stateTime.toDateString(), (new Date('2017-03-07T14:46:17')).toDateString()); }); }); }); diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js index 2a2a56a46..e57336819 100644 --- a/app/addons/replication/reducers.js +++ b/app/addons/replication/reducers.js @@ -277,7 +277,6 @@ const replication = (state = initialState, {type, options}) => { }; case ActionTypes.REPLICATION_STATUS: - console.log('STATUS', options); return { ...state, activityLoading: false, From 77f24576b7729a1ae109c6ff9e1a79b1eeb754c8 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 25 Apr 2018 12:19:02 +0200 Subject: [PATCH 4/9] fixes for old api support --- app/addons/replication/__tests__/api.tests.js | 4 ++-- app/addons/replication/api.js | 4 ++-- app/addons/replication/components/activity.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js index 1a5a1157b..7cc04e0b4 100644 --- a/app/addons/replication/__tests__/api.tests.js +++ b/app/addons/replication/__tests__/api.tests.js @@ -578,7 +578,7 @@ describe('Replication API', () => { it("returns parsedReplicationDocs and ignores all design docs", () => { fetchMock.getOnce('/_scheduler/jobs', 404); - fetchMock.get('/_replicator/_all_docs?limit=6&skip=0&include_docs=true', _repDocs); + fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=6&skip=0&include_docs=true', _repDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 5, page: 1})) .then(({docs}) => { @@ -595,7 +595,7 @@ describe('Replication API', () => { it("returns parsedReplicationDocs", () => { fetchMock.getOnce('/_scheduler/jobs', 200); - fetchMock.get('/_replicator/_all_docs?limit=6&skip=0&include_docs=true', _repDocs); + fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=6&skip=0&include_docs=true', _repDocs); fetchMock.get('/_scheduler/docs?limit=6&skip=0&include_docs=true', _schedDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 5, page: 1})) diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js index d459e3dd1..2825ae6a7 100644 --- a/app/addons/replication/api.js +++ b/app/addons/replication/api.js @@ -319,7 +319,7 @@ export const fetchReplicationDocs = ({docsPerPage, page}) => { return supportNewApi() .then(newApi => { - const url = Helpers.getServerUrl(`/_replicator/_all_docs?${app.utils.queryParams(parent)}`); + const url = Helpers.getServerUrl(`/_replicator/_all_docs?startkey=%22_designZ%22&${app.utils.queryParams(params)}`); const docsPromise = get(url) .then((res) => { if (res.error) { @@ -335,7 +335,7 @@ export const fetchReplicationDocs = ({docsPerPage, page}) => { const { finalDocList, canShowNext - } = removeOverflowDocsAndCalculateHasNext(docs, false, docsPerPage); + } = removeOverflowDocsAndCalculateHasNext(docs, false, docsPerPage + 1); return { docs: finalDocList, canShowNext diff --git a/app/addons/replication/components/activity.js b/app/addons/replication/components/activity.js index 0dad752e1..c6f43d0ad 100644 --- a/app/addons/replication/components/activity.js +++ b/app/addons/replication/components/activity.js @@ -15,7 +15,7 @@ import React from 'react'; import {DeleteModal} from './modals'; import {ReplicationTable} from './common-table'; import {ReplicationHeader} from './common-activity'; -import PaginationFooter from '../../documents/index-results/components/pagination/paginationfooter'; +import PaginationFooter from '../../documents/index-results/components/pagination/PaginationFooter'; export default class Activity extends React.Component { constructor (props) { From 5ef53c5af22fbf838b1984232e702d555548a3eb Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Thu, 26 Apr 2018 11:18:46 +0200 Subject: [PATCH 5/9] add tests --- app/addons/replication/__tests__/api.tests.js | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js index 7cc04e0b4..fcea61dea 100644 --- a/app/addons/replication/__tests__/api.tests.js +++ b/app/addons/replication/__tests__/api.tests.js @@ -586,6 +586,50 @@ describe('Replication API', () => { assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea"); }); }); + + it("paginates to page 2 correctly", () => { + fetchMock.getOnce('/_scheduler/jobs', 200); + fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=11&skip=10&include_docs=true', _repDocs); + fetchMock.get('/_scheduler/docs?limit=11&skip=10&include_docs=true', _schedDocs); + return supportNewApi(true) + .then(() => fetchReplicationDocs({docsPerPage: 10, page: 2})) + .then(({canShowNext}) => { + assert.notOk(canShowNext); + }); + }); + + it("sets canShowNext true and trims docs correctly", () => { + const clonedDoc = _repDocs.rows[2]; + const repDocs = { + ..._repDocs, + "total_rows":4, + "offset":0, + rows: [{ + ...clonedDoc, + _id: '1', + }, { + ...clonedDoc, + _id: '2', + }, { + + ...clonedDoc, + _id: '3', + }, { + ...clonedDoc, + _id: '4', + }] + }; + + fetchMock.getOnce('/_scheduler/jobs', 200); + fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=4&skip=6&include_docs=true', repDocs); + fetchMock.get('/_scheduler/docs?limit=4&skip=6&include_docs=true', _schedDocs); + return supportNewApi(true) + .then(() => fetchReplicationDocs({docsPerPage: 3, page: 3})) + .then(({docs, canShowNext}) => { + assert.ok(canShowNext); + assert.deepEqual(docs.length, 3); + }); + }); }); describe('new api', () => { @@ -605,6 +649,50 @@ describe('Replication API', () => { assert.deepEqual(docs[0].stateTime.toDateString(), (new Date('2017-03-07T14:46:17')).toDateString()); }); }); + + it("paginates to page 2 correctly", () => { + fetchMock.getOnce('/_scheduler/jobs', 200); + fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=11&skip=10&include_docs=true', _repDocs); + fetchMock.get('/_scheduler/docs?limit=11&skip=10&include_docs=true', _schedDocs); + return supportNewApi(true) + .then(() => fetchReplicationDocs({docsPerPage: 10, page: 2})) + .then(({canShowNext}) => { + assert.notOk(canShowNext); + }); + }); + + it("sets canShowNext true and trims docs correctly", () => { + const clonedDoc = _repDocs.rows[2]; + const repDocs = { + ..._repDocs, + "total_rows":4, + "offset":0, + rows: [{ + ...clonedDoc, + _id: '1', + }, { + ...clonedDoc, + _id: '2', + }, { + + ...clonedDoc, + _id: '3', + }, { + ...clonedDoc, + _id: '4', + }] + }; + + fetchMock.getOnce('/_scheduler/jobs', 200); + fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=4&skip=6&include_docs=true', repDocs); + fetchMock.get('/_scheduler/docs?limit=4&skip=6&include_docs=true', _schedDocs); + return supportNewApi(true) + .then(() => fetchReplicationDocs({docsPerPage: 3, page: 3})) + .then(({docs, canShowNext}) => { + assert.ok(canShowNext); + assert.deepEqual(docs.length, 3); + }); + }); }); }); }); From 79cb32fd140c05c55b20119e5cb4eae6bcd76eaa Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Mon, 11 Jun 2018 16:22:36 +0200 Subject: [PATCH 6/9] fixes from review --- app/addons/replication/actions.js | 10 +++++----- app/addons/replication/components/activity.js | 2 +- app/addons/replication/components/newreplication.js | 6 ++++-- app/addons/replication/container.js | 2 +- app/addons/replication/controller.js | 1 + 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js index 99cb652a4..25affec98 100644 --- a/app/addons/replication/actions.js +++ b/app/addons/replication/actions.js @@ -84,9 +84,9 @@ export const replicate = (params) => dispatch => { clear: true }); - dispatch(getReplicationActivity()); - FauxtonAPI.navigate('#/replication'); - }).catch(json => { + dispatch(getReplicationActivity(params.pagination)); + }) + .catch(json => { if (json.error && json.error === "not_found") { return createReplicatorDB().then(() => { return replicate(params)(dispatch); @@ -197,7 +197,7 @@ export const clearSelectedReplicates = () => { }; }; -export const deleteDocs = (docs) => dispatch => { +export const deleteDocs = (docs, pagination) => dispatch => { const bulkDocs = docs.map(({raw: doc}) => { doc._deleted = true; return doc; @@ -233,7 +233,7 @@ export const deleteDocs = (docs) => dispatch => { }); dispatch(clearSelectedDocs()); - dispatch(getReplicationActivity()); + dispatch(getReplicationActivity(pagination)); }) .catch(resp => { resp.json() diff --git a/app/addons/replication/components/activity.js b/app/addons/replication/components/activity.js index c6f43d0ad..680605d85 100644 --- a/app/addons/replication/components/activity.js +++ b/app/addons/replication/components/activity.js @@ -49,7 +49,7 @@ export default class Activity extends React.Component { docs = this.props.docs.filter(doc => doc.selected); } - this.props.deleteDocs(docs); + this.props.deleteDocs(docs, this.props.pagination); this.closeModal(); } diff --git a/app/addons/replication/components/newreplication.js b/app/addons/replication/components/newreplication.js index 08da3b3a9..bf78b9f9c 100644 --- a/app/addons/replication/components/newreplication.js +++ b/app/addons/replication/components/newreplication.js @@ -249,7 +249,8 @@ export default class NewReplicationController extends React.Component { sourceAuth, targetAuthType, targetAuth, - targetDatabasePartitioned + targetDatabasePartitioned, + pagination } = this.props; let _rev; @@ -274,7 +275,8 @@ export default class NewReplicationController extends React.Component { sourceAuth, targetAuthType, targetAuth, - targetDatabasePartitioned + targetDatabasePartitioned, + pagination }); } diff --git a/app/addons/replication/container.js b/app/addons/replication/container.js index f7c74b9b7..29fd11178 100644 --- a/app/addons/replication/container.js +++ b/app/addons/replication/container.js @@ -138,7 +138,7 @@ const mapDispatchToProps = (dispatch) => { filterReplicate: (filter) => dispatch(filterReplicate(filter)), filterDocs: (filter) => dispatch(filterDocs(filter)), selectDoc: (doc) => dispatch(selectDoc(doc)), - deleteDocs: (docs) => dispatch(deleteDocs(docs)), + deleteDocs: (docs, pagination) => dispatch(deleteDocs(docs, pagination)), selectAllDocs: () => dispatch(selectAllDocs()), changeActivitySort: (sort) => dispatch(changeActivitySort(sort)), selectAllReplicates: () => dispatch(selectAllReplicates()), diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js index c71c04f94..1429f76ce 100644 --- a/app/addons/replication/controller.js +++ b/app/addons/replication/controller.js @@ -107,6 +107,7 @@ export default class ReplicationController extends React.Component { checkReplicationDocID={checkReplicationDocID} authenticated={authenticated} submittedNoChange={submittedNoChange} + pagination={pagination} />; } From 4753053ee6bc7afde109263389287e1e3b41c9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20C=C3=B4t=C3=A9?= Date: Tue, 9 Jun 2020 00:14:53 -0400 Subject: [PATCH 7/9] Fix replication activity pagination - Update tests - Filter design docs with mango query --- app/addons/replication/__tests__/api.tests.js | 179 +++++++++--------- app/addons/replication/api.js | 70 +++---- app/addons/replication/controller.js | 2 +- app/app.js | 2 +- 4 files changed, 126 insertions(+), 127 deletions(-) diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js index fcea61dea..097592f3e 100644 --- a/app/addons/replication/__tests__/api.tests.js +++ b/app/addons/replication/__tests__/api.tests.js @@ -478,71 +478,40 @@ describe('Replication API', () => { describe("fetchReplicationDocs", () => { const _repDocs = { - "total_rows":2, - "offset":0, - "rows":[ + "docs":[ { - "id":"_design/_replicator", - "key":"_design/_replicator", - "value":{ - "rev":"1-1390740c4877979dbe8998382876556c" + "_id": "c94d4839d1897105cb75e1251e0003ea", + "_rev": "3-4559cb522de85ce03bd0e1991025e89a", + "user_ctx": { + "name": "tester", + "roles": ["_admin", "_reader", "_writer"] }, - "doc":{"_id":"_design/_replicator", - "_rev":"1-1390740c4877979dbe8998382876556c", - "language":"javascript", - "validate_doc_update":"\n function(newDoc, oldDoc, userCtx) {\n function reportError(error_msg) {\n log('Error writing document `' + newDoc._id +\n '\\' to the replicator database: ' + error_msg);\n throw({forbidden: error_msg});\n }\n\n function validateEndpoint(endpoint, fieldName) {\n if ((typeof endpoint !== 'string') &&\n ((typeof endpoint !== 'object') || (endpoint === null))) {\n\n reportError('The `' + fieldName + '\\' property must exist' +\n ' and be either a string or an object.');\n }\n\n if (typeof endpoint === 'object') {\n if ((typeof endpoint.url !== 'string') || !endpoint.url) {\n reportError('The url property must exist in the `' +\n fieldName + '\\' field and must be a non-empty string.');\n }\n\n if ((typeof endpoint.auth !== 'undefined') &&\n ((typeof endpoint.auth !== 'object') ||\n endpoint.auth === null)) {\n\n reportError('`' + fieldName +\n '.auth\\' must be a non-null object.');\n }\n\n if ((typeof endpoint.headers !== 'undefined') &&\n ((typeof endpoint.headers !== 'object') ||\n endpoint.headers === null)) {\n\n reportError('`' + fieldName +\n '.headers\\' must be a non-null object.');\n }\n }\n }\n\n var isReplicator = (userCtx.roles.indexOf('_replicator') >= 0);\n var isAdmin = (userCtx.roles.indexOf('_admin') >= 0);\n\n if (oldDoc && !newDoc._deleted && !isReplicator &&\n (oldDoc._replication_state === 'triggered')) {\n reportError('Only the replicator can edit replication documents ' +\n 'that are in the triggered state.');\n }\n\n if (!newDoc._deleted) {\n validateEndpoint(newDoc.source, 'source');\n validateEndpoint(newDoc.target, 'target');\n\n if ((typeof newDoc.create_target !== 'undefined') &&\n (typeof newDoc.create_target !== 'boolean')) {\n\n reportError('The `create_target\\' field must be a boolean.');\n }\n\n if ((typeof newDoc.continuous !== 'undefined') &&\n (typeof newDoc.continuous !== 'boolean')) {\n\n reportError('The `continuous\\' field must be a boolean.');\n }\n\n if ((typeof newDoc.doc_ids !== 'undefined') &&\n !isArray(newDoc.doc_ids)) {\n\n reportError('The `doc_ids\\' field must be an array of strings.');\n }\n\n if ((typeof newDoc.selector !== 'undefined') &&\n (typeof newDoc.selector !== 'object')) {\n\n reportError('The `selector\\' field must be an object.');\n }\n\n if ((typeof newDoc.filter !== 'undefined') &&\n ((typeof newDoc.filter !== 'string') || !newDoc.filter)) {\n\n reportError('The `filter\\' field must be a non-empty string.');\n }\n\n if ((typeof newDoc.doc_ids !== 'undefined') &&\n (typeof newDoc.selector !== 'undefined')) {\n\n reportError('`doc_ids\\' field is incompatible with `selector\\'.');\n }\n\n if ( ((typeof newDoc.doc_ids !== 'undefined') ||\n (typeof newDoc.selector !== 'undefined')) &&\n (typeof newDoc.filter !== 'undefined') ) {\n\n reportError('`filter\\' field is incompatible with `selector\\' and `doc_ids\\'.');\n }\n\n if ((typeof newDoc.query_params !== 'undefined') &&\n ((typeof newDoc.query_params !== 'object') ||\n newDoc.query_params === null)) {\n\n reportError('The `query_params\\' field must be an object.');\n }\n\n if (newDoc.user_ctx) {\n var user_ctx = newDoc.user_ctx;\n\n if ((typeof user_ctx !== 'object') || (user_ctx === null)) {\n reportError('The `user_ctx\\' property must be a ' +\n 'non-null object.');\n }\n\n if (!(user_ctx.name === null ||\n (typeof user_ctx.name === 'undefined') ||\n ((typeof user_ctx.name === 'string') &&\n user_ctx.name.length > 0))) {\n\n reportError('The `user_ctx.name\\' property must be a ' +\n 'non-empty string or null.');\n }\n\n if (!isAdmin && (user_ctx.name !== userCtx.name)) {\n reportError('The given `user_ctx.name\\' is not valid');\n }\n\n if (user_ctx.roles && !isArray(user_ctx.roles)) {\n reportError('The `user_ctx.roles\\' property must be ' +\n 'an array of strings.');\n }\n\n if (!isAdmin && user_ctx.roles) {\n for (var i = 0; i < user_ctx.roles.length; i++) {\n var role = user_ctx.roles[i];\n\n if (typeof role !== 'string' || role.length === 0) {\n reportError('Roles must be non-empty strings.');\n }\n if (userCtx.roles.indexOf(role) === -1) {\n reportError('Invalid role (`' + role +\n '\\') in the `user_ctx\\'');\n }\n }\n }\n } else {\n if (!isAdmin) {\n reportError('The `user_ctx\\' property is missing (it is ' +\n 'optional for admins only).');\n }\n }\n } else {\n if (!isAdmin) {\n if (!oldDoc.user_ctx || (oldDoc.user_ctx.name !== userCtx.name)) {\n reportError('Replication documents can only be deleted by ' +\n 'admins or by the users who created them.');\n }\n }\n }\n }\n" - } - }, - { - "id":"_design/filters", - "key":"_design/filters", - "value":{ - "rev":"1-1390740c4877979dbe8998382876556c" - }, - "doc":{ - "_id":"_design/filters", - "_rev":"1-1390740c4877979dbe8998382876556c", - "filters": { - "afilter": "\n function (doc, req) { if (doc.type === 'a-doc') { return true; } \n return false }" - } - } - }, - { - "id":"c94d4839d1897105cb75e1251e0003ea", - "key":"c94d4839d1897105cb75e1251e0003ea", - "value":{ - "rev":"3-4559cb522de85ce03bd0e1991025e89a" + "source": { + "headers": { + "Authorization": "Basic dGVzdGVyOnRlc3RlcnBhc3M=" + }, + "url": "http://dev:5984/animaldb" }, - "doc":{"_id":"c94d4839d1897105cb75e1251e0003ea", - "_rev":"3-4559cb522de85ce03bd0e1991025e89a", - "user_ctx":{ - "name":"tester", - "roles":["_admin", "_reader", "_writer"]}, - "source":{ - "headers":{ - "Authorization":"Basic dGVzdGVyOnRlc3RlcnBhc3M=" - }, - "url":"http://dev:5984/animaldb"}, - "target":{ - "headers":{ - "Authorization":"Basic dGVzdGVyOnRlc3RlcnBhc3M="}, - "url":"http://dev:5984/animaldb-clone" + "target": { + "headers": { + "Authorization": "Basic dGVzdGVyOnRlc3RlcnBhc3M=" }, - "create_target":true, - "continuous":false, - "owner":"tester", - "_replication_state":"completed", - "_replication_state_time":"2017-02-28T12:16:28+00:00", - "_replication_id":"0ce2939af29317b5dbe11c15570ddfda", - "_replication_stats":{ - "revisions_checked":14, - "missing_revisions_found":14, - "docs_read":14, - "docs_written":14, - "changes_pending":null, - "doc_write_failures":0, - "checkpointed_source_seq":"15-g1AAAAJDeJyV0N0NgjAQAOAKRnlzBJ3AcKWl9Uk20ZbSEII4gm6im-gmugke1AQJ8aFpck3u50vuakJIVIaGrJqzKSADKrYxPqixECii123bVmWoFidMLGVsqEjYtP0voTcY9f6rzHqFKcglsz5K1imHkcJTnoJVPsqxUy4jxepEioJ7KM0cI7nih9BtkDSlkAif2zjp7qRHJwW9lLNdDkZ6S08nvQZJMsNT4b_d20k_d4oVE1aK6VT1AXTajes" - } + "url": "http://dev:5984/animaldb-clone" + }, + "create_target": true, + "continuous": false, + "owner": "tester", + "_replication_state": "completed", + "_replication_state_time": "2017-02-28T12:16:28+00:00", + "_replication_id": "0ce2939af29317b5dbe11c15570ddfda", + "_replication_stats": { + "revisions_checked": 14, + "missing_revisions_found": 14, + "docs_read": 14, + "docs_written": 14, + "changes_pending": null, + "doc_write_failures": 0, + "checkpointed_source_seq": "15-g1AAAAJDeJyV0N0NgjAQAOAKRnlzBJ3AcKWl9Uk20ZbSEII4gm6im-gmugke1AQJ8aFpck3u50vuakJIVIaGrJqzKSADKrYxPqixECii123bVmWoFidMLGVsqEjYtP0voTcY9f6rzHqFKcglsz5K1imHkcJTnoJVPsqxUy4jxepEioJ7KM0cI7nih9BtkDSlkAif2zjp7qRHJwW9lLNdDkZ6S08nvQZJMsNT4b_d20k_d4oVE1aK6VT1AXTajes" } } ]}; @@ -578,33 +547,40 @@ describe('Replication API', () => { it("returns parsedReplicationDocs and ignores all design docs", () => { fetchMock.getOnce('/_scheduler/jobs', 404); - fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=6&skip=0&include_docs=true', _repDocs); + fetchMock.post((url, options) => { + const body = JSON.parse(options.body); + return url === "/_replicator/_find" + && body.limit === 6 + && body.skip === 0; + }, _repDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 5, page: 1})) .then(({docs}) => { - assert.deepEqual(docs.length, 1); - assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea"); + expect(docs).toHaveLength(1); + expect(docs[0]._id).toBe("c94d4839d1897105cb75e1251e0003ea"); }); }); it("paginates to page 2 correctly", () => { fetchMock.getOnce('/_scheduler/jobs', 200); - fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=11&skip=10&include_docs=true', _repDocs); - fetchMock.get('/_scheduler/docs?limit=11&skip=10&include_docs=true', _schedDocs); + fetchMock.post((url, options) => { + const body = JSON.parse(options.body); + return url === "/_replicator/_find" + && body.limit === 11 + && body.skip === 10; + }, _repDocs); + fetchMock.get('/_scheduler/docs?limit=11&skip=10', _schedDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 10, page: 2})) .then(({canShowNext}) => { - assert.notOk(canShowNext); + expect(canShowNext).toBeFalsy; }); }); it("sets canShowNext true and trims docs correctly", () => { - const clonedDoc = _repDocs.rows[2]; + const clonedDoc = _repDocs.docs[2]; const repDocs = { - ..._repDocs, - "total_rows":4, - "offset":0, - rows: [{ + docs: [{ ...clonedDoc, _id: '1', }, { @@ -621,13 +597,18 @@ describe('Replication API', () => { }; fetchMock.getOnce('/_scheduler/jobs', 200); - fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=4&skip=6&include_docs=true', repDocs); - fetchMock.get('/_scheduler/docs?limit=4&skip=6&include_docs=true', _schedDocs); + fetchMock.post((url, options) => { + const body = JSON.parse(options.body); + return url === "/_replicator/_find" + && body.limit === 4 + && body.skip === 6; + }, repDocs); + fetchMock.get('/_scheduler/docs?limit=4&skip=6', _schedDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 3, page: 3})) .then(({docs, canShowNext}) => { - assert.ok(canShowNext); - assert.deepEqual(docs.length, 3); + expect(canShowNext).toBeTruthy(); + expect(docs).toHaveLength(3); }); }); }); @@ -639,35 +620,42 @@ describe('Replication API', () => { it("returns parsedReplicationDocs", () => { fetchMock.getOnce('/_scheduler/jobs', 200); - fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=6&skip=0&include_docs=true', _repDocs); - fetchMock.get('/_scheduler/docs?limit=6&skip=0&include_docs=true', _schedDocs); + fetchMock.post((url, options) => { + const body = JSON.parse(options.body); + return url === "/_replicator/_find" + && body.limit === 6 + && body.skip === 0; + }, _repDocs); + fetchMock.get('/_scheduler/docs?limit=6&skip=0', _schedDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 5, page: 1})) .then(({docs}) => { - assert.deepEqual(docs.length, 1); - assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea"); - assert.deepEqual(docs[0].stateTime.toDateString(), (new Date('2017-03-07T14:46:17')).toDateString()); + expect(docs).toHaveLength(1); + expect(docs[0]._id).toBe("c94d4839d1897105cb75e1251e0003ea"); + expect(docs[0].stateTime.toDateString()).toBe(new Date('2017-03-07T14:46:17').toDateString()); }); }); it("paginates to page 2 correctly", () => { fetchMock.getOnce('/_scheduler/jobs', 200); - fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=11&skip=10&include_docs=true', _repDocs); - fetchMock.get('/_scheduler/docs?limit=11&skip=10&include_docs=true', _schedDocs); + fetchMock.post((url, options) => { + const body = JSON.parse(options.body); + return url === "/_replicator/_find" + && body.limit === 11 + && body.skip === 10; + }, _repDocs); + fetchMock.get('/_scheduler/docs?limit=11&skip=10', _schedDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 10, page: 2})) .then(({canShowNext}) => { - assert.notOk(canShowNext); + expect(canShowNext).toBeFalsy(); }); }); it("sets canShowNext true and trims docs correctly", () => { - const clonedDoc = _repDocs.rows[2]; + const clonedDoc = _repDocs.docs[2]; const repDocs = { - ..._repDocs, - "total_rows":4, - "offset":0, - rows: [{ + docs: [{ ...clonedDoc, _id: '1', }, { @@ -684,13 +672,18 @@ describe('Replication API', () => { }; fetchMock.getOnce('/_scheduler/jobs', 200); - fetchMock.get('/_replicator/_all_docs?startkey=%22_designZ%22&limit=4&skip=6&include_docs=true', repDocs); - fetchMock.get('/_scheduler/docs?limit=4&skip=6&include_docs=true', _schedDocs); + fetchMock.post((url, options) => { + const body = JSON.parse(options.body); + return url === "/_replicator/_find" + && body.limit === 4 + && body.skip === 6; + }, repDocs); + fetchMock.get('/_scheduler/docs?limit=4&skip=6', _schedDocs); return supportNewApi(true) .then(() => fetchReplicationDocs({docsPerPage: 3, page: 3})) .then(({docs, canShowNext}) => { - assert.ok(canShowNext); - assert.deepEqual(docs.length, 3); + expect(canShowNext).toBeTruthy(); + expect(docs).toHaveLength(3); }); }); }); diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js index 2825ae6a7..803ebcff5 100644 --- a/app/addons/replication/api.js +++ b/app/addons/replication/api.js @@ -262,24 +262,22 @@ export const getDocUrl = (doc) => { return removeSensitiveUrlInfo(url); }; -export const parseReplicationDocs = (rows) => { - return rows.map(row => row.doc).map(doc => { - return { - _id: doc._id, - _rev: doc._rev, - selected: false, //use this field for bulk delete in the ui - source: getDocUrl(doc.source), - target: getDocUrl(doc.target), - createTarget: doc.create_target, - continuous: doc.continuous === true ? true : false, - status: doc._replication_state, - errorMsg: doc._replication_state_reason ? doc._replication_state_reason : '', - statusTime: new Date(doc._replication_state_time), - startTime: new Date(doc._replication_start_time), - url: `#/database/_replicator/${encodeURIComponent(doc._id)}`, - raw: doc - }; - }); +export const parseReplicationDocs = docs => { + return docs.map(doc => ({ + _id: doc._id, + _rev: doc._rev, + selected: false, //use this field for bulk delete in the ui + source: getDocUrl(doc.source), + target: getDocUrl(doc.target), + createTarget: doc.create_target, + continuous: doc.continuous === true, + status: doc._replication_state, + errorMsg: doc._replication_state_reason ? doc._replication_state_reason : '', + statusTime: new Date(doc._replication_state_time), + startTime: new Date(doc._replication_start_time), + url: `#/database/_replicator/${encodeURIComponent(doc._id)}`, + raw: doc + })); }; export const convertState = (state) => { @@ -311,22 +309,30 @@ export const combineDocsAndScheduler = (docs, schedulerDocs) => { }; export const fetchReplicationDocs = ({docsPerPage, page}) => { - const params = { - limit: docsPerPage + 1, - skip: (page - 1) * docsPerPage, - include_docs: true + const limit = docsPerPage + 1; + const skip = (page - 1) * docsPerPage; + + const mangoPayload = { + limit, + skip, + selector: { + _id: { + $gte: null + } + } + }; + + const schedulerDocsPayload = { + limit, + skip }; return supportNewApi() .then(newApi => { - const url = Helpers.getServerUrl(`/_replicator/_all_docs?startkey=%22_designZ%22&${app.utils.queryParams(params)}`); - const docsPromise = get(url) + const url = Helpers.getServerUrl('/_replicator/_find'); + const docsPromise = post(url, mangoPayload) .then((res) => { - if (res.error) { - return []; - } - - return parseReplicationDocs(res.rows.filter(row => row.id.indexOf("_design/") === -1)); + return parseReplicationDocs(res.docs); }); if (!newApi) { @@ -342,7 +348,7 @@ export const fetchReplicationDocs = ({docsPerPage, page}) => { }; }); } - const schedulerPromise = fetchSchedulerDocs(params); + const schedulerPromise = fetchSchedulerDocs(schedulerDocsPayload); return FauxtonAPI.Promise.join(docsPromise, schedulerPromise, (docs, schedulerDocs) => { return combineDocsAndScheduler(docs, schedulerDocs); }) @@ -362,8 +368,8 @@ export const fetchReplicationDocs = ({docsPerPage, page}) => { }); }; -export const fetchSchedulerDocs = () => { - const url = Helpers.getServerUrl(`/_scheduler/docs?${app.utils.queryParams(parent)}`); +export const fetchSchedulerDocs = (params) => { + const url = Helpers.getServerUrl(`/_scheduler/docs?${app.utils.queryParams(params)}`); return get(url) .then((res) => { if (res.error) { diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js index 1429f76ce..027e156c3 100644 --- a/app/addons/replication/controller.js +++ b/app/addons/replication/controller.js @@ -71,7 +71,7 @@ export default class ReplicationController extends React.Component { filterReplicate, replicate, clearReplicationForm, selectAllDocs, changeActivitySort, selectDoc, deleteDocs, deleteReplicates, selectAllReplicates, selectReplicate, sourceAuthType, sourceAuth, targetAuthType, targetAuth, pageStart, pageEnd, docsPerPage, - updatePerPageResults, paginateNext, pagination, paginatePrevious + updatePerPageResults, paginateNext, pagination, paginatePrevious, targetDatabasePartitioned, allowNewPartitionedLocalDbs } = this.props; if (tabSection === 'new replication') { diff --git a/app/app.js b/app/app.js index 713b8de50..4679a376d 100644 --- a/app/app.js +++ b/app/app.js @@ -9,13 +9,13 @@ // 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-toastify/dist/ReactToastify.min.css'; import "jquery"; import app from "./initialize"; import _ from "lodash"; import Helpers from "./helpers"; import Utils from "./core/utils"; import FauxtonAPI from "./core/api"; -import 'react-toastify/dist/ReactToastify.min.css'; import "../assets/less/fauxton.less"; // Make sure we have a console.log From a35a657a75d2e046b6699da09be0044be3170e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20C=C3=B4t=C3=A9?= Date: Sat, 13 Jun 2020 15:57:45 -0400 Subject: [PATCH 8/9] Fix things - Change statusTime to startTime in table - Handle _replicatorDB not found - Fix replication reducer - Fix polling refresh --- app/addons/replication/api.js | 3 ++- app/addons/replication/components/common-table.js | 4 ++-- app/addons/replication/controller.js | 13 +++++++++++-- app/addons/replication/reducers.js | 2 ++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js index 803ebcff5..dc66c977d 100644 --- a/app/addons/replication/api.js +++ b/app/addons/replication/api.js @@ -332,7 +332,8 @@ export const fetchReplicationDocs = ({docsPerPage, page}) => { const url = Helpers.getServerUrl('/_replicator/_find'); const docsPromise = post(url, mangoPayload) .then((res) => { - return parseReplicationDocs(res.docs); + const docs = res.error ? [] : res.docs; + return parseReplicationDocs(docs); }); if (!newApi) { diff --git a/app/addons/replication/components/common-table.js b/app/addons/replication/components/common-table.js index 02ab1b769..2ab648cd3 100644 --- a/app/addons/replication/components/common-table.js +++ b/app/addons/replication/components/common-table.js @@ -400,9 +400,9 @@ export class ReplicationTable extends React.Component { Target + Start Time - + Type diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js index 027e156c3..e395a38a5 100644 --- a/app/addons/replication/controller.js +++ b/app/addons/replication/controller.js @@ -24,6 +24,11 @@ const {LoadLines, Polling, RefreshBtn} = Components; export default class ReplicationController extends React.Component { + constructor(props) { + super(props); + this.onRefresh = this.onRefresh.bind(this); + } + loadReplicationInfo (props, oldProps) { this.props.initReplicator(props.routeLocalSource, props.localSource); this.getAllActivity(props.pagination); @@ -42,6 +47,10 @@ export default class ReplicationController extends React.Component { this.props.getReplicateActivity(); } + onRefresh() { + this.getAllActivity(this.props.pagination); + } + componentDidMount () { this.props.checkForNewApi(); this.props.getDatabasesList(); @@ -173,10 +182,10 @@ export default class ReplicationController extends React.Component { max={600} startValue={300} stepSize={60} - onPoll={this.getAllActivity.bind(this, this.props.pagination)} + onPoll={this.onRefresh} /> ); diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js index e57336819..7279fe646 100644 --- a/app/addons/replication/reducers.js +++ b/app/addons/replication/reducers.js @@ -389,6 +389,7 @@ const replication = (state = initialState, {type, options}) => { return { ...state, pagination: { + ...state.pagination, docsPerPage: state.pagination.docsPerPage, page: state.pagination.page + 1 } @@ -398,6 +399,7 @@ const replication = (state = initialState, {type, options}) => { return { ...state, pagination: { + ...state.pagination, docsPerPage: state.pagination.docsPerPage, page: state.pagination.page - 1 } From 303b42f3caf15a9b300cc1577977d8c45f49aea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20C=C3=B4t=C3=A9?= Date: Sun, 11 Apr 2021 19:41:49 -0400 Subject: [PATCH 9/9] Set proper page end Co-authored-by: Antonio Maranhao <30349380+Antonio-Maranhao@users.noreply.github.com> --- app/addons/replication/reducers.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js index 7279fe646..c9539e8c5 100644 --- a/app/addons/replication/reducers.js +++ b/app/addons/replication/reducers.js @@ -465,7 +465,13 @@ export const getReplicateInfo = (state) => { export const getPagination = (state) => state.pagination; export const getPageStart = (state) => 1 + (state.pagination.page - 1) * state.pagination.docsPerPage; -export const getPageEnd = (state) => state.pagination.docsPerPage + (state.pagination.page - 1) * state.pagination.docsPerPage; +export const getPageEnd = (state) => { + if (state.isLoading || !state.statusDocs || state.statusDocs.length === 0) { + return false; + } + const pageStart = (state.pagination.page - 1) * state.pagination.docsPerPage; + return pageStart + state.statusDocs.length; +}; export const getDocsPerPage = (state) => state.pagination.docsPerPage; export const canShowNext = (state) => state.pagination.canShowNext;