[partitioned-dbs] Partition key selector (#1128)

* Set partitionedDatabasesAvailable flag on redux store

* Add PartitionKeySelector component

* Set up PartitionKeySelector based on routes

* Add tests
diff --git a/app/addons/databases/actions.js b/app/addons/databases/actions.js
index 6b1c570..1935491 100644
--- a/app/addons/databases/actions.js
+++ b/app/addons/databases/actions.js
@@ -199,12 +199,14 @@
   },
 
   setPartitionedDatabasesAvailable(available) {
-    FauxtonAPI.dispatch({
+    const action = {
       type: ActionTypes.DATABASES_PARTITIONED_DB_AVAILABLE,
       options: {
         available
       }
-    });
+    };
+    FauxtonAPI.dispatch(action);
+    FauxtonAPI.reduxDispatch(action);
   },
 
   checkPartitionedQueriesIsAvailable() {
diff --git a/app/addons/databases/base.js b/app/addons/databases/base.js
index 92f2e75..c4218d4 100644
--- a/app/addons/databases/base.js
+++ b/app/addons/databases/base.js
@@ -32,7 +32,7 @@
   // Checks if the CouchDB server supports Partitioned Databases
   return get(Helpers.getServerUrl("/")).then((couchdb) => {
     //TODO: needs to be updated with the correct feature name
-    return couchdb.features && couchdb.features.includes('partitioned-dbs');
+    return couchdb.features && couchdb.features.includes('partitions');
   }).catch(() => {
     return false;
   });
diff --git a/app/addons/databases/reducers.js b/app/addons/databases/reducers.js
new file mode 100644
index 0000000..74da7c4
--- /dev/null
+++ b/app/addons/databases/reducers.js
@@ -0,0 +1,27 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import ActionTypes from './actiontypes';
+const initialState = {
+  partitionedDatabasesAvailable: false
+};
+export default function databases(state = initialState, action) {
+  switch (action.type) {
+    case ActionTypes.DATABASES_PARTITIONED_DB_AVAILABLE:
+      return {
+        ...state,
+        partitionedDatabasesAvailable: action.options.available
+      };
+    default:
+      return state;
+  }
+}
diff --git a/app/addons/documents/__tests__/partition-key.js b/app/addons/documents/__tests__/partition-key.js
new file mode 100644
index 0000000..75005e9
--- /dev/null
+++ b/app/addons/documents/__tests__/partition-key.js
@@ -0,0 +1,122 @@
+// 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 ReactDOM from 'react-dom';
+import { shallow } from 'enzyme';
+import PartitionKeySelector from '../partition-key/PartitionKeySelector';
+import sinon from 'sinon';
+
+describe('PartitionKeySelector', () => {
+  // const props = {
+  //   includeDocs: false,
+  //   queryOptionsToggleIncludeDocs: () => {},
+  //   reduce: false,
+  //   contentVisible: true,
+  //   perPage: 10,
+  //   queryOptionsToggleStable: () => {},
+  //   queryOptionsChangeUpdate: () => {},
+  //   stable: false,
+  //   update: 'true'
+  // };
+
+  const defaultProps = {
+    selectorVisible: true,
+    partitionKey: '',
+    checkDbPartitioned: () => {},
+    onPartitionKeySelected: () => {}
+  };
+
+  it('is only rendered when selectorVisible is set to true', () => {
+    const wrapper = shallow(<PartitionKeySelector
+      {...defaultProps}
+      selectorVisible={false}
+    />);
+    expect(wrapper.find('div.partition-selector').exists()).toBe(false);
+
+    const wrapper2 = shallow(<PartitionKeySelector
+      {...defaultProps}
+      selectorVisible={true}
+    />);
+    expect(wrapper2.find('div.partition-selector').exists()).toBe(true);
+  });
+
+  it('uses global mode by default', () => {
+    const wrapper = shallow(<PartitionKeySelector
+      {...defaultProps}
+      partitionKey='part1'
+    />);
+
+    const btLabel = wrapper.find('div').text();
+    expect(btLabel).toMatch('No partition selected');
+  });
+
+  it('switches from global mode to partition mode when a key is already set', () => {
+    const spyOnKeySelected = sinon.spy();
+    const wrapper = shallow(<PartitionKeySelector
+      {...defaultProps}
+      partitionKey='part1'
+      globalMode={true}
+      onPartitionKeySelected={spyOnKeySelected}
+    />);
+
+    wrapper.find('button').simulate('click');
+    expect(spyOnKeySelected.calledOnce).toBe(true);
+  });
+
+  it('if a key is not set switching from global mode shows the text input', () => {
+    const spyOnKeySelected = sinon.spy();
+    const wrapper = shallow(<PartitionKeySelector
+      {...defaultProps}
+      partitionKey=''
+      globalMode={true}
+      onPartitionKeySelected={spyOnKeySelected}
+    />);
+
+    expect(wrapper.state().editMode).toBe(false);
+    wrapper.find('button').simulate('click');
+    expect(wrapper.state().editMode).toBe(true);
+    expect(spyOnKeySelected.calledOnce).toBe(false);
+  });
+
+  it('switches from partition mode to global mode', () => {
+    const spyOnGlobalSelected = sinon.spy();
+    const wrapper = shallow(<PartitionKeySelector
+      {...defaultProps}
+      partitionKey='part1'
+      globalMode={false}
+      onGlobalModeSelected={spyOnGlobalSelected}
+    />);
+
+    wrapper.find('button').simulate('click');
+    expect(spyOnGlobalSelected.calledOnce).toBe(true);
+  });
+
+  it('calls onPartitionKeySelected when a new value is set in the text input', () => {
+    const spyOnKeySelected = sinon.spy();
+    const wrapper = shallow(<PartitionKeySelector
+      {...defaultProps}
+      partitionKey=''
+      globalMode={true}
+      onPartitionKeySelected={spyOnKeySelected}
+    />);
+
+    // Start edit mode
+    wrapper.find('button').simulate('click');
+    expect(wrapper.state().editMode).toBe(true);
+    // Set new value
+    wrapper.find('input').simulate('change', { target: { value: 'new_part_key' } });
+    wrapper.find('input').simulate('keypress', {key: 'Enter'});
+    expect(spyOnKeySelected.calledOnce).toBe(true);
+  });
+
+});
diff --git a/app/addons/documents/assets/less/documents.less b/app/addons/documents/assets/less/documents.less
index be7f460..c161e9b 100644
--- a/app/addons/documents/assets/less/documents.less
+++ b/app/addons/documents/assets/less/documents.less
@@ -22,6 +22,7 @@
 @import "header.less";
 @import "revision-browser.less";
 @import "header-docs-left.less";
+@import "partition-key.less";
 
 
 button.beautify {
diff --git a/app/addons/documents/assets/less/partition-key.less b/app/addons/documents/assets/less/partition-key.less
new file mode 100644
index 0000000..bf8d1a9
--- /dev/null
+++ b/app/addons/documents/assets/less/partition-key.less
@@ -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 "../../../../../assets/less/variables.less";
+
+.partition-selector {
+  display: flex;
+  align-items: center;
+  padding: 0;
+  min-height: 30px;
+}
+
+.partition-selector__switch {
+  background-color: transparent;
+  border: none;
+  color: #666;
+  font-size: 1rem;
+  .fonticon-filter {
+    font-size: 1.25rem;
+    padding-right: 6px;
+  }
+  &:hover {
+    color: @hoverHighlight;
+  }
+}
+
+.partition-selector__switch--active {
+  color: @brandHighlight;
+  padding-right: 0px;
+}
+
+.partition-selector__key {
+  flex: 1;
+  color: #666;
+}
+
+.partition-selector__key--active {
+  color: @brandHighlight;
+}
+
+.partition-selector__global {
+  color: #666;
+}
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index 144be20..6809d5f 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -17,6 +17,7 @@
 import reducers from "./index-results/reducers";
 import mangoReducers from "./mango/mango.reducers";
 import sidebarReducers from "./sidebar/reducers";
+import partitionKeyReducers from "./partition-key/reducers";
 import revisionBrowserReducers from './rev-browser/reducers';
 import "./assets/less/documents.less";
 
@@ -24,7 +25,8 @@
   indexResults: reducers,
   mangoQuery: mangoReducers,
   sidebar: sidebarReducers,
-  revisionBrowser: revisionBrowserReducers
+  revisionBrowser: revisionBrowserReducers,
+  partitionKey: partitionKeyReducers
 });
 
 function getQueryParam (query) {
@@ -49,6 +51,12 @@
   }
 });
 
