You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by wi...@apache.org on 2019/09/25 12:58:12 UTC

[couchdb-fauxton] branch search_addon created (now 7b2bbfc)

This is an automated email from the ASF dual-hosted git repository.

willholley pushed a change to branch search_addon
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git.


      at 7b2bbfc  Add Search support

This branch includes the following new commits:

     new b2069d4  Import search
     new 7b2bbfc  Add Search support

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[couchdb-fauxton] 01/02: Import search

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to branch search_addon
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

commit b2069d491372d73df4667bbb97fe2788165e2608
Author: Will Holley <wi...@gmail.com>
AuthorDate: Tue Sep 24 15:35:21 2019 +0100

    Import search
---
 .gitignore                                         |   1 +
 app/addons/search/__tests__/components.test.js     | 160 ++++++++
 app/addons/search/__tests__/search.actions.test.js |  52 +++
 .../search/__tests__/search.reducers.test.js       | 142 +++++++
 app/addons/search/actions.js                       | 428 +++++++++++++++++++++
 app/addons/search/actiontypes.js                   |  36 ++
 app/addons/search/api.js                           |  32 ++
 app/addons/search/assets/less/search.less          | 159 ++++++++
 app/addons/search/base.js                          | 125 ++++++
 app/addons/search/components/Analyzer.js           | 142 +++++++
 app/addons/search/components/AnalyzerDropdown.js   |  77 ++++
 app/addons/search/components/AnalyzerMultiple.js   |  94 +++++
 app/addons/search/components/AnalyzerRow.js        |  84 ++++
 app/addons/search/components/SearchForm.js         | 126 ++++++
 .../search/components/SearchFormContainer.js       |  36 ++
 app/addons/search/components/SearchIndexEditor.js  | 164 ++++++++
 .../components/SearchIndexEditorContainer.js       |  82 ++++
 app/addons/search/constants.js                     |  18 +
 app/addons/search/layout.js                        | 101 +++++
 app/addons/search/reducers.js                      | 314 +++++++++++++++
 app/addons/search/resources.js                     |  72 ++++
 app/addons/search/routes.js                        | 272 +++++++++++++
 .../search/tests/nightwatch/cloneSearchIndex.js    |  68 ++++
 .../search/tests/nightwatch/createNewSearch.js     | 130 +++++++
 .../search/tests/nightwatch/deleteSearchIndex.js   | 107 ++++++
 .../search/tests/nightwatch/searchPageApiBar.js    |  55 +++
 app/addons/search/tests/nightwatch/sharedSearch.js |  39 ++
 27 files changed, 3116 insertions(+)

