blob: 5d040c3b943609c4f3f5ca526b02bec8a084e7d2 [file] [log] [blame]
// 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 = {
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: false,
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'
}
};
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,
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_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,
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;