Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control amount of replications displayed #1391

Merged
merged 2 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions app/addons/replication/__tests__/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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: {
Expand Down Expand Up @@ -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();
Expand All @@ -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: {
Expand All @@ -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();
});
Expand Down Expand Up @@ -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);
});
});
});
10 changes: 6 additions & 4 deletions app/addons/replication/__tests__/api.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down
63 changes: 63 additions & 0 deletions app/addons/replication/__tests__/replication-footer.test.js
Original file line number Diff line number Diff line change
@@ -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:https://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);
});
});
25 changes: 15 additions & 10 deletions app/addons/replication/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -237,7 +234,7 @@ export const deleteDocs = (docs) => dispatch => {
});

dispatch(clearSelectedDocs());
dispatch(getReplicationActivity());
dispatch(getReplicationActivity(pageLimit));
})
.catch(resp => {
resp.json()
Expand Down Expand Up @@ -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);
};
3 changes: 2 additions & 1 deletion app/addons/replication/actiontypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
};
14 changes: 9 additions & 5 deletions app/addons/replication/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions app/addons/replication/assets/less/replication.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -179,7 +179,7 @@ div.replication__page {
}

.replication__activity {
padding: 0 10px 0 10px !important;
padding: 0 10px 40px 10px !important;
width:100%;
}

Expand Down
1 change: 0 additions & 1 deletion app/addons/replication/components/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
52 changes: 52 additions & 0 deletions app/addons/replication/components/replication-footer.js
Original file line number Diff line number Diff line change
@@ -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:https://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
};
Loading