-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 Also added a CouchDB 2 / 3 / dev build matrix to Travis since the official CouchDB image doesn't include the search feature yet.
- Loading branch information
1 parent
9dada0e
commit 4450d4d
Showing
39 changed files
with
3,170 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Default environment variables for the Docker Compose files in /docker | ||
# This file needs to be placed where the docker-compose command is run from. | ||
|
||
# Used to provide a CouchDB 2 / 3 build matrix in .travis.yml | ||
COUCHDB_IMAGE=ibmcom/couchdb3:preview-1569600329 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// 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 | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
// License for the specific language governing permissions and limitations under | ||
// the License. | ||
|
||
|
||
|
||
var newDatabaseName = 'fauxton-selenium-tests-db-create'; | ||
var invalidDatabaseName = 'fauxton-selenium-tests-#####'; | ||
var helpers = require('../../../../../test/nightwatch_tests/helpers/helpers.js'); | ||
module.exports = { | ||
'@tags': ['partitioned'], | ||
|
||
before: function (client, done) { | ||
const nano = helpers.getNanoInstance(client.globals.test_settings.db_url); | ||
nano.db.destroy(newDatabaseName).then(() => { | ||
done(); | ||
}).catch(() => { | ||
done(); | ||
}); | ||
}, | ||
|
||
after: function (client, done) { | ||
const nano = helpers.getNanoInstance(client.globals.test_settings.db_url); | ||
nano.db.destroy(newDatabaseName).then(() => { | ||
done(); | ||
}).catch(() => { | ||
console.warn(`Could not delete ${newDatabaseName} db`); | ||
done(); | ||
}); | ||
}, | ||
|
||
'Creates a Database' : function (client) { | ||
var waitTime = client.globals.maxWaitTime, | ||
baseUrl = client.globals.test_settings.launch_url; | ||
|
||
client | ||
.loginToGUI() | ||
.checkForDatabaseDeleted(newDatabaseName, waitTime) | ||
.url(baseUrl) | ||
|
||
// ensure the page has fully loaded | ||
.waitForElementPresent('.databases.table', waitTime, false) | ||
.clickWhenVisible('.add-new-database-btn') | ||
.waitForElementVisible('#js-new-database-name', waitTime, false) | ||
.setValue('#js-new-database-name', [newDatabaseName]) | ||
.clickWhenVisible('#non-partitioned-db', waitTime, false) | ||
.clickWhenVisible('#js-create-database', waitTime, false) | ||
.waitForElementNotPresent('.new-database-tray', waitTime, false) | ||
.checkForDatabaseCreated(newDatabaseName, waitTime) | ||
.url(baseUrl + '/_all_dbs') | ||
.waitForElementVisible('html', waitTime, false) | ||
.getText('html', function (result) { | ||
var data = result.value, | ||
createdDatabaseIsPresent = data.indexOf(newDatabaseName); | ||
|
||
this.verify.ok(createdDatabaseIsPresent > 0, | ||
'Checking if new database shows up in _all_dbs.'); | ||
}) | ||
.end(); | ||
}, | ||
|
||
'Creates a Database with invalid name' : function (client) { | ||
var waitTime = client.globals.maxWaitTime, | ||
baseUrl = client.globals.test_settings.launch_url; | ||
|
||
client | ||
.loginToGUI() | ||
.checkForDatabaseDeleted(invalidDatabaseName, waitTime) | ||
.url(baseUrl) | ||
|
||
// ensure the page has fully loaded | ||
.waitForElementPresent('.databases.table', waitTime, false) | ||
.clickWhenVisible('.add-new-database-btn') | ||
.waitForElementVisible('#js-new-database-name', waitTime, false) | ||
.setValue('#js-new-database-name', [invalidDatabaseName]) | ||
.clickWhenVisible('#non-partitioned-db', waitTime, false) | ||
.clickWhenVisible('#js-create-database', waitTime, false) | ||
.waitForElementVisible('.global-notification.alert.alert-error', waitTime, false) | ||
.url(baseUrl + '/_all_dbs') | ||
.waitForElementVisible('html', waitTime, false) | ||
.getText('html', function (result) { | ||
var data = result.value, | ||
createdDatabaseIsPresent = data.indexOf(invalidDatabaseName); | ||
|
||
this.verify.ok(createdDatabaseIsPresent === -1, | ||
'Checking if new database shows up in _all_dbs.'); | ||
}) | ||
.end(); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// 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 | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
// License for the specific language governing permissions and limitations under | ||
// the License. | ||
import {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(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// 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 | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
// License for the specific language governing permissions and limitations under | ||
// the License. | ||
|
||
import 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'); | ||
}); | ||
}); | ||
|
||
}); |
Oops, something went wrong.