app/addons/documents/index-results/reducers.js (318 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 app from "../../../app"; import ActionTypes from './actiontypes'; import Constants from '../constants'; import {getJsonViewData} from './helpers/json-view'; import {getTableViewData} from './helpers/table-view'; import {getDefaultPerPage, getDocId, isJSONDocBulkDeletable} from './helpers/shared-helpers'; const initialState = { noResultsWarning: '', docs: [], // raw documents returned from couch selectedDocs: [], // documents selected for manipulation isLoading: false, tableView: { selectedFieldsTableView: [], // current columns to display showAllFieldsTableView: false, // do we show all possible columns? }, isEditable: true, // can the user manipulate the results returned? selectedLayout: Constants.LAYOUT_ORIENTATION.METADATA, textEmptyIndex: 'No Documents Found', docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW, resultsStyle: loadStyle(), fetchParams: { limit: getDefaultPerPage() + 1, skip: 0 }, pagination: { pageStart: 1, // index of first doc in this page of results currentPage: 1, // what page of results are we showing? perPage: getDefaultPerPage(), canShowNext: false // flag indicating if we can show a next page }, queryOptionsPanel: { isVisible: false, showByKeys: true, showBetweenKeys: false, includeDocs: false, betweenKeys: { include: true, startkey: '', endkey: '' }, byKeys: '', descending: false, skip: '', limit: 'none', reduce: false, groupLevel: 'exact', showReduce: false, stable: false, update: 'true' }, executionStats: null, warning: null, }; function loadStyle() { let style = app.utils.localStorageGet('fauxton:results_style'); if (!style) { style = { textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED, fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM }; } return style; } function storeStyle(style) { app.utils.localStorageSet('fauxton:results_style', style); } export default function resultsState(state = initialState, action) { switch (action.type) { case ActionTypes.INDEX_RESULTS_SET_STYLE: const newStyle = { ...state.resultsStyle, ...action.resultsStyle }; storeStyle(newStyle); return { ...state, resultsStyle: newStyle }; case ActionTypes.INDEX_RESULTS_REDUX_RESET_STATE: return { ...initialState, executionStats: state.executionStats, warning: state.warning, noResultsWarning: state.noResultsWarning, selectedLayout: state.selectedLayout, selectedDocs: [], fetchParams: { limit: getDefaultPerPage() + 1, skip: 0 }, pagination: Object.assign({}, initialState.pagination, { perPage: state.pagination.perPage }), queryOptionsPanel: Object.assign({}, initialState.queryOptionsPanel, state.queryOptionsPanel, {reduce: false, groupLevel: 'exact', showReduce: false}), isLoading: false, resultsStyle: state.resultsStyle }; case ActionTypes.INDEX_RESULTS_REDUX_IS_LOADING: return { ...state, isLoading: true }; case ActionTypes.INDEX_RESULTS_REDUX_PARTITION_PARAM_NOT_SUPPORTED: return Object.assign({}, state, { noResultsWarning: 'The selected index does not support partitions. Switch back to global mode.' }); case ActionTypes.INDEX_RESULTS_REDUX_PARTITION_PARAM_MANDATORY: return Object.assign({}, state, { noResultsWarning: 'The selected index requires a partition key. Use the selector at the top to enter a partition key.' }); case ActionTypes.INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS: return { ...state, selectedDocs: action.selectedDocs }; case ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS: let selectedLayout = state.selectedLayout; // Change layout if it's set to METADATA because this option is not available for Mango queries if (action.docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY) { if (state.selectedLayout === Constants.LAYOUT_ORIENTATION.METADATA) { selectedLayout = Constants.LAYOUT_ORIENTATION.TABLE; } } return { ...state, docs: action.docs, isLoading: false, noResultsWarning: '', isEditable: true, //TODO: determine logic for this fetchParams: Object.assign({}, state.fetchParams, action.params), pagination: Object.assign({}, state.pagination, { canShowNext: action.canShowNext }), docType: action.docType, selectedLayout: selectedLayout, executionStats: action.executionStats, warning: action.warning }; case ActionTypes.INDEX_RESULTS_REDUX_CHANGE_LAYOUT: return { ...state, selectedLayout: action.layout }; case ActionTypes.INDEX_RESULTS_REDUX_TOGGLE_SHOW_ALL_COLUMNS: return { ...state, tableView: Object.assign({}, state.tableView, { showAllFieldsTableView: !state.tableView.showAllFieldsTableView, cachedFieldsTableView: state.tableView.selectedFieldsTableView }) }; case ActionTypes.INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE: return { ...state, tableView: Object.assign({}, state.tableView, { selectedFieldsTableView: action.selectedFieldsTableView }) }; case ActionTypes.INDEX_RESULTS_REDUX_SET_PER_PAGE: app.utils.localStorageSet('fauxton:perpageredux', action.perPage); return { ...state, pagination: Object.assign({}, initialState.pagination, { perPage: action.perPage }) }; case ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_NEXT: return { ...state, pagination: Object.assign({}, state.pagination, { pageStart: state.pagination.pageStart + state.pagination.perPage, currentPage: state.pagination.currentPage + 1 }) }; case ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_PREVIOUS: return { ...state, pagination: Object.assign({}, state.pagination, { pageStart: state.pagination.pageStart - state.pagination.perPage, currentPage: state.pagination.currentPage - 1 }) }; case ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS: // includeDocs or reduce should be mutually exclusive if (action.options.includeDocs && action.options.reduce) { // includeDocs has precedence if both are set at the same time action.options.reduce = false; } else if (action.options.includeDocs && state.queryOptionsPanel.reduce) { // Switch off reduce when includeDocs is being set to true action.options.reduce = false; } else if (action.options.reduce && state.queryOptionsPanel.includeDocs) { // Switch off includeDocs when reduce is being set to true action.options.includeDocs = false; } return { ...state, queryOptionsPanel: Object.assign({}, state.queryOptionsPanel, action.options) }; default: return state; } } // we don't want to muddy the waters with autogenerated mango docs export const removeGeneratedMangoDocs = (doc) => { return doc.language !== 'query'; }; // transform the docs in to a state ready for rendering on the page export const getDataForRendering = (state, databaseName, deleteEnabled = true) => { const {docs} = state; const options = { databaseName: databaseName, selectedLayout: state.selectedLayout, selectedFieldsTableView: state.tableView.selectedFieldsTableView, showAllFieldsTableView: state.tableView.showAllFieldsTableView, docType: state.docType, deleteEnabled: deleteEnabled }; const docsWithoutGeneratedMangoDocs = docs.filter(removeGeneratedMangoDocs); if (Constants.LAYOUT_ORIENTATION.JSON === options.selectedLayout) { return getJsonViewData(docsWithoutGeneratedMangoDocs, options); } return getTableViewData(docsWithoutGeneratedMangoDocs, options); }; // Should we show the input checkbox where the user can elect to display // all possible columns in the table view? export const getShowPrioritizedEnabled = (state) => { return state.selectedLayout === Constants.LAYOUT_ORIENTATION.TABLE; }; // returns the index of the last result in the total possible results. export const getPageEnd = (state) => { if (!getHasResults(state)) { return false; } return state.pagination.pageStart + state.docs.length - 1; }; // do we have any docs in the state tree currently? export const getHasResults = (state) => { return !state.isLoading && state.docs.length > 0; }; // helper function to determine if all selectable docs on the current page are selected. export const getAllDocsSelected = (state) => { if (state.docs.length === 0 || state.selectedDocs.length === 0) { return false; } // Iterate over the results and determine if each one is included // in the selectedDocs array. // // This is O(n^2) which makes me unhappy. We know // that the number of docs will never be that large due to the // per page limitations we force on the user. // // We need to use a for loop here instead of a forEach since there // is no way to short circuit Array.prototype.forEach. for (let i = 0; i < state.docs.length; i++) { const doc = state.docs[i]; if (!isJSONDocBulkDeletable(doc, state.docType)) { //Only check selectable docs continue; } // Helper function for finding index of a doc in the current // selected docs list. const exists = (selectedDoc) => { return getDocId(doc, state.docType) === selectedDoc._id; }; if (!state.selectedDocs.some(exists)) { return false; } } return true; }; // are there any documents selected in the state tree? export const getHasDocsSelected = (state) => { return state.selectedDocs.length > 0; }; // how many documents are selected in the state tree? export const getNumDocsSelected = (state) => { return state.selectedDocs.length; }; // is there a previous page of results? We only care if the current page // of results is greater than 1 (i.e. the first page of results). export const getCanShowPrevious = (state) => { return state.pagination.currentPage > 1; }; export const getDisplayedFields = (state, databaseName) => { return getDataForRendering(state, databaseName).displayedFields || {}; }; export const getQueryOptionsParams = (state) => { const {queryOptionsPanel} = state; const params = {}; if (queryOptionsPanel.includeDocs) { params.include_docs = queryOptionsPanel.includeDocs; } if (queryOptionsPanel.showBetweenKeys) { const betweenKeys = queryOptionsPanel.betweenKeys; params.inclusive_end = betweenKeys.include; if (betweenKeys.startkey && betweenKeys.startkey != '') { params.start_key = betweenKeys.startkey; } if (betweenKeys.endkey && betweenKeys.endkey != '') { params.end_key = betweenKeys.endkey; } } else if (queryOptionsPanel.showByKeys) { if (queryOptionsPanel.byKeys.trim()) { params.keys = queryOptionsPanel.byKeys.replace(/\r?\n/g, ''); } } if (queryOptionsPanel.limit !== 'none') { params.limit = parseInt(queryOptionsPanel.limit, 10); } if (queryOptionsPanel.skip) { params.skip = parseInt(queryOptionsPanel.skip, 10); } if (queryOptionsPanel.descending) { params.descending = queryOptionsPanel.descending; } if (queryOptionsPanel.reduce) { params.reduce = true; if (queryOptionsPanel.groupLevel === 'exact') { params.group = true; } else { params.group_level = queryOptionsPanel.groupLevel; } } // Only add UPDATE and STABLE parameters when different than // their respective default values. This prevent errors in // older CouchDB versions that don't support these parameters. if (queryOptionsPanel.update !== undefined && queryOptionsPanel.update !== 'true') { params.update = queryOptionsPanel.update; } if (queryOptionsPanel.stable === true) { params.stable = queryOptionsPanel.stable; } return params; }; // Here be simple getters export const getDocs = state => state.docs; export const getSelectedDocs = state => state.selectedDocs; export const getIsLoading = state => state.isLoading; export const getIsEditable = state => state.isEditable; export const getSelectedLayout = state => state.selectedLayout; export const getTextEmptyIndex = state => state.textEmptyIndex; export const getDocType = state => state.docType; export const getPageStart = state => state.pagination.pageStart; export const getPrioritizedEnabled = state => state.tableView.showAllFieldsTableView; export const getCanShowNext = state => state.pagination.canShowNext; export const getQueryOptionsPanel = state => state.queryOptionsPanel; export const getPerPage = state => state.pagination.perPage; export const getFetchParams = state => state.fetchParams; export const getResultsStyle = state => state.resultsStyle;