+FauxtonAPI.registerUrls('partitioned_allDocs', {
+  app: function (databaseName, partitionKey, query) {
+    return 'database/' + databaseName + '/_partition/' + partitionKey + '/_all_docs' + getQueryParam(query);
+  }
+});
+
 FauxtonAPI.registerUrls('allDocsSanitized', {
   server: function (id, query) {
     id = encodeURIComponent(id);
@@ -124,6 +132,18 @@
   }
 });
 
+FauxtonAPI.registerUrls('partitioned_view', {
+  server: function (database, partitionKey, designDoc, viewName) {
+    return Helpers.getServerUrl('/' + database + '/_partition/' + partitionKey + '/_design/' + designDoc + '/_view/' + viewName);
+  },
+  app: function (database, partitionKey, designDoc) {
+    return 'database/' + database + '/_partition/' + partitionKey + '/_design/' + designDoc + '/_view/';
+  },
+  apiurl: function (database, partitionKey, designDoc, viewName) {
+    return Helpers.getApiUrl('/' + database + '/_partition/' + partitionKey + '/_design/' + designDoc + '/_view/' + viewName);
+  }
+});
+
 FauxtonAPI.registerUrls('document', {
   server: function (database, doc, query) {
     if (_.isUndefined(query)) {
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 8a0c05c..bd6f680 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -24,6 +24,7 @@
 import PaginationContainer from './index-results/containers/PaginationContainer';
 import ApiBarContainer from './index-results/containers/ApiBarContainer';
 import { queryAllDocs, queryMapReduceView } from './index-results/api';
+import PartitionKeySelectorContainer from './partition-key/container';
 import Constants from './constants';
 import Helpers from './helpers';
 
@@ -39,8 +40,21 @@
   fetchUrl,
   ddocsOnly,
   queryDocs,
-  selectedNavItem
+  selectedNavItem,
+  showPartitionKeySelector,
+  partitionKey,
+  onPartitionKeySelected,
+  onGlobalModeSelected,
+  globalMode
 }) => {
+  const partKeySelector = (<PartitionKeySelectorContainer
+    databaseName={dbName}
+    partitionKey={partitionKey}
+    onPartitionKeySelected={onPartitionKeySelected}
+    onGlobalModeSelected={onGlobalModeSelected}
+    globalMode={globalMode}/>
+  );
+
   return (
     <header className="two-panel-header">
       <div className="flex-layout flex-row">
@@ -51,6 +65,9 @@
           />
         </div>
         <div className="right-header-wrapper flex-layout flex-row flex-body">
+          <div style={{flex:1, padding: '18px 6px 12px 12px'}}>
+            {showPartitionKeySelector ? partKeySelector : null}
+          </div>
           <div id="right-header" className="flex-fill">
             <RightAllDocsHeader
               hideQueryOptions={hideQueryOptions}
@@ -81,11 +98,17 @@
   hideJumpToDoc: PropTypes.bool,
   database: PropTypes.object.isRequired,
   queryDocs: PropTypes.func,
-  selectedNavItem: PropTypes.object
+  selectedNavItem: PropTypes.object,
+  showPartitionKeySelector: PropTypes.bool,
+  partitionKey: PropTypes.string,
+  onPartitionKeySelected: PropTypes.func.isRequired,
+  onGlobalModeSelected: PropTypes.bool,
+  globalMode: PropTypes.bool
 };
 
 TabsSidebarHeader.defaultProps = {
-  hideHeaderBar: false
+  hideHeaderBar: false,
+  showPartitionKeySelector: false
 };
 
 export const TabsSidebarContent = ({
@@ -140,7 +163,11 @@
   fetchUrl,
   ddocsOnly,
   deleteEnabled = true,
-  selectedNavItem
+  selectedNavItem,
+  partitionKey,
+  onPartitionKeySelected,
+  onGlobalModeSelected,
+  globalMode
 }) => {
   let queryDocs = (params) => { return queryAllDocs(fetchUrl, params); };
   if (Helpers.isViewSelected(selectedNavItem)) {
@@ -169,6 +196,11 @@
         ddocsOnly={ddocsOnly}
         queryDocs={queryDocs}
         selectedNavItem={selectedNavItem}
+        showPartitionKeySelector={!ddocsOnly}
+        partitionKey={partitionKey}
+        onPartitionKeySelected={onPartitionKeySelected}
+        onGlobalModeSelected={onGlobalModeSelected}
+        globalMode={globalMode}
       />
       <TabsSidebarContent
         lowerContent={lowerContent}
diff --git a/app/addons/documents/partition-key/PartitionKeySelector.js b/app/addons/documents/partition-key/PartitionKeySelector.js
new file mode 100644
index 0000000..1bd8b5f
--- /dev/null
+++ b/app/addons/documents/partition-key/PartitionKeySelector.js
@@ -0,0 +1,139 @@
+// 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 ReactDOM from "react-dom";
+
+
+export default class PartitionKeySelector extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      editorValue: '',
+      editMode: false
+    };
+    this.onModeSwitchClick = this.onModeSwitchClick.bind(this);
+    this.onBlur = this.onBlur.bind(this);
+    this.startEdit = this.startEdit.bind(this);
+    this.onKeyPress = this.onKeyPress.bind(this);
+    this.onChange = this.onChange.bind(this);
+    this.props.checkDbPartitioned(this.props.databaseName);
+  }
+
+  onModeSwitchClick(e) {
+    if (e && e.preventDefault) {
+      e.preventDefault();
+    }
+    if (!this.props.globalMode && this.props.onGlobalModeSelected) {
+      this.props.onGlobalModeSelected();
+    } else if (this.props.partitionKey) {
+      this.props.onPartitionKeySelected(this.state.editorValue);
+    } else {
+      this.startEdit();
+    }
+  }
+
+  startEdit() {
+    this.setState({editMode: true, editorValue: this.props.partitionKey});
+    setTimeout(() => this.textInput.focus());
+  }
+
+  onBlur(e) {
+    if (e && e.preventDefault) {
+      e.preventDefault();
+    }
+    this.setState({editMode: false});
+  }
+
+  onKeyPress(e) {
+    if (e.key === 'Enter') {
+      this.setState({
+        editMode: false
+      });
+      this.props.onPartitionKeySelected(this.state.editorValue);
+    }
+  }
+
+  isPartitionSelected() {
+    return !this.state.global && (this.props.partitionKey.trim().length > 0);
+  }
+
+  onChange(e) {
+    this.setState({editorValue: e.target.value});
+  }
+
+  globalHeader() {
+    return (
+      <button onClick={this.onModeSwitchClick} title="Partition Key Selector" className="button partition-selector__switch">
+        <i className="fonticon-filter"></i>
+        No partition selected
+      </button>
+    );
+  }
+
+  partitionHeader() {
+    const editor = (
+      <input type="text"
+        style={{padding:2, fontSize:16, margin: 0, display: this.state.editMode ? 'block' : 'none'}}
+        onKeyPress={this.onKeyPress}
+        onChange={this.onChange}
+        onBlur={this.onBlur}
+        value={this.state.editorValue}
+        ref={(input) => { this.textInput = input; }} />
+    );
+    let partName = 'Click to select a partition';
+    let className = 'partition-selector__key';
+    if (this.props.partitionKey !== '') {
+      partName = this.props.partitionKey;
+      className += ' partition-selector__key--active';
+    }
+    return (
+      <React.Fragment>
+        <button onClick={this.onModeSwitchClick} title="Partition Key Selector" className="button partition-selector__switch button partition-selector__switch--active">
+          <i className="fonticon-filter"></i>
+        </button>
+        <div className={className}>
+          <span onClick={this.startEdit} style={{display: this.state.editMode ? 'none' : 'block'}}>{partName}</span>
+          {editor}
+        </div>
+      </React.Fragment>
+    );
+  }
+
+  render() {
+    if (!this.props.selectorVisible) {
+      return null;
+    }
+    const global = this.props.globalMode && !this.state.editMode;
+    return (
+      <div className="partition-selector" >
+        {global ? this.globalHeader() : this.partitionHeader()}
+      </div>
+    );
+  }
+}
+
+PartitionKeySelector.defaultProps = {
+  partitionKey: '',
+  globalMode: true
+};
+
+PartitionKeySelector.propTypes = {
+  selectorVisible: PropTypes.bool.isRequired,
+  partitionKey: PropTypes.string.isRequired,
+  checkDbPartitioned: PropTypes.func.isRequired,
+  onPartitionKeySelected: PropTypes.func.isRequired,
+  onGlobalModeSelected: PropTypes.func,
+  globalMode: PropTypes.bool
+};
diff --git a/app/addons/documents/partition-key/actions.js b/app/addons/documents/partition-key/actions.js
new file mode 100644
index 0000000..ebf076c
--- /dev/null
+++ b/app/addons/documents/partition-key/actions.js
@@ -0,0 +1,29 @@
+// 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 * as API from './api';
+import ActionTypes from './actiontypes';
+
+export const checkDbPartitioned = (databaseName) => (dispatch) => {
+  //Reset visibility to false
+  dispatch({
+    type: ActionTypes.PARTITON_KEY_HIDE_SELECTOR
+  });
+
+  API.fetchDatabaseInfo(databaseName).then(dbInfo => {
+    if (dbInfo.props && dbInfo.props.partitioned === true) {
+      dispatch({
+        type: ActionTypes.PARTITON_KEY_SHOW_SELECTOR
+      });
+    }
+  });
+};
diff --git a/app/addons/documents/partition-key/actiontypes.js b/app/addons/documents/partition-key/actiontypes.js
new file mode 100644
index 0000000..76368ef
--- /dev/null
+++ b/app/addons/documents/partition-key/actiontypes.js
@@ -0,0 +1,15 @@
+// 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.
+export default {
+  PARTITON_KEY_SHOW_SELECTOR: 'PARTITON_KEY_SHOW_SELECTOR',
+  PARTITON_KEY_HIDE_SELECTOR: 'PARTITON_KEY_HIDE_SELECTOR'
+};
diff --git a/app/addons/documents/partition-key/api.js b/app/addons/documents/partition-key/api.js
new file mode 100644
index 0000000..720d69f
--- /dev/null
+++ b/app/addons/documents/partition-key/api.js
@@ -0,0 +1,20 @@
+// 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 '@webcomponents/url';
+import {get} from '../../../core/ajax';
+import Helpers from "../../../helpers";
+
+export const fetchDatabaseInfo = (databaseName) => {
+  const url = Helpers.getServerUrl("/" + encodeURIComponent(databaseName));
+  return get(url);
+};
diff --git a/app/addons/documents/partition-key/container.js b/app/addons/documents/partition-key/container.js
new file mode 100644
index 0000000..0a1d373
--- /dev/null
+++ b/app/addons/documents/partition-key/container.js
@@ -0,0 +1,49 @@
+// 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 { connect } from 'react-redux';
+import PartitionKeySelector from './PartitionKeySelector';
+import { checkDbPartitioned } from './actions';
+
+const mapStateToProps = ({ partitionKey }, ownProps) => {
+  return {
+    selectorVisible: partitionKey.selectorVisible,
+    databaseName: ownProps.databaseName,
+    partitionKey: ownProps.partitionKey,
+    onPartitionKeySelected: ownProps.onPartitionKeySelected,
+    globalMode: ownProps.globalMode
+  };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    checkDbPartitioned: (databaseName) => {
+      dispatch(checkDbPartitioned(databaseName));
+    }
+  };
+};
+
+const PartitionKeySelectorContainer = connect (
+  mapStateToProps,
+  mapDispatchToProps
+)(PartitionKeySelector);
+
+export default PartitionKeySelectorContainer;
+
+PartitionKeySelectorContainer.propTypes = {
+  databaseName: PropTypes.string.isRequired,
+  partitionKey: PropTypes.string.isRequired,
+  onPartitionKeySelected: PropTypes.func.isRequired,
+  onGlobalModeSelected: PropTypes.func.isRequired,
+  globalMode: PropTypes.bool
+};
diff --git a/app/addons/documents/partition-key/reducers.js b/app/addons/documents/partition-key/reducers.js
new file mode 100644
index 0000000..8da4ad3
--- /dev/null
+++ b/app/addons/documents/partition-key/reducers.js
@@ -0,0 +1,37 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import ActionTypes from './actiontypes';
+
+const initialState = {
+  selectorVisible: false
+};
+
+export default function partitionKey(state = initialState, action) {
+  switch (action.type) {
+
+    case ActionTypes.PARTITON_KEY_SHOW_SELECTOR:
+      return {
+        ...state,
+        selectorVisible: true
+      };
+
+    case ActionTypes.PARTITON_KEY_HIDE_SELECTOR:
+      return {
+        ...state,
+        selectorVisible: false
+      };
+
+    default:
+      return state;
+  }
+}
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index 453003a..41c7493 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -23,8 +23,12 @@
 
 var DocumentsRouteObject = BaseRoute.extend({
   routes: {
+    "database/:database/_partition/:partitionkey/_all_docs": {
+      route: "partitionedAllDocs",
+      roles: ["fx_loggedIn"]
+    },
     "database/:database/_all_docs(:extra)": {
-      route: "allDocs",
+      route: "globalAllDocs",
       roles: ["fx_loggedIn"]
     },
     "database/:database/_design/:ddoc/_info": {
@@ -70,12 +74,20 @@
     />;
   },
 
+  globalAllDocs: function (databaseName, options) {
+    return this.allDocs(databaseName, '', options);
+  },
+
+  partitionedAllDocs: function (databaseName, partitionKey) {
+    return this.allDocs(databaseName, partitionKey);
+  },
+
   /*
   * docParams are the options fauxton uses to fetch from the server
   * urlParams are what are shown in the url and to the user
   * They are not the same when paginating
   */
-  allDocs: function (databaseName, options) {
+  allDocs: function (databaseName, partitionKey, options) {
     const params = this.createParams(options),
           docParams = params.docParams;
 
@@ -95,7 +107,15 @@
 
     const endpoint = this.database.allDocs.urlRef("apiurl", {});
     const docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
-
+    const navigateToPartitionedAllDocs = (partKey) => {
+      const baseUrl = FauxtonAPI.urls('partitioned_allDocs', 'app', encodeURIComponent(databaseName),
+        encodeURIComponent(partKey));
+      FauxtonAPI.navigate('#/' + baseUrl);
+    };
+    const navigateToGlobalAllDocs = () => {
+      const baseUrl = FauxtonAPI.urls('allDocs', 'app', encodeURIComponent(databaseName));
+      FauxtonAPI.navigate('#/' + baseUrl);
+    };
     const dropDownLinks = this.getCrumbs(this.database);
     return <DocsTabsSidebarLayout
       docURL={docURL}
@@ -107,6 +127,10 @@
       fetchUrl={url}
       ddocsOnly={onlyShowDdocs}
       selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
+      onPartitionKeySelected={navigateToPartitionedAllDocs}
+      onGlobalModeSelected={navigateToGlobalAllDocs}
+      globalMode={partitionKey === ''}
     />;
   },
 
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 935ff47..1485b56 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -29,8 +29,12 @@
       route: 'createView',
       roles: ['fx_loggedIn']
     },
+    'database/:database/_partition/:partitionkey/_design/:ddoc/_view/:view': {
+      route: 'showPartitionedView',
+      roles: ['fx_loggedIn']
+    },
     'database/:database/_design/:ddoc/_view/:view': {
-      route: 'showView',
+      route: 'showGlobalView',
       roles: ['fx_loggedIn']
     },
     'database/:database/_design/:ddoc/_view/:view/edit': {
@@ -47,8 +51,15 @@
     this.addSidebar();
   },
 
-  showView: function (databaseName, ddoc, viewName) {
+  showGlobalView: function (databaseName, ddoc, viewName) {
+    return this.showView(databaseName, '', ddoc, viewName);
+  },
 
+  showPartitionedView: function (databaseName, partitionKey, ddoc, viewName) {
+    return this.showView(databaseName, partitionKey, ddoc, viewName);
+  },
+
+  showView: function (databaseName, partitionKey, ddoc, viewName) {
     viewName = viewName.replace(/\?.*$/, '');
 
     ActionsIndexEditor.clearIndex();
@@ -72,7 +83,11 @@
     const endpoint = FauxtonAPI.urls('view', 'apiurl', encodeURIComponent(databaseName),
       encodeURIComponent(ddoc), encodeURIComponent(viewName));
     const docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
-
+    const navigateToPartitionedView = (partKey) => {
+      const baseUrl = FauxtonAPI.urls('partitioned_view', 'app', encodeURIComponent(databaseName),
+        encodeURIComponent(partKey), encodeURIComponent(ddoc));
+      FauxtonAPI.navigate('#/' + baseUrl + encodeURIComponent(viewName));
+    };
     const dropDownLinks = this.getCrumbs(this.database);
     return <DocsTabsSidebarLayout
       docURL={docURL}
@@ -84,6 +99,9 @@
       ddocsOnly={false}
       deleteEnabled={false}
       selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
+      onPartitionKeySelected={navigateToPartitionedView}
+      globalMode={partitionKey === ''}
     />;
   },