app/addons/search/actions.js (360 lines of code) (raw):

// 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'; import Search from './resources'; 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) => { const {database, ddocID, designDocs, indexName} = params; FauxtonAPI.reduxDispatch({ type: ActionTypes.SEARCH_INDEX_SET_LOADING, options: { loading: true } }); designDocs.fetch().then(ddocs => { const ddoc = ddocs.rows.find(ddoc => ddoc._id === ddocID).doc; if (!ddoc.indexes || !ddoc.indexes[indexName]) { throw Error(`Index "${indexName}" not found`); } FauxtonAPI.reduxDispatch({ type: ActionTypes.SEARCH_INDEX_INIT_EDIT_SEARCH_INDEX, options: { indexName, database, ddocID, designDocs, } }); }).catch(err => { const details = err.message ? err.message : ''; FauxtonAPI.addNotification({ msg: `There was a problem editing the search index "${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 "' + options.indexName + '" 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 encodedDatabaseName = encodeURIComponent(databaseName); const encodedPartitionKey = encodeURIComponent(partitionKey); const encodedDdocName = encodeURIComponent(ddocName); const encodedIndexName = encodeURIComponent(indexName); const encodedSearchQuery = encodeURIComponent(searchQuery); const baseUrl = partitionKey ? FauxtonAPI.urls('partitioned_search', 'app', encodedDatabaseName, encodedPartitionKey, encodedDdocName, encodedIndexName) : FauxtonAPI.urls('search', 'app', encodedDatabaseName, encodedDdocName, encodedIndexName); FauxtonAPI.navigate(`${baseUrl}${encodedIndexName}?${encodedSearchQuery}`, {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 = () => ({ type: ActionTypes.SEARCH_INDEX_PARTITION_PARAM_NOT_SUPPORTED }); const partitionParamIsMandatory = () => ({ 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 Search.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 };