| // 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 SidebarActions from '../../sidebar/actions'; |
| import Constants from '../../constants'; |
| import { errorReason } from '../helpers/shared-helpers'; |
| import * as IndexResultsAPI from '../api'; |
| import { nowLoading, newResultsAvailable, newSelectedDocs, |
| changeLayout, resetState, partitionParamNotSupported, partitionParamIsMandatory } from './base'; |
| |
| const maxDocLimit = 10000; |
| |
| // This is a helper function to determine what params need to be sent to couch based |
| // on what the user entered (i.e. queryOptionsParams) and what fauxton is using to |
| // emulate pagination (i.e. fetchParams). |
| export const mergeParams = (fetchParams, queryOptionsParams) => { |
| const params = {}; |
| |
| // determine the final "index" or "position" in the total result list based on the |
| // user's skip and limit inputs. If queryOptionsParams.limit is empty, |
| // finalDocPosition will be NaN. That's ok. |
| const finalDocPosition = (queryOptionsParams.skip || 0) + queryOptionsParams.limit; |
| |
| // The skip value sent to couch will be the max of our current pagination skip |
| // (i.e. fetchParams.skip) and the user's original skip input (i.e. queryOptionsParams.skip). |
| // The limit will continue to be our pagination limit. |
| params.skip = Math.max(fetchParams.skip, queryOptionsParams.skip || 0); |
| params.limit = fetchParams.limit; |
| if (fetchParams.conflicts) { |
| params.conflicts = true; |
| } |
| |
| // Determine the total number of documents remaining based on the user's skip and |
| // limit inputs. Again, note that this will be NaN if queryOptionsParams.limit is |
| // empty. That's ok. |
| const totalDocsRemaining = finalDocPosition - params.skip; |
| |
| // return the merged params to send to couch and the num docs remaining. |
| return { |
| params: Object.assign({}, queryOptionsParams, params), |
| totalDocsRemaining: totalDocsRemaining |
| }; |
| }; |
| |
| export const removeOverflowDocsAndCalculateHasNext = (docs, totalDocsRemaining, fetchLimit) => { |
| // Now is the time to determine if we have another page of results |
| // after this set of documents. We also want to manipulate the array |
| // of docs because we always search with a limit larger than the desired |
| // number of results. This is necessaary to emulate pagination. |
| let canShowNext = false; |
| if (totalDocsRemaining && docs.length > totalDocsRemaining) { |
| // We know the user manually entered a limit and we've reached the |
| // end of their desired results. We need to remove any extra results |
| // that were returned because of our pagination emulation logic. |
| docs.splice(totalDocsRemaining); |
| } else if (docs.length === fetchLimit) { |
| // The number of docs returned is equal to our params.limit, which is |
| // one more than our perPage size. We know that there is another |
| // page of results after this. |
| docs.splice(fetchLimit - 1); |
| canShowNext = true; |
| } |
| |
| return { |
| finalDocList: docs, |
| canShowNext |
| }; |
| }; |
| |
| // All the business logic for fetching docs from couch. |
| // Arguments: |
| // - queryDocs -> a function that fetches the docs. Accepts fetch params and returns a Promise of {docs[], docType}. |
| // - fetchParams -> the internal params fauxton uses to emulate pagination |
| // - queryOptionsParams -> manual query params entered by user |
| export const fetchDocs = (queryDocs, fetchParams, queryOptionsParams) => { |
| const { params, totalDocsRemaining } = mergeParams(fetchParams, queryOptionsParams); |
| params.limit = Math.min(params.limit, maxDocLimit); |
| |
| return (dispatch) => { |
| // first, tell app state that we're loading |
| dispatch(nowLoading()); |
| |
| // now fetch the results |
| return queryDocs(params).then(({ docs, docType, executionStats, warning, layout }) => { |
| const { |
| finalDocList, |
| canShowNext |
| } = removeOverflowDocsAndCalculateHasNext(docs, totalDocsRemaining, params.limit); |
| |
| if (layout) { |
| dispatch(changeLayout(layout)); |
| } |
| // dispatch that we're all done |
| dispatch(newResultsAvailable(finalDocList, params, canShowNext, docType, executionStats, warning)); |
| }).catch((error) => { |
| if (error && error.message.includes('`partition` parameter is not supported')) { |
| dispatch(partitionParamNotSupported()); |
| } else if (error && error.message.includes('`partition` parameter is mandatory')) { |
| dispatch(partitionParamIsMandatory()); |
| } else { |
| FauxtonAPI.addNotification({ |
| msg: 'Error running query. ' + errorReason(error), |
| type: 'error', |
| clear: true |
| }); |
| } |
| dispatch(resetState()); |
| }); |
| }; |
| }; |
| |
| |
| export const deleteErrorMessage = (ids) => { |
| let msg = 'Failed to delete your document!'; |
| |
| if (ids) { |
| msg = 'Failed to delete: ' + ids.join(', '); |
| } |
| |
| FauxtonAPI.addNotification({ |
| msg: msg, |
| type: 'error', |
| clear: true |
| }); |
| }; |
| |
| export const validateBulkDelete = (docs) => { |
| const itemsLength = docs.length; |
| |
| const msg = (itemsLength === 1) ? 'Are you sure you want to delete this doc?' : |
| 'Are you sure you want to delete these ' + itemsLength + ' docs?'; |
| |
| if (itemsLength === 0) { |
| window.alert('Please select the document rows you want to delete.'); |
| return false; |
| } |
| |
| if (!window.confirm(msg)) { |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| export const bulkDeleteDocs = (databaseName, queryDocs, docs, designDocs, fetchParams, queryOptionsParams, docType) => { |
| if (!validateBulkDelete(docs)) { |
| return false; |
| } |
| |
| return (dispatch) => { |
| let postPromise, payload; |
| if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) { |
| payload = { docids: docs.map(doc => doc._id) }; |
| postPromise = IndexResultsAPI.postToIndexBulkDelete(databaseName, payload); |
| } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW |
| || docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY) { |
| payload = { docs: docs }; |
| postPromise = IndexResultsAPI.postToBulkDocs(databaseName, payload); |
| } else { |
| throw new Error('Invalid document type: ' + docType); |
| } |
| return postPromise.then((res) => { |
| if (res.error) { |
| deleteErrorMessage(); |
| return; |
| } |
| processBulkDeleteResponse(res, docs, designDocs, docType); |
| dispatch(newSelectedDocs()); |
| dispatch(fetchDocs(queryDocs, fetchParams, queryOptionsParams)); |
| }); |
| }; |
| }; |
| |
| export const processBulkDeleteResponse = (res, deletedDocs, designDocs, docType) => { |
| FauxtonAPI.addNotification({ |
| msg: 'Successfully deleted your docs', |
| clear: true |
| }); |
| |
| let failedDocs = []; |
| if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) { |
| if (res.fail) { |
| failedDocs = res.fail.map(doc => doc.id); |
| } |
| } else { |
| failedDocs = res.filter(doc => !!doc.error).map(doc => doc.id); |
| } |
| |
| const hasDesignDocs = !!deletedDocs.map(d => d._id).find((_id) => /_design/.test(_id)); |
| |
| if (failedDocs.length > 0) { |
| deleteErrorMessage(failedDocs); |
| } |
| |
| if (designDocs && hasDesignDocs) { |
| SidebarActions.dispatchUpdateDesignDocs(designDocs); |
| } |
| }; |