You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by GitBox <gi...@apache.org> on 2018/11/06 21:03:57 UTC

[GitHub] Antonio-Maranhao closed pull request #1150: WIP - [partitioned dbs] Show results for partitioned views and All Docs filtered by partition key

Antonio-Maranhao closed pull request #1150: WIP - [partitioned dbs] Show results for partitioned views and All Docs filtered by partition key
URL: https://github.com/apache/couchdb-fauxton/pull/1150
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/app/addons/databases/actions.js b/app/addons/databases/actions.js
index d8927af50..85b1a2ece 100644
--- a/app/addons/databases/actions.js
+++ b/app/addons/databases/actions.js
@@ -16,7 +16,6 @@ import { get } from "../../core/ajax";
 import DatabasesBase from '../databases/base';
 import Stores from "./stores";
 import ActionTypes from "./actiontypes";
-import Resources from "./resources";
 import * as API from './api';
 
 function getDatabaseDetails (dbList, fullDbList) {
@@ -154,7 +153,7 @@ export default {
         type: 'success',
         clear: true
       });
-      const route = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(databaseName), '?limit=' + Resources.DocLimit);
+      const route = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(databaseName));
       app.router.navigate(route, { trigger: true });
     }
     ).fail(function (xhr) {
diff --git a/app/addons/databases/base.js b/app/addons/databases/base.js
index 2a082bbab..9f711c8d4 100644
--- a/app/addons/databases/base.js
+++ b/app/addons/databases/base.js
@@ -29,6 +29,10 @@ Databases.initialize = function () {
   Actions.checkPartitionedQueriesIsAvailable();
 };
 
+function partitionUrlComponent(partitionKey) {
+  return partitionKey ? `/_partition/${partitionKey}` : '';
+}
+
 function checkPartitionedDatabaseFeature () {
   // Checks if the CouchDB server supports Partitioned Databases
   return get(Helpers.getServerUrl("/")).then((couchdb) => {
@@ -63,8 +67,8 @@ FauxtonAPI.registerUrls('changes', {
     return Helpers.getServerUrl('/' + id + '/_changes' + query);
   },
 
-  app: function (id, query) {
-    return '/database/' + id + '/_changes' + query;
+  app: function (id, partitionKey, query) {
+    return '/database/' + id + partitionUrlComponent(partitionKey) + '/_changes' + query;
   },
 
   apiurl: function (id, query) {
@@ -103,8 +107,8 @@ FauxtonAPI.registerUrls('permissions', {
     return Helpers.getServerUrl('/' + db + '/_security');
   },
 
-  app: function (db) {
-    return '/database/' + db + '/permissions';
+  app: function (db, partitionKey) {
+    return '/database/' + db + partitionUrlComponent(partitionKey) + '/permissions';
   },
 
   apiurl: function (db) {
diff --git a/app/addons/databases/resources.js b/app/addons/databases/resources.js
index 468532297..cd3af48a7 100644
--- a/app/addons/databases/resources.js
+++ b/app/addons/databases/resources.js
@@ -56,7 +56,7 @@ Databases.Model = FauxtonAPI.Model.extend({
     } else if (context === "apiurl") {
       return Helpers.getApiUrl("/database/" + this.safeID() + "/_all_docs");
     } else if (context === "changes") {
-      return FauxtonAPI.urls('changes', 'app', this.safeID(), '?descending=true&limit=100&include_docs=true');
+      return FauxtonAPI.urls('changes', 'app', this.safeID(), '', '?descending=true&limit=100&include_docs=true');
     } else if (context === "changes-apiurl") {
       return FauxtonAPI.urls('changes', 'apiurl', this.safeID(), '?descending=true&limit=100&include_docs=true');
     } else if (context === "app") {
diff --git a/app/addons/documents/__tests__/fetch-actions.test.js b/app/addons/documents/__tests__/fetch-actions.test.js
index ec7af92e5..7940487ec 100644
--- a/app/addons/documents/__tests__/fetch-actions.test.js
+++ b/app/addons/documents/__tests__/fetch-actions.test.js
@@ -181,10 +181,6 @@ describe('Docs Fetch API', () => {
   });
 
   describe('queryAllDocs', () => {
-    const params = {
-      limit: 21,
-      skip: 0
-    };
     const docs = {
       "total_rows": 2,
       "offset": 0,
@@ -207,12 +203,52 @@ describe('Docs Fetch API', () => {
     };
 
     it('queries _all_docs with default params', () => {
+      const params = {
+        limit: 21,
+        skip: 0
+      };
+      const fetchUrl = '/testdb/_all_docs';
+      const query = app.utils.queryString(params);
+      const url = `${fetchUrl}?${query}`;
+      fetchMock.getOnce(url, docs);
+
+      return queryAllDocs(fetchUrl, '', params).then((res) => {
+        expect(res).toEqual({
+          docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
+          docs: [
+            {
+              id: "foo",
+              key: "foo",
+              value: {
+                rev: "1-1390740c4877979dbe8998382876556c"
+              }
+            },
+            {
+              id: "foo2",
+              key: "foo2",
+              value: {
+                rev: "2-1390740c4877979dbe8998382876556c"
+              }
+            }]
+        });
+      });
+    });
+
+    it('queries _all_docs with a partition key', () => {
+      const partitionKey = 'key1';
+      const params = {
+        limit: 21,
+        skip: 0,
+        inclusive_end: false,
+        start_key: `"${partitionKey}:"`,
+        end_key: `"${partitionKey}:\ufff0"`
+      };
       const fetchUrl = '/testdb/_all_docs';
       const query = app.utils.queryString(params);
       const url = `${fetchUrl}?${query}`;
       fetchMock.getOnce(url, docs);
 
-      return queryAllDocs(fetchUrl, params).then((res) => {
+      return queryAllDocs(fetchUrl, partitionKey, params).then((res) => {
         expect(res).toEqual({
           docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
           docs: [
diff --git a/app/addons/documents/__tests__/results-toolbar.test.js b/app/addons/documents/__tests__/results-toolbar.test.js
index 09b4c9ca6..ffa91ab41 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -10,13 +10,13 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import sinon from "sinon";
-import utils from "../../../../test/mocha/testUtils";
-import FauxtonAPI from "../../../core/api";
-import {ResultsToolBar} from "../components/results-toolbar";
-import React from 'react';
-import ReactDOM from 'react-dom';
 import { mount } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+import utils from '../../../../test/mocha/testUtils';
+import FauxtonAPI from '../../../core/api';
+import {ResultsToolBar} from '../components/results-toolbar';
+import Constants from '../constants';
 
 describe('Results Toolbar', () => {
   const restProps = {
@@ -26,7 +26,10 @@ describe('Results Toolbar', () => {
     toggleSelectAll: () => {},
     isLoading: false,
     queryOptionsParams: {},
-    databaseName: 'mydb'
+    databaseName: 'mydb',
+    fetchUrl: '/db1/_all_docs',
+    docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
+    hasResults: true
   };
 
   beforeEach(() => {
@@ -55,4 +58,22 @@ describe('Results Toolbar', () => {
     const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps} partitionKey={'partKey1'}/>);
     expect(wrapper.find('a').prop('href')).toMatch(/\?partitionKey=partKey1$/);
   });
+
+  it('shows Table, Metadata and JSON modes when querying a global view', () => {
+    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps}
+      partitionKey={''} fetchUrl='/my_db/_design/ddoc1/_view/view1'/>);
+    expect(wrapper.find('button')).toHaveLength(3);
+  });
+
+  it('hides Table and JSON modes when querying a partitioned view', () => {
+    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps}
+      partitionKey={'partKey1'} fetchUrl='/my_db/_partition/my_partition/_design/ddoc1/_view/view1'/>);
+    expect(wrapper.find('button')).toHaveLength(1);
+  });
+
+  it('shows Table, Metadata and JSON modes when showing All Documents filtered by partition', () => {
+    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps}
+      partitionKey={'partKey1'} fetchUrl='/my_db/_all_docs'/>);
+    expect(wrapper.find('button')).toHaveLength(3);
+  });
 });
diff --git a/app/addons/documents/assets/less/index-results.less b/app/addons/documents/assets/less/index-results.less
index ead6b89c3..128ff7493 100644
--- a/app/addons/documents/assets/less/index-results.less
+++ b/app/addons/documents/assets/less/index-results.less
@@ -67,6 +67,12 @@ a.document-result-screen__toolbar-create-btn:visited {
     padding-top: 8px;
     margin: 0 auto;
   }
+  .no-results-screen-warning {
+    text-align: center;
+    i {
+      padding-right: 0.5rem;
+    } 
+  }
 }
 .watermark-logo {
   background: transparent url('../../../../../assets/img/couch-watermark.png') no-repeat 50% 50%;
@@ -220,4 +226,4 @@ a.document-result-screen__toolbar-create-btn:visited {
   .fonticon-attention-circled {
     margin-right: 4px;
   }
-}
\ No newline at end of file
+}
diff --git a/app/addons/documents/assets/less/sidenav.less b/app/addons/documents/assets/less/sidenav.less
index 1e623968d..df2ab1411 100644
--- a/app/addons/documents/assets/less/sidenav.less
+++ b/app/addons/documents/assets/less/sidenav.less
@@ -159,6 +159,9 @@
       text-overflow: ellipsis;
       overflow: hidden;
     }
+    i {
+      padding-right: 6px;
+    }
   }
 
   .index-group-header {
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index 147eb84f8..2f46daa3b 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -42,17 +42,18 @@ function getQueryParam (query) {
   return query;
 }
 
+function partitionUrlComponent(partitionKey) {
+  return partitionKey ? `/_partition/${partitionKey}` : '';
+}
+
 FauxtonAPI.registerUrls('allDocs', {
   server: function (id, query) {
-    /** XXX DEPRECATED: use allDocsSanitized **/
     return Helpers.getServerUrl('/' + id + '/_all_docs' + getQueryParam(query));
   },
-  app: function (id, query) {
-    /** XXX DEPRECATED: use allDocsSanitized **/
-    return 'database/' + id + '/_all_docs' + getQueryParam(query);
+  app: function (id, partitionKey, query) {
+    return 'database/' + id + partitionUrlComponent(partitionKey) + '/_all_docs' + getQueryParam(query);
   },
   apiurl: function (id, query) {
-    /** XXX DEPRECATED: use allDocsSanitized **/
     return Helpers.getApiUrl('/' + id + '/_all_docs' + getQueryParam(query));
   }
 });
@@ -103,8 +104,8 @@ FauxtonAPI.registerUrls('designDocs', {
     return Helpers.getServerUrl('/' + id + '/' + designDoc + '/_info');
   },
 
-  app: function (id, designDoc) {
-    return 'database/' + id + '/_design/' + app.utils.safeURLName(designDoc) + '/_info';
+  app: function (id, partitionKey, designDoc) {
+    return 'database/' + id + partitionUrlComponent(partitionKey) + '/_design/' + app.utils.safeURLName(designDoc) + '/_info';
   },
 
   apiurl: function (id, designDoc) {
@@ -113,20 +114,20 @@ FauxtonAPI.registerUrls('designDocs', {
 });
 
 FauxtonAPI.registerUrls('view', {
-  server: function (database, designDoc, viewName) {
-    return Helpers.getServerUrl('/' + database + '/_design/' + designDoc + '/_view/' + viewName);
+  server: function (database, partitionKey, designDoc, viewName) {
+    return Helpers.getServerUrl('/' + database + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_view/' + viewName);
   },
 
   app: function (database, designDoc) {
     return 'database/' + database + '/_design/' + designDoc + '/_view/';
   },
 
-  apiurl: function (id, designDoc, viewName) {
-    return Helpers.getApiUrl('/' + id + '/_design/' + designDoc + '/_view/' + viewName);
+  apiurl: function (id, partitionKey, designDoc, viewName) {
+    return Helpers.getApiUrl('/' + id + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_view/' + viewName);
   },
 
-  edit: function (database, designDoc, indexName) {
-    return 'database/' + database + '/_design/' + designDoc + '/_view/' + indexName + '/edit';
+  edit: function (database, partitionKey, designDoc, indexName) {
+    return 'database/' + database + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_view/' + indexName + '/edit';
   },
 
   showView: function (database, designDoc, viewName) {
@@ -187,12 +188,12 @@ FauxtonAPI.registerUrls('new', {
     return '/database/' + database + '/new';
   },
 
-  newView: function (database) {
-    return '/database/' + database + '/new_view';
+  newView: function (database, partitionKey) {
+    return '/database/' + database + partitionUrlComponent(partitionKey) + '/new_view';
   },
 
-  addView: function (database, ddoc) {
-    return '/database/' + database + '/new_view/' + ddoc;
+  addView: function (database, partitionKey, ddoc) {
+    return '/database/' + database + partitionUrlComponent(partitionKey) + '/new_view/' + ddoc;
   }
 });
 
@@ -252,12 +253,12 @@ FauxtonAPI.registerUrls('mango', {
     return Helpers.getApiUrl('/' + db + '/_find' + query);
   },
 
-  'query-app': function (db, query) {
+  'query-app': function (db, partitionKey, query) {
     if (!query) {
       query = '';
     }
 
-    return 'database/' + db + '/_find' + query;
+    return 'database/' + db + partitionUrlComponent(partitionKey) + '/_find' + query;
   },
 
   'explain-server': function (db) {
diff --git a/app/addons/documents/components/header-docs-left.js b/app/addons/documents/components/header-docs-left.js
index 8be0e5843..cca5693c4 100644
--- a/app/addons/documents/components/header-docs-left.js
+++ b/app/addons/documents/components/header-docs-left.js
@@ -10,8 +10,8 @@
 // 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";
 import Helpers from "../helpers";
 import FauxtonAPI from '../../../core/api';
 import { Dropdown } from "react-bootstrap";
@@ -20,8 +20,8 @@ function getModififyDbLinks (dbName) {
   return Helpers.getModifyDatabaseLinks(dbName);
 }
 
-function getAllDatabaseModalLinks (dbName) {
-  const dropdownMenuLinks = Helpers.getNewButtonLinks(dbName);
+function getAllDatabaseModalLinks (dbName, partitionKey) {
+  const dropdownMenuLinks = Helpers.getNewButtonLinks(dbName, partitionKey);
   return getModififyDbLinks(dbName).concat(dropdownMenuLinks);
 }
 
@@ -57,8 +57,8 @@ const Item = ({url, icon, onClick, children}) => {
   );
 };
 
-const HeaderDocsLeft = ({dbName}) => {
-  const items = getAllDatabaseModalLinks(dbName);
+const HeaderDocsLeft = ({dbName, partitionKey}) => {
+  const items = getAllDatabaseModalLinks(dbName, partitionKey);
   const dropdownItems = getDropdownItems(items);
 
   return (
@@ -81,4 +81,9 @@ const HeaderDocsLeft = ({dbName}) => {
   );
 };
 
+HeaderDocsLeft.propTypes = {
+  dbName: PropTypes.string.isRequired,
+  partitionKey: PropTypes.string
+};
+
 export default HeaderDocsLeft;
diff --git a/app/addons/documents/components/results-toolbar.js b/app/addons/documents/components/results-toolbar.js
index c7dbccc2c..5a9809d8b 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -83,5 +83,6 @@ ResultsToolBar.propTypes = {
   isLoading: PropTypes.bool.isRequired,
   hasResults: PropTypes.bool.isRequired,
   isListDeletable: PropTypes.bool,
-  partitionKey: PropTypes.string
+  partitionKey: PropTypes.string,
+  docType: PropTypes.string
 };
diff --git a/app/addons/documents/doc-editor/components/DocEditorScreen.js b/app/addons/documents/doc-editor/components/DocEditorScreen.js
index f49aef718..8edd15315 100644
--- a/app/addons/documents/doc-editor/components/DocEditorScreen.js
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -67,7 +67,7 @@ export default class DocEditorScreen extends React.Component {
 
     const docContent = this.props.doc.attributes;
     if (this.props.isDbPartitioned) {
-      if (!docContent._id.includes(':')) {
+      if (!docContent._id.includes(':') && !docContent._id.startsWith('_design')) {
         docContent._id = ':' + docContent._id;
       }
     }
diff --git a/app/addons/documents/header/header.js b/app/addons/documents/header/header.js
index ba7c3b4b5..0f8f20145 100644
--- a/app/addons/documents/header/header.js
+++ b/app/addons/documents/header/header.js
@@ -23,7 +23,9 @@ export default class BulkDocumentHeaderController extends React.Component {
     const {
       selectedLayout,
       docType,
-      queryOptionsParams
+      queryOptionsParams,
+      partitionKey,
+      fetchUrl
     } = this.props;
 
     let metadata, json, table;
@@ -38,10 +40,11 @@ export default class BulkDocumentHeaderController extends React.Component {
       return null;
     }
 
-    // reduce doesn't allow for include_docs=true, so we'll prevent JSON and table
-    // views since they force include_docs=true when reduce is checked in the
-    // query options panel.
-    if (!queryOptionsParams.reduce) {
+    // Reduce doesn't allow for include_docs=true, so we'll prevent JSON and table
+    // views since they force include_docs=true when reduce is checked in the query options panel.
+    // Partitioned queries don't supprt include_docs=true either.
+    const isAllDocsQuery = fetchUrl.includes('/_all_docs');
+    if (isAllDocsQuery || (!queryOptionsParams.reduce && !partitionKey)) {
       table = <Button
         className={selectedLayout === Constants.LAYOUT_ORIENTATION.TABLE ? 'active' : ''}
         onClick={this.toggleLayout.bind(this, Constants.LAYOUT_ORIENTATION.TABLE)}
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index 42879ac0b..69347aa79 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -21,27 +21,33 @@ const getSeqNum = (val) => {
   return _.isArray(val) ? val[1] : val;
 };
 
-const getNewButtonLinks = (databaseName) => {
+const getNewButtonLinks = (databaseName, partitionKey) => {
   const addLinks = FauxtonAPI.getExtensions('sidebar:links');
   const newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName));
+  let partitionKeyComponent = '';
+  let partitionKeyQueryParam = '';
+  if (partitionKey) {
+    partitionKeyComponent = '/_partition/' + encodeURIComponent(partitionKey);
+    partitionKeyQueryParam = '?partitionKey=' + encodeURIComponent(partitionKey);
+  }
 
-  const addNewLinks = _.reduce(addLinks, function (menuLinks, link) {
+  const addNewLinks = addLinks.reduce((menuLinks, link) => {
     menuLinks.push({
       title: link.title,
-      url: newUrlPrefix + '/' + link.url,
+      url: newUrlPrefix + partitionKeyComponent + '/' + link.url,
       icon: 'fonticon-plus-circled'
     });
 
     return menuLinks;
   }, [{
     title: 'New Doc',
-    url: newUrlPrefix + '/new',
+    url: newUrlPrefix + '/new' + partitionKeyQueryParam,
     icon: 'fonticon-plus-circled'
   }, {
     title: 'New View',
-    url: newUrlPrefix + '/new_view',
+    url: newUrlPrefix + partitionKeyComponent + '/new_view',
     icon: 'fonticon-plus-circled'
-  }, getMangoLink(databaseName)]);
+  }, getMangoLink(databaseName, partitionKey)]);
 
   return [{
     title: 'Add New',
@@ -49,8 +55,9 @@ const getNewButtonLinks = (databaseName) => {
   }];
 };
 
-const getMangoLink = (databaseName) => {
-  const newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName));
+const getMangoLink = (databaseName, partitionKey) => {
+  const newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName)) +
+    (partitionKey ? '/_partition/' + encodeURIComponent(partitionKey) : '');
 
   return {
     title: app.i18n.en_US['new-mango-index'],
diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js
index 22b9a9b81..c25fa99d4 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -127,7 +127,7 @@ function deleteView (options) {
 
     // if the user was on the index that was just deleted, redirect them back to all docs
     if (options.isOnIndex) {
-      var url = FauxtonAPI.urls('allDocs', 'app', options.database.safeID(), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT);
+      const url = FauxtonAPI.urls('allDocs', 'app', options.database.safeID());
       FauxtonAPI.navigate(url);
     }
 
@@ -186,8 +186,9 @@ function cloneView (params) {
   });
 }
 
-function gotoEditViewPage (databaseName, designDocName, indexName) {
+function gotoEditViewPage (databaseName, partitionKey, designDocName, indexName) {
   FauxtonAPI.navigate('#' + FauxtonAPI.urls('view', 'edit', encodeURIComponent(databaseName),
+    (partitionKey ? encodeURIComponent(partitionKey) : ''),
     encodeURIComponent(designDocName), encodeURIComponent(indexName)));
 }
 
diff --git a/app/addons/documents/index-results/actions/base.js b/app/addons/documents/index-results/actions/base.js
index 6fac0ffea..d96126649 100644
--- a/app/addons/documents/index-results/actions/base.js
+++ b/app/addons/documents/index-results/actions/base.js
@@ -13,6 +13,18 @@
 import ActionTypes from '../actiontypes';
 import { getDocId, getDocRev, isJSONDocBulkDeletable } from "../helpers/shared-helpers";
 
+export const partitionParamNotSupported = () => {
+  return {
+    type: ActionTypes.INDEX_RESULTS_REDUX_PARTITION_PARAM_NOT_SUPPORTED
+  };
+};
+
+export const partitionParamIsMandatory = () => {
+  return {
+    type: ActionTypes.INDEX_RESULTS_REDUX_PARTITION_PARAM_MANDATORY
+  };
+};
+
 export const nowLoading = () => {
   return {
     type: ActionTypes.INDEX_RESULTS_REDUX_IS_LOADING
diff --git a/app/addons/documents/index-results/actions/fetch.js b/app/addons/documents/index-results/actions/fetch.js
index fc8df4eb6..6fdb32c07 100644
--- a/app/addons/documents/index-results/actions/fetch.js
+++ b/app/addons/documents/index-results/actions/fetch.js
@@ -16,7 +16,7 @@ import Constants from '../../constants';
 import { errorReason } from '../helpers/shared-helpers';
 import * as IndexResultsAPI from '../api';
 import { nowLoading, newResultsAvailable, newSelectedDocs,
-  changeLayout, resetState } from './base';
+  changeLayout, resetState, partitionParamNotSupported, partitionParamIsMandatory } from './base';
 
 const maxDocLimit = 10000;
 
@@ -91,23 +91,29 @@ export const fetchDocs = (queryDocs, fetchParams, queryOptionsParams) => {
     dispatch(nowLoading());
 
     // now fetch the results
-    return queryDocs(params).then(({ docs, docType, executionStats, warning }) => {
+    return queryDocs(params).then(({ docs, docType, executionStats, warning, layout }) => {
       const {
         finalDocList,
         canShowNext
       } = removeOverflowDocsAndCalculateHasNext(docs, totalDocsRemaining, params.limit);
 
-      if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
-        dispatch(changeLayout(Constants.LAYOUT_ORIENTATION.JSON));
+      if (layout) {
+        dispatch(changeLayout(layout));
       }
       // dispatch that we're all done
       dispatch(newResultsAvailable(finalDocList, params, canShowNext, docType, executionStats, warning));
     }).catch((error) => {
-      FauxtonAPI.addNotification({
-        msg: 'Error running query. ' + errorReason(error),
-        type: 'error',
-        clear: true
-      });
+      if (error && error.message.includes('partition query is not supported')) {
+        dispatch(partitionParamNotSupported());
+      } else if (error && error.message.includes('`partition` parameter is mandatory')) {
+        dispatch(partitionParamIsMandatory());
+      } else {
+        FauxtonAPI.addNotification({
+          msg: 'Error running query. ' + errorReason(error),
+          type: 'error',
+          clear: true
+        });
+      }
       dispatch(resetState());
     });
   };
diff --git a/app/addons/documents/index-results/actiontypes.js b/app/addons/documents/index-results/actiontypes.js
index ddd17e680..cafc6cdec 100644
--- a/app/addons/documents/index-results/actiontypes.js
+++ b/app/addons/documents/index-results/actiontypes.js
@@ -29,5 +29,7 @@ export default {
   INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS: 'INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS',
   INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE: 'INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE',
   INDEX_RESULTS_REDUX_RESET_STATE: 'INDEX_RESULTS_REDUX_RESET_STATE',
-  INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS: 'INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS'
+  INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS: 'INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS',
+  INDEX_RESULTS_REDUX_PARTITION_PARAM_NOT_SUPPORTED: 'INDEX_RESULTS_REDUX_PARTITION_PARAM_NOT_SUPPORTED',
+  INDEX_RESULTS_REDUX_PARTITION_PARAM_MANDATORY: 'INDEX_RESULTS_REDUX_PARTITION_PARAM_MANDATORY'
 };
diff --git a/app/addons/documents/index-results/api.js b/app/addons/documents/index-results/api.js
index 9f836fedc..9278879b8 100644
--- a/app/addons/documents/index-results/api.js
+++ b/app/addons/documents/index-results/api.js
@@ -16,9 +16,13 @@ import app from '../../../app';
 import Constants from '../constants';
 import FauxtonAPI from '../../../core/api';
 
-export const queryAllDocs = (fetchUrl, params) => {
+export const queryAllDocs = (fetchUrl, partitionKey, params) => {
   // Exclude params 'group', 'reduce' and 'group_level' if present since they not allowed for '_all_docs'
   Object.assign(params, {reduce: undefined, group: undefined, group_level: undefined});
+  if (partitionKey) {
+    // partition filter overrides any 'between keys' values set
+    Object.assign(params, {inclusive_end: false, start_key: `"${partitionKey}:"`, end_key: `"${partitionKey}:\ufff0"`});
+  }
   const query = app.utils.queryString(params);
   const url = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${query}`;
   return get(url).then(json => {
@@ -43,6 +47,13 @@ export const queryMapReduceView = (fetchUrl, params) => {
     params.group = undefined;
     params.group_level = undefined;
   }
+  // removes params not supported by partitioned views
+  const isPartitioned = fetchUrl.includes('/_partition/');
+  if (isPartitioned) {
+    params.include_docs = undefined;
+    params.stable = undefined;
+    params.conflicts = undefined;
+  }
   const query = app.utils.queryString(params);
   const url = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${query}`;
   return get(url).then(json => {
@@ -51,7 +62,8 @@ export const queryMapReduceView = (fetchUrl, params) => {
     }
     return {
       docs: json.rows,
-      docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
+      docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
+      layout: isPartitioned ? Constants.LAYOUT_ORIENTATION.METADATA : undefined
     };
   });
 };
diff --git a/app/addons/documents/index-results/components/results/IndexResults.js b/app/addons/documents/index-results/components/results/IndexResults.js
index 484d3799b..01982fa45 100644
--- a/app/addons/documents/index-results/components/results/IndexResults.js
+++ b/app/addons/documents/index-results/components/results/IndexResults.js
@@ -41,11 +41,12 @@ export default class IndexResults extends React.Component {
       queryOptionsParams,
       ddocsOnly,
       fetchUrl,
-      resetState
+      resetState,
+      partitionKey
     } = nextProps;
 
     // Indicates the selected sidebar item has changed, so it needs to fetch the new list of docs
-    if (this.props.ddocsOnly !== ddocsOnly || this.props.fetchUrl !== fetchUrl) {
+    if (this.props.ddocsOnly !== ddocsOnly || this.props.fetchUrl !== fetchUrl || this.props.partitionKey !== partitionKey) {
       resetState();
       // Need to reset skip and reduce here because 'resetState()'
       // won't change props until the next update cycle
diff --git a/app/addons/documents/index-results/components/results/NoResultsScreen.js b/app/addons/documents/index-results/components/results/NoResultsScreen.js
index 0d4e8623d..c01fb01ae 100644
--- a/app/addons/documents/index-results/components/results/NoResultsScreen.js
+++ b/app/addons/documents/index-results/components/results/NoResultsScreen.js
@@ -14,11 +14,18 @@ import PropTypes from 'prop-types';
 
 import React from 'react';
 
-export default function NoResultsScreen ({ text }) {
+export default function NoResultsScreen ({ text, isWarning }) {
+  const warningMsg = (
+    <div className='no-results-screen-warning'>
+      <i className='fonticon-attention-circled'></i>
+      {text}
+    </div>
+  );
   return (
     <div className="no-results-screen">
+      {isWarning ? warningMsg : null}
       <div className="watermark-logo"></div>
-      <h3>{text}</h3>
+      {!isWarning ? <h3>{text}</h3> :  null}
     </div>
   );
 }
diff --git a/app/addons/documents/index-results/components/results/ResultsScreen.js b/app/addons/documents/index-results/components/results/ResultsScreen.js
index ac209dc0e..cfd6f2b2d 100644
--- a/app/addons/documents/index-results/components/results/ResultsScreen.js
+++ b/app/addons/documents/index-results/components/results/ResultsScreen.js
@@ -114,6 +114,8 @@ export default class ResultsScreen extends React.Component {
 
     if (this.props.isLoading) {
       mainView = <div className="loading-lines-wrapper"><LoadLines /></div>;
+    } else if (this.props.noResultsWarning) {
+      mainView = <NoResultsScreen text={this.props.noResultsWarning} isWarning={true}/>;
     } else if (!this.props.hasResults) {
       mainView = <NoResultsScreen text={this.props.textEmptyIndex}/>;
     } else if (this.props.selectedLayout === Constants.LAYOUT_ORIENTATION.JSON) {
diff --git a/app/addons/documents/index-results/containers/IndexResultsContainer.js b/app/addons/documents/index-results/containers/IndexResultsContainer.js
index ca940ecc9..d81f16e01 100644
--- a/app/addons/documents/index-results/containers/IndexResultsContainer.js
+++ b/app/addons/documents/index-results/containers/IndexResultsContainer.js
@@ -42,6 +42,7 @@ import {
 
 const mapStateToProps = ({indexResults}, ownProps) => {
   return {
+    noResultsWarning: indexResults.noResultsWarning,
     docs: getDocs(indexResults),
     selectedDocs: getSelectedDocs(indexResults),
     isLoading: getIsLoading(indexResults),
diff --git a/app/addons/documents/index-results/reducers.js b/app/addons/documents/index-results/reducers.js
index 2f90d3a1f..ad007ce34 100644
--- a/app/addons/documents/index-results/reducers.js
+++ b/app/addons/documents/index-results/reducers.js
@@ -18,6 +18,7 @@ import {getTableViewData} from './helpers/table-view';
 import {getDefaultPerPage, getDocId, isJSONDocBulkDeletable} from './helpers/shared-helpers';
 
 const initialState = {
+  noResultsWarning: '',
   docs: [],  // raw documents returned from couch
   selectedDocs: [],  // documents selected for manipulation
   isLoading: false,
@@ -66,6 +67,7 @@ export default function resultsState(state = initialState, action) {
 
     case ActionTypes.INDEX_RESULTS_REDUX_RESET_STATE:
       return Object.assign({}, initialState, {
+        noResultsWarning: state.noResultsWarning,
         selectedLayout: state.selectedLayout,
         selectedDocs: [],
         fetchParams: {
@@ -85,6 +87,16 @@ export default function resultsState(state = initialState, action) {
         isLoading: true
       });
 
+    case ActionTypes.INDEX_RESULTS_REDUX_PARTITION_PARAM_NOT_SUPPORTED:
+      return Object.assign({}, state, {
+        noResultsWarning: 'The selected index does not support partitions. Switch back to global mode.'
+      });
+
+    case ActionTypes.INDEX_RESULTS_REDUX_PARTITION_PARAM_MANDATORY:
+      return Object.assign({}, state, {
+        noResultsWarning: 'The selected index requires a partition key. Use the selector at the top to enter a partition key.'
+      });
+
     case ActionTypes.INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS:
       return Object.assign({}, state, {
         selectedDocs: action.selectedDocs
@@ -101,6 +113,7 @@ export default function resultsState(state = initialState, action) {
       return Object.assign({}, state, {
         docs: action.docs,
         isLoading: false,
+        noResultsWarning: '',
         isEditable: true, //TODO: determine logic for this
         fetchParams: Object.assign({}, state.fetchParams, action.params),
         pagination: Object.assign({}, state.pagination, {
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index fe6e48ed2..2a5b354a5 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -65,6 +65,7 @@ export const TabsSidebarHeader = ({
         <div id="header-docs-left">
           <HeaderDocsLeft
             dbName={dbName}
+            partitionKey={partitionKey}
             dropDownLinks={dropDownLinks}
           />
         </div>
@@ -80,6 +81,7 @@ export const TabsSidebarHeader = ({
               fetchUrl={fetchUrl}
               ddocsOnly={ddocsOnly}
               queryDocs={queryDocs}
+              partitionKey={partitionKey}
               selectedNavItem={selectedNavItem} />
           </div>
           <ApiBarContainer docURL={docURL} endpoint={endpoint} endpointAddQueryOptions={endpointAddQueryOptions} />
@@ -122,12 +124,13 @@ export const TabsSidebarContent = ({
   fetchUrl,
   databaseName,
   queryDocs,
-  selectedNavItem
+  selectedNavItem,
+  partitionKey
 }) => {
   return (
     <div className="with-sidebar tabs-with-sidebar content-area">
       <aside id="sidebar-content" className="scrollable">
-        <SidebarControllerContainer selectedNavItem={selectedNavItem}/>
+        <SidebarControllerContainer selectedNavItem={selectedNavItem} selectedPartitionKey={partitionKey}/>
       </aside>
       <section id="dashboard-content" className="flex-layout flex-col">
         <div id="dashboard-upper-content">
@@ -173,7 +176,8 @@ export const DocsTabsSidebarLayout = ({
   onGlobalModeSelected,
   globalMode
 }) => {
-  let queryDocs = (params) => { return queryAllDocs(fetchUrl, params); };
+  const partitionFilter = selectedNavItem.navItem === 'all-docs' && partitionKey ? partitionKey : '';
+  let queryDocs = (params) => { return queryAllDocs(fetchUrl, partitionFilter, params); };
   if (Helpers.isViewSelected(selectedNavItem)) {
     queryDocs = (params) => { return queryMapReduceView(fetchUrl, params); };
   }
@@ -213,12 +217,13 @@ export const DocsTabsSidebarLayout = ({
         databaseName={dbName}
         queryDocs={queryDocs}
         selectedNavItem={selectedNavItem}
+        partitionKey={partitionKey}
       />
     </div>
   );
 };
 
-export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, dropDownLinks, selectedNavItem }) => {
+export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, dropDownLinks, selectedNavItem, partitionKey }) => {
   return (
     <div id="dashboard" className="with-sidebar">
       <TabsSidebarHeader
@@ -234,13 +239,14 @@ export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, dropD
         lowerContent={<ChangesContainer databaseName={dbName}/>}
         hideFooter={true}
         selectedNavItem={selectedNavItem}
+        partitionKey={partitionKey}
       />
     </div>
   );
 };
 
 export const ViewsTabsSidebarLayout = ({showEditView, database, docURL, endpoint,
-  dbName, dropDownLinks, selectedNavItem, designDocInfo }) => {
+  dbName, dropDownLinks, selectedNavItem, designDocInfo, partitionKey }) => {
 
   const content = showEditView ?
     <IndexEditorComponents.EditorController /> :
@@ -255,6 +261,7 @@ export const ViewsTabsSidebarLayout = ({showEditView, database, docURL, endpoint
         dbName={dbName}
         dropDownLinks={dropDownLinks}
         database={database}
+        partitionKey={partitionKey}
         queryDocs={() => { }}
         hideQueryOptions={true}
         hideJumpToDoc={true}
@@ -263,6 +270,7 @@ export const ViewsTabsSidebarLayout = ({showEditView, database, docURL, endpoint
         lowerContent={content}
         hideFooter={true}
         selectedNavItem={selectedNavItem}
+        partitionKey={partitionKey}
       />
     </div>
   );
diff --git a/app/addons/documents/mango/components/MangoIndexEditor.js b/app/addons/documents/mango/components/MangoIndexEditor.js
index 739a65169..dc3006370 100644
--- a/app/addons/documents/mango/components/MangoIndexEditor.js
+++ b/app/addons/documents/mango/components/MangoIndexEditor.js
@@ -64,7 +64,7 @@ export default class MangoIndexEditor extends Component {
   }
 
   editor() {
-    const editQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(this.props.databaseName));
+    const editQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(this.props.databaseName), '');
     return (
       <div className="mango-editor-wrapper">
         <form className="form-horizontal" onSubmit={(ev) => { this.saveIndex(ev); }}>
diff --git a/app/addons/documents/mango/mango.actions.js b/app/addons/documents/mango/mango.actions.js
index e0e6c8c20..67f31baef 100644
--- a/app/addons/documents/mango/mango.actions.js
+++ b/app/addons/documents/mango/mango.actions.js
@@ -79,7 +79,7 @@ export default {
       return MangoAPI.createIndex(databaseName, indexCode)
         .then(() => {
           const runQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app',
-            app.utils.safeURLName(databaseName));
+            app.utils.safeURLName(databaseName), '');
 
           const queryIndexes = (params) => { return MangoAPI.fetchIndexes(databaseName, params); };
           dispatch(IndexResultActions.fetchDocs(queryIndexes, fetchParams, {}));
diff --git a/app/addons/documents/mango/mango.api.js b/app/addons/documents/mango/mango.api.js
index 067117225..21e5ecd20 100644
--- a/app/addons/documents/mango/mango.api.js
+++ b/app/addons/documents/mango/mango.api.js
@@ -49,7 +49,8 @@ export const fetchIndexes = (databaseName, params) => {
     }
     return {
       docs: json.indexes,
-      docType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX
+      docType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX,
+      layout: Constants.LAYOUT_ORIENTATION.JSON
     };
   });
 };
diff --git a/app/addons/documents/mangolayout.js b/app/addons/documents/mangolayout.js
index fd7958dee..918cd011c 100644
--- a/app/addons/documents/mangolayout.js
+++ b/app/addons/documents/mangolayout.js
@@ -152,10 +152,11 @@ class MangoLayout extends Component {
   }
 }
 
-const mapStateToProps = ({ mangoQuery }) => {
+const mapStateToProps = ({ mangoQuery }, ownProps) => {
   return {
     explainPlan: mangoQuery.explainPlan,
-    queryFindCode: mangoQuery.queryFindCode
+    queryFindCode: mangoQuery.queryFindCode,
+    partitionKey: ownProps.partitionKey
   };
 };
 
diff --git a/app/addons/documents/partition-key/PartitionKeySelector.js b/app/addons/documents/partition-key/PartitionKeySelector.js
index 1bd8b5fde..4d8694ab2 100644
--- a/app/addons/documents/partition-key/PartitionKeySelector.js
+++ b/app/addons/documents/partition-key/PartitionKeySelector.js
@@ -12,8 +12,6 @@
 
 import PropTypes from 'prop-types';
 import React from "react";
-import ReactDOM from "react-dom";
-
 
 export default class PartitionKeySelector extends React.Component {
 
@@ -61,14 +59,15 @@ export default class PartitionKeySelector extends React.Component {
       this.setState({
         editMode: false
       });
-      this.props.onPartitionKeySelected(this.state.editorValue);
+      const trimmedValue = this.state.editorValue.trim();
+      if (trimmedValue) {
+        this.props.onPartitionKeySelected(this.state.editorValue);
+      } else {
+        this.props.onGlobalModeSelected();
+      }
     }
   }
 
-  isPartitionSelected() {
-    return !this.state.global && (this.props.partitionKey.trim().length > 0);
-  }
-
   onChange(e) {
     this.setState({editorValue: e.target.value});
   }
diff --git a/app/addons/documents/partition-key/actions.js b/app/addons/documents/partition-key/actions.js
index ebf076ca1..63f9e0082 100644
--- a/app/addons/documents/partition-key/actions.js
+++ b/app/addons/documents/partition-key/actions.js
@@ -10,7 +10,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import * as API from './api';
+import * as DatabasesAPI from '../../databases/api';
 import ActionTypes from './actiontypes';
 
 export const checkDbPartitioned = (databaseName) => (dispatch) => {
@@ -19,7 +19,7 @@ export const checkDbPartitioned = (databaseName) => (dispatch) => {
     type: ActionTypes.PARTITON_KEY_HIDE_SELECTOR
   });
 
-  API.fetchDatabaseInfo(databaseName).then(dbInfo => {
+  DatabasesAPI.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/api.js b/app/addons/documents/partition-key/api.js
deleted file mode 100644
index 1cbb9bff1..000000000
--- a/app/addons/documents/partition-key/api.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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 FauxtonAPI from '../../../core/api';
-import {get} from '../../../core/ajax';
-import Helpers from "../../../helpers";
-
-export const fetchDatabaseInfo = (databaseName) => {
-  const base = FauxtonAPI.urls('databaseBaseURL', 'server', encodeURIComponent(databaseName));
-  const url = Helpers.getServerUrl(base);
-  return get(url);
-};
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index 9def382f2..b86cb6b60 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -13,6 +13,7 @@
 import React from 'react';
 import FauxtonAPI from '../../core/api';
 import BaseRoute from './shared-routes';
+import DatabaseActions from '../databases/actions';
 import Databases from '../databases/base';
 import Resources from './resources';
 import {SidebarItemSelection} from './sidebar/helpers';
@@ -29,11 +30,22 @@ var DocumentsRouteObject = BaseRoute.extend({
       route: "globalAllDocs",
       roles: ["fx_loggedIn"]
     },
-    "database/:database/_design/:ddoc/_info": {
+    "database/:database/_partition/:partitionkey/_design/:ddoc/_info": {
       route: "designDocMetadata",
       roles: ['fx_loggedIn']
     },
-    'database/:database/_changes': 'changes'
+    "database/:database/_design/:ddoc/_info": {
+      route: "designDocMetadataNoPartition",
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_partition/:partitionKey/_changes': {
+      route: 'changes',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_changes': {
+      route: 'changes',
+      roles: ['fx_loggedIn']
+    }
   },
 
   initialize (route, options) {
@@ -49,7 +61,11 @@ var DocumentsRouteObject = BaseRoute.extend({
     this.addSidebar();
   },
 
-  designDocMetadata: function (database, ddoc) {
+  designDocMetadataNoPartition: function (database, ddoc) {
+    return this.designDocMetadata(database, '', ddoc);
+  },
+
+  designDocMetadata: function (database, partitionKey, ddoc) {
     const designDocInfo = new Resources.DdocInfo({ _id: "_design/" + ddoc }, { database: this.database });
     const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddoc,
@@ -66,6 +82,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       database={this.database}
       selectedNavItem={selectedNavItem}
       designDocInfo={designDocInfo}
+      partitionKey={partitionKey}
     />;
   },
 
@@ -83,8 +100,8 @@ var DocumentsRouteObject = BaseRoute.extend({
   * They are not the same when paginating
   */
   allDocs: function (databaseName, partitionKey, options) {
-    const params = this.createParams(options),
-          docParams = params.docParams;
+    const params = this.createParams(options);
+    const docParams = params.docParams;
 
     const url = FauxtonAPI.urls('allDocsSanitized', 'server', databaseName);
 
@@ -112,6 +129,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       FauxtonAPI.navigate('#/' + baseUrl);
     };
     const dropDownLinks = this.getCrumbs(this.database);
+    DatabaseActions.fetchSelectedDatabaseInfo(databaseName);
     return <DocsTabsSidebarLayout
       docURL={docURL}
       endpoint={endpoint}
@@ -129,7 +147,7 @@ var DocumentsRouteObject = BaseRoute.extend({
     />;
   },
 
-  changes: function () {
+  changes: function (_, partitionKey) {
     const selectedNavItem = new SidebarItemSelection('changes');
 
     return <ChangesSidebarLayout
@@ -139,6 +157,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       dropDownLinks={this.getCrumbs(this.database)}
       database={this.database}
       selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
     />;
   }
 
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 1485b56af..3dd39d97b 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -21,14 +21,22 @@ import {DocsTabsSidebarLayout, ViewsTabsSidebarLayout} from './layouts';
 
 const IndexEditorAndResults = BaseRoute.extend({
   routes: {
-    'database/:database/new_view': {
+    'database/:database/_partition/:partitionkey/new_view': {
       route: 'createView',
       roles: ['fx_loggedIn']
     },
-    'database/:database/new_view/:designDoc': {
+    'database/:database/_partition/:partitionkey/new_view/:designDoc': {
       route: 'createView',
       roles: ['fx_loggedIn']
     },
+    'database/:database/new_view': {
+      route: 'createViewNoPartition',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/new_view/:designDoc': {
+      route: 'createViewNoPartition',
+      roles: ['fx_loggedIn']
+    },
     'database/:database/_partition/:partitionkey/_design/:ddoc/_view/:view': {
       route: 'showPartitionedView',
       roles: ['fx_loggedIn']
@@ -37,9 +45,13 @@ const IndexEditorAndResults = BaseRoute.extend({
       route: 'showGlobalView',
       roles: ['fx_loggedIn']
     },
-    'database/:database/_design/:ddoc/_view/:view/edit': {
+    'database/:database/_partition/:partitionkey/_design/:ddoc/_view/:view/edit': {
       route: 'editView',
       roles: ['fx_loggedIn']
+    },
+    'database/:database/_design/:ddoc/_view/:view/edit': {
+      route: 'editViewNoPartition',
+      roles: ['fx_loggedIn']
     }
   },
 
@@ -78,9 +90,10 @@ const IndexEditorAndResults = BaseRoute.extend({
     });
     SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
 
-    const url = FauxtonAPI.urls('view', 'server', encodeURIComponent(databaseName),
+    const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+    const url = FauxtonAPI.urls('view', 'server', encodeURIComponent(databaseName), encodedPartKey,
       encodeURIComponent(ddoc), encodeURIComponent(viewName));
-    const endpoint = FauxtonAPI.urls('view', 'apiurl', encodeURIComponent(databaseName),
+    const endpoint = FauxtonAPI.urls('view', 'apiurl', encodeURIComponent(databaseName), encodedPartKey,
       encodeURIComponent(ddoc), encodeURIComponent(viewName));
     const docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
     const navigateToPartitionedView = (partKey) => {
@@ -88,6 +101,10 @@ const IndexEditorAndResults = BaseRoute.extend({
         encodeURIComponent(partKey), encodeURIComponent(ddoc));
       FauxtonAPI.navigate('#/' + baseUrl + encodeURIComponent(viewName));
     };
+    const navigateToGlobalView = () => {
+      const baseUrl = FauxtonAPI.urls('view', 'app', encodeURIComponent(databaseName), encodeURIComponent(ddoc));
+      FauxtonAPI.navigate('#/' + baseUrl + encodeURIComponent(viewName));
+    };
     const dropDownLinks = this.getCrumbs(this.database);
     return <DocsTabsSidebarLayout
       docURL={docURL}
@@ -101,13 +118,18 @@ const IndexEditorAndResults = BaseRoute.extend({
       selectedNavItem={selectedNavItem}
       partitionKey={partitionKey}
       onPartitionKeySelected={navigateToPartitionedView}
+      onGlobalModeSelected={navigateToGlobalView}
       globalMode={partitionKey === ''}
     />;
   },
 
-  createView: function (database, _designDoc) {
-    var newDesignDoc = true;
-    var designDoc = 'new-doc';
+  createViewNoPartition: function (databaseName, _designDoc) {
+    return this.createView(databaseName, '', _designDoc);
+  },
+
+  createView: function (database, partitionKey, _designDoc) {
+    let newDesignDoc = true;
+    let designDoc = 'new-doc';
 
     if (_designDoc) {
       designDoc = '_design/' + _designDoc;
@@ -133,10 +155,15 @@ const IndexEditorAndResults = BaseRoute.extend({
       dropDownLinks={dropDownLinks}
       database={this.database}
       selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
     />;
   },
 
-  editView: function (databaseName, ddocName, viewName) {
+  editViewNoPartition: function (databaseName, ddocName, viewName) {
+    return this.editView(databaseName, '', ddocName, viewName);
+  },
+
+  editView: function (databaseName, partitionKey, ddocName, viewName) {
     ActionsIndexEditor.fetchDesignDocsBeforeEdit({
       viewName: viewName,
       newView: false,
@@ -164,6 +191,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       dropDownLinks={dropDownLinks}
       database={this.database}
       selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
     />;
   }
 
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
index c59b6f53c..03495eeb4 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -22,14 +22,22 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
   hideApiBar: true,
   hideNotificationCenter: true,
   routes: {
-    'database/:database/_index': {
+    'database/:database/_partition/:partitionkey/_index': {
       route: 'createIndex',
       roles: ['fx_loggedIn']
     },
-    'database/:database/_find': {
+    'database/:database/_index': {
+      route: 'createIndexNoPartition',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_partition/:partitionkey/_find': {
       route: 'findUsingIndex',
       roles: ['fx_loggedIn']
     },
+    'database/:database/_find': {
+      route: 'findUsingIndexNoPartition',
+      roles: ['fx_loggedIn']
+    },
   },
 
   initialize: function (route, options) {
@@ -38,9 +46,14 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
     this.database = new Databases.Model({id: databaseName});
   },
 
-  findUsingIndex: function (database) {
+  findUsingIndexNoPartition: function (database) {
+    return this.findUsingIndex(database, '');
+  },
+
+  findUsingIndex: function (database, partitionKey) {
+    const encodedPartitionKey = partitionKey ? encodeURIComponent(partitionKey) : '';
     const url = FauxtonAPI.urls(
-      'allDocs', 'app', encodeURIComponent(this.databaseName), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
+      'allDocs', 'app', encodeURIComponent(this.databaseName), encodedPartitionKey
     );
 
     const fetchUrl = '/' + encodeURIComponent(this.databaseName) + '/_find';
@@ -58,13 +71,17 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       docURL={FauxtonAPI.constants.DOC_URLS.MANGO_SEARCH}
       endpoint={endpoint}
       edit={false}
-
+      partitionKey={partitionKey}
       databaseName={this.databaseName}
       fetchUrl={fetchUrl}
     />;
   },
 
-  createIndex: function (database) {
+  createIndexNoPartition: function (database) {
+    return this.createIndex(database, '');
+  },
+
+  createIndex: function (database, partitionKey) {
     const designDocs = new Documents.AllDocs(null, {
       database: this.database,
       paging: {
@@ -78,8 +95,9 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       }
     });
 
+    const encodedPartitionKey = partitionKey ? encodeURIComponent(partitionKey) : '';
     const url = FauxtonAPI.urls(
-      'allDocs', 'app', encodeURIComponent(this.databaseName), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
+      'allDocs', 'app', encodeURIComponent(this.databaseName), encodedPartitionKey
     );
     const endpoint = FauxtonAPI.urls('mango', 'index-apiurl', encodeURIComponent(this.databaseName));
 
diff --git a/app/addons/documents/sidebar/SidebarControllerContainer.js b/app/addons/documents/sidebar/SidebarControllerContainer.js
index 3a3ea2621..550f3b7b0 100644
--- a/app/addons/documents/sidebar/SidebarControllerContainer.js
+++ b/app/addons/documents/sidebar/SidebarControllerContainer.js
@@ -48,16 +48,17 @@ const selectedNavItem = (selectedItem) => {
   return settings;
 };
 
-const mapStateToProps = ({ sidebar }, ownProps) => {
+const mapStateToProps = ({ sidebar, databases }, ownProps) => {
   return {
     database: getDatabase(sidebar),
     selectedNav: selectedNavItem(ownProps.selectedNavItem),
     designDocs: sidebar.designDocs,
-    // designDocList: getDesignDocList(sidebar),
     designDocList: sidebar.designDocList,
     availableDesignDocIds: getAvailableDesignDocs(sidebar),
     toggledSections: sidebar.toggledSections,
-    isLoading: sidebar.loading,
+    isLoading: sidebar.loading || databases.isLoadingDbInfo,
+    selectedPartitionKey: ownProps.selectedPartitionKey,
+    isDbPartitioned: databases.isDbPartitioned,
 
     deleteIndexModalVisible: sidebar.deleteIndexModalVisible,
     deleteIndexModalText: sidebar.deleteIndexModalText,
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
index 4e306b5a9..c89721c42 100644
--- a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
+++ b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
@@ -10,20 +10,18 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import utils from '../../../../../test/mocha/testUtils';
-import FauxtonAPI from "../../../../core/api";
-import React from "react";
-import ReactDOM from "react-dom";
-import sinon from "sinon";
 import { mount } from 'enzyme';
-import Components from "../sidebar";
+import React from 'react';
+import sinon from 'sinon';
+import utils from '../../../../../test/mocha/testUtils';
+import FauxtonAPI from '../../../../core/api';
 import '../../base';
-
-const {DesignDoc} = Components;
+import DesignDoc from '../components/DesignDoc';
+import IndexSection from '../components/IndexSection';
+import MainSidebar from '../components/MainSidebar';
 
 const { assert, restore} = utils;
 
-
 describe('DesignDoc', () => {
   const database = { id: 'test-db' };
   const selectedNavInfo = {
@@ -32,29 +30,29 @@ describe('DesignDoc', () => {
     designDocSection: '',
     indexName: ''
   };
+  const defaultProps = {
+    database,
+    toggle: () => {},
+    sidebarListTypes: [],
+    isExpanded: true,
+    isPartitioned: false,
+    designDocName: 'doc-$-#-.1',
+    selectedNavInfo,
+    toggledSections: {},
+    designDoc: {},
+    showDeleteIndexModal: () => {},
+    showCloneIndexModal: () => {}
+  };
 
   afterEach(() => {
     restore(FauxtonAPI.urls);
   });
 
   it('confirm URLs are properly encoded when design doc name has special chars', () => {
-    sinon.stub(FauxtonAPI, 'urls').callsFake((a, b, c, d) => {
-      if (a === 'designDocs') {
-        return '#/database/MOCK/_design/' + encodeURIComponent(c) + '/' + encodeURIComponent(d);
-      }
-      return '' + (a || '') + '/' + (b || '') + '/' + (c || '') + '/' + (d || '');
-    });
     const wrapper = mount(<DesignDoc
-      database={database}
-      toggle={sinon.stub()}
-      sidebarListTypes={[]}
-      isExpanded={true}
+      {...defaultProps}
       designDocName={'doc-$-#-.1'}
-      selectedNavInfo={selectedNavInfo}
-      toggledSections={{}}
-      designDoc={{}}
-      showDeleteIndexModal={() => {}}
-      showCloneIndexModal={() => {}} />);
+    />);
 
     assert.include(wrapper.find('a.icon .fonticon-plus-circled').at(1).props()['href'], '/doc-%24-%23-.1');
     assert.include(wrapper.find('a.toggle-view .accordion-header').props()['href'], '/doc-%24-%23-.1');
@@ -65,16 +63,11 @@ describe('DesignDoc', () => {
 
     const toggleStub = sinon.stub();
     const wrapper = mount(<DesignDoc
-      database={database}
+      {...defaultProps}
       toggle={toggleStub}
-      sidebarListTypes={[]}
-      isExpanded={true}
       designDocName={'id#1'}
       selectedNavInfo={{}}
-      toggledSections={{}}
-      designDoc={{}}
-      showDeleteIndexModal={() => {}}
-      showCloneIndexModal={() => {}} />);
+    />);
 
     // NOTE: wrapper.find doesn't work special chars so we use class name instead
     wrapper.find('div.accordion-list-item').simulate('click', {preventDefault: sinon.stub()});
@@ -85,16 +78,9 @@ describe('DesignDoc', () => {
 
   it('confirm only single sub-option is shown by default (metadata link)', function () {
     const el = mount(<DesignDoc
-      database={database}
-      toggle={function () {}}
-      sidebarListTypes={[]}
-      isExpanded={true}
-      selectedNavInfo={selectedNavInfo}
-      toggledSections={{}}
+      {...defaultProps}
       designDoc={{ customProp: { one: 'something' } }}
       designDocName={'doc-$-#-.1'}
-      showDeleteIndexModal={() => {}}
-      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -104,8 +90,7 @@ describe('DesignDoc', () => {
   it('confirm design doc sidebar extensions appear', function () {
     sinon.stub(FauxtonAPI, 'urls');
     const el = mount(<DesignDoc
-      database={database}
-      toggle={function () {}}
+      {...defaultProps}
       sidebarListTypes={[{
         selector: 'customProp',
         name: 'Search Indexes',
@@ -115,13 +100,8 @@ describe('DesignDoc', () => {
         onDelete: () => {},
         onClone: () => {}
       }]}
-      isExpanded={true}
-      selectedNavInfo={selectedNavInfo}
-      toggledSections={{}}
       designDoc={{ customProp: { one: 'something' } }}
       designDocName={'doc-$-#-.1'}
-      showDeleteIndexModal={() => {}}
-      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -131,8 +111,7 @@ describe('DesignDoc', () => {
   it('confirm design doc sidebar extensions do not appear when they have no content', function () {
     sinon.stub(FauxtonAPI, 'urls');
     const el = mount(<DesignDoc
-      database={database}
-      toggle={function () {}}
+      {...defaultProps}
       sidebarListTypes={[{
         selector: 'customProp',
         name: 'Search Indexes',
@@ -142,13 +121,7 @@ describe('DesignDoc', () => {
         onDelete: () => {},
         onClone: () => {}
       }]}
-      isExpanded={true}
-      selectedNavInfo={selectedNavInfo}
-      designDoc={{}} // note that this is empty
       designDocName={'doc-$-#-.1'}
-      toggledSections={{}}
-      showDeleteIndexModal={() => {}}
-      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -157,10 +130,7 @@ describe('DesignDoc', () => {
 
   it('confirm doc metadata page is highlighted if selected', function () {
     const el = mount(<DesignDoc
-      database={database}
-      toggle={function () {}}
-      sidebarListTypes={[]}
-      isExpanded={true}
+      {...defaultProps}
       selectedNavInfo={{
         navItem: 'designDoc',
         designDocName: 'id',
@@ -168,11 +138,117 @@ describe('DesignDoc', () => {
         indexName: ''
       }}
       designDocName={'doc-$-#-.1'}
-      toggledSections={{}}
-      designDoc={{}}
-      showDeleteIndexModal={() => {}}
-      showCloneIndexModal={() => {}} />);
+    />);
 
     assert.equal(el.find('.accordion-body li.active a').text(), 'Metadata');
   });
+
+  it('shows different icons for global and partitioned ddocs', () => {
+    const wrapper = mount(<DesignDoc
+      {...defaultProps}
+      designDocName={'doc-$-#-.1'}
+      isPartitioned={false}
+    />);
+    assert.ok(wrapper.find('i.fonticon-document').exists());
+
+    const wrapper2 = mount(<DesignDoc
+      {...defaultProps}
+      designDocName={'doc-$-#-.1'}
+      isPartitioned={true}
+    />);
+    assert.ok(wrapper2.find('i.fonticon-documents').exists());
+  });
+
+  it('confirms links only include the partition key when one is selected', () => {
+    const wrapper = mount(<DesignDoc
+      {...defaultProps}
+      selectedPartitionKey={'part-key-$-%1'}
+    />);
+    // Metadata link
+    assert.include(wrapper.find('a.toggle-view .accordion-header').props()['href'], '/_partition/part-key-%24-%251/');
+    // New View link
+    assert.include(wrapper.find('li > a.icon .fonticon-plus-circled').props()['href'], '/_partition/part-key-%24-%251/');
+
+    const wrapper2 = mount(<DesignDoc
+      {...defaultProps}
+    />);
+
+    assert.notInclude(wrapper2.find('a.toggle-view .accordion-header').props()['href'], '/_partition/');
+    assert.notInclude(wrapper2.find('li > a.icon .fonticon-plus-circled').props()['href'], '/_partition/');
+  });
+});
+
+describe('MainSidebar', () => {
+  const defaultProps = {
+    databaseName: 'test-db',
+    selectedNavItem: 'Metadata'
+  };
+
+  it('confirm links are properly encoded and include the partition key when provided', () => {
+    const wrapper = mount(<MainSidebar
+      {...defaultProps}
+      selectedPartitionKey={'part-key-$-%1'}
+    />);
+
+    assert.include(wrapper.find('a#all-docs').props()['href'], '/_partition/part-key-%24-%251/');
+    assert.include(wrapper.find('a#design-docs').props()['href'], '/_partition/part-key-%24-%251/');
+    assert.include(wrapper.find('a#mango-query').props()['href'], '/_partition/part-key-%24-%251/');
+  });
+
+  it('confirm New links are properly encoded and include the partition key when provided', () => {
+    const wrapper = mount(<MainSidebar
+      {...defaultProps}
+      selectedPartitionKey={'part-key-$-%1'}
+    />);
+
+    const newLinks = wrapper.instance().getNewButtonLinks()[0].links;
+    newLinks.forEach(link => {
+      if (link.title === 'New Doc') {
+        assert.include(link.url, '?partitionKey=part-key-%24-%251');
+      } else {
+        assert.include(link.url, '/_partition/part-key-%24-%251/');
+      }
+    });
+  });
+});
+
+describe('IndexSection', () => {
+  const defaultProps = {
+    urlNamespace: 'view',
+    indexLabel: 'Views',
+    database: { id: 'test-db' },
+    designDocName: 'ddoc-%-1',
+    items: ['viewA-$', 'viewB/#'],
+    isExpanded: true,
+    isPartitioned: false,
+    selectedPartitionKey: undefined,
+    selectedIndex: 'bla',
+    onDelete: () => {},
+    onClone: () => {},
+    showDeleteIndexModal: () => {},
+    showCloneIndexModal: () => {}
+  };
+
+  it('encodes the links for each item', () => {
+    const wrapper = mount(<IndexSection
+      {...defaultProps}
+    />);
+
+    defaultProps.items.forEach((view, idx) => {
+      assert.include(wrapper.find('a.toggle-view').at(idx).prop('href'),
+        '/_design/' + encodeURIComponent('ddoc-%-1') + '/_view/' + encodeURIComponent(view));
+    });
+  });
+
+  it('links include partition key when one is selected', () => {
+    const wrapper = mount(<IndexSection
+      {...defaultProps}
+      selectedPartitionKey={'part%1'}
+    />);
+
+    defaultProps.items.forEach((view, idx) => {
+      assert.include(wrapper.find('a.toggle-view').at(idx).prop('href'),
+        '/_partition/' + encodeURIComponent('part%1') + '/');
+    });
+  });
 });
diff --git a/app/addons/documents/sidebar/components/DesignDoc.js b/app/addons/documents/sidebar/components/DesignDoc.js
index c0cc2e54b..9830827a6 100644
--- a/app/addons/documents/sidebar/components/DesignDoc.js
+++ b/app/addons/documents/sidebar/components/DesignDoc.js
@@ -13,7 +13,6 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 import { Collapse } from 'react-bootstrap';
-import ReactDOM from 'react-dom';
 import FauxtonAPI from '../../../../core/api';
 import Components from '../../../components/react-components';
 import IndexEditorActions from '../../index-editor/actions';
@@ -26,6 +25,8 @@ export default class DesignDoc extends React.Component {
     database: PropTypes.object.isRequired,
     sidebarListTypes: PropTypes.array.isRequired,
     isExpanded: PropTypes.bool.isRequired,
+    isPartitioned: PropTypes.bool.isRequired,
+    selectedPartitionKey: PropTypes.string,
     selectedNavInfo: PropTypes.object.isRequired,
     toggledSections: PropTypes.object.isRequired,
     designDocName:  PropTypes.string.isRequired,
@@ -82,6 +83,8 @@ export default class DesignDoc extends React.Component {
           title={index.name}
           selector={index.selector}
           items={_.keys(this.props.designDoc[index.selector])}
+          isPartitioned={this.props.isPartitioned}
+          selectedPartitionKey={this.props.selectedPartitionKey}
           showDeleteIndexModal={this.props.showDeleteIndexModal}
           showCloneIndexModal={this.props.showCloneIndexModal} />
       );
@@ -94,19 +97,24 @@ export default class DesignDoc extends React.Component {
   };
 
   getNewButtonLinks = () => {
-    const newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', encodeURIComponent(this.props.database.id));
+    const encodedPartKey = this.props.selectedPartitionKey ? encodeURIComponent(this.props.selectedPartitionKey) : '';
+    const newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', encodeURIComponent(this.props.database.id))
+      + (encodedPartKey ? '/_partition/' + encodedPartKey : '');
     const designDocName = this.props.designDocName;
 
-    const addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) {
-      menuLinks.push({
-        title: link.title,
-        url: '#' + newUrlPrefix + '/' + link.url + '/' + encodeURIComponent(designDocName),
-        icon: 'fonticon-plus-circled'
-      });
+    const addonLinks = FauxtonAPI.getExtensions('sidebar:links');
+    const addNewLinks = addonLinks.reduce((menuLinks, link) => {
+      if (!this.props.isPartitioned || link.showForPartitionedDDocs) {
+        menuLinks.push({
+          title: link.title,
+          url: '#' + newUrlPrefix + '/' + link.url + '/' + encodeURIComponent(designDocName),
+          icon: 'fonticon-plus-circled'
+        });
+      }
       return menuLinks;
     }, [{
       title: 'New View',
-      url: '#' + FauxtonAPI.urls('new', 'addView', encodeURIComponent(this.props.database.id), encodeURIComponent(designDocName)),
+      url: '#' + FauxtonAPI.urls('new', 'addView', encodeURIComponent(this.props.database.id), encodedPartKey, encodeURIComponent(designDocName)),
       icon: 'fonticon-plus-circled'
     }]);
 
@@ -126,16 +134,22 @@ export default class DesignDoc extends React.Component {
       toggleBodyClassNames += ' in';
     }
     const designDocName = this.props.designDocName;
-    const designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', encodeURIComponent(this.props.database.id), designDocName);
+    const encodedPartKey = this.props.selectedPartitionKey ? encodeURIComponent(this.props.selectedPartitionKey) : '';
+    const designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app',
+      encodeURIComponent(this.props.database.id), encodedPartKey, designDocName);
     const metadataRowClass = (this.props.selectedNavInfo.designDocSection === 'metadata') ? 'active' : '';
-
+    const iconTitle = this.props.isPartitioned ? 'Partitioned design document' : 'Global design document';
+    const iconClass = this.props.isPartitioned ? 'fonticon-documents' : 'fonticon-document';
     return (
       <li className="nav-header">
         <div id={"sidebar-tab-" + designDocName} className={toggleClassNames}>
           <div id={"nav-header-" + designDocName} onClick={this.toggle} className='accordion-list-item'>
             <div className="fonticon-play"></div>
             <p className='design-doc-name'>
-              <span title={'_design/' + designDocName}>{designDocName}</span>
+              <span title={'_design/' + designDocName}>
+                <i className={iconClass} title={iconTitle}></i>
+                {designDocName}
+              </span>
             </p>
           </div>
           <div className='new-button add-dropdown'>
diff --git a/app/addons/documents/sidebar/components/DesignDocList.js b/app/addons/documents/sidebar/components/DesignDocList.js
index 79deacedc..21134702b 100644
--- a/app/addons/documents/sidebar/components/DesignDocList.js
+++ b/app/addons/documents/sidebar/components/DesignDocList.js
@@ -28,6 +28,8 @@ export default class DesignDocList extends React.Component {
       indexName: PropTypes.string,
       navItem: PropTypes.string
     }).isRequired,
+    isDbPartitioned: PropTypes.bool.isRequired,
+    selectedPartitionKey: PropTypes.string,
     showDeleteIndexModal: PropTypes.func.isRequired,
     showCloneIndexModal: PropTypes.func.isRequired
   };
@@ -41,6 +43,12 @@ export default class DesignDocList extends React.Component {
   designDocList = () => {
     return _.map(this.props.designDocs, (designDoc, key) => {
       const ddName = decodeURIComponent(designDoc.safeId);
+      // By default a design doc is partitioned if the database is partitioned
+      let isDDocPartitioned = this.props.isDbPartitioned;
+      // Check if it is explictly set to not partitioned
+      if (this.props.isDbPartitioned && designDoc.options && designDoc.options.partitioned === false) {
+        isDDocPartitioned = false;
+      }
 
       // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
       let expanded = false,
@@ -62,6 +70,8 @@ export default class DesignDocList extends React.Component {
           isExpanded={expanded}
           toggledSections={toggledSections}
           selectedNavInfo={selectedNavInfo}
+          selectedPartitionKey={this.props.selectedPartitionKey}
+          isPartitioned={isDDocPartitioned}
           key={key}
           designDoc={designDoc}
           designDocName={ddName}
diff --git a/app/addons/documents/sidebar/components/IndexSection.js b/app/addons/documents/sidebar/components/IndexSection.js
index cbee3a8dc..ec57842c6 100644
--- a/app/addons/documents/sidebar/components/IndexSection.js
+++ b/app/addons/documents/sidebar/components/IndexSection.js
@@ -28,7 +28,9 @@ export default class IndexSection extends React.Component {
     onDelete: PropTypes.func.isRequired,
     onClone: PropTypes.func.isRequired,
     showDeleteIndexModal: PropTypes.func.isRequired,
-    showCloneIndexModal: PropTypes.func.isRequired
+    showCloneIndexModal: PropTypes.func.isRequired,
+    isPartitioned: PropTypes.bool.isRequired,
+    selectedPartitionKey: PropTypes.string
   };
 
   state = {
@@ -50,7 +52,13 @@ export default class IndexSection extends React.Component {
     const sortedItems = this.props.items.sort();
 
     return _.map(sortedItems, (indexName, index) => {
-      const href = FauxtonAPI.urls(this.props.urlNamespace, 'app', encodeURIComponent(this.props.database.id), encodeURIComponent(this.props.designDocName));
+      let href = FauxtonAPI.urls(this.props.urlNamespace, 'app', encodeURIComponent(this.props.database.id), encodeURIComponent(this.props.designDocName));
+      if (this.props.selectedPartitionKey) {
+        href = FauxtonAPI.urls('partitioned_' + this.props.urlNamespace, 'app',
+          encodeURIComponent(this.props.database.id),
+          encodeURIComponent(this.props.selectedPartitionKey),
+          encodeURIComponent(this.props.designDocName));
+      }
       const className = (this.props.selectedIndex === indexName) ? 'active' : '';
 
       return (
@@ -105,7 +113,7 @@ export default class IndexSection extends React.Component {
         this.props.showCloneIndexModal(params.indexName, this.props.designDocName, this.props.indexLabel, params.onClone);
         break;
       case 'edit':
-        params.onEdit(this.props.database.id, this.props.designDocName, params.indexName);
+        params.onEdit(this.props.database.id, this.props.selectedPartitionKey, this.props.designDocName, params.indexName);
         break;
     }
   };
diff --git a/app/addons/documents/sidebar/components/MainSidebar.js b/app/addons/documents/sidebar/components/MainSidebar.js
index 14c40c0c2..0bbad14a2 100644
--- a/app/addons/documents/sidebar/components/MainSidebar.js
+++ b/app/addons/documents/sidebar/components/MainSidebar.js
@@ -12,7 +12,6 @@
 
 import PropTypes from 'prop-types';
 import React from 'react';
-import ReactDOM from 'react-dom';
 import app from "../../../../app";
 import FauxtonAPI from '../../../../core/api';
 import DocumentHelper from "../../../documents/helpers";
@@ -22,11 +21,13 @@ const { MenuDropDown } = Components;
 
 export default class MainSidebar extends React.Component {
   static propTypes = {
-    selectedNavItem: PropTypes.string.isRequired
+    databaseName: PropTypes.string.isRequired,
+    selectedNavItem: PropTypes.string.isRequired,
+    selectedPartitionKey: PropTypes.string
   };
 
   getNewButtonLinks = () => {  // these are links for the sidebar '+' on All Docs and All Design Docs
-    return DocumentHelper.getNewButtonLinks(this.props.databaseName);
+    return DocumentHelper.getNewButtonLinks(this.props.databaseName, this.props.selectedPartitionKey);
   };
 
   buildDocLinks = () => {
@@ -47,10 +48,11 @@ export default class MainSidebar extends React.Component {
   render() {
     const docLinks = this.buildDocLinks();
     const dbEncoded = FauxtonAPI.url.encode(this.props.databaseName);
-    const changesUrl = '#' + FauxtonAPI.urls('changes', 'app', dbEncoded, '');
-    const permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', dbEncoded);
-    const databaseUrl = FauxtonAPI.urls('allDocs', 'app', dbEncoded, '');
-    const mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', dbEncoded);
+    const partKeyEncoded = this.props.selectedPartitionKey ? encodeURIComponent(this.props.selectedPartitionKey) : '';
+    const changesUrl = '#' + FauxtonAPI.urls('changes', 'app', dbEncoded, partKeyEncoded, '');
+    const permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', dbEncoded, partKeyEncoded);
+    const databaseUrl = FauxtonAPI.urls('allDocs', 'app', dbEncoded, partKeyEncoded, '');
+    const mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', dbEncoded, partKeyEncoded);
     const runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
     const buttonLinks = this.getNewButtonLinks();
 
diff --git a/app/addons/documents/sidebar/components/SidebarController.js b/app/addons/documents/sidebar/components/SidebarController.js
index d9a548434..b37c8ac53 100644
--- a/app/addons/documents/sidebar/components/SidebarController.js
+++ b/app/addons/documents/sidebar/components/SidebarController.js
@@ -107,9 +107,12 @@ export default class SidebarController extends React.Component {
       <nav className="sidenav">
         <MainSidebar
           selectedNavItem={this.props.selectedNav.navItem}
-          databaseName={this.props.database.id} />
+          databaseName={this.props.database.id}
+          selectedPartitionKey={this.props.selectedPartitionKey}/>
         <DesignDocList
           selectedNav={this.props.selectedNav}
+          selectedPartitionKey={this.props.selectedPartitionKey}
+          isDbPartitioned={this.props.isDbPartitioned}
           toggle={this.props.toggleContent}
           toggledSections={this.props.toggledSections}
           designDocs={this.props.designDocList}
diff --git a/app/addons/permissions/layout.js b/app/addons/permissions/layout.js
index 353473386..435a37baf 100644
--- a/app/addons/permissions/layout.js
+++ b/app/addons/permissions/layout.js
@@ -16,7 +16,7 @@ import PermissionsContainer from './container/PermissionsContainer';
 import SidebarControllerContainer from "../documents/sidebar/SidebarControllerContainer";
 import {SidebarItemSelection} from '../documents/sidebar/helpers';
 
-export const PermissionsLayout = ({docURL, database, endpoint, dbName, dropDownLinks}) => {
+export const PermissionsLayout = ({docURL, database, endpoint, dbName, dropDownLinks, partitionKey}) => {
   const selectedNavItem = new SidebarItemSelection('permissions');
   return (
     <div id="dashboard" className="with-sidebar">
@@ -31,7 +31,7 @@ export const PermissionsLayout = ({docURL, database, endpoint, dbName, dropDownL
       />
       <div className="with-sidebar tabs-with-sidebar content-area">
         <aside id="sidebar-content" className="scrollable">
-          <SidebarControllerContainer selectedNavItem={selectedNavItem}/>
+          <SidebarControllerContainer selectedNavItem={selectedNavItem} selectedPartitionKey={partitionKey}/>
         </aside>
         <section id="dashboard-content" className="flex-layout flex-col">
           <PermissionsContainer url={endpoint} />
diff --git a/app/addons/permissions/routes.js b/app/addons/permissions/routes.js
index eb7e25db5..f18e1e1b2 100644
--- a/app/addons/permissions/routes.js
+++ b/app/addons/permissions/routes.js
@@ -10,19 +10,25 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import Databases from "../databases/base";
-
-import BaseRoute from "../documents/shared-routes";
-import Layout from './layout';
 import React from 'react';
+import app from '../../app';
+import FauxtonAPI from '../../core/api';
+import Databases from '../databases/base';
+import BaseRoute from '../documents/shared-routes';
+import Layout from './layout';
 
 const PermissionsRouteObject = BaseRoute.extend({
 
   roles: ['fx_loggedIn'],
   routes: {
-    'database/:database/permissions': 'permissions'
+    'database/:database/_partition/:partitionKey/permissions': {
+      route: 'permissions',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/permissions': {
+      route: 'permissions',
+      roles: ['fx_loggedIn']
+    }
   },
 
   initialize: function () {
@@ -31,7 +37,7 @@ const PermissionsRouteObject = BaseRoute.extend({
     docOptions.include_docs = true;
   },
 
-  permissions: function (databaseId) {
+  permissions: function (databaseId, partitionKey) {
 
     // XXX magic inheritance props we need to maintain for BaseRoute
     this.database = new Databases.Model({ id: databaseId });
@@ -53,7 +59,8 @@ const PermissionsRouteObject = BaseRoute.extend({
       endpoint={url}
       dbName={this.database.id}
       dropDownLinks={crumbs}
-      database={this.database} />;
+      database={this.database}
+      partitionKey={partitionKey} />;
 
   }
 });


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services