diff --git a/.gitignore b/.gitignore
index 7865768..e8b3d39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ app/addons/*
 !app/addons/styletests
 !app/addons/cors
 !app/addons/setup
+!app/addons/search
 settings.json*
 i18n.json
 !settings.json.default
diff --git a/app/addons/search/__tests__/components.test.js b/app/addons/search/__tests__/components.test.js
new file mode 100644
index 0000000..6bb6f39
--- /dev/null
+++ b/app/addons/search/__tests__/components.test.js
@@ -0,0 +1,160 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import {mount} from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+import FauxtonAPI from '../../../core/api';
+import AnalyzerDropdown from '../components/AnalyzerDropdown';
+import SearchForm from '../components/SearchForm';
+import SearchIndexEditor from '../components/SearchIndexEditor';
+import '../base';
+
+describe('SearchIndexEditor', () => {
+  const defaultProps = {
+    isLoading: false,
+    isCreatingIndex: false,
+    database: { id: 'my_db' },
+    lastSavedDesignDocName: 'last_ddoc',
+    lastSavedSearchIndexName: 'last_idx',
+    searchIndexFunction: '',
+    saveDoc: {},
+    designDocs: [],
+    searchIndexName: '',
+    ddocPartitioned: false,
+    newDesignDocPartitioned: false,
+    analyzerType: '',
+    singleAnalyzer: '',
+    defaultAnalyzer: '',
+    defaultMultipleAnalyzer: '',
+    analyzerFields: [],
+    setAnalyzerType: () => {},
+    setDefaultMultipleAnalyzer: () => {},
+    setSingleAnalyzer: () => {},
+    addAnalyzerRow: () => {},
+    setSearchIndexName: () => {},
+    saveSearchIndex: () => {},
+    selectDesignDoc: () => {},
+    updateNewDesignDocName: () => {}
+  };
+
+  it('generates the correct cancel link when db, ddoc and views have special chars', () => {
+    const editorEl = mount(<SearchIndexEditor
+      {...defaultProps}
+      database={{ id: 'db%$1' }}
+      lastSavedDesignDocName={'_design/doc/1$2'}
+      lastSavedSearchIndexName={'search?abc/123'}
+    />);
+    const expectedUrl = `/${encodeURIComponent('db%$1')}/_design/${encodeURIComponent('doc/1$2')}/_search/${encodeURIComponent('search?abc/123')}`;
+    expect(editorEl.find('a.index-cancel-link').prop('href')).toMatch(expectedUrl);
+  });
+
+  it('does not save when missing the index name', () => {
+    const spy = sinon.stub();
+    const editorEl = mount(<SearchIndexEditor
+      {...defaultProps}
+      database={{ id: 'test_db' }}
+      designDocs={[{id: '_design/d1'}, {id: '_design/d2'}]}
+      ddocName='_design/d1'
+      searchIndexName={''}
+      saveSearchIndex={spy}
+      saveDoc={{id: '_design/d'}}
+    />);
+
+    editorEl.find('button#save-index').simulate('click', {preventDefault: () => {}});
+    sinon.assert.notCalled(spy);
+  });
+});
+
+describe('AnalyzerDropdown', () => {
+
+  it('check default values and settings', () => {
+    const el = mount(<AnalyzerDropdown />);
+
+    // confirm default label
+    expect(el.find('label').length).toBe(2);
+    expect(el.find('label').first().text()).toBe('Type');
+
+    // confirm default value
+    expect(el.find('select').hasClass('standard')).toBeTruthy();
+  });
+
+  it('omits label element if empty label passed', () => {
+    const el = mount(<AnalyzerDropdown label="" />);
+
+    // (1, because there are normally 2 labels, see prev test)
+    expect(el.find('label').length).toBe(1);
+  });
+
+  it('custom ID works', () => {
+    const customID = 'myCustomID';
+    const el = mount(<AnalyzerDropdown id={customID} />);
+    expect(el.find('select').prop('id')).toBe(customID);
+  });
+
+  it('sets default value', () => {
+    const defaultSelected = 'russian';
+    const el = mount(
+      <AnalyzerDropdown defaultSelected={defaultSelected} />
+    );
+
+    expect(el.find('select').hasClass(defaultSelected)).toBeTruthy();
+  });
+
+  it('custom classes get applied', () => {
+    const el = mount(<AnalyzerDropdown classes="nuthatch vulture" />);
+    expect(el.find('.nuthatch').exists()).toBeTruthy();
+    expect(el.find('.vulture').exists()).toBeTruthy();
+  });
+
+  it('custom change handler gets called', () => {
+    const spy = sinon.spy();
+    const el = mount(<AnalyzerDropdown onChange={spy} />);
+    const newVal = 'whitespace';
+    el.find('select').simulate('change', { target: { value: newVal }});
+    expect(spy.calledOnce).toBeTruthy();
+  });
+
+});
+
+describe('SearchForm', () => {
+  const defaultProps = {
+    searchResults: [{id: 'elephant'}],
+    searchPerformed: true,
+    hasActiveQuery: false,
+    searchQuery: 'a_search',
+    database: { id: 'foo' },
+    querySearch: () => {},
+    setSearchQuery: () => {}
+  };
+
+  beforeEach(() => {
+    sinon.stub(FauxtonAPI, 'urls').returns('/fake/url');
+  });
+
+  afterEach(() => {
+    FauxtonAPI.urls.restore();
+  });
+
+  it('renders docs from the search results', () => {
+    const el = mount(<SearchForm
+      {...defaultProps}
+    />);
+    expect(el.find('pre').first().text('elephant')).toBeTruthy();
+  });
+
+  it('renders with links', () => {
+    const el = mount(<SearchForm
+      {...defaultProps}
+    />);
+    expect(el.find('a')).toBeTruthy();
+  });
+});
diff --git a/app/addons/search/__tests__/search.actions.test.js b/app/addons/search/__tests__/search.actions.test.js
new file mode 100644
index 0000000..99edd28
--- /dev/null
+++ b/app/addons/search/__tests__/search.actions.test.js
@@ -0,0 +1,52 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import sinon from 'sinon';
+import utils from '../../../../test/mocha/testUtils';
+import FauxtonAPI from '../../../core/api';
+import Actions from '../actions';
+import * as API from '../api';
+import '../base';
+import '../../documents/base';
+
+const {restore} = utils;
+FauxtonAPI.router = new FauxtonAPI.Router([]);
+
+describe('search actions', () => {
+
+  afterEach(() => {
+    restore(FauxtonAPI.navigate);
+    restore(FauxtonAPI.addNotification);
+    restore(API.fetchSearchResults);
+  });
+
+  it("should show a notification and redirect if database doesn't exist", () => {
+    const navigateSpy = sinon.spy(FauxtonAPI, 'navigate');
+    const notificationSpy = sinon.spy(FauxtonAPI, 'addNotification');
+    sinon.stub(API, 'fetchSearchResults').rejects(new Error('db not found'));
+    FauxtonAPI.reduxDispatch = () => {};
+
+    const params = {
+      databaseName: 'safe-id-db',
+      designDoc: 'design-doc',
+      indexName: 'idx1',
+      query: 'a_query'
+    };
+    return Actions.dispatchInitSearchIndex(params)
+      .then(() => {
+        expect(notificationSpy.calledOnce).toBeTruthy();
+        expect(/db not found/.test(notificationSpy.args[0][0].msg)).toBeTruthy();
+        expect(navigateSpy.calledOnce).toBeTruthy();
+        expect(navigateSpy.args[0][0]).toBe('database/safe-id-db/_all_docs');
+      });
+  });
+
+});
diff --git a/app/addons/search/__tests__/search.reducers.test.js b/app/addons/search/__tests__/search.reducers.test.js
new file mode 100644
index 0000000..f08d269
--- /dev/null
+++ b/app/addons/search/__tests__/search.reducers.test.js
@@ -0,0 +1,142 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import reducer from '../reducers';
+import ActionTypes from '../actiontypes';
+
+describe('Search Reducer', () => {
+
+  it('adds an analyzer row', () => {
+    const action = {
+      type: ActionTypes.SEARCH_INDEX_ADD_ANALYZER_ROW,
+      options: {
+        analyzer: 'sample',
+        fieldName: 'f1'
+      }
+    };
+    let newState = reducer(undefined, { type: 'DO_NOTHING' });
+    expect(newState.analyzerFields).toHaveLength(0);
+    newState = reducer(newState, action);
+    expect(newState.analyzerFields).toHaveLength(1);
+    expect(newState.analyzerFields[0].analyzer).toBe('sample');
+    expect(newState.analyzerFields[0].valid).toBe(true);
+  });
+
+  it('updates field name of an existing row', () => {
+    const action = {
+      type: ActionTypes.SEARCH_INDEX_ADD_ANALYZER_ROW,
+      options: {
+        analyzer: 'sample',
+        fieldName: 'f1'
+      }
+    };
+    let newState = reducer(undefined, action);
+    action.options.fieldName = 'f2';
+    newState = reducer(newState, action);
+    action.options.fieldName = 'f3';
+    newState = reducer(newState, action);
+
+    const removeAction = {
+      type: ActionTypes.SEARCH_INDEX_SET_ANALYZER_ROW_FIELD_NAME,
+      options: { rowIndex: 1, fieldName: 'f100' }
+    };
+    newState = reducer(newState, removeAction);
+    expect(newState.analyzerFields[0].fieldName).toBe('f1');
+    expect(newState.analyzerFields[1].fieldName).toBe('f100');
+    expect(newState.analyzerFields[2].fieldName).toBe('f3');
+  });
+
+  it('updates analyzer of an existing row', () => {
+    const action = {
+      type: ActionTypes.SEARCH_INDEX_ADD_ANALYZER_ROW,
+      options: {
+        analyzer: 'sample',
+        fieldName: 'f1'
+      }
+    };
+    let newState = reducer(undefined, action);
+    action.options.fieldName = 'f2';
+    newState = reducer(newState, action);
+    action.options.fieldName = 'f3';
+    newState = reducer(newState, action);
+
+    const removeAction = {
+      type: ActionTypes.SEARCH_INDEX_SET_ANALYZER_ROW,
+      options: { rowIndex: 1, analyzer: 'keyword' }
+    };
+    newState = reducer(newState, removeAction);
+    expect(newState.analyzerFields[0].analyzer).toBe('sample');
+    expect(newState.analyzerFields[1].analyzer).toBe('keyword');
+    expect(newState.analyzerFields[2].analyzer).toBe('sample');
+  });
+
+  it('removes an analyzer row', () => {
+    const action = {
+      type: ActionTypes.SEARCH_INDEX_ADD_ANALYZER_ROW,
+      options: {
+        analyzer: 'sample',
+        fieldName: 'f1'
+      }
+    };
+    let newState = reducer(undefined, action);
+    action.options.fieldName = 'f2';
+    newState = reducer(newState, action);
+    action.options.fieldName = 'f3';
+    newState = reducer(newState, action);
+
+    const removeAction = {
+      type: ActionTypes.SEARCH_INDEX_REMOVE_ANALYZER_ROW,
+      options: { rowIndex: 1 }
+    };
+    newState = reducer(newState, removeAction);
+    expect(newState.analyzerFields).toHaveLength(2);
+    expect(newState.analyzerFields[0].fieldName).toBe('f1');
+    expect(newState.analyzerFields[1].fieldName).toBe('f3');
+  });
+
+  it('updates search results and resets the hasActiveQuery flag', () => {
+    let newState = reducer(undefined, {
+      type: ActionTypes.SEARCH_INDEX_PREVIEW_REQUEST_MADE
+    });
+    expect(newState.hasActiveQuery).toBe(true);
+
+    const action = {
+      type: ActionTypes.SEARCH_INDEX_PREVIEW_MODEL_UPDATED,
+      options: {
+        searchResults: ['result1', 'result2']
+      }
+    };
+    newState = reducer(newState, action);
+    expect(newState.searchResults).toHaveLength(2);
+    expect(newState.hasActiveQuery).toBe(false);
+  });
+
+  it('resets the search results when the search term is empty', () => {
+    const action = {
+      type: ActionTypes.SEARCH_INDEX_PREVIEW_MODEL_UPDATED,
+      options: {
+        searchResults: ['result1', 'result2']
+      }
+    };
+    let newState = reducer(undefined, action);
+    expect(newState.searchResults).toHaveLength(2);
+
+    const initAction = {
+      type: ActionTypes.SEARCH_INDEX_INIT,
+      options: {
+        searchQuery: ''
+      }
+    };
+    newState = reducer(newState, initAction);
+    expect(newState.searchResults).toBeUndefined();
+  });
+
+});
diff --git a/app/addons/search/actions.js b/app/addons/search/actions.js
new file mode 100644
index 0000000..82ac0f1
--- /dev/null
+++ b/app/addons/search/actions.js
@@ -0,0 +1,428 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import FauxtonAPI from '../../core/api';
+import ActionTypes from './actiontypes';
+import CloudantSearch from './resources';
+import Documents from '../documents/base';
+import SidebarActions from '../documents/sidebar/actions';
+import IndexEditorActions from '../documents/index-editor/actions';
+import * as API from './api';
+
+const dispatchInitNewSearchIndex = (params) => {
+  // ensure we start with a clear slate
+  FauxtonAPI.reduxDispatch({ type: ActionTypes.SEARCH_INDEX_CLEAR });
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_LOADING,
+    options: {
+      loading: true
+    }
+  });
+
+  params.designDocs.fetch().then(() => {
+    FauxtonAPI.reduxDispatch({
+      type: ActionTypes.SEARCH_INDEX_DESIGN_DOCS_LOADED,
+      options: {
+        designDocs: params.designDocs,
+        defaultDDoc: params.defaultDDoc,
+        database: params.database
+      }
+    });
+  });
+};
+
+const dispatchInitSearchIndex = (params)  => {
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_LOADING,
+    options: {
+      loading: true
+    }
+  });
+
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SEARCH_INDEX_INIT,
+    options: {
+      databaseName: params.databaseName,
+      partitionKey: params.partitionKey,
+      ddocName: params.designDoc,
+      indexName: params.indexName,
+      searchQuery: params.query ? params.query : ''
+    }
+  });
+
+  if (params.query) {
+    return executSearchQuery(params.databaseName, params.partitionKey, params.designDoc,
+      params.indexName, params.query, FauxtonAPI.reduxDispatch);
+  }
+};
+
+const dispatchEditSearchIndex = (params) => {
+  var ddocInfo = new Documents.DdocInfo({ _id: params.ddocID }, { database: params.database });
+
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_LOADING,
+    options: {
+      loading: true
+    }
+  });
+
+  FauxtonAPI.Promise.all([params.designDocs.fetch(), ddocInfo.fetch()]).then(([ddocs]) => {
+    const ddoc = ddocs.rows.find(ddoc => ddoc._id === ddocInfo.id).doc;
+    if (!ddoc.indexes || !ddoc.indexes[params.indexName]) {
+      throw Error(`Index "${params.indexName}" not found`);
+    }
+    FauxtonAPI.reduxDispatch({
+      type: ActionTypes.SEARCH_INDEX_INIT_EDIT_SEARCH_INDEX,
+      options: {
+        indexName: params.indexName,
+        database: params.database,
+        ddocInfo: ddocInfo,
+        designDocs: params.designDocs
+      }
+    });
+  }).catch(err => {
+    const details = err.message ? err.message : '';
+    FauxtonAPI.addNotification({
+      msg: `There was a problem editing the search index "${params.indexName}". ` + details,
+      type: 'error',
+      clear: true
+    });
+  });
+};
+
+const selectTab = (tab) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SELECT_TAB,
+    options: {
+      tab: tab
+    }
+  });
+};
+
+const setSearchIndexName = (str) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_NAME,
+    options: {
+      value: str
+    }
+  });
+};
+
+const setAnalyzerType = (type) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_ANALYZER_TYPE,
+    options: {
+      value: type
+    }
+  });
+};
+
+const addAnalyzerRow = (analyzer) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_ADD_ANALYZER_ROW,
+    options: {
+      analyzer: analyzer
+    }
+  });
+};
+
+const removeAnalyzerRow = (rowIndex) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_REMOVE_ANALYZER_ROW,
+    options: {
+      rowIndex: rowIndex
+    }
+  });
+};
+
+const setAnalyzerRowFieldName = (params) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_ANALYZER_ROW_FIELD_NAME,
+    options: {
+      rowIndex: params.rowIndex,
+      fieldName: params.fieldName
+    }
+  });
+};
+
+const setAnalyzer = (params) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_ANALYZER_ROW,
+    options: {
+      rowIndex: params.rowIndex,
+      analyzer: params.analyzer
+    }
+  });
+};
+
+const setDefaultMultipleAnalyzer = (analyzer) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_DEFAULT_MULTIPLE_ANALYZER,
+    options: {
+      analyzer: analyzer
+    }
+  });
+};
+
+const setSingleAnalyzer = (analyzer) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_SINGLE_ANALYZER,
+    options: {
+      analyzer: analyzer
+    }
+  });
+};
+
+const saveSearchIndex = (doc, info, navigateToUrl) => {
+  doc.setIndex(info.indexName, info.indexFunction, info.analyzerInfo);
+
+  if (info.lastSavedDesignDocName === doc.id && info.lastSavedSearchIndexName !== info.indexName) {
+    var indexes = doc.get('indexes') || {};
+    delete indexes[info.lastSavedSearchIndexName];
+    doc.set({ indexes: indexes });
+  }
+
+  doc.save().then(() => {
+    info.designDocs.add(doc, { merge: true });
+
+    FauxtonAPI.addNotification({
+      msg: 'The search index has been saved.',
+      type: 'success',
+      clear: true
+    });
+
+    // if the user just saved the view to a different design doc, remove the view from the old design doc and
+    // maybe even delete if it's empty
+    if (!info.isCreatingIndex && info.lastSavedDesignDocName !== doc.id) {
+      const oldDesignDoc = IndexEditorActions.helpers.findDesignDoc(info.designDocs, info.lastSavedDesignDocName);
+      IndexEditorActions.safeDeleteIndex(oldDesignDoc, info.designDocs, 'indexes', info.lastSavedSearchIndexName, {
+        onSuccess: () => {
+          SidebarActions.dispatchUpdateDesignDocs(info.designDocs);
+        }
+      });
+    }
+
+    SidebarActions.dispatchUpdateDesignDocs(info.designDocs);
+    FauxtonAPI.navigate(navigateToUrl, { trigger: true });
+  }, (xhr) => {
+    const responseText = JSON.parse(xhr.responseText).reason;
+    FauxtonAPI.addNotification({
+      msg: 'Save failed: ' + responseText,
+      type: 'error',
+      clear: true
+    });
+  });
+};
+
+
+const deleteSearchIndex = (options) => {
+  const onSuccess = () => {
+
+    // if the user was on the index that was just deleted, redirect them back to all docs
+    if (options.isOnIndex) {
+      const url = FauxtonAPI.urls('allDocs', 'app', options.database.safeID());
+      FauxtonAPI.navigate(url);
+    }
+    SidebarActions.dispatchUpdateDesignDocs(options.designDocs);
+
+    FauxtonAPI.addNotification({
+      msg: 'The <code>' + _.escape(options.indexName) + '</code> search index has been deleted.',
+      type: 'info',
+      escape: false,
+      clear: true
+    });
+    SidebarActions.dispatchHideDeleteIndexModal();
+  };
+
+  IndexEditorActions.safeDeleteIndex(options.designDoc, options.designDocs, 'indexes', options.indexName, { onSuccess });
+};
+
+const cloneSearchIndex = (params) => {
+  const targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName, params.database);
+  let indexes = targetDesignDoc.get('indexes');
+  if (indexes && _.has(indexes, params.newIndexName)) {
+    FauxtonAPI.addNotification({
+      msg: 'That index name is already used in this design doc. Please enter a new name.',
+      type: 'error',
+      clear: true
+    });
+    return;
+  }
+  if (!indexes) {
+    indexes = {};
+  }
+  const sourceDesignDoc = IndexEditorActions.helpers.findDesignDoc(params.designDocs, '_design/' + params.sourceDesignDocName);
+  const sourceDesignDocJSON = sourceDesignDoc.toJSON();
+
+  // this sets whatever content is in the source index into the target design doc under the new index name
+  indexes[params.newIndexName] = sourceDesignDocJSON.indexes[params.sourceIndexName];
+  targetDesignDoc.set({ indexes: indexes });
+
+  targetDesignDoc.save().then(function () {
+    params.onComplete();
+    FauxtonAPI.addNotification({
+      msg: 'The search index has been cloned.',
+      type: 'success',
+      clear: true
+    });
+
+    SidebarActions.dispatchUpdateDesignDocs(params.designDocs);
+  },
+  function (xhr) {
+    params.onComplete();
+    var responseText = JSON.parse(xhr.responseText).reason;
+    FauxtonAPI.addNotification({
+      msg: 'Clone failed: ' + responseText,
+      type: 'error',
+      clear: true
+    });
+  });
+};
+
+const gotoEditSearchIndexPage = (databaseName, partitionKey, designDocName, indexName) => {
+  const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+  FauxtonAPI.navigate('#' + FauxtonAPI.urls('search', 'edit', encodeURIComponent(databaseName),
+    encodedPartKey, encodeURIComponent(designDocName), encodeURIComponent(indexName)));
+};
+
+const selectDesignDoc = (designDoc) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SELECT_DESIGN_DOC,
+    options: {
+      value: designDoc
+    }
+  });
+};
+
+// const querySearch = (searchQuery, partitionKey) => {
+const querySearch = (databaseName, partitionKey, ddocName, indexName, searchQuery) => {
+  const baseUrl = partitionKey ?
+    FauxtonAPI.urls('partitioned_search', 'app', encodeURIComponent(databaseName), encodeURIComponent(partitionKey), encodeURIComponent(ddocName), encodeURIComponent(indexName)) :
+    FauxtonAPI.urls('search', 'app', encodeURIComponent(databaseName), encodeURIComponent(ddocName), encodeURIComponent(indexName));
+  FauxtonAPI.navigate(`${baseUrl}${encodeURIComponent(indexName)}?${encodeURIComponent(searchQuery)}`, {trigger: true});
+};
+
+const executSearchQuery = (database, partitionKey, ddoc, index, searchQuery, dispatch) => {
+  dispatch({ type: ActionTypes.SEARCH_INDEX_PREVIEW_REQUEST_MADE });
+
+  return API.fetchSearchResults(database, partitionKey, ddoc, index, searchQuery)
+    .then(rows => {
+      dispatch({
+        type: ActionTypes.SEARCH_INDEX_PREVIEW_MODEL_UPDATED,
+        options: {
+          searchResults: rows
+        }
+      });
+    }).catch(err => {
+      dispatch({ type: ActionTypes.SEARCH_INDEX_PREVIEW_REQUEST_ERROR });
+
+      if (err && err.message.includes('`partition` not supported')) {
+        dispatch(partitionParamNotSupported());
+      } else if (err && err.message.includes('`partition` parameter is mandatory')) {
+        dispatch(partitionParamIsMandatory());
+      } else {
+        FauxtonAPI.addNotification({
+          msg: 'Search failed: ' + err.message,
+          type: 'error',
+          clear: true
+        });
+      }
+
+      if (err.message.includes('not found')) {
+        FauxtonAPI.navigate(FauxtonAPI.urls('allDocsSanitized', 'app', database), {trigger: true});
+      }
+    });
+};
+
+const setSearchQuery = (query) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_SET_SEARCH_QUERY,
+    options: {
+      query: query
+    }
+  });
+};
+
+const updateNewDesignDocName = (designDocName) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_NEW_DESIGN_DOC_NAME_UPDATED,
+    options: {
+      value: designDocName
+    }
+  });
+};
+
+const updateNewDesignDocPartitioned = (isPartitioned) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SEARCH_INDEX_NEW_DESIGN_DOC_PARTITONED_UPDATED,
+    options: {
+      value: isPartitioned
+    }
+  });
+};
+
+const partitionParamNotSupported = () => {
+  return {
+    type: ActionTypes.SEARCH_INDEX_PARTITION_PARAM_NOT_SUPPORTED
+  };
+};
+
+const partitionParamIsMandatory = () => {
+  return {
+    type: ActionTypes.SEARCH_INDEX_PARTITION_PARAM_MANDATORY
+  };
+};
+
+
+// helpers
+
+function getDesignDoc (designDocs, targetDesignDocName, newDesignDocName, database) {
+  if (targetDesignDocName === 'new-doc') {
+    var doc = {
+      "_id": "_design/" + newDesignDocName,
+      "indexes": {},
+      "language": "javascript"
+    };
+    return new CloudantSearch.Doc(doc, { database: database });
+  }
+
+  var foundDoc = designDocs.find(function (ddoc) {
+    return ddoc.id === targetDesignDocName;
+  });
+  return (!foundDoc) ? null : foundDoc.dDocModel();
+}
+
+
+export default {
+  dispatchInitNewSearchIndex,
+  dispatchInitSearchIndex,
+  dispatchEditSearchIndex,
+  selectTab,
+  setSearchIndexName,
+  setAnalyzerType,
+  addAnalyzerRow,
+  removeAnalyzerRow,
+  setAnalyzerRowFieldName,
+  setAnalyzer,
+  setDefaultMultipleAnalyzer,
+  selectDesignDoc,
+  saveSearchIndex,
+  cloneSearchIndex,
+  deleteSearchIndex,
+  gotoEditSearchIndexPage,
+  querySearch,
+  setSearchQuery,
+  setSingleAnalyzer,
+  updateNewDesignDocName,
+  updateNewDesignDocPartitioned,
+  partitionParamNotSupported,
+  partitionParamIsMandatory
+};
diff --git a/app/addons/search/actiontypes.js b/app/addons/search/actiontypes.js
new file mode 100644
index 0000000..c5bbb93
--- /dev/null
+++ b/app/addons/search/actiontypes.js
@@ -0,0 +1,36 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+export default {
+  SEARCH_INDEX_DESIGN_DOCS_LOADED: 'SEARCH_INDEX_DESIGN_DOCS_LOADED',
+  SEARCH_INDEX_INIT: 'SEARCH_INDEX_INIT',
+  SEARCH_INDEX_SELECT_TAB: 'SEARCH_INDEX_SELECT_TAB',
+  SEARCH_INDEX_SET_NAME: 'SEARCH_INDEX_SET_NAME',
+  SEARCH_INDEX_SET_ANALYZER_TYPE: 'SEARCH_INDEX_SET_ANALYZER_TYPE',
+  SEARCH_INDEX_ADD_ANALYZER_ROW: 'SEARCH_INDEX_ADD_ANALYZER_ROW',
+  SEARCH_INDEX_REMOVE_ANALYZER_ROW: 'SEARCH_INDEX_REMOVE_ANALYZER_ROW',
+  SEARCH_INDEX_SET_ANALYZER_ROW_FIELD_NAME: 'SEARCH_INDEX_SET_ANALYZER_ROW_FIELD_NAME',
+  SEARCH_INDEX_SET_ANALYZER_ROW: 'SEARCH_INDEX_SET_ANALYZER_ROW',
+  SEARCH_INDEX_SET_DEFAULT_MULTIPLE_ANALYZER: 'SEARCH_INDEX_SET_DEFAULT_MULTIPLE_ANALYZER',
+  SEARCH_INDEX_INIT_EDIT_SEARCH_INDEX: 'SEARCH_INDEX_INIT_EDIT_SEARCH_INDEX',
+  SEARCH_INDEX_PREVIEW_MODEL_UPDATED: 'SEARCH_INDEX_PREVIEW_MODEL_UPDATED',
+  SEARCH_INDEX_PREVIEW_REQUEST_MADE: 'SEARCH_INDEX_PREVIEW_REQUEST_MADE',
+  SEARCH_INDEX_PREVIEW_REQUEST_ERROR: 'PREVIEW_MODEL_REQUEST_ERROR',
+  SEARCH_INDEX_SELECT_DESIGN_DOC: 'SEARCH_INDEX_SELECT_DESIGN_DOC',
+  SEARCH_INDEX_SET_SINGLE_ANALYZER: 'SEARCH_INDEX_SET_SINGLE_ANALYZER',
+  SEARCH_INDEX_SET_SEARCH_QUERY: 'SEARCH_INDEX_SET_SEARCH_QUERY',
+  SEARCH_INDEX_SET_LOADING: 'SEARCH_INDEX_SET_LOADING',
+  SEARCH_INDEX_CLEAR: 'SEARCH_INDEX_CLEAR',
+  SEARCH_INDEX_UPDATE_INDEX_FUNCTION: 'SEARCH_INDEX_UPDATE_INDEX_FUNCTION',
+  SEARCH_INDEX_NEW_DESIGN_DOC_NAME_UPDATED: 'SEARCH_INDEX_NEW_DESIGN_DOC_NAME_UPDATED',
+  SEARCH_INDEX_NEW_DESIGN_DOC_PARTITONED_UPDATED: 'SEARCH_INDEX_NEW_DESIGN_DOC_PARTITONED_UPDATED',
+  SEARCH_INDEX_PARTITION_PARAM_NOT_SUPPORTED: 'SEARCH_INDEX_PARTITION_PARAM_NOT_SUPPORTED',
+  SEARCH_INDEX_PARTITION_PARAM_MANDATORY: 'SEARCH_INDEX_PARTITION_PARAM_MANDATORY'
+};
diff --git a/app/addons/search/api.js b/app/addons/search/api.js
new file mode 100644
index 0000000..cbfdbcd
--- /dev/null
+++ b/app/addons/search/api.js
@@ -0,0 +1,32 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import FauxtonAPI from '../../core/api';
+import { get } from '../../core/ajax';
+
+function searchUrl(database, partitionKey, ddoc, index, searchQuery) {
+  //https://[username].cloudant.com/animaldb/_design/views101/_search/animals?q=kookaburra
+  const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+  return FauxtonAPI.urls('search', 'server', encodeURIComponent(database), encodedPartKey,
+    encodeURIComponent(ddoc), encodeURIComponent(index),
+    '?limit=10&q=' + encodeURIComponent(searchQuery));
+}
+
+export const fetchSearchResults = (database, partitionKey, ddoc, index, searchQuery) => {
+  const url = searchUrl(database, partitionKey, ddoc, index, searchQuery);
+  return get(url).then((res) => {
+    if (res.error) {
+      throw new Error(res.reason);
+    }
+
+    return res.rows;
+  });
+};
diff --git a/app/addons/search/assets/less/search.less b/app/addons/search/assets/less/search.less
new file mode 100644
index 0000000..68c7589
--- /dev/null
+++ b/app/addons/search/assets/less/search.less
@@ -0,0 +1,159 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+@import "../../../style/assets/less/variables.less";
+@import "../../../style/assets/less/mixins.less";
+
+.search-index-page-loading {
+  margin-top: 20px;
+}
+
+.edit-search-index-page-loading {
+  margin-top: 25px;
+}
+
+.index-tabs.dashboard-upper-menu {
+  position: inherit;
+  &.index-tabs {
+    border-bottom: 0;
+    .fonticon:before {
+      margin-right: 6px;
+    }
+  }
+}
+
+#queryText{
+  max-width: 300px;
+}
+
+.ace-editor-section .ace_editor {
+  font-size: 13px;
+  width: 100%;
+  line-height: 22px;
+}
+
+.search-query-save {
+  max-width: 100%;
+  .design-doc-group #new-ddoc-section {
+    margin-top: 24px;
+    label {
+      padding-top: 12px;
+    }
+  }
+}
+
+#analyzer-fields {
+  margin-top: 10px;
+
+  li {
+    margin-bottom: 5px;
+
+    &:first-child {
+      button {
+        margin-top: 27px;
+      }
+    }
+    div:first-child {
+      margin-left: 0;
+    }
+  }
+
+  .span4 {
+    margin-right: 3px;
+    width: 30%;
+
+    .styled-select, select {
+      width: 100%;
+    }
+  }
+}
+
+.delete-analyzer {
+  margin-top: 3px;
+  margin-left: 15px;
+}
+
+.search-index-content>div {
+  margin: 20px 10px 0 20px;
+  .icon-question-sign {
+    margin-left: 4px;
+    &:hover {
+      color: @hoverHighlight;
+    }
+  }
+}
+
+.search-index-function {
+  label {
+    margin-right: 0;
+  }
+  .ace_editor {
+    line-height: 22px;
+  }
+}
+
+#search-query-submit {
+  padding: 12px;
+}
+
+#search-index-preview-form {
+  margin: 5px 0 20px;
+  .input-append {
+    vertical-align: bottom;
+  }
+  .help-link {
+    display: inline-block;
+    margin-left: 12px;
+    padding-bottom: 23px;
+  }
+}
+
+#search-index-help {
+  h4 {
+    margin-top: 18px;
+  }
+
+  ul {
+    margin: 0 0 10px;
+    padding: 0;
+    list-style-type: none;
+  }
+  .search-index-examples {
+    li {
+      line-height: 28px;
+      span {
+        .search-index-code-section;
+      }
+    }
+  }
+}
+
+.search-index-tab-content {
+  a:hover {
+    text-decoration: none;
+  }
+}
+
+.search-index-code-section {
+  font-size: 12px;
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+  background-color: #ddd;
+  padding: 5px;
+  border-radius: 3px;
+}
+
+#search-index-query-button {
+  height: 46px;
+  .border-right-radius(@radius);
+}
+
+body #dashboard-content #search-index-form {
+  position: relative;
+}
diff --git a/app/addons/search/base.js b/app/addons/search/base.js
new file mode 100644
index 0000000..483b8b9
--- /dev/null
+++ b/app/addons/search/base.js
@@ -0,0 +1,125 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+import app from '../../app';
+import Helpers from '../../helpers';
+import FauxtonAPI from '../../core/api';
+import Actions from './actions';
+import SearchRoutes from './routes';
+import CloudantDocuments from '../cloudantdocuments/routes';
+import reducers from './reducers';
+import './assets/less/search.less';
+
+SearchRoutes.initialize = function () {
+  FauxtonAPI.registerExtension('sidebar:list', {
+    selector: 'indexes',
+    name: 'Search Indexes',
+    urlNamespace: 'search',
+    indexLabel: 'search index', // used for labels
+    onDelete: Actions.deleteSearchIndex,
+    onClone: Actions.cloneSearchIndex,
+    onEdit: Actions.gotoEditSearchIndexPage
+  });
+  FauxtonAPI.registerExtension('sidebar:links', {
+    title: "New Search Index",
+    url: "new_search",
+    icon: 'fonticon-plus-circled',
+    showForPartitionedDDocs: true
+  });
+  FauxtonAPI.registerExtension('sidebar:newLinks', {
+    url: 'new_search',
+    name: 'Search Index'
+  });
+
+  // this tells Fauxton of the new Search Index type. It's used to determine when a design doc is really empty
+  FauxtonAPI.registerExtension('IndexTypes:propNames', 'indexes');
+};
+
+function partitionUrlComponent(partitionKey) {
+  return partitionKey ? '/_partition/' + partitionKey : '';
+}
+
+var proxyUrl = CloudantDocuments.proxyUrl;
+_.extend(CloudantDocuments.sharedUrlPaths, {
+  search: {
+    server: function (db, partitionKey, designDoc, searchName, query) {
+      query = CloudantDocuments.addRemoteAccount(query);
+      return proxyUrl + db + '/_design/' + designDoc + '/_search/' + searchName + query;
+    },
+
+    //NOTE: partitionKey is included here for compatibility with base registered url functions
+    //but is ignored because it's not supported for shared databases
+    app: function (id, designDoc) {
+      return CloudantDocuments.sharedUrlPaths.userHash() + id + '/_design/' + app.utils.safeURLName(designDoc) + '/_search/';
+    },
+
+    edit: function (database, partitionKey, designDoc, indexName) {
+      return CloudantDocuments.sharedUrlPaths.userHash() + database + '/_design/' + designDoc + '/_search/' + indexName + '/edit';
+    },
+
+    apiurl: function (db, partitionKey, designDoc, searchName) {
+      return CloudantDocuments.sharedUrlPaths.host() + encodeURIComponent(db) + '/_design/' +
+        encodeURIComponent(designDoc) + '/_search/' + encodeURIComponent(searchName);
+    },
+
+    fragment: function (id, partitionKey, designDoc, search) {
+      return CloudantDocuments.sharedUrlPaths.userHash() + id + '/_design/' + designDoc + '/_search/' + search;
+    },
+
+    showIndex: function (id, partitionKey, designDoc, search) {
+      return CloudantDocuments.sharedUrlPaths.host() + id + '/' + designDoc +
+        '/_search/' + search;
+    },
+  },
+  partitioned_search: {
+    app: function (id, partitionKey, designDoc) {
+      return CloudantDocuments.sharedUrlPaths.userHash() + id + '/_design/' + app.utils.safeURLName(designDoc) + '/_search/';
+    }
+  }
+});
+
+FauxtonAPI.registerUrls('partitioned_search', {
+  app: function (id, partitionKey, designDoc) {
+    return 'database/' + id + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_search/';
+  }
+});
+
+FauxtonAPI.registerUrls('search', {
+  server: function (id, partitionKey, designDoc, searchName, query) {
+    return Helpers.getServerUrl('/' + id + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_search/' + searchName + query);
+  },
+
+  app: function (id, designDoc) {
+    return 'database/' + id + '/_design/' + designDoc + '/_search/';
+  },
+
+  edit: function (database, partitionKey, designDoc, indexName) {
+    return 'database/' + database + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_search/' + indexName + '/edit';
+  },
+
+  fragment: function (id, partitionKey, designDoc, search) {
+    return 'database/' + id + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_search/' + search;
+  },
+
+  showIndex: function (id, partitionKey, designDoc, search) {
+    return 'database/' + id + partitionUrlComponent(partitionKey) + '/' + designDoc + '/_search/' + search;
+  },
+
+  apiurl: function (id, partitionKey, designDoc, searchName, query) {
+    return window.location.origin + '/' + encodeURIComponent(id) + '/_design/' + encodeURIComponent(designDoc) +
+      '/_search/' + encodeURIComponent(searchName) + '?q=' + encodeURIComponent(query);
+  }
+});
+
+FauxtonAPI.addReducers({
+  search: reducers
+});
+
+export default SearchRoutes;
diff --git a/app/addons/search/components/Analyzer.js b/app/addons/search/components/Analyzer.js
new file mode 100644
index 0000000..78c8a3c
--- /dev/null
+++ b/app/addons/search/components/Analyzer.js
@@ -0,0 +1,142 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import Constants from '../constants';
+import AnalyzerMultiple from './AnalyzerMultiple';
+import AnalyzerDropdown from './AnalyzerDropdown';
+
+// handles the entire Analyzer section: Simple and Multiple analyzers
+export default class Analyzer extends React.Component {
+  static propTypes = {
+    analyzerType: PropTypes.string.isRequired,
+    analyzerFields: PropTypes.array.isRequired,
+    defaultMultipleAnalyzer: PropTypes.string.isRequired,
+    singleAnalyzer: PropTypes.string.isRequired,
+    setAnalyzerType: PropTypes.func.isRequired,
+    setSingleAnalyzer: PropTypes.func.isRequired,
+    setDefaultMultipleAnalyzer: PropTypes.func.isRequired,
+    addAnalyzerRow: PropTypes.func.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    this.selectAnalyzerType = this.selectAnalyzerType.bind(this);
+    this.selectSingleAnalyzer = this.selectSingleAnalyzer.bind(this);
+    this.selectDefaultMultipleAnalyzer = this.selectDefaultMultipleAnalyzer.bind(this);
+  }
+
+  selectAnalyzerType = (e) => {
+    this.props.setAnalyzerType(e.target.value);
+  };
+
+  validate = () => {
+    if (this.props.analyzerType === Constants.ANALYZER_SINGLE) {
+      return true;
+    }
+    return this.analyzerMultiple.validate();
+  };
+
+  getAnalyzerFieldsAsObject = () => {
+    const obj = {};
+    this.props.analyzerFields.forEach(row => {
+      const fieldName = row.fieldName.replace(/["']/g, '');
+      obj[fieldName] = row.analyzer;
+    });
+    return obj;
+  };
+
+  getInfo = () => {
+    let analyzerInfo;
+    if (this.props.analyzerType === Constants.ANALYZER_SINGLE) {
+      analyzerInfo = this.props.singleAnalyzer;
+    } else {
+      analyzerInfo = {
+        name: 'perfield',
+        default: this.props.defaultMultipleAnalyzer,
+        fields: this.getAnalyzerFieldsAsObject()
+      };
+    }
+    return analyzerInfo;
+  };
+
+  selectSingleAnalyzer = (e) => {
+    this.props.setSingleAnalyzer(e.target.value);
+  };
+
+  selectDefaultMultipleAnalyzer = (e) => {
+    this.props.setDefaultMultipleAnalyzer(e.target.value);
+  };
+
+  getAnalyzerType = () => {
+    if (this.props.analyzerType === Constants.ANALYZER_SINGLE) {
+      return (
+        <AnalyzerDropdown
+          label="Type"
+          defaultSelected={this.props.singleAnalyzer}
+          onChange={this.selectSingleAnalyzer}
+        />
+      );
+    }
+    return (
+      <AnalyzerMultiple
+        ref={node => this.analyzerMultiple = node}
+        defaultAnalyzer={this.props.defaultMultipleAnalyzer}
+        selectDefaultMultipleAnalyzer={this.selectDefaultMultipleAnalyzer}
+        fields={this.props.analyzerFields}
+        addAnalyzerRow={this.props.addAnalyzerRow}
+        removeAnalyzerRow={this.props.removeAnalyzerRow}
+        setAnalyzerRowFieldName={this.props.setAnalyzerRowFieldName}
+        setAnalyzer={this.props.setAnalyzer}
+      />
+    );
+
+  };
+
+  render() {
+    let multipleClasses = 'btn';
+    if (this.props.analyzerType === Constants.ANALYZER_MULTIPLE) {
+      multipleClasses += ' active';
+    }
+    let singleClasses = 'btn';
+    if (this.props.analyzerType === Constants.ANALYZER_SINGLE) {
+      singleClasses += ' active';
+    }
+
+    return (
+      <div className="well">
+        <div className="control-group">
+          <label htmlFor="search-analyzer">Analyzer</label>
+          <div className="btn-group toggle-btns" id="analyzer">
+            <label style={{width: '82px'}}  htmlFor="single-analyzer" className={singleClasses}>Single</label>
+            <input
+              type="radio"
+              id="single-analyzer"
+              name="search-analyzer"
+              value="single"
+              checked={this.props.analyzerType === Constants.ANALYZER_SINGLE}
+              onChange={this.selectAnalyzerType} />
+            <input
+              type="radio"
+              id="multiple-analyzer"
+              name="search-analyzer"
+              value="multiple"
+              checked={this.props.analyzerType === Constants.ANALYZER_MULTIPLE}
+              onChange={this.selectAnalyzerType} />
+            <label style={{width: '82px'}} htmlFor="multiple-analyzer" className={multipleClasses}>Multiple</label>
+          </div>
+        </div>
+        {this.getAnalyzerType()}
+      </div>
+    );
+  }
+}
diff --git a/app/addons/search/components/AnalyzerDropdown.js b/app/addons/search/components/AnalyzerDropdown.js
new file mode 100644
index 0000000..88ac8e5
--- /dev/null
+++ b/app/addons/search/components/AnalyzerDropdown.js
@@ -0,0 +1,77 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import GeneralComponents from '../../components/react-components';
+
+const StyledSelect = GeneralComponents.StyledSelect;
+
+export default class AnalyzerDropdown extends React.Component {
+  static defaultProps = {
+    defaultSelected: 'standard',
+    label: 'Type',
+    id: 'analyzer-default',
+    classes: '',
+    onChange: function () { }
+  };
+  static propTypes = {
+    defaultSelected: PropTypes.string.isRequired,
+    label: PropTypes.string.isRequired,
+    id: PropTypes.string.isRequired,
+    classes: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired
+  };
+
+  getAnalyzers = () => {
+    const analyzers = [
+      'Standard', 'Keyword', 'Simple', 'Whitespace', 'Classic', 'Email'
+    ];
+    return analyzers.map((i) => {
+      return (<option value={i.toLowerCase()} key={i}>{i}</option>);
+    });
+  };
+
+  getLanguages = () => {
+    const languages = [
+      'Arabic', 'Armenian', 'Basque', 'Bulgarian', 'Brazilian', 'Catalan', 'Cjk', 'Chinese', 'Czech',
+      'Danish', 'Dutch', 'English', 'Finnish', 'French', 'Galician', 'German', 'Greek', 'Hindi', 'Hungarian',
+      'Indonesian', 'Irish', 'Italian', 'Japanese', 'Latvian', 'Norwegian', 'Persian', 'Polish', 'Portuguese',
+      'Romanian', 'Russian', 'Spanish', 'Swedish', 'Thai', 'Turkish'
+    ];
+    return languages.map((lang) => {
+      return (<option value={lang.toLowerCase()} key={lang}>{lang}</option>);
+    });
+  };
+
+  getLabel = () => {
+    return this.props.label === '' ? null : <label htmlFor={this.props.id}>{this.props.label}</label>;
+  };
+
+  render() {
+    const languages =
+      <optgroup label="Language-specific" key="languages">
+        {this.getLanguages()}
+      </optgroup>;
+
+    return (
+      <div className={this.props.classes}>
+        {this.getLabel()}
+        <StyledSelect
+          selectChange={this.props.onChange}
+          selectValue={this.props.defaultSelected}
+          selectId={this.props.id}
+          selectContent={[this.getAnalyzers(), languages]}
+        />
+      </div>
+    );
+  }
+}
diff --git a/app/addons/search/components/AnalyzerMultiple.js b/app/addons/search/components/AnalyzerMultiple.js
new file mode 100644
index 0000000..6aa4785
--- /dev/null
+++ b/app/addons/search/components/AnalyzerMultiple.js
@@ -0,0 +1,94 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import FauxtonAPI from '../../../core/api';
+import PropTypes from 'prop-types';
+import React from 'react';
+import AnalyzerRow from './AnalyzerRow';
+import AnalyzerDropdown from './AnalyzerDropdown';
+
+export default class AnalyzerMultiple extends React.Component {
+  static propTypes = {
+    addAnalyzerRow: PropTypes.func.isRequired,
+    defaultAnalyzer: PropTypes.string.isRequired,
+    selectDefaultMultipleAnalyzer: PropTypes.func.isRequired,
+    fields: PropTypes.array.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      showErrors: false
+    };
+  }
+
+  addRow = (e) => {
+    e.preventDefault();
+    this.props.addAnalyzerRow(this.props.defaultAnalyzer);
+  };
+
+  getRows = () => {
+    return this.props.fields.map((row, i) => {
+      return (
+        <AnalyzerRow
+          row={row}
+          key={row.key}
+          rowIndex={i}
+          showErrors={this.state.showErrors}
+          setAnalyzer={this.props.setAnalyzer}
+          setAnalyzerRowFieldName={this.props.setAnalyzerRowFieldName}
+          removeAnalyzerRow={this.props.removeAnalyzerRow}
+        />
+      );
+    });
+  };
+
+  validate = () => {
+    this.setState({ showErrors: true });
+
+    let hasDuplicate = false;
+    const fieldNames = [];
+    const allValid = this.props.fields.every((row) => {
+      if (fieldNames.includes(row.fieldName)) {
+        hasDuplicate = true;
+      }
+      fieldNames.push(row.fieldName);
+      return row.valid;
+    });
+
+    if (!allValid || hasDuplicate) {
+      FauxtonAPI.addNotification({
+        msg: 'Fieldnames cannot be empty and must be unique.',
+        type: 'error',
+        clear: true
+      });
+    }
+    return allValid;
+  };
+
+  render() {
+    return (
+      <div>
+        <AnalyzerDropdown
+          label="Default"
+          id="defaultAnalyzer"
+          defaultSelected={this.props.defaultAnalyzer}
+          onChange={this.props.selectDefaultMultipleAnalyzer}
+          isValidating={this.validate} />
+        <ul id="analyzer-fields" className="unstyled">{this.getRows()}</ul>
+        <button className="addfield btn btn-small btn-primary" onClick={this.addRow}>
+          <i className="cloudant-circle-plus"></i>
+          Add Field
+        </button>
+      </div>
+    );
+  }
+}
diff --git a/app/addons/search/components/AnalyzerRow.js b/app/addons/search/components/AnalyzerRow.js
new file mode 100644
index 0000000..c8ca8d1
--- /dev/null
+++ b/app/addons/search/components/AnalyzerRow.js
@@ -0,0 +1,84 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import AnalyzerDropdown from './AnalyzerDropdown';
+
+export default class AnalyzerRow extends React.Component {
+  static propTypes = {
+    rowIndex: PropTypes.number.isRequired,
+    row: PropTypes.object.isRequired,
+    showErrors: PropTypes.bool.isRequired,
+    removeAnalyzerRow: PropTypes.func.isRequired,
+    setAnalyzerRowFieldName: PropTypes.func.isRequired,
+    setAnalyzer: PropTypes.func.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+  }
+
+  deleteRow = (e) => {
+    e.preventDefault();
+    this.props.removeAnalyzerRow(this.props.rowIndex);
+  };
+
+  getFieldNameHeading = (analyzerId) => {
+    return (this.props.rowIndex === 0) ? <label htmlFor={analyzerId}>Fieldname</label> : false;
+  };
+
+  changeFieldName = (e) => {
+    this.props.setAnalyzerRowFieldName({
+      rowIndex: this.props.rowIndex,
+      fieldName: e.target.value
+    });
+  };
+
+  selectAnalyzer = (e) => {
+    this.props.setAnalyzer({
+      rowIndex: this.props.rowIndex,
+      analyzer: e.target.value
+    });
+  };
+
+  render() {
+    const analyzerId = "analyzer-row-" + this.props.rowIndex;
+    const analyzerHeading = (this.props.rowIndex === 0) ? 'Analyzer' : '';
+
+    let fieldNameClasses = 'span12';
+    if (this.props.showErrors && !this.props.row.valid) {
+      fieldNameClasses += ' unhappy';
+    }
+
+    return (
+      <li>
+        <div className="row-fluid">
+          <div className="span4">
+            {this.getFieldNameHeading(analyzerId)}
+            <input type="text" value={this.props.row.fieldName} className={fieldNameClasses} onChange={this.changeFieldName} />
+          </div>
+
+          <AnalyzerDropdown
+            id={analyzerId}
+            label={analyzerHeading}
+            defaultSelected={this.props.row.analyzer}
+            classes="span4"
+            onChange={this.selectAnalyzer} />
+
+          <div className="span4">
+            <button className="btn btn-danger delete-analyzer" onClick={this.deleteRow}>delete</button>
+          </div>
+        </div>
+      </li>
+    );
+  }
+}
diff --git a/app/addons/search/components/SearchForm.js b/app/addons/search/components/SearchForm.js
new file mode 100644
index 0000000..9a85be1
--- /dev/null
+++ b/app/addons/search/components/SearchForm.js
@@ -0,0 +1,126 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import FauxtonAPI from '../../../core/api';
+import GeneralComponents from '../../components/react-components';
+
+export default class SearchForm extends React.Component {
+  static propTypes = {
+    hasActiveQuery: PropTypes.bool.isRequired,
+    searchQuery: PropTypes.string.isRequired,
+    searchPerformed: PropTypes.bool.isRequired,
+    querySearch: PropTypes.func.isRequired,
+    setSearchQuery: PropTypes.func.isRequired,
+    searchResults: PropTypes.array
+  };
+
+  componentDidMount() {
+    this.searchInput.focus();
+  }
+
+  querySearch = (e) => {
+    e.preventDefault();
+    if (this.props.searchQuery.trim() === '') {
+      FauxtonAPI.addNotification({
+        msg: 'Please enter a search term.',
+        type: 'error',
+        clear: true
+      });
+      this.searchInput.focus();
+      return;
+    }
+    const {databaseName, partitionKey, ddocName, indexName, searchQuery} = this.props;
+    this.props.querySearch(databaseName, partitionKey, ddocName, indexName, searchQuery);
+  };
+
+  getRows = () => {
+    const database = encodeURIComponent(this.props.databaseName);
+    return this.props.searchResults.map((item) => {
+      const doc = {
+        header: item.id,
+        content: JSON.stringify(item, null, '  '),
+        url: FauxtonAPI.urls('document', 'app', database, encodeURIComponent(item.id))
+      };
+      return <GeneralComponents.Document
+        key={item.id}
+        keylabel={'id:'}
+        doc={doc}
+        header={item.id}
+        isDeletable={false}
+        docContent={doc.content}
+        onClick={this.onClick}
+        docChecked={() => { }}
+        docIdentifier={item.id} />;
+    });
+  };
+
+  onClick = (id, doc) => {
+    if (doc.url) {
+      FauxtonAPI.navigate(doc.url);
+    }
+  };
+
+  onType = (e) => {
+    this.props.setSearchQuery(e.target.value);
+  };
+
+  getResults = () => {
+    if (this.props.hasActiveQuery) {
+      return (<GeneralComponents.LoadLines />);
+    }
+
+    if (this.props.noResultsWarning) {
+      return (<div data-select="search-result-set">{this.props.noResultsWarning}</div>);
+    }
+
+    if (!this.props.searchResults) {
+      return false;
+    }
+
+    if (this.props.searchResults.length === 0) {
+      return (<div data-select="search-result-set">No results found.</div>);
+    }
+
+    return (
+      <div id="doc-list" data-select="search-result-set">
+        {this.getRows()}
+      </div>
+    );
+  };
+
+  render() {
+    const buttonLabel = this.props.hasActiveQuery ? 'Querying...' : 'Query';
+    return (
+      <div>
+        <form id="search-index-preview-form">
+          <span className="input-append">
+            <input
+              className="span4"
+              ref={el => this.searchInput = el}
+              type="text"
+              placeholder="Enter your search query"
+              onChange={this.onType}
+              value={this.props.searchQuery} />
+            <button className="btn btn-primary" id="search-index-query-button" type="submit" disabled={this.props.hasActiveQuery}
+              onClick={this.querySearch}>{buttonLabel}</button>
+          </span>
+          <a className="help-link" data-bypass="true" href={FauxtonAPI.constants.DOC_URLS.SEARCH_INDEX_QUERIES} target="_blank" rel="noopener noreferrer">
+            <i className="icon-question-sign" />
+          </a>
+        </form>
+
+        {this.getResults()}
+      </div>
+    );
+  }
+}
diff --git a/app/addons/search/components/SearchFormContainer.js b/app/addons/search/components/SearchFormContainer.js
new file mode 100644
index 0000000..d5bbba8
--- /dev/null
+++ b/app/addons/search/components/SearchFormContainer.js
@@ -0,0 +1,36 @@
+import { connect } from 'react-redux';
+import SearchForm from './SearchForm';
+import Actions from '../actions';
+
+const mapStateToProps = ({ search }) => {
+  return {
+    databaseName: search.databaseName,
+    partitionKey: search.partitionKey,
+    ddocName: search.ddocName,
+    indexName: search.indexName,
+    hasActiveQuery: search.hasActiveQuery,
+    searchQuery: search.searchQuery,
+    searchPerformed: search.searchPerformed,
+    searchResults: search.searchResults,
+    noResultsWarning: search.noResultsWarning
+  };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    querySearch: (databaseName, partitionKey, ddocName, indexName, searchQuery) => {
+      Actions.querySearch(databaseName, partitionKey, ddocName, indexName, searchQuery);
+    },
+
+    setSearchQuery: (query) => {
+      dispatch(Actions.setSearchQuery(query));
+    }
+  };
+};
+
+const SearchFormContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(SearchForm);
+
+export default SearchFormContainer;
diff --git a/app/addons/search/components/SearchIndexEditor.js b/app/addons/search/components/SearchIndexEditor.js
new file mode 100644
index 0000000..3135ad3
--- /dev/null
+++ b/app/addons/search/components/SearchIndexEditor.js
@@ -0,0 +1,164 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import FauxtonAPI from '../../../core/api';
+import app from '../../../app';
+import PropTypes from 'prop-types';
+import React from 'react';
+import GeneralComponents from '../../components/react-components';
+import IndexEditorComponents from '../../documents/index-editor/components';
+import Analyzer from './Analyzer';
+
+const DesignDocSelector = IndexEditorComponents.DesignDocSelector;
+
+export default class SearchIndexEditor extends React.Component {
+  static defaultProps = {
+    isCreatingIndex: true,
+    blur: function () { },
+    isLoading: true
+  };
+
+  static propTypes = {
+    isLoading: PropTypes.bool,
+    isCreatingIndex: PropTypes.bool,
+    database: PropTypes.object.isRequired,
+    saveDoc: PropTypes.object.isRequired,
+    newDesignDocName: PropTypes.string,
+    blur: PropTypes.func,
+    setSearchIndexName: PropTypes.func.isRequired,
+    searchIndexFunction: PropTypes.string.isRequired,
+    saveSearchIndex: PropTypes.func.isRequired,
+    selectDesignDoc: PropTypes.func.isRequired,
+    updateNewDesignDocName: PropTypes.func.isRequired
+  };
+
+  updateSearchIndexName = (e) => {
+    this.props.setSearchIndexName(e.target.value);
+  };
+
+  saveIndex = (e) => {
+    e.preventDefault();
+
+    // pass off validation work to the individual form sections
+    if (!this.designDocSelector.validate() || !this.analyzer.validate()) {
+      return;
+    }
+
+    if (!this.props.searchIndexName.trim()) {
+      FauxtonAPI.addNotification({
+        msg: 'Please enter the index name.',
+        type: 'error',
+        clear: true
+      });
+      return;
+    }
+
+    const dDocNameClean = this.props.saveDoc.id.replace(/_design\//, '');
+    const encodedPartKey = this.props.partitionKey ? encodeURIComponent(this.props.partitionKey) : '';
+    const url = FauxtonAPI.urls('search', 'fragment', encodeURIComponent(this.props.database.id), encodedPartKey,
+      encodeURIComponent(dDocNameClean), encodeURIComponent(this.props.searchIndexName));
+
+    this.props.saveSearchIndex(this.props.saveDoc, {
+      isCreatingIndex: this.props.isCreatingIndex,
+      indexName: this.props.searchIndexName,
+      designDocs: this.props.designDocs,
+      database: this.props.database,
+      indexFunction: this.getIndexFunction(),
+      analyzerInfo: this.analyzer.getInfo(),
+      lastSavedSearchIndexName: this.props.lastSavedSearchIndexName,
+      lastSavedDesignDocName: this.props.lastSavedDesignDocName
+    }, url);
+  };
+
+  getIndexFunction = () => {
+    return this.searchIndexEditor.getValue();
+  };
+
+  getDesignDocList = () => {
+    return this.props.designDocs.map(function (doc) {
+      return doc.id;
+    });
+  };
+
+  getCancelLink() {
+    const encodedDatabase = encodeURIComponent(this.props.database.id);
+    const encodedPartitionKey = this.props.partitionKey ? encodeURIComponent(this.props.partitionKey) : '';
+    if (!this.props.lastSavedDesignDocName || this.props.isCreatingIndex) {
+      return '#' + FauxtonAPI.urls('allDocs', 'app', encodedDatabase, encodedPartitionKey);
+    }
+
+    const cleanDDocName = this.props.lastSavedDesignDocName.replace(/^_design\//, '');
+    const encodedDDoc = '_design/' + encodeURIComponent(cleanDDocName);
+    const encodedIndex = encodeURIComponent(this.props.lastSavedSearchIndexName);
+    return '#' + FauxtonAPI.urls('search', 'showIndex', encodedDatabase,
+      encodedPartitionKey, encodedDDoc, encodedIndex);
+  }
+
+  render() {
+    if (this.props.isLoading) {
+      return (
+        <div className="search-index-page-loading">
+          <GeneralComponents.LoadLines />
+        </div>
+      );
+    }
+    // If failed to load
+    if (!this.props.database) {
+      return null;
+    }
+
+    const pageHeader = this.props.isCreatingIndex ? 'New Search Index' : 'Edit Search Index';
+    const btnLabel = this.props.isCreatingIndex ? 'Create Document and Build Index' : 'Save Document and Build Index';
+    return (
+      <form className="form-horizontal search-query-save" id="search-index">
+        <h3 className="simple-header">{pageHeader}</h3>
+
+        <DesignDocSelector
+          ref={node => this.designDocSelector = node}
+          designDocLabel="Save to design document"
+          designDocList={this.getDesignDocList()}
+          isDbPartitioned={this.props.isDbPartitioned}
+          newDesignDocName={this.props.newDesignDocName}
+          newDesignDocPartitioned={this.props.newDesignDocPartitioned}
+          selectedDesignDocName={this.props.ddocName}
+          selectedDesignDocPartitioned={this.props.ddocPartitioned}
+          onSelectDesignDoc={this.props.selectDesignDoc}
+          onChangeNewDesignDocName={this.props.updateNewDesignDocName}
+          onChangeNewDesignDocPartitioned={this.props.updateNewDesignDocPartitioned}
+          docLink={app.helpers.getDocUrl('DOC_URL_DESIGN_DOCS')} />
+
+        <div className="control-group">
+          <label htmlFor="search-name">Index name</label>
+          <input type="text" id="search-name" value={this.props.searchIndexName} onChange={this.updateSearchIndexName} />
+        </div>
+
+        <GeneralComponents.CodeEditorPanel
+          id={'search-function'}
+          className="ace-editor-section"
+          ref={node => this.searchIndexEditor = node}
+          title={"Search index function"}
+          allowZenMode={false}
+          docLink={app.helpers.getDocUrl('SEARCH_INDEXES')}
+          defaultCode={this.props.searchIndexFunction}
+          blur={this.props.blur} />
+
+        <Analyzer ref={node => this.analyzer = node} {...this.props}/>
+
+        <div className="control-group">
+          <button id="save-index" className="btn btn-primary save" onClick={this.saveIndex}>
+            <i className="icon fonticon-ok-circled" />{btnLabel}
+          </button>
+          <a href={this.getCancelLink()} className="index-cancel-link">Cancel</a>
+        </div>
+      </form>
+    );
+  }
+}
diff --git a/app/addons/search/components/SearchIndexEditorContainer.js b/app/addons/search/components/SearchIndexEditorContainer.js
new file mode 100644
index 0000000..ebbde2d
--- /dev/null
+++ b/app/addons/search/components/SearchIndexEditorContainer.js
@@ -0,0 +1,82 @@
+import { connect } from 'react-redux';
+import SearchIndexEditor from './SearchIndexEditor';
+import Actions from '../actions';
+import { getSaveDesignDoc, getSelectedDesignDocPartitioned } from '../reducers';
+
+const mapStateToProps = ({ search, databases }, ownProps) => {
+  const isSelectedDDocPartitioned = getSelectedDesignDocPartitioned(search, databases.isDbPartitioned);
+  return {
+    isCreatingIndex: ownProps.isCreatingIndex,
+    isLoading: search.loading,
+    database: search.database,
+    designDocs: search.designDocs,
+    searchIndexFunction: search.searchIndexFunction,
+    ddocName: search.ddocName,
+    ddocPartitioned: isSelectedDDocPartitioned,
+    lastSavedDesignDocName: search.lastSavedDesignDocName,
+    lastSavedSearchIndexName: search.lastSavedSearchIndexName,
+    searchIndexName: search.searchIndexName,
+    analyzerType: search.analyzerType,
+    analyzerFields: search.analyzerFields,
+    analyzerFieldsObj: search.analyzerFieldsObj,
+    defaultMultipleAnalyzer: search.defaultMultipleAnalyzer,
+    singleAnalyzer: search.singleAnalyzer,
+    saveDoc: getSaveDesignDoc(search, databases.isDbPartitioned),
+    newDesignDocName: search.newDesignDocName,
+    newDesignDocPartitioned: search.newDesignDocPartitioned,
+    isDbPartitioned: databases.isDbPartitioned,
+    partitionKey: ownProps.partitionKey
+  };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    setSearchIndexName: (name) => {
+      dispatch(Actions.setSearchIndexName(name));
+    },
+
+    saveSearchIndex: (doc, info, navigateToUrl) => {
+      Actions.saveSearchIndex(doc, info, navigateToUrl);
+    },
+
+    selectDesignDoc: (designDoc) => {
+      dispatch(Actions.selectDesignDoc(designDoc));
+    },
+
+    updateNewDesignDocName: (designDocName) => {
+      dispatch(Actions.updateNewDesignDocName(designDocName));
+    },
+    updateNewDesignDocPartitioned: (isPartitioned) => {
+      dispatch(Actions.updateNewDesignDocPartitioned(isPartitioned));
+    },
+    setAnalyzerType: (type) => {
+      dispatch(Actions.setAnalyzerType(type));
+    },
+    setSingleAnalyzer: (analyzer) => {
+      dispatch(Actions.setSingleAnalyzer(analyzer));
+    },
+    setDefaultMultipleAnalyzer: (analyzer) => {
+      dispatch(Actions.setDefaultMultipleAnalyzer(analyzer));
+    },
+    addAnalyzerRow: (analyzer) => {
+      dispatch(Actions.addAnalyzerRow(analyzer));
+    },
+    removeAnalyzerRow: (rowIndex) => {
+      dispatch(Actions.removeAnalyzerRow(rowIndex));
+    },
+    setAnalyzerRowFieldName: (params) => {
+      dispatch(Actions.setAnalyzerRowFieldName(params));
+    },
+    setAnalyzer: (params) => {
+      dispatch(Actions.setAnalyzer(params));
+    }
+
+  };
+};
+
+const SearchIndexEditorContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(SearchIndexEditor);
+
+export default SearchIndexEditorContainer;
diff --git a/app/addons/search/constants.js b/app/addons/search/constants.js
new file mode 100644
index 0000000..408a988
--- /dev/null
+++ b/app/addons/search/constants.js
@@ -0,0 +1,18 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+export default {
+  ANALYZER_SINGLE: 'single',
+  ANALYZER_MULTIPLE: 'multiple',
+  DEFAULT_SEARCH_INDEX_NAME: 'newSearch',
+  DEFAULT_ANALYZER_TYPE: 'single',
+  DEFAULT_ANALYZER: 'standard',
+  DEFAULT_SEARCH_INDEX_FUNCTION: 'function (doc) {\n  index("name", doc.name);\n}'
+};
diff --git a/app/addons/search/layout.js b/app/addons/search/layout.js
new file mode 100644
index 0000000..fb41a92
--- /dev/null
+++ b/app/addons/search/layout.js
@@ -0,0 +1,101 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+import PropTypes from 'prop-types';
+import React from 'react';
+import {TabsSidebarHeader} from '../documents/layouts';
+import SidebarControllerContainer from '../documents/sidebar/SidebarControllerContainer';
+import SearchFormContainer from './components/SearchFormContainer';
+import SearchIndexEditorContainer from './components/SearchIndexEditorContainer';
+
+
+const getContent = (section, database, indexName,
+  ddocName, designDocs, ddoc, partitionKey) => {
+  if (section === 'create') {
+    return <SearchIndexEditorContainer
+      designDocs={designDocs}
+      ddoc={ddoc}
+      database={database}
+      isCreatingIndex={true}
+      partitionKey={partitionKey}
+    />;
+
+  } else if (section === 'edit') {
+    return <SearchIndexEditorContainer
+      database={database}
+      indexName={indexName}
+      ddocName={ddocName}
+      isCreatingIndex={false}
+      partitionKey={partitionKey}
+    />;
+  }
+
+  return <SearchFormContainer />;
+};
+
+export const SearchLayout = ({
+  section,
+  database,
+  indexName,
+  ddocName,
+  docURL, endpoint,
+  dropDownLinks,
+  designDocs,
+  ddoc,
+  selectedNavItem,
+  partitionKey,
+  onPartitionKeySelected,
+  onGlobalModeSelected,
+  globalMode
+}) => {
+  return (
+    <div id="dashboard" className="with-sidebar">
+      <TabsSidebarHeader
+        hideQueryOptions={true}
+        docURL={docURL}
+        endpoint={endpoint}
+        dbName={database.id}
+        dropDownLinks={dropDownLinks}
+        database={database}
+        showPartitionKeySelector={section === 'search'}
+        partitionKey={partitionKey}
+        onPartitionKeySelected={onPartitionKeySelected}
+        onGlobalModeSelected={onGlobalModeSelected}
+        globalMode={globalMode}
+      />
+      <div className="with-sidebar tabs-with-sidebar content-area">
+        <aside id="sidebar-content" className="scrollable">
+          <SidebarControllerContainer selectedNavItem={selectedNavItem} selectedPartitionKey={partitionKey}/>
+        </aside>
+        <section id="dashboard-content" className="flex-layout flex-col">
+          <div className="flex-body" id='dashboard-lower-content'>
+            <div className="search-index-content">
+              <div className="tab-content search-index-tab-content">
+                {getContent(section, database, indexName, ddocName, designDocs, ddoc, partitionKey)}
+              </div>
+            </div>
+          </div>
+        </section>
+      </div>
+    </div>
+  );
+};
+
+SearchLayout.propTypes = {
+  section: PropTypes.string.isRequired,
+  docURL: PropTypes.string,
+  endpoint: PropTypes.string,
+  ddocName: PropTypes.string,
+  indexName: PropTypes.string,
+  dropDownLinks: PropTypes.array.isRequired,
+  database: PropTypes.object.isRequired,
+};
+
+export default SearchLayout;
diff --git a/app/addons/search/reducers.js b/app/addons/search/reducers.js
new file mode 100644
index 0000000..12f27b5
--- /dev/null
+++ b/app/addons/search/reducers.js
@@ -0,0 +1,314 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import StyleHelpers from '../style/helpers';
+import ActionTypes from './actiontypes';
+import Constants from './constants';
+import SearchResources from './resources';
+import DocumentsHelpers from '../documents/helpers';
+
+const initialState = {
+  designDocs: new Backbone.Collection(),
+  database: {},
+  loading: true,
+  ddocName: 'new-doc',
+  partitionKey: '',
+  indexName: '',
+  databaseName: '',
+  ...softReset()
+};
+
+// called on first load (e.g. editing a search index) and every time the create search index page loads
+function softReset() {
+  return {
+    noResultsWarning: '',
+    hasActiveQuery: false,
+    searchQuery: '',
+    searchResults: undefined,
+    searchPerformed: false,
+    newDesignDocName: '',
+    newDesignDocPartitioned: true,
+    lastSavedSearchIndexName: '',
+    searchIndexFunction: Constants.DEFAULT_SEARCH_INDEX_FUNCTION,
+    searchIndexName: Constants.DEFAULT_SEARCH_INDEX_NAME,
+    analyzerType: Constants.DEFAULT_ANALYZER_TYPE,
+    analyzerFields: [],
+    singleAnalyzer: Constants.DEFAULT_ANALYZER,
+    defaultMultipleAnalyzer: Constants.DEFAULT_ANALYZER
+  };
+}
+
+function addAnalyzerRow (state, { analyzer, fieldName }) {
+  const newAnalyzerFields = state.analyzerFields.slice();
+  newAnalyzerFields.push({
+    key: StyleHelpers.getUniqueKey(),
+    fieldName: (fieldName) ? fieldName : '',
+    analyzer: analyzer,
+    valid: (fieldName && fieldName.trim().length > 0)
+  });
+  return newAnalyzerFields;
+}
+
+function removeAnalyzerRowByIndex (state, rowIndex) {
+  const newAnalyzerFields = state.analyzerFields.slice();
+  rowIndex = parseInt(rowIndex, 10);
+  newAnalyzerFields.splice(rowIndex, 1);
+  return newAnalyzerFields;
+}
+
+function setAnalyzerRowFieldName (state, { fieldName, rowIndex }) {
+  const newAnalyzerFields = state.analyzerFields.slice();
+  const idx = parseInt(rowIndex, 10);
+  newAnalyzerFields[idx].fieldName = fieldName;
+  newAnalyzerFields[idx].valid = fieldName !== '';
+  return newAnalyzerFields;
+}
+
+function setAnalyzerRow (state, { analyzer, rowIndex }) {
+  const newAnalyzerFields = state.analyzerFields.slice();
+  const idx = parseInt(rowIndex, 10);
+  newAnalyzerFields[idx].analyzer = analyzer;
+  return newAnalyzerFields;
+}
+
+function initEditSearch (state, { database, designDocs, ddocInfo, indexName }) {
+  const ddoc = designDocs.find(ddoc => {
+    return ddoc.id === ddocInfo.id;
+  }).dDocModel();
+
+  // the selected analyzer returned in the ddoc can be applied to both the single analyzer and the default multiple
+  // analyzer. We store them separately in the store so those values don't change when toggling from Single to Multiple
+  const analyzer = ddoc.getAnalyzer(indexName);
+  let newSingleAnalyzer;
+  if (_.isString(analyzer)) {
+    newSingleAnalyzer = analyzer;
+  } else {
+    if (_.has(analyzer, 'default')) {
+      newSingleAnalyzer = analyzer.default;
+    } else {
+      newSingleAnalyzer = Constants.DEFAULT_ANALYZER_TYPE;
+    }
+  }
+  const newAnalyzerFields = [];
+  if (analyzer && analyzer.fields) {
+    Object.keys(analyzer.fields).forEach(fieldName => {
+      newAnalyzerFields.push({
+        key: StyleHelpers.getUniqueKey(),
+        fieldName: (fieldName) ? fieldName : '',
+        analyzer: analyzer.fields[fieldName],
+        valid: !_.isUndefined(fieldName) && !_.isEmpty(fieldName)
+      });
+    });
+  }
+
+  return {
+    loading: false,
+    searchPerformed: false,
+    database: database,
+    designDocs: designDocs,
+    searchIndexName: indexName,
+    ddocName: ddocInfo.id,
+    lastSavedSearchIndexName: indexName,
+    lastSavedDesignDocName: ddocInfo.id,
+    searchIndexFunction: ddoc.getIndex(indexName),
+    analyzerType: ddoc.analyzerType(indexName),
+    // this either returns a simple string (single) or a complex object (multiple)
+    singleAnalyzer: newSingleAnalyzer,
+    defaultMultipleAnalyzer: newSingleAnalyzer,
+    analyzerFields: newAnalyzerFields
+  };
+}
+
+export function getSaveDesignDoc (state, isDbPartitioned) {
+  if (state.ddocName === 'new-doc') {
+    const doc = {
+      _id: '_design/' + state.newDesignDocName,
+      views: {},
+      language: 'javascript'
+    };
+    const dDoc = new SearchResources.Doc(doc, { database: state.database });
+    if (isDbPartitioned) {
+      dDoc.setDDocPartitionedOption(state.newDesignDocPartitioned);
+    }
+    return dDoc;
+  }
+
+  const foundDoc = state.designDocs.find((ddoc) => {
+    return ddoc.id === state.ddocName;
+  });
+  return (!foundDoc) ? null : foundDoc.dDocModel();
+}
+
+export function getSelectedDesignDocPartitioned(state, isDbPartitioned) {
+  const designDoc = state.designDocs.find(ddoc => {
+    return state.ddocName === ddoc.id;
+  });
+  if (designDoc) {
+    return DocumentsHelpers.isDDocPartitioned(designDoc.get('doc'), isDbPartitioned);
+  }
+  return false;
+}
+
+export default function search(state = initialState, action) {
+  const options = action.options;
+  switch (action.type) {
+
+    case ActionTypes.SEARCH_INDEX_SET_LOADING:
+      return {
+        ...state,
+        loading: options.loading
+      };
+
+    case ActionTypes.SEARCH_INDEX_DESIGN_DOCS_LOADED:
+      const newState = {
+        ...state,
+        loading: false,
+        designDocs: options.designDocs,
+        database: options.database,
+        ...softReset()
+      };
+      if (options.defaultDDoc) {
+        newState.ddocName = '_design/' + options.defaultDDoc;
+      }
+      return newState;
+
+    case ActionTypes.SEARCH_INDEX_SET_NAME:
+      return {
+        ...state,
+        searchIndexName: options.value
+      };
+
+    case ActionTypes.SEARCH_INDEX_SET_ANALYZER_TYPE:
+      return {
+        ...state,
+        analyzerType: options.value
+      };
+
+    case ActionTypes.SEARCH_INDEX_SET_SINGLE_ANALYZER:
+      return {
+        ...state,
+        singleAnalyzer: options.analyzer
+      };
+
+    case ActionTypes.SEARCH_INDEX_ADD_ANALYZER_ROW:
+      return {
+        ...state,
+        analyzerFields: addAnalyzerRow(state, options)
+      };
+
+    case ActionTypes.SEARCH_INDEX_PREVIEW_REQUEST_MADE:
+      return {
+        ...state,
+        hasActiveQuery: true
+      };
+
+    case ActionTypes.SEARCH_INDEX_PREVIEW_REQUEST_ERROR:
+      return {
+        ...state,
+        hasActiveQuery: false,
+        searchResults: []
+      };
+
+    case ActionTypes.SEARCH_INDEX_SET_SEARCH_QUERY:
+      return {
+        ...state,
+        searchQuery: options.query
+      };
+
+    case ActionTypes.SEARCH_INDEX_PREVIEW_MODEL_UPDATED:
+      return {
+        ...state,
+        searchResults: options.searchResults,
+        hasActiveQuery: false,
+        searchPerformed: true
+      };
+
+    case ActionTypes.SEARCH_INDEX_REMOVE_ANALYZER_ROW:
+      return {
+        ...state,
+        analyzerFields: removeAnalyzerRowByIndex(state, options.rowIndex)
+      };
+
+    case ActionTypes.SEARCH_INDEX_SET_ANALYZER_ROW_FIELD_NAME:
+      return {
+        ...state,
+        analyzerFields: setAnalyzerRowFieldName(state, options)
+      };
+
+    case ActionTypes.SEARCH_INDEX_SET_ANALYZER_ROW:
+      return {
+        ...state,
+        analyzerFields: setAnalyzerRow(state, options)
+      };
+
+    case ActionTypes.SEARCH_INDEX_SET_DEFAULT_MULTIPLE_ANALYZER:
+      return {
+        ...state,
+        defaultMultipleAnalyzer: options.analyzer
+      };
+
+    case ActionTypes.SEARCH_INDEX_INIT_EDIT_SEARCH_INDEX:
+      return {
+        ...state,
+        ...initEditSearch(state, options)
+      };
+
+    case ActionTypes.SEARCH_INDEX_SELECT_DESIGN_DOC:
+      return {
+        ...state,
+        ddocName: options.value
+      };
+
+    case ActionTypes.SEARCH_INDEX_CLEAR:
+      return {
+        ...initialState,
+        designDocs: new Backbone.Collection()
+      };
+
+    case ActionTypes.SEARCH_INDEX_INIT:
+      return {
+        ...state,
+        loading: false,
+        databaseName: options.databaseName,
+        partitionKey: options.partitionKey,
+        ddocName: options.ddocName,
+        indexName: options.indexName,
+        searchQuery: options.searchQuery,
+        searchResults: options.searchQuery === '' ? undefined : state.searchResults,
+        noResultsWarning: ''
+      };
+
+    case ActionTypes.SEARCH_INDEX_NEW_DESIGN_DOC_NAME_UPDATED:
+      return {
+        ...state,
+        newDesignDocName: options.value
+      };
+
+    case ActionTypes.SEARCH_INDEX_NEW_DESIGN_DOC_PARTITONED_UPDATED:
+      return {
+        ...state,
+        newDesignDocPartitioned: options.value
+      };
+
+    case ActionTypes.SEARCH_INDEX_PARTITION_PARAM_NOT_SUPPORTED:
+      return Object.assign({}, state, {
+        noResultsWarning: 'The selected index does not support partitions. Switch back to global mode.'
+      });
+
+    case ActionTypes.SEARCH_INDEX_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.'
+      });
+
+    default:
+      return state;
+  }
+}
diff --git a/app/addons/search/resources.js b/app/addons/search/resources.js
new file mode 100644
index 0000000..c653fbb
--- /dev/null
+++ b/app/addons/search/resources.js
@@ -0,0 +1,72 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+
+import Constants from './constants';
+import Documents from '../documents/resources';
+
+var CloudantSearch = {};
+
+CloudantSearch.Doc = Documents.Doc.extend({
+  setIndex: function (name, index, analyzer) {
+    if (!this.isDdoc()) {
+      return false;
+    }
+
+    var indexes = this.get('indexes');
+    if (!indexes) {
+      indexes = {};
+    }
+    if (!indexes[name]) {
+      indexes[name] = {};
+    }
+
+    if (analyzer) {
+      indexes[name].analyzer = analyzer;
+    }
+
+    indexes[name].index = index;
+    return this.set({indexes: indexes});
+  },
+
+  getIndex: function (indexName) {
+    return this.get('indexes')[indexName].index;
+  },
+
+  getAnalyzer: function (indexName) {
+    return this.get('indexes')[indexName].analyzer;
+  },
+
+  analyzerType: function (indexName) {
+    if (typeof this.getAnalyzer(indexName) === 'object') {
+      return Constants.ANALYZER_MULTIPLE;
+    }
+    return Constants.ANALYZER_SINGLE;
+  },
+
+  dDocModel: function () {
+    if (!this.isDdoc()) {
+      return false;
+    }
+
+    var doc = this.get('doc');
+    if (doc) {
+      return new CloudantSearch.Doc(doc, { database: this.database });
+    }
+    return false;
+  }
+});
+
+CloudantSearch.AllDocs = Documents.AllDocs.extend({
+  model: CloudantSearch.Doc
+});
+
+
+export default CloudantSearch;
diff --git a/app/addons/search/routes.js b/app/addons/search/routes.js
new file mode 100644
index 0000000..f4ba4d3
--- /dev/null
+++ b/app/addons/search/routes.js
@@ -0,0 +1,272 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+import app from '../../app';
+import FauxtonAPI from '../../core/api';
+import Resources from './resources';
+import SearchActions from './actions';
+import DatabasesActions from '../databases/actions';
+import Databases from '../databases/base';
+import BaseRoute from '../documents/shared-routes';
+import {SidebarItemSelection} from '../documents/sidebar/helpers';
+import CloudantDocuments from '../cloudantdocuments/resources';
+import CloudantDatabases from '../cloudantdatabases/resources';
+import Account from '../account/resources';
+import SidebarActions from '../documents/sidebar/actions';
+import Actions from './actions';
+import Layout from './layout';
+import React from 'react';
+
+var SearchRouteObject = BaseRoute.extend({
+  routes: {
+    'database/:database/_design/:ddoc/_search/:search(?*searchQuery)': {
+      route: 'searchNoPartition',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_partition/:partitionkey/_design/:ddoc/_search/:search(?*searchQuery)': {
+      route: 'search',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_design/:ddoc/_indexes/:search(?*searchQuery)': {
+      route: 'searchNoPartition',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_partition/:partitionkey/_design/:ddoc/_indexes/:search(?*searchQuery)': {
+      route: 'search',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_design/:ddoc/_search/:search/edit': {
+      route: 'editNoPartition',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/_partition/:partitionkey/_design/:ddoc/_search/:search/edit': {
+      route: 'edit',
+      roles: ['fx_loggedIn']
+    },
+    'database/:database/new_search': 'createNoPartition',
+    'database/:database/_partition/:partitionkey/new_search': 'create',
+    'database/:database/new_search/:designDoc': 'createNoPartition',
+    'database/:database/_partition/:partitionkey/new_search/:designDoc': 'create'
+  },
+
+  initialize: function (route, options) {
+    this.allDatabases = new Databases.List();
+    this.databaseName = options[0];
+    this.database = new Databases.Model({ id: this.databaseName });
+    this.data = {
+      database: new Databases.Model({ id: this.databaseName })
+    };
+
+    this.data.designDocs = new Resources.AllDocs(null, {
+      database: this.data.database,
+      params: {
+        startkey: '"_design"',
+        endkey: '"_design1"',
+        include_docs: true
+      }
+    });
+
+    SidebarActions.dispatchNewOptions({
+      database: this.data.database,
+      designDocs: this.data.designDocs
+    });
+  },
+
+  searchNoPartition: function (databaseName, ddocName, indexName, query) {
+    return this.search(databaseName, '', ddocName, indexName, query);
+  },
+
+  search: function (databaseName, partitionKey, ddocName, indexName, query) {
+    this.databaseName = databaseName;
+    this.ddocName     = ddocName;
+    this.indexName    = indexName;
+
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
+      designDocName: ddocName,
+      designDocSection: 'Search Indexes',
+      indexName: indexName
+    });
+    SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
+
+    const dropDownLinks = this.getCrumbs(this.database);
+    Actions.dispatchInitSearchIndex({
+      databaseName: databaseName,
+      partitionKey: partitionKey,
+      designDoc: ddocName,
+      indexName: indexName,
+      query: query
+    });
+    DatabasesActions.fetchSelectedDatabaseInfo(databaseName);
+
+    const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+    const endpointUrl = FauxtonAPI.urls('search', 'apiurl', this.databaseName, encodedPartKey, this.ddocName,
+      this.indexName, (query ? query : '*:*'));
+
+    const encodedQuery = query ? `?${encodeURIComponent(query)}` : '';
+    const navigateToPartitionedView = (partKey) => {
+      const baseUrl = FauxtonAPI.urls('partitioned_search', 'app', encodeURIComponent(databaseName),
+        encodeURIComponent(partKey), encodeURIComponent(ddocName));
+      FauxtonAPI.navigate('#/' + baseUrl + encodeURIComponent(indexName) + encodedQuery);
+    };
+    const navigateToGlobalView = () => {
+      const baseUrl = FauxtonAPI.urls('search', 'app', encodeURIComponent(databaseName), encodeURIComponent(ddocName));
+      FauxtonAPI.navigate('#/' + baseUrl + encodeURIComponent(indexName) + encodedQuery);
+    };
+    return <Layout
+      section={'search'}
+      dropDownLinks={dropDownLinks}
+      endpoint={endpointUrl}
+      docURL={FauxtonAPI.constants.DOC_URLS.SEARCH_INDEXES}
+      database={this.database}
+      indexName={indexName}
+      selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
+      onPartitionKeySelected={navigateToPartitionedView}
+      onGlobalModeSelected={navigateToGlobalView}
+      globalMode={partitionKey === ''}
+    />;
+  },
+
+  editNoPartition: function (database, ddocName, indexName) {
+    return this.edit(database, '', ddocName, indexName);
+  },
+
+  edit: function (database, partitionKey, ddocName, indexName) {
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
+      designDocName: ddocName,
+      designDocSection: 'Search Indexes',
+      indexName: indexName
+    });
+    SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
+
+    SearchActions.dispatchEditSearchIndex({
+      ddocID: '_design/' + ddocName,
+      database: this.database,
+      indexName: indexName,
+      designDocs: this.data.designDocs
+    });
+    DatabasesActions.fetchSelectedDatabaseInfo(database);
+
+    const dropDownLinks = this.getCrumbs(this.database);
+    return <Layout
+      section={'edit'}
+      dropDownLinks={dropDownLinks}
+      database={this.database}
+      indexName={indexName}
+      ddocName={ddocName}
+      selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
+    />;
+  },
+
+  createNoPartition: function (database, ddoc) {
+    return this.create(database, '', ddoc);
+  },
+
+  create: function (database, partitionKey, ddoc) {
+    const selectedNavItem = new SidebarItemSelection('');
+
+    SearchActions.dispatchInitNewSearchIndex({
+      database: this.database,
+      designDocs: this.data.designDocs,
+      defaultDDoc: ddoc,
+    });
+    DatabasesActions.fetchSelectedDatabaseInfo(database);
+
+    const dropDownLinks = this.getCrumbs(this.database);
+    return <Layout
+      section={'create'}
+      dropDownLinks={dropDownLinks}
+      database={this.database}
+      designDocs={this.data.designDocs}
+      ddoc={ddoc}
+      selectedNavItem={selectedNavItem}
+      partitionKey={partitionKey}
+    />;
+  },
+
+  getCrumbs: function (database) {
+    return [
+      { type: "back", link: "/_all_dbs"},
+      { name: database.id }
+    ];
+  }
+});
+
+
+var SharedSearchRouteObject = SearchRouteObject.extend({
+  routes: {
+    'database/shared/:user/:database/_design/:ddoc/_search/:search(?*searchQuery)': {
+      route: 'sharedSearch',
+      roles: ['fx_loggedIn']
+    },
+    'database/shared/:user/:database/_design/:ddoc/_indexes/:search(?*searchQuery)': {
+      route: 'sharedSearch',
+      roles: ['fx_loggedIn']
+    },
+    'database/shared/:user/:database/new_search': 'sharedNewSearch',
+    'database/shared/:user/:database/new_search/:designDoc': 'sharedNewSearch',
+    'database/shared/:user/:database/_design/:ddoc/_search/:search/edit': {
+      route: 'sharedEdit',
+      roles: ['fx_loggedIn']
+    }
+  },
+
+  initialize: function (route, options) {
+    var docOptions = app.getParams();
+    docOptions.include_docs = true;
+
+    this.username = CloudantDocuments.sharedUsername = options[0];
+    this.databaseName = options[1];
+    this.database = new CloudantDatabases.SharedModel({
+      id: this.databaseName,
+      name: this.username
+    });
+    this.user = new Account.User();
+
+    this.data = {
+      username: this.username,
+      database: this.database
+    };
+
+    this.data.designDocs = new CloudantDocuments.SharedAllDocs(null, {
+      username: this.username,
+      database: this.database,
+      paging: {
+        pageSize: 500
+      },
+      params: {
+        startkey: '"_design"',
+        endkey: '"_design1"',
+        include_docs: true,
+        limit: 500
+      }
+    });
+
+    var initOptions = options.slice(1);
+    SearchRouteObject.prototype.initialize.call(this, route, initOptions);
+  },
+
+  sharedSearch: function (user, database, ddoc, search, query) {
+    return this.searchNoPartition(database, ddoc, search, query);
+  },
+
+  sharedEdit: function (user, database, ddoc, indexName) {
+    return this.editNoPartition(database, ddoc, indexName);
+  },
+
+  sharedNewSearch: function (user, database, ddoc) {
+    return this.createNoPartition(database, ddoc);
+  }
+});
+
+Resources.RouteObjects = [SearchRouteObject, SharedSearchRouteObject];
+
+export default Resources;
diff --git a/app/addons/search/tests/nightwatch/cloneSearchIndex.js b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
new file mode 100644
index 0000000..69d6748
--- /dev/null
+++ b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
@@ -0,0 +1,68 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+// 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.
+
+// 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.
+
+module.exports = {
+
+  'Clones a search index': function (client) {
+    var waitTime = client.globals.maxWaitTime,
+        newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .createDatabase(newDatabaseName)
+      .loginToGUI()
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+
+      // create a search index
+      .waitForElementPresent('#new-design-docs-button', waitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .waitForElementVisible('#new-ddoc', waitTime, false)
+      .setValue('#new-ddoc', 'test1')
+      .clearValue('#search-name')
+      .setValue('#search-name', 'test1-index')
+      .clickWhenVisible('#save-index')
+      .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+
+      .clickWhenVisible('.index-list li span', waitTime, true)
+      .clickWhenVisible('.popover-content .fonticon-files-o', waitTime, true)
+      .waitForElementVisible('#new-index-name', waitTime, true)
+      .setValue('#new-index-name', 'cloned-search-index')
+      .clickWhenVisible('.clone-index-modal .btn-primary', waitTime, true)
+
+      // now wait for the sidebar to be updated with the new view
+      .waitForElementVisible('#test1_cloned-search-index', waitTime, true)
+      .end();
+  }
+};
diff --git a/app/addons/search/tests/nightwatch/createNewSearch.js b/app/addons/search/tests/nightwatch/createNewSearch.js
new file mode 100644
index 0000000..6265b67
--- /dev/null
+++ b/app/addons/search/tests/nightwatch/createNewSearch.js
@@ -0,0 +1,130 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+module.exports = {
+
+  'Creates new Search index for Dash': function (client) {
+    /*jshint multistr: true */
+
+    var newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    var searchFunctionString = function (append) {
+      return 'function (doc) {'                  +
+        'index("name", doc.name ' + append + ');' +
+        '}';
+    };
+
+    client
+      .loginToGUI()
+      .populateDatabase(newDatabaseName)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+      .waitForElementPresent('#new-design-docs-button', client.globals.maxWaitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .waitForElementVisible('.faux-header__doc-header-title', client.globals.maxWaitTime, true)
+
+      .clickWhenVisible('.styled-select select')
+      .keys(['_design/keyview', '\uE006'])
+
+      .clearValue('#search-name')
+      .setValue('#search-name', 'fancy_search')
+      .execute('\
+        var editor = ace.edit("search-function");\
+        editor.getSession().setValue("' + searchFunctionString(0) + '");\
+      ')
+
+      .execute('document.querySelector("#save-index").scrollIntoView();')
+      .clickWhenVisible('#save-index')
+      .waitForElementPresent('#keyview_fancy_search', client.globals.maxWaitTime, false)
+      .end();
+  },
+
+  'Creating a new index has a clean slate': function (client) {
+    var newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .loginToGUI()
+      .populateDatabase(newDatabaseName)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+
+      // 1. create a search index in _design/keyview design doc
+      .waitForElementPresent('#new-design-docs-button', client.globals.maxWaitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .clickWhenVisible('.styled-select select')
+      .keys(['_design/keyview', '\uE006'])
+
+      .clearValue('#search-name')
+      .setValue('#search-name', 'clean-slate-test')
+      .clickWhenVisible('#save-index')
+      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+      .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
+
+      // 2. create a new search index. The "Save to design doc" dropdown should default to New Document
+      .waitForElementPresent('#new-design-docs-button', client.globals.maxWaitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .waitForElementVisible('.styled-select select', client.globals.maxWaitTime, false)
+      .assert.value('.styled-select select', 'new-doc')
+      .end();
+  },
+
+  'Adding two indexes in a row does not add multiple indexes': function (client) {
+    var newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .loginToGUI()
+      .createDatabase(newDatabaseName)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+
+      // 1. create a search index in _design/test1
+      .waitForElementPresent('#new-design-docs-button', client.globals.maxWaitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .waitForElementVisible('#new-ddoc', client.globals.maxWaitTime, false)
+      .setValue('#new-ddoc', 'test1')
+      .clearValue('#search-name')
+      .setValue('#search-name', 'test1-index')
+      .clickWhenVisible('#save-index')
+      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+      .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
+
+      // 2. create a second index in _design/test2
+      .waitForElementPresent('#new-design-docs-button', client.globals.maxWaitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .waitForElementVisible('#new-ddoc', client.globals.maxWaitTime, false)
+      .setValue('#new-ddoc', 'test2')
+      .clearValue('#search-name')
+      .setValue('#search-name', 'test2-index')
+      .clickWhenVisible('#save-index')
+
+      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+
+      // 3. confirm that the nav bar shows ONLY one search index each:
+      //  _design/test1 has the single _design/test1-index
+      //  _design/test2 has the single _design/test2-index
+      .waitForElementPresent('#test1_test1-index', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('#test1_test2-index', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('#test2_test1-index', client.globals.maxWaitTime, false)
+      .waitForElementPresent('#test2_test2-index', client.globals.maxWaitTime, false)
+
+      .end();
+  }
+
+
+};
diff --git a/app/addons/search/tests/nightwatch/deleteSearchIndex.js b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
new file mode 100644
index 0000000..f4cdc86
--- /dev/null
+++ b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
@@ -0,0 +1,107 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+// 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.
+
+// 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.
+
+module.exports = {
+
+  'Deletes a search index': function (client) {
+    var newDatabaseName = client.globals.testDatabaseName,
+        waitTime = client.globals.maxWaitTime,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .loginToGUI()
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+
+      // create a search index
+      .waitForElementPresent('#new-design-docs-button', waitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .waitForElementVisible('#new-ddoc', waitTime, false)
+      .setValue('#new-ddoc', 'test1')
+      .clearValue('#search-name')
+      .setValue('#search-name', 'test1-index')
+      .clickWhenVisible('#save-index')
+      .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+      .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('.loading-lines', waitTime, false)
+
+      // confirm search index appears in sidebar
+      .waitForElementVisible('#test1_test1-index', waitTime, true)
+
+      // now delete it and confirm that the entire design doc gets removed (because it's the last index)
+      .clickWhenVisible('.index-list li span', waitTime, true)
+      .clickWhenVisible('.popover-content .fonticon-trash', waitTime, true)
+      .clickWhenVisible('.confirmation-modal button.btn.btn-primary')
+
+      // now wait for the sidebar to have removed the design doc
+      .waitForElementNotPresent('#testdesigndoc', waitTime, true)
+      .end();
+  },
+
+  'Deleting view when design doc has search index does not remove design doc': function (client) {
+    var waitTime = client.globals.maxWaitTime,
+        newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      // this creates a view
+      .populateDatabase(newDatabaseName)
+      .loginToGUI()
+
+      // now create a search index
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/new_search/testdesigndoc')
+      .waitForElementVisible('#save-index', waitTime, false)
+      .clearValue('#search-name')
+      .setValue('#search-name', 'search-index1')
+      .clickWhenVisible('#save-index')
+      .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+      .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('.loading-lines', waitTime, false)
+
+      // now delete the search index. Since there's a view in this design doc, the design doc should not be removed
+      .clickWhenVisible('#nav-design-function-testdesigndocindexes .index-list li span', waitTime, true)
+      .clickWhenVisible('.popover-content .fonticon-trash', waitTime, true)
+      .waitForElementVisible('div.confirmation-modal', waitTime, false)
+      .clickWhenVisible('.confirmation-modal button.btn.btn-primary')
+
+      // just assert the search indexes section has been removed, but the design doc still exists
+      .waitForElementNotPresent('#nav-design-function-testdesigndocindexes', waitTime, true)
+      .waitForElementPresent('#testdesigndoc', waitTime, true)
+
+      .end();
+  }
+
+};
diff --git a/app/addons/search/tests/nightwatch/searchPageApiBar.js b/app/addons/search/tests/nightwatch/searchPageApiBar.js
new file mode 100644
index 0000000..ac503c6
--- /dev/null
+++ b/app/addons/search/tests/nightwatch/searchPageApiBar.js
@@ -0,0 +1,55 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+module.exports = {
+
+  'Check API Bar is present/hidden on appropriate page and is encoded': function (client) {
+    const newDatabaseName = client.globals.testDatabaseName,
+          baseUrl = client.globals.test_settings.launch_url;
+
+    const searchStr = "class:bird";
+    const searchStrEncoded = encodeURIComponent(searchStr);
+    const fullURL = baseUrl + '/' + newDatabaseName + '/_design/keyview/_search/api-bar-test?q=' + searchStrEncoded;
+
+    client
+      .loginToGUI()
+      .populateDatabase(newDatabaseName)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
+
+      // start creating a search index in _design/keyview design doc
+      .waitForElementPresent('#new-design-docs-button', client.globals.maxWaitTime, false)
+      .click('#new-design-docs-button a')
+      .click('#new-design-docs-button a[href="#/database/' + newDatabaseName + '/new_search"]')
+      .clickWhenVisible('.styled-select select')
+
+      // confirm there's no API URL field on the create index page
+      .pause(5000)
+      .assert.elementNotPresent('.faux__jsonlink')
+
+      // now create the rest of the index
+      .keys(['_design/keyview', '\uE006'])
+      .clearValue('#search-name')
+      .setValue('#search-name', 'api-bar-test')
+      .clickWhenVisible('#save-index')
+      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+
+      // confirm the API URL field now shows up (we're on the edit search index page now)
+      .assert.elementPresent('.faux__jsonlink')
+
+      // now enter a search and confirm it's properly encoded in the api URL bar
+      .setValue('#search-index-preview-form input', searchStr)
+      .clickWhenVisible('#search-index-preview-form button')
+
+      .waitForElementNotVisible('#global-notifications', client.globals.maxWaitTime, false)
+      .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
+      .assert.attributeContains('.faux__jsonlink-link', 'href', fullURL)
+      .end();
+  }
+};
diff --git a/app/addons/search/tests/nightwatch/sharedSearch.js b/app/addons/search/tests/nightwatch/sharedSearch.js
new file mode 100644
index 0000000..7cae1e2
--- /dev/null
+++ b/app/addons/search/tests/nightwatch/sharedSearch.js
@@ -0,0 +1,39 @@
+/*
+* Licensed Materials - Property of IBM
+*
+* "Restricted Materials of IBM"
+*
+* (C) Copyright IBM Corp. 2018 All Rights Reserved
+*
+* US Government Users Restricted Rights - Use, duplication or disclosure
+* restricted by GSA ADP Schedule Contract with IBM Corp.
+*/
+module.exports = {
+
+  'Edits existing search route works': function (client) {
+    /*jshint multistr: true */
+    const baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .loginToGUI()
+      .url(baseUrl + '/#database/shared/dashboard-test-account/shareddatabasesrule/_design/for-tests/_search/test-can-edit/edit')
+      // wait for the page to fully load
+      .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
+      .waitForElementVisible('#save-index', client.globals.maxWaitTime)
+      .end();
+  },
+
+  'Create new search route works': function (client) {
+    /*jshint multistr: true */
+    const baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .loginToGUI()
+      .url(baseUrl + '/#database/shared/dashboard-test-account/shareddatabasesrule/new_search')
+      // wait for the page to fully load
+      .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
+      .waitForElementVisible('#save-index', client.globals.maxWaitTime)
+      .end();
+  }
+
+};


[couchdb-fauxton] 02/02: Add Search support

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

willholley pushed a commit to branch search_addon
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

commit 7b2bbfc0bab8d9ddb5853b2e289c052967376037
Author: Will Holley <wi...@gmail.com>
AuthorDate: Tue Sep 24 15:42:27 2019 +0100

    Add Search support
    
    Adds a search module to Fauxton. The functionality is only enabled
    upon detection of "search" in the reported CouchDB features.
    
    When enabled, it adds:
     * New dropdown options to create/update search indexes in the sidebar
     * New panel to run search queries from the sidebar
     * Text index templates to the Mango Index editor
---
 .../documents/assets/less/query-options.less       |   4 +-
 app/addons/search/__tests__/components.test.js     |  22 ++--
 app/addons/search/__tests__/search.actions.test.js |  21 ++--
 .../search/__tests__/search.reducers.test.js       |  21 ++--
 app/addons/search/actions.js                       |  21 ++--
 app/addons/search/actiontypes.js                   |  22 ++--
 app/addons/search/api.js                           |  23 ++--
 app/addons/search/assets/less/search.less          |  25 +++--
 app/addons/search/base.js                          | 123 +++++++++------------
 app/addons/search/components/Analyzer.js           |  21 ++--
 app/addons/search/components/AnalyzerDropdown.js   |  21 ++--
 app/addons/search/components/AnalyzerMultiple.js   |  21 ++--
 app/addons/search/components/AnalyzerRow.js        |  21 ++--
 app/addons/search/components/SearchForm.js         |  21 ++--
 app/addons/search/components/SearchIndexEditor.js  |  21 ++--
 app/addons/search/constants.js                     |  80 ++++++++++++--
 app/addons/search/layout.js                        |  21 ++--
 app/addons/search/reducers.js                      |  47 +++++---
 app/addons/search/resources.js                     |  22 ++--
 app/addons/search/routes.js                        |  97 +++-------------
 .../search/tests/nightwatch/cloneSearchIndex.js    |  22 ----
 .../search/tests/nightwatch/createNewSearch.js     |  21 ++--
 .../search/tests/nightwatch/deleteSearchIndex.js   |  22 ----
 .../search/tests/nightwatch/searchPageApiBar.js    |  21 ++--
 app/addons/search/tests/nightwatch/sharedSearch.js |  21 ++--
 assets/less/fauxton.less                           |   9 ++
 package-lock.json                                  |  91 +++++----------
 settings.json.default.json                         |   1 +
 webpack.config.dev.js                              |   2 +-
 29 files changed, 403 insertions(+), 482 deletions(-)

diff --git a/app/addons/documents/assets/less/query-options.less b/app/addons/documents/assets/less/query-options.less
index 2caa9b3..eea473d 100644
--- a/app/addons/documents/assets/less/query-options.less
+++ b/app/addons/documents/assets/less/query-options.less
@@ -108,7 +108,7 @@
     .btn.active {
       background: #fff;
       color: @linkColorHover;
-      box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.25) inset, 2px 2px 2px rgba(0, 0, 0, 0.15);
+      box-shadow: none;
     }
     label:first-child {
       .border-radius(5px 0 0 5px);
@@ -174,7 +174,7 @@
   .hide {
     display: none;
   }
-  
+
   .additionalParams {
     margin-bottom: 2px;
   }
diff --git a/app/addons/search/__tests__/components.test.js b/app/addons/search/__tests__/components.test.js
index 6bb6f39..4d053d5 100644
--- a/app/addons/search/__tests__/components.test.js
+++ b/app/addons/search/__tests__/components.test.js
@@ -1,14 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-
+// 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 {mount} from 'enzyme';
 import React from 'react';
 import sinon from 'sinon';
diff --git a/app/addons/search/__tests__/search.actions.test.js b/app/addons/search/__tests__/search.actions.test.js
index 99edd28..7765521 100644
--- a/app/addons/search/__tests__/search.actions.test.js
+++ b/app/addons/search/__tests__/search.actions.test.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 sinon from 'sinon';
 import utils from '../../../../test/mocha/testUtils';
diff --git a/app/addons/search/__tests__/search.reducers.test.js b/app/addons/search/__tests__/search.reducers.test.js
index f08d269..1705b2a 100644
--- a/app/addons/search/__tests__/search.reducers.test.js
+++ b/app/addons/search/__tests__/search.reducers.test.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 reducer from '../reducers';
 import ActionTypes from '../actiontypes';
diff --git a/app/addons/search/actions.js b/app/addons/search/actions.js
index 82ac0f1..12d8d6e 100644
--- a/app/addons/search/actions.js
+++ b/app/addons/search/actions.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 FauxtonAPI from '../../core/api';
 import ActionTypes from './actiontypes';
diff --git a/app/addons/search/actiontypes.js b/app/addons/search/actiontypes.js
index c5bbb93..c99abad 100644
--- a/app/addons/search/actiontypes.js
+++ b/app/addons/search/actiontypes.js
@@ -1,13 +1,15 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 {
   SEARCH_INDEX_DESIGN_DOCS_LOADED: 'SEARCH_INDEX_DESIGN_DOCS_LOADED',
   SEARCH_INDEX_INIT: 'SEARCH_INDEX_INIT',
diff --git a/app/addons/search/api.js b/app/addons/search/api.js
index cbfdbcd..5711c98 100644
--- a/app/addons/search/api.js
+++ b/app/addons/search/api.js
@@ -1,19 +1,20 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 FauxtonAPI from '../../core/api';
 import { get } from '../../core/ajax';
 
 function searchUrl(database, partitionKey, ddoc, index, searchQuery) {
-  //https://[username].cloudant.com/animaldb/_design/views101/_search/animals?q=kookaburra
+  //https://mycouchdb/db/_design/views101/_search/animals?q=kookaburra
   const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
   return FauxtonAPI.urls('search', 'server', encodeURIComponent(database), encodedPartKey,
     encodeURIComponent(ddoc), encodeURIComponent(index),
diff --git a/app/addons/search/assets/less/search.less b/app/addons/search/assets/less/search.less
index 68c7589..9725d55 100644
--- a/app/addons/search/assets/less/search.less
+++ b/app/addons/search/assets/less/search.less
@@ -1,15 +1,16 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-@import "../../../style/assets/less/variables.less";
-@import "../../../style/assets/less/mixins.less";
+// 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/bootstrap/mixins.less";
+@import "../../../../../assets/less/variables.less";
 
 .search-index-page-loading {
   margin-top: 20px;
diff --git a/app/addons/search/base.js b/app/addons/search/base.js
index 483b8b9..0562256 100644
--- a/app/addons/search/base.js
+++ b/app/addons/search/base.js
@@ -1,89 +1,66 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-import app from '../../app';
+// 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 Helpers from '../../helpers';
 import FauxtonAPI from '../../core/api';
 import Actions from './actions';
 import SearchRoutes from './routes';
-import CloudantDocuments from '../cloudantdocuments/routes';
 import reducers from './reducers';
+import { get } from "../../core/ajax";
+import Constants from './constants';
 import './assets/less/search.less';
 
-SearchRoutes.initialize = function () {
-  FauxtonAPI.registerExtension('sidebar:list', {
-    selector: 'indexes',
-    name: 'Search Indexes',
-    urlNamespace: 'search',
-    indexLabel: 'search index', // used for labels
-    onDelete: Actions.deleteSearchIndex,
-    onClone: Actions.cloneSearchIndex,
-    onEdit: Actions.gotoEditSearchIndexPage
-  });
-  FauxtonAPI.registerExtension('sidebar:links', {
-    title: "New Search Index",
-    url: "new_search",
-    icon: 'fonticon-plus-circled',
-    showForPartitionedDDocs: true
-  });
-  FauxtonAPI.registerExtension('sidebar:newLinks', {
-    url: 'new_search',
-    name: 'Search Index'
+function checkSearchFeature () {
+  // Checks if the CouchDB server supports Search
+  return get(Helpers.getServerUrl("/")).then((couchdb) => {
+    return couchdb.features && couchdb.features.includes('search');
+  }).catch(() => {
+    return false;
   });
-
-  // this tells Fauxton of the new Search Index type. It's used to determine when a design doc is really empty
-  FauxtonAPI.registerExtension('IndexTypes:propNames', 'indexes');
-};
+}
 
 function partitionUrlComponent(partitionKey) {
   return partitionKey ? '/_partition/' + partitionKey : '';
 }
 
-var proxyUrl = CloudantDocuments.proxyUrl;
-_.extend(CloudantDocuments.sharedUrlPaths, {
-  search: {
-    server: function (db, partitionKey, designDoc, searchName, query) {
-      query = CloudantDocuments.addRemoteAccount(query);
-      return proxyUrl + db + '/_design/' + designDoc + '/_search/' + searchName + query;
-    },
-
-    //NOTE: partitionKey is included here for compatibility with base registered url functions
-    //but is ignored because it's not supported for shared databases
-    app: function (id, designDoc) {
-      return CloudantDocuments.sharedUrlPaths.userHash() + id + '/_design/' + app.utils.safeURLName(designDoc) + '/_search/';
-    },
-
-    edit: function (database, partitionKey, designDoc, indexName) {
-      return CloudantDocuments.sharedUrlPaths.userHash() + database + '/_design/' + designDoc + '/_search/' + indexName + '/edit';
-    },
-
-    apiurl: function (db, partitionKey, designDoc, searchName) {
-      return CloudantDocuments.sharedUrlPaths.host() + encodeURIComponent(db) + '/_design/' +
-        encodeURIComponent(designDoc) + '/_search/' + encodeURIComponent(searchName);
-    },
-
-    fragment: function (id, partitionKey, designDoc, search) {
-      return CloudantDocuments.sharedUrlPaths.userHash() + id + '/_design/' + designDoc + '/_search/' + search;
-    },
-
-    showIndex: function (id, partitionKey, designDoc, search) {
-      return CloudantDocuments.sharedUrlPaths.host() + id + '/' + designDoc +
-        '/_search/' + search;
-    },
-  },
-  partitioned_search: {
-    app: function (id, partitionKey, designDoc) {
-      return CloudantDocuments.sharedUrlPaths.userHash() + id + '/_design/' + app.utils.safeURLName(designDoc) + '/_search/';
-    }
-  }
-});
+SearchRoutes.initialize = function () {
+  checkSearchFeature().then(function(enabled) {
+    if (!enabled) return;
+
+    FauxtonAPI.registerExtension('sidebar:list', {
+      selector: 'indexes',
+      name: 'Search Indexes',
+      urlNamespace: 'search',
+      indexLabel: 'search index', // used for labels
+      onDelete: Actions.deleteSearchIndex,
+      onClone: Actions.cloneSearchIndex,
+      onEdit: Actions.gotoEditSearchIndexPage
+    });
+    FauxtonAPI.registerExtension('sidebar:links', {
+      title: "New Search Index",
+      url: "new_search",
+      icon: 'fonticon-plus-circled',
+      showForPartitionedDDocs: true
+    });
+    FauxtonAPI.registerExtension('sidebar:newLinks', {
+      url: 'new_search',
+      name: 'Search Index'
+    });
+
+    // this tells Fauxton of the new Search Index type. It's used to determine when a design doc is really empty
+    FauxtonAPI.registerExtension('IndexTypes:propNames', 'indexes');
+
+    FauxtonAPI.registerExtension('mango:indexTemplates', Constants.SEARCH_MANGO_INDEX_TEMPLATES);
+  });
+};
 
 FauxtonAPI.registerUrls('partitioned_search', {
   app: function (id, partitionKey, designDoc) {
diff --git a/app/addons/search/components/Analyzer.js b/app/addons/search/components/Analyzer.js
index 78c8a3c..6cd0683 100644
--- a/app/addons/search/components/Analyzer.js
+++ b/app/addons/search/components/Analyzer.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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';
diff --git a/app/addons/search/components/AnalyzerDropdown.js b/app/addons/search/components/AnalyzerDropdown.js
index 88ac8e5..fa28612 100644
--- a/app/addons/search/components/AnalyzerDropdown.js
+++ b/app/addons/search/components/AnalyzerDropdown.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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';
diff --git a/app/addons/search/components/AnalyzerMultiple.js b/app/addons/search/components/AnalyzerMultiple.js
index 6aa4785..43faa9d 100644
--- a/app/addons/search/components/AnalyzerMultiple.js
+++ b/app/addons/search/components/AnalyzerMultiple.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 FauxtonAPI from '../../../core/api';
 import PropTypes from 'prop-types';
diff --git a/app/addons/search/components/AnalyzerRow.js b/app/addons/search/components/AnalyzerRow.js
index c8ca8d1..124bc38 100644
--- a/app/addons/search/components/AnalyzerRow.js
+++ b/app/addons/search/components/AnalyzerRow.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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';
diff --git a/app/addons/search/components/SearchForm.js b/app/addons/search/components/SearchForm.js
index 9a85be1..31a183f 100644
--- a/app/addons/search/components/SearchForm.js
+++ b/app/addons/search/components/SearchForm.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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';
diff --git a/app/addons/search/components/SearchIndexEditor.js b/app/addons/search/components/SearchIndexEditor.js
index 3135ad3..dbff53a 100644
--- a/app/addons/search/components/SearchIndexEditor.js
+++ b/app/addons/search/components/SearchIndexEditor.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 FauxtonAPI from '../../../core/api';
 import app from '../../../app';
diff --git a/app/addons/search/constants.js b/app/addons/search/constants.js
index 408a988..432e301 100644
--- a/app/addons/search/constants.js
+++ b/app/addons/search/constants.js
@@ -1,18 +1,76 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 {
   ANALYZER_SINGLE: 'single',
   ANALYZER_MULTIPLE: 'multiple',
   DEFAULT_SEARCH_INDEX_NAME: 'newSearch',
   DEFAULT_ANALYZER_TYPE: 'single',
   DEFAULT_ANALYZER: 'standard',
-  DEFAULT_SEARCH_INDEX_FUNCTION: 'function (doc) {\n  index("name", doc.name);\n}'
+  DEFAULT_SEARCH_INDEX_FUNCTION: 'function (doc) {\n  index("name", doc.name);\n}',
+  SEARCH_MANGO_INDEX_TEMPLATES: [{
+    label: 'Single field (json)',
+    code: {
+      "index": {
+        "fields": ["foo"]
+      },
+      "name": "foo-json-index",
+      "type": "json"
+    }
+  }, {
+    label: 'Multiple fields (json)',
+    code: {
+      "index": {
+        "fields": ["foo", "bar"]
+      },
+      "name": "foo-bar-json-index",
+      "type": "json"
+    }
+  },
+  {
+    label: 'Single field (text)',
+    code: {
+      "index": {
+        "fields": [
+          {
+            "name": "foo",
+            "type": "string"
+          }
+        ]
+      },
+      "name": "foo-text",
+      "type": "text"
+    }
+  },
+  {
+    label: 'All fields (text)',
+    code: {
+      "index": {
+        "default_field": {
+          "enabled": false
+        },
+        "index_array_lengths": false
+      },
+      "name": "all-text",
+      "type": "text"
+    }
+  },
+  {
+    label: 'All fields with default field (text)',
+    code: {
+      "index": {
+        "index_array_lengths": false
+      },
+      "name": "all-text",
+      "type": "text"
+    }
+  }]
 };
diff --git a/app/addons/search/layout.js b/app/addons/search/layout.js
index fb41a92..607627a 100644
--- a/app/addons/search/layout.js
+++ b/app/addons/search/layout.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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 {TabsSidebarHeader} from '../documents/layouts';
diff --git a/app/addons/search/reducers.js b/app/addons/search/reducers.js
index 12f27b5..9df4fd4 100644
--- a/app/addons/search/reducers.js
+++ b/app/addons/search/reducers.js
@@ -1,15 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-
-import StyleHelpers from '../style/helpers';
+// 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';
 import Constants from './constants';
 import SearchResources from './resources';
@@ -26,6 +25,20 @@ const initialState = {
   ...softReset()
 };
 
+var keys = [];
+function getUniqueKey() {
+  function keygen () {
+    return Math.random().toString(36).substring(7);
+  }
+
+  var newKey = keygen();
+  while (keys.includes(newKey)) {
+    newKey = keygen();
+  }
+  keys.push(newKey);
+  return newKey;
+}
+
 // called on first load (e.g. editing a search index) and every time the create search index page loads
 function softReset() {
   return {
@@ -49,7 +62,7 @@ function softReset() {
 function addAnalyzerRow (state, { analyzer, fieldName }) {
   const newAnalyzerFields = state.analyzerFields.slice();
   newAnalyzerFields.push({
-    key: StyleHelpers.getUniqueKey(),
+    key: getUniqueKey(),
     fieldName: (fieldName) ? fieldName : '',
     analyzer: analyzer,
     valid: (fieldName && fieldName.trim().length > 0)
@@ -101,7 +114,7 @@ function initEditSearch (state, { database, designDocs, ddocInfo, indexName }) {
   if (analyzer && analyzer.fields) {
     Object.keys(analyzer.fields).forEach(fieldName => {
       newAnalyzerFields.push({
-        key: StyleHelpers.getUniqueKey(),
+        key: getUniqueKey(),
         fieldName: (fieldName) ? fieldName : '',
         analyzer: analyzer.fields[fieldName],
         valid: !_.isUndefined(fieldName) && !_.isEmpty(fieldName)
@@ -142,14 +155,16 @@ export function getSaveDesignDoc (state, isDbPartitioned) {
   }
 
   const foundDoc = state.designDocs.find((ddoc) => {
-    return ddoc.id === state.ddocName;
+    return ddoc.id === state.ddocName ||
+      ddoc.id === '_design/' + state.ddocName;
   });
   return (!foundDoc) ? null : foundDoc.dDocModel();
 }
 
 export function getSelectedDesignDocPartitioned(state, isDbPartitioned) {
   const designDoc = state.designDocs.find(ddoc => {
-    return state.ddocName === ddoc.id;
+    return ddoc.id === state.ddocName ||
+      ddoc.id === '_design/' + state.ddocName;
   });
   if (designDoc) {
     return DocumentsHelpers.isDDocPartitioned(designDoc.get('doc'), isDbPartitioned);
diff --git a/app/addons/search/resources.js b/app/addons/search/resources.js
index c653fbb..34d2eaa 100644
--- a/app/addons/search/resources.js
+++ b/app/addons/search/resources.js
@@ -1,14 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-
+// 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 Constants from './constants';
 import Documents from '../documents/resources';
 
diff --git a/app/addons/search/routes.js b/app/addons/search/routes.js
index f4ba4d3..a578067 100644
--- a/app/addons/search/routes.js
+++ b/app/addons/search/routes.js
@@ -1,28 +1,25 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-import app from '../../app';
+// 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 FauxtonAPI from '../../core/api';
 import Resources from './resources';
 import SearchActions from './actions';
 import DatabasesActions from '../databases/actions';
 import Databases from '../databases/base';
 import BaseRoute from '../documents/shared-routes';
-import {SidebarItemSelection} from '../documents/sidebar/helpers';
-import CloudantDocuments from '../cloudantdocuments/resources';
-import CloudantDatabases from '../cloudantdatabases/resources';
-import Account from '../account/resources';
 import SidebarActions from '../documents/sidebar/actions';
 import Actions from './actions';
 import Layout from './layout';
 import React from 'react';
+import {SidebarItemSelection} from '../documents/sidebar/helpers';
 
 var SearchRouteObject = BaseRoute.extend({
   routes: {
@@ -57,7 +54,6 @@ var SearchRouteObject = BaseRoute.extend({
   },
 
   initialize: function (route, options) {
-    this.allDatabases = new Databases.List();
     this.databaseName = options[0];
     this.database = new Databases.Model({ id: this.databaseName });
     this.data = {
@@ -200,73 +196,6 @@ var SearchRouteObject = BaseRoute.extend({
   }
 });
 
-
-var SharedSearchRouteObject = SearchRouteObject.extend({
-  routes: {
-    'database/shared/:user/:database/_design/:ddoc/_search/:search(?*searchQuery)': {
-      route: 'sharedSearch',
-      roles: ['fx_loggedIn']
-    },
-    'database/shared/:user/:database/_design/:ddoc/_indexes/:search(?*searchQuery)': {
-      route: 'sharedSearch',
-      roles: ['fx_loggedIn']
-    },
-    'database/shared/:user/:database/new_search': 'sharedNewSearch',
-    'database/shared/:user/:database/new_search/:designDoc': 'sharedNewSearch',
-    'database/shared/:user/:database/_design/:ddoc/_search/:search/edit': {
-      route: 'sharedEdit',
-      roles: ['fx_loggedIn']
-    }
-  },
-
-  initialize: function (route, options) {
-    var docOptions = app.getParams();
-    docOptions.include_docs = true;
-
-    this.username = CloudantDocuments.sharedUsername = options[0];
-    this.databaseName = options[1];
-    this.database = new CloudantDatabases.SharedModel({
-      id: this.databaseName,
-      name: this.username
-    });
-    this.user = new Account.User();
-
-    this.data = {
-      username: this.username,
-      database: this.database
-    };
-
-    this.data.designDocs = new CloudantDocuments.SharedAllDocs(null, {
-      username: this.username,
-      database: this.database,
-      paging: {
-        pageSize: 500
-      },
-      params: {
-        startkey: '"_design"',
-        endkey: '"_design1"',
-        include_docs: true,
-        limit: 500
-      }
-    });
-
-    var initOptions = options.slice(1);
-    SearchRouteObject.prototype.initialize.call(this, route, initOptions);
-  },
-
-  sharedSearch: function (user, database, ddoc, search, query) {
-    return this.searchNoPartition(database, ddoc, search, query);
-  },
-
-  sharedEdit: function (user, database, ddoc, indexName) {
-    return this.editNoPartition(database, ddoc, indexName);
-  },
-
-  sharedNewSearch: function (user, database, ddoc) {
-    return this.createNoPartition(database, ddoc);
-  }
-});
-
-Resources.RouteObjects = [SearchRouteObject, SharedSearchRouteObject];
+Resources.RouteObjects = [SearchRouteObject];
 
 export default Resources;
diff --git a/app/addons/search/tests/nightwatch/cloneSearchIndex.js b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
index 69d6748..a18951c 100644
--- a/app/addons/search/tests/nightwatch/cloneSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
@@ -1,25 +1,3 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-// 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.
-
 // 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
diff --git a/app/addons/search/tests/nightwatch/createNewSearch.js b/app/addons/search/tests/nightwatch/createNewSearch.js
index 6265b67..5710143 100644
--- a/app/addons/search/tests/nightwatch/createNewSearch.js
+++ b/app/addons/search/tests/nightwatch/createNewSearch.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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.
 module.exports = {
 
   'Creates new Search index for Dash': function (client) {
diff --git a/app/addons/search/tests/nightwatch/deleteSearchIndex.js b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
index f4cdc86..0981f22 100644
--- a/app/addons/search/tests/nightwatch/deleteSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
@@ -1,25 +1,3 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
-// 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.
-
 // 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
diff --git a/app/addons/search/tests/nightwatch/searchPageApiBar.js b/app/addons/search/tests/nightwatch/searchPageApiBar.js
index ac503c6..6052e55 100644
--- a/app/addons/search/tests/nightwatch/searchPageApiBar.js
+++ b/app/addons/search/tests/nightwatch/searchPageApiBar.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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.
 module.exports = {
 
   'Check API Bar is present/hidden on appropriate page and is encoded': function (client) {
diff --git a/app/addons/search/tests/nightwatch/sharedSearch.js b/app/addons/search/tests/nightwatch/sharedSearch.js
index 7cae1e2..1f8de00 100644
--- a/app/addons/search/tests/nightwatch/sharedSearch.js
+++ b/app/addons/search/tests/nightwatch/sharedSearch.js
@@ -1,13 +1,14 @@
-/*
-* Licensed Materials - Property of IBM
-*
-* "Restricted Materials of IBM"
-*
-* (C) Copyright IBM Corp. 2018 All Rights Reserved
-*
-* US Government Users Restricted Rights - Use, duplication or disclosure
-* restricted by GSA ADP Schedule Contract with IBM Corp.
-*/
+// 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./
 module.exports = {
 
   'Edits existing search route works': function (client) {
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index 93c876c..c2564ad 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -712,3 +712,12 @@ body .control-toggle-include-docs span {
   clip: rect(0,0,0,0);
   border: 0;
 }
+
+.btn-group.toggle-btns{
+  input[type=radio] {
+    display: none;
+  }
+  label.btn {
+    margin-right: 0;
+  }
+}
diff --git a/package-lock.json b/package-lock.json
index 85728e6..0151e11 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6437,8 +6437,7 @@
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -6456,13 +6455,11 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -6475,18 +6472,15 @@
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -6589,8 +6583,7 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "ini": {
           "version": "1.3.5",
@@ -6600,7 +6593,6 @@
         "is-fullwidth-code-point": {
           "version": "1.0.0",
           "bundled": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -6613,20 +6605,17 @@
         "minimatch": {
           "version": "3.0.4",
           "bundled": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -6643,7 +6632,6 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -6716,8 +6704,7 @@
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -6727,7 +6714,6 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -6803,8 +6789,7 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -6834,7 +6819,6 @@
         "string-width": {
           "version": "1.0.2",
           "bundled": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -6852,7 +6836,6 @@
         "strip-ansi": {
           "version": "3.0.1",
           "bundled": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -6891,13 +6874,11 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         }
       }
     },
@@ -8400,7 +8381,6 @@
             "align-text": {
               "version": "0.1.4",
               "bundled": true,
-              "optional": true,
               "requires": {
                 "kind-of": "^3.0.2",
                 "longest": "^1.0.1",
@@ -9103,8 +9083,7 @@
             },
             "longest": {
               "version": "1.0.1",
-              "bundled": true,
-              "optional": true
+              "bundled": true
             },
             "loose-envify": {
               "version": "1.3.1",
@@ -9880,8 +9859,7 @@
         "safe-buffer": {
           "version": "5.1.1",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
-          "optional": true
+          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
         },
         "semver": {
           "version": "1.0.14",
@@ -12312,8 +12290,7 @@
             "ansi-regex": {
               "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "aproba": {
               "version": "1.2.0",
@@ -12334,14 +12311,12 @@
             "balanced-match": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "brace-expansion": {
               "version": "1.1.11",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -12356,20 +12331,17 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "concat-map": {
               "version": "0.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "core-util-is": {
               "version": "1.0.2",
@@ -12486,8 +12458,7 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "ini": {
               "version": "1.3.5",
@@ -12499,7 +12470,6 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "number-is-nan": "^1.0.0"
               }
@@ -12514,7 +12484,6 @@
               "version": "3.0.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "brace-expansion": "^1.1.7"
               }
@@ -12522,14 +12491,12 @@
             "minimist": {
               "version": "0.0.8",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "minipass": {
               "version": "2.3.5",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.2",
                 "yallist": "^3.0.0"
@@ -12548,7 +12515,6 @@
               "version": "0.5.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "minimist": "0.0.8"
               }
@@ -12629,8 +12595,7 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "object-assign": {
               "version": "4.1.1",
@@ -12642,7 +12607,6 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -12728,8 +12692,7 @@
             "safe-buffer": {
               "version": "5.1.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "safer-buffer": {
               "version": "2.1.2",
@@ -12765,7 +12728,6 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
@@ -12785,7 +12747,6 @@
               "version": "3.0.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "ansi-regex": "^2.0.0"
               }
@@ -12829,14 +12790,12 @@
             "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "yallist": {
               "version": "3.0.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             }
           }
         },
@@ -18471,7 +18430,7 @@
     "tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
       "requires": {
         "os-tmpdir": "~1.0.2"
       }
diff --git a/settings.json.default.json b/settings.json.default.json
index 31935fb..9bd9cf2 100644
--- a/settings.json.default.json
+++ b/settings.json.default.json
@@ -9,6 +9,7 @@
   { "name": "cluster" },
   { "name": "config" },
   { "name": "replication" },
+  { "name": "search" },
   { "name": "cors" },
   { "name": "permissions" },
   { "name": "auth" },
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index bb8cb07..4eaa0c5 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -117,5 +117,5 @@ module.exports = {
       "underscore": "lodash",
     }
   },
-  devtool: 'source-map'
+  devtool: 'eval-source-map'
 };