Add option to create target db as partitioned in replication page (#1192)
diff --git a/app/addons/replication/__tests__/actions.test.js b/app/addons/replication/__tests__/actions.test.js
index 969c73e..b9cfa64 100644
--- a/app/addons/replication/__tests__/actions.test.js
+++ b/app/addons/replication/__tests__/actions.test.js
@@ -145,7 +145,8 @@
"replicationTarget": "REPLICATION_TARGET_EXISTING_LOCAL_DATABASE",
"localTarget": "boom123",
"targetAuthType":"BASIC_AUTH",
- "targetAuth":{"username":"tester", "password":"testerpass"}
+ "targetAuth":{"username":"tester", "password":"testerpass"},
+ "targetDatabasePartitioned": false
};
it('builds up correct state', (done) => {
@@ -192,6 +193,7 @@
"localTarget": "boom123",
"targetAuthType":"TEST_CUSTOM_AUTH",
"targetAuth":{"creds":"target_user_creds"},
+ "targetDatabasePartitioned": false
};
FauxtonAPI.registerExtension('Replication:Auth', {
typeValue: 'TEST_CUSTOM_AUTH',
diff --git a/app/addons/replication/__tests__/newreplication.test.js b/app/addons/replication/__tests__/newreplication.test.js
index 687d49e..11ee0c6 100644
--- a/app/addons/replication/__tests__/newreplication.test.js
+++ b/app/addons/replication/__tests__/newreplication.test.js
@@ -75,7 +75,7 @@
remoteSource={"anotherdb"}
localTarget={""}
localSource={""}
- replicationSource={""}
+ replicationSource={Constants.REPLICATION_SOURCE.REMOTE}
replicationType={""}
replicationDocName={""}
conflictModalVisible={false}
diff --git a/app/addons/replication/__tests__/target.test.js b/app/addons/replication/__tests__/target.test.js
new file mode 100644
index 0000000..16b879b
--- /dev/null
+++ b/app/addons/replication/__tests__/target.test.js
@@ -0,0 +1,81 @@
+// 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 { ReplicationTarget } from '../components/target';
+import Constants from '../constants';
+
+describe('ReplicationTarget', () => {
+
+ describe('NewTargetDatabaseOptionsRow', () => {
+
+ const defaultProps = {
+ databases: ["db1"],
+ onTargetChange: () => {},
+ onLocalTargetChange: () => {},
+ onRemoteTargetChange: () => {},
+ remoteTarget: "",
+ localTarget: "",
+ replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+ onTargetDatabasePartitionedChange: () => {},
+ targetDatabasePartitioned: false,
+ allowNewPartitionedLocalDbs: false
+ };
+
+ it('Does not show partitioned option for existing remote database', () => {
+ const repRemoteTarget = mount(<ReplicationTarget
+ {...defaultProps}
+ replicationTarget={Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE}
+ />);
+
+ expect(repRemoteTarget.find('input#target-db-is-partitioned').exists()).toBe(false);
+ });
+
+ it('Does not show partitioned option for existing local database', () => {
+ const repLocalTarget = mount(<ReplicationTarget
+ {...defaultProps}
+ replicationTarget={Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE}
+ />);
+
+ expect(repLocalTarget.find('input#target-db-is-partitioned').exists()).toBe(false);
+ });
+
+ it('Shows partitioned option for new remote database', () => {
+ const repRemoteTarget = mount(<ReplicationTarget
+ {...defaultProps}
+ replicationTarget={Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE}
+ />);
+
+ expect(repRemoteTarget.find('input#target-db-is-partitioned').exists()).toBe(true);
+ });
+
+ it('Shows partitioned option for new local database', () => {
+ const repLocalTarget = mount(<ReplicationTarget
+ {...defaultProps}
+ replicationTarget={Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE}
+ allowNewPartitionedLocalDbs={false}
+ />);
+
+ var input = repLocalTarget.find('input#target-db-is-partitioned');
+ expect(input.exists()).toBe(true);
+ expect(input.prop("disabled")).toBeTruthy();
+
+ repLocalTarget.setProps({allowNewPartitionedLocalDbs: true});
+ input = repLocalTarget.find('input#target-db-is-partitioned');
+ expect(input.exists()).toBe(true);
+ expect(input.prop("disabled")).toBeFalsy();
+ });
+
+ });
+
+});
diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js
index 724a041..745a361 100644
--- a/app/addons/replication/actions.js
+++ b/app/addons/replication/actions.js
@@ -325,6 +325,13 @@
return authTypeAndCreds;
};
+const getTargetDatabasePartitioned = (createTargetParams) => {
+ if (createTargetParams && createTargetParams.partitioned === true) {
+ return true;
+ }
+ return false;
+};
+
export const getReplicationStateFrom = (id) => dispatch => {
dispatch({
type: ActionTypes.REPLICATION_FETCHING_FORM_STATE
@@ -365,6 +372,8 @@
stateDoc.targetAuthType = targetAuth.type;
stateDoc.targetAuth = targetAuth.creds;
+ stateDoc.targetDatabasePartitioned = getTargetDatabasePartitioned(doc.create_target_params);
+
dispatch({
type: ActionTypes.REPLICATION_SET_STATE_FROM_DOC,
options: stateDoc
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index b092e15..75dee5f 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -204,10 +204,11 @@
sourceAuthType,
sourceAuth,
targetAuthType,
- targetAuth
+ targetAuth,
+ targetDatabasePartitioned
}) => {
const username = getUsername();
- return addDocIdAndRev(replicationDocName, _rev, {
+ const replicationDoc = {
user_ctx: {
name: username,
roles: ['_admin', '_reader', '_writer']
@@ -229,7 +230,13 @@
}),
create_target: createTarget(replicationTarget),
continuous: continuous(replicationType),
- });
+ };
+ if (targetDatabasePartitioned) {
+ replicationDoc.create_target_params = {
+ partitioned: true
+ };
+ }
+ return addDocIdAndRev(replicationDocName, _rev, replicationDoc);
};
export const removeSensitiveUrlInfo = (url) => {
diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less
index 1619fbb..ec1c0ad 100644
--- a/app/addons/replication/assets/less/replication.less
+++ b/app/addons/replication/assets/less/replication.less
@@ -41,9 +41,29 @@
width: 400px;
}
+.replication__input-checkbox {
+ display: flex;
+ align-items: center;
+ width: 400px;
+ height: 55px;
+
+ input[type="checkbox"] {
+ width: auto;
+ margin-right: 0.65rem;
+ }
+
+ label {
+ padding-bottom: 10px;
+ }
+
+ &--disabled label {
+ cursor: not-allowed;
+ }
+}
+
.replication__input-label {
padding-right: 15px;
- width: 160px;
+ width: 165px;
text-align: right;
margin-top: 12px;
font-size: 14px;
diff --git a/app/addons/replication/components/newreplication.js b/app/addons/replication/components/newreplication.js
index 38ca807..08da3b3 100644
--- a/app/addons/replication/components/newreplication.js
+++ b/app/addons/replication/components/newreplication.js
@@ -138,6 +138,7 @@
const {
remoteTarget,
remoteSource,
+ replicationSource,
replicationTarget,
localTarget,
localSource,
@@ -174,7 +175,8 @@
}
//check if remote source/target URL is valid
- if (!isEmpty(remoteSource)) {
+ const isRemoteSource = replicationSource === Constants.REPLICATION_SOURCE.REMOTE;
+ if (isRemoteSource && !isEmpty(remoteSource)) {
let errorMessage = '';
try {
const url = new URL(remoteSource);
@@ -194,7 +196,9 @@
return false;
}
}
- if (!isEmpty(remoteTarget)) {
+ const isRemoteTarget = replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
+ replicationTarget === Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE;
+ if (isRemoteTarget && !isEmpty(remoteTarget)) {
let errorMessage = '';
try {
const url = new URL(remoteTarget);
@@ -244,7 +248,8 @@
sourceAuthType,
sourceAuth,
targetAuthType,
- targetAuth
+ targetAuth,
+ targetDatabasePartitioned
} = this.props;
let _rev;
@@ -268,7 +273,8 @@
sourceAuthType,
sourceAuth,
targetAuthType,
- targetAuth
+ targetAuth,
+ targetDatabasePartitioned
});
}
@@ -321,6 +327,8 @@
remoteSource,
remoteTarget,
localTarget,
+ targetDatabasePartitioned,
+ allowNewPartitionedLocalDbs,
updateFormField,
clearReplicationForm,
sourceAuthType,
@@ -354,8 +362,11 @@
databases={databases}
localTarget={localTarget}
remoteTarget={remoteTarget}
+ allowNewPartitionedLocalDbs={allowNewPartitionedLocalDbs}
+ targetDatabasePartitioned={targetDatabasePartitioned}
onRemoteTargetChange={updateFormField('remoteTarget')}
onLocalTargetChange={updateFormField('localTarget')}
+ onTargetDatabasePartitionedChange={updateFormField('targetDatabasePartitioned')}
/>
<ReplicationAuth
credentials={targetAuth}
diff --git a/app/addons/replication/components/target.js b/app/addons/replication/components/target.js
index 4cfe1c4..3bf3b5d 100644
--- a/app/addons/replication/components/target.js
+++ b/app/addons/replication/components/target.js
@@ -9,9 +9,11 @@
// 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 classnames from 'classnames';
+import PropTypes from 'prop-types';
import React from 'react';
+import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import Constants from '../constants';
import Components from '../../components/react-components';
import ReactSelect from 'react-select';
@@ -32,7 +34,7 @@
});
};
-const ReplicationTargetSelect = ({value, onChange}) => {
+const ReplicationTargetSelect = ({ value, onChange }) => {
return (
<div className="replication__section">
<div className="replication__input-label">
@@ -54,7 +56,7 @@
onChange: PropTypes.func.isRequired
};
-const RemoteTargetReplicationRow = ({onChange, value}) => {
+const RemoteTargetReplicationRow = ({ onChange, value }) => {
return (
<div>
<input
@@ -73,8 +75,8 @@
onChange: PropTypes.func.isRequired
};
-const ExistingLocalTargetReplicationRow = ({onChange, value, databases}) => {
- const options = databases.map(db => ({value: db, label: db}));
+const ExistingLocalTargetReplicationRow = ({ onChange, value, databases }) => {
+ const options = databases.map(db => ({ value: db, label: db }));
return (
<div id="replication-target-local" className="replication__input-react-select">
<ReactSelect
@@ -82,7 +84,7 @@
options={options}
placeholder="Database name"
clearable={false}
- onChange={({value}) => onChange(value)}
+ onChange={({ value }) => onChange(value)}
/>
</div>
);
@@ -94,7 +96,7 @@
onChange: PropTypes.func.isRequired
};
-const NewLocalTargetReplicationRow = ({onChange, value}) =>
+const NewLocalTargetReplicationRow = ({ onChange, value }) =>
<input
type="text"
className="replication__new-input"
@@ -144,7 +146,7 @@
let targetLabel = 'Name:';
if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
- replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
+ replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
targetLabel = 'New database:';
}
@@ -167,9 +169,62 @@
replicationTarget: PropTypes.string.isRequired
};
+const NewTargetDatabaseOptionsRow = ({
+ replicationTarget,
+ targetDatabasePartitioned,
+ onTargetDatabasePartitionedChange,
+ allowNewPartitionedLocalDbs
+}) => {
+ if (!replicationTarget) {
+ return null;
+ }
+
+ if (replicationTarget !== Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE &&
+ replicationTarget !== Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE) {
+ return null;
+ }
+
+ const disablePartitionedOption = replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE &&
+ !allowNewPartitionedLocalDbs;
+ let msg = disablePartitionedOption ? "Local server does not support partitioned databases" :
+ "Creates a new partitioned database";
+ const tooltip = <Tooltip id="new-db-partitioned-tooltip">{msg}</Tooltip>;
+ const togglePartitioned = () => {
+ onTargetDatabasePartitionedChange(!targetDatabasePartitioned);
+ };
+
+ return (
+ <div className="replication__section">
+ <div className="replication__input-label">New database options:</div>
+ <div className={classnames('replication__input-checkbox', { 'replication__input-checkbox--disabled': disablePartitionedOption})}>
+
+ <input id="target-db-is-partitioned"
+ type="checkbox"
+ value="true"
+ checked={targetDatabasePartitioned}
+ onChange={togglePartitioned}
+ disabled={disablePartitionedOption}
+ />
+
+
+ <OverlayTrigger placement="right" overlay={tooltip}>
+ <label htmlFor="target-db-is-partitioned" >Partitioned</label>
+ </OverlayTrigger>
+ </div >
+ </div>
+ );
+};
+
+NewTargetDatabaseOptionsRow.propTypes = {
+ onTargetDatabasePartitionedChange: PropTypes.func.isRequired,
+ targetDatabasePartitioned: PropTypes.bool.isRequired,
+ replicationTarget: PropTypes.string.isRequired,
+ allowNewPartitionedLocalDbs: PropTypes.bool.isRequired
+};
+
export class ReplicationTarget extends React.Component {
- render () {
+ render() {
const {
replicationTarget,
onLocalTargetChange,
@@ -177,7 +232,10 @@
databases,
localTarget,
onRemoteTargetChange,
- remoteTarget
+ remoteTarget,
+ targetDatabasePartitioned,
+ onTargetDatabasePartitionedChange,
+ allowNewPartitionedLocalDbs
} = this.props;
return (
<div>
@@ -194,6 +252,12 @@
onRemoteTargetChange={onRemoteTargetChange}
onLocalTargetChange={onLocalTargetChange}
/>
+ <NewTargetDatabaseOptionsRow
+ replicationTarget={replicationTarget}
+ onTargetDatabasePartitionedChange={onTargetDatabasePartitionedChange}
+ targetDatabasePartitioned={targetDatabasePartitioned}
+ allowNewPartitionedLocalDbs={allowNewPartitionedLocalDbs}
+ />
</div>
);
}
diff --git a/app/addons/replication/container.js b/app/addons/replication/container.js
index b146843..5be9079 100644
--- a/app/addons/replication/container.js
+++ b/app/addons/replication/container.js
@@ -56,8 +56,10 @@
someReplicateSelected
} from './reducers';
-const mapStateToProps = ({replication}, ownProps) => {
+const mapStateToProps = ({replication, databases}, ownProps) => {
return {
+ allowNewPartitionedLocalDbs: databases.partitionedDatabasesAvailable,
+
routeLocalSource: ownProps.routeLocalSource,
replicationId: ownProps.replicationId,
tabSection: ownProps.section,
@@ -80,6 +82,7 @@
remoteTarget: getRemoteTarget(replication),
targetAuthType: replication.targetAuthType,
targetAuth: replication.targetAuth,
+ targetDatabasePartitioned: replication.targetDatabasePartitioned,
// other
isConflictModalVisible: isConflictModalVisible(replication),
diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js
index cf948d1..0c7cc00 100644
--- a/app/addons/replication/controller.js
+++ b/app/addons/replication/controller.js
@@ -70,7 +70,7 @@
hideConflictModal, isConflictModalVisible, filterDocs,
filterReplicate, replicate, clearReplicationForm, selectAllDocs, changeActivitySort, selectDoc,
deleteDocs, deleteReplicates, selectAllReplicates, selectReplicate,
- sourceAuthType, sourceAuth, targetAuthType, targetAuth
+ sourceAuthType, sourceAuth, targetAuthType, targetAuth, targetDatabasePartitioned, allowNewPartitionedLocalDbs
} = this.props;
if (tabSection === 'new replication') {
@@ -97,6 +97,8 @@
sourceAuth={sourceAuth}
targetAuthType={targetAuthType}
targetAuth={targetAuth}
+ targetDatabasePartitioned={targetDatabasePartitioned}
+ allowNewPartitionedLocalDbs={allowNewPartitionedLocalDbs}
updateFormField={updateFormField}
conflictModalVisible={isConflictModalVisible}
hideConflictModal={hideConflictModal}
diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js
index 15b6a53..40a276a 100644
--- a/app/addons/replication/reducers.js
+++ b/app/addons/replication/reducers.js
@@ -46,7 +46,8 @@
sourceAuthType: 'sourceAuthType',
sourceAuth: 'sourceAuth',
targetAuthType: 'targetAuthType',
- targetAuth: 'targetAuth'
+ targetAuth: 'targetAuth',
+ targetDatabasePartitioned: 'targetDatabasePartitioned'
};
const initialState = {
@@ -66,6 +67,7 @@
remoteTarget: '',
targetAuthType: Constants.REPLICATION_AUTH_METHOD.NO_AUTH,
targetAuth: {},
+ targetDatabasePartitioned: false,
// other
isConflictModalVisible: false,
@@ -97,6 +99,8 @@
Object.values(validFieldMap).forEach(field => {
if (field === 'sourceAuth' || field === 'targetAuth') {
newState[field] = {};
+ } else if (field === 'targetDatabasePartitioned') {
+ newState[field] = false;
} else {
newState[field] = '';
}