diff --git a/package.json b/package.json index e0161118..29590146 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "expect": "^26.5.3", "json": "^10.0.0", "lodash": "^4.17.20", - "node-sass": "^4.12.0", + "node-sass": "^4.14.0", "qs": "^6.8.0", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/src/lib/api/UrlHandlerApi.js b/src/lib/api/UrlHandlerApi.js index a70407d6..f9025aeb 100644 --- a/src/lib/api/UrlHandlerApi.js +++ b/src/lib/api/UrlHandlerApi.js @@ -14,13 +14,13 @@ import _isNaN from 'lodash/isNaN'; import _isNil from 'lodash/isNil'; import _cloneDeep from 'lodash/cloneDeep'; -const pushHistory = query => { +const pushHistory = (query) => { if (window.history.pushState) { window.history.pushState({ path: query }, '', query); } }; -const replaceHistory = query => { +const replaceHistory = (query) => { if (window.history.replaceState) { window.history.replaceState({ path: query }, '', query); } @@ -32,7 +32,7 @@ class UrlParser { this.parse = this.parse.bind(this); } - _sanitizeParamValue = value => { + _sanitizeParamValue = (value) => { let parsedValue = parseInt(value); if (_isNaN(parsedValue)) { try { @@ -58,7 +58,7 @@ class UrlParser { parse(queryString = '') { const parsedParams = Qs.parse(queryString, { ignoreQueryPrefix: true }); const params = {}; - Object.entries(parsedParams).forEach(entry => { + Object.entries(parsedParams).forEach((entry) => { const key = entry[0]; const value = entry[1]; params[key] = this._sanitizeParamValue(value); @@ -115,7 +115,7 @@ export class UrlHandlerApi { // build the serializer from URL params to Query state by flipping the urlParamsMapping this.fromUrlParamsMapping = {}; - Object.keys(this.urlParamsMapping).forEach(stateKey => { + Object.keys(this.urlParamsMapping).forEach((stateKey) => { this.fromUrlParamsMapping[this.urlParamsMapping[stateKey]] = stateKey; }); @@ -128,7 +128,7 @@ export class UrlHandlerApi { * Map filters from list to string that is human readable * [ 'type', 'photo', [ 'subtype', 'png' ]] => type:photo+subtype:png */ - _filterListToString = filter => { + _filterListToString = (filter) => { const childFilter = filter.length === 3 ? this.urlFilterSeparator.concat(this._filterListToString(filter[2])) @@ -139,11 +139,11 @@ export class UrlHandlerApi { /** * Map each query state field to an URL param */ - _mapQueryStateToUrlParams = queryState => { + _mapQueryStateToUrlParams = (queryState) => { const params = {}; Object.keys(queryState) - .filter(stateKey => stateKey in this.urlParamsMapping) - .filter(stateKey => { + .filter((stateKey) => stateKey in this.urlParamsMapping) + .filter((stateKey) => { // filter out negative or null values if ( (stateKey === 'page' || stateKey === 'size') && @@ -151,15 +151,15 @@ export class UrlHandlerApi { ) { return false; } - if (stateKey === 'hiddenParams'){ + if (stateKey === 'hiddenParams') { return false; } return queryState[stateKey] !== null; }) - .forEach(stateKey => { + .forEach((stateKey) => { const paramKey = this.urlParamsMapping[stateKey]; if (stateKey === 'filters') { - params[paramKey] = queryState[stateKey].map(filter => + params[paramKey] = queryState[stateKey].map((filter) => this._filterListToString(filter) ); } else { @@ -179,7 +179,7 @@ export class UrlHandlerApi { * Map filters from string to list * type:photo+subtype:png => [ 'type', 'photo', [ 'subtype', 'png' ]] */ - _filterStringToList = filterStr => { + _filterStringToList = (filterStr) => { const childSepPos = filterStr.indexOf(this.urlFilterSeparator); const hasChild = childSepPos > -1; @@ -203,9 +203,9 @@ export class UrlHandlerApi { /** * Map each URL param to a query state field */ - _mapUrlParamsToQueryState = urlParamsObj => { + _mapUrlParamsToQueryState = (urlParamsObj) => { const result = {}; - Object.keys(urlParamsObj).forEach(paramKey => { + Object.keys(urlParamsObj).forEach((paramKey) => { if (this.urlParamValidator.isValid(paramKey, urlParamsObj[paramKey])) { const queryStateKey = this.fromUrlParamsMapping[paramKey]; result[queryStateKey] = urlParamsObj[paramKey]; @@ -215,7 +215,7 @@ export class UrlHandlerApi { // if only 1 filter, create an array with one element urlParamsObj[paramKey] = [urlParamsObj[paramKey]]; } - result[queryStateKey] = urlParamsObj[paramKey].map(filter => + result[queryStateKey] = urlParamsObj[paramKey].map((filter) => this._filterStringToList(filter) ); } @@ -226,7 +226,7 @@ export class UrlHandlerApi { _mergeParamsIntoState = (urlStateObj, queryState) => { const _queryState = _cloneDeep(queryState); - Object.keys(urlStateObj).forEach(stateKey => { + Object.keys(urlStateObj).forEach((stateKey) => { if (stateKey in _queryState) { _queryState[stateKey] = urlStateObj[stateKey]; } diff --git a/src/lib/api/contrib/elasticsearch/ESResponseSerializer.js b/src/lib/api/contrib/elasticsearch/ESResponseSerializer.js index 17f6865b..0115772c 100644 --- a/src/lib/api/contrib/elasticsearch/ESResponseSerializer.js +++ b/src/lib/api/contrib/elasticsearch/ESResponseSerializer.js @@ -16,10 +16,12 @@ export class ESResponseSerializer { * @param {object} payload the backend response payload */ serialize(payload) { + const { aggregations, hits, ...extras } = payload; return { - aggregations: payload.aggregations || {}, - hits: payload.hits.hits.map(hit => hit._source), - total: payload.hits.total.value, + aggregations: aggregations || {}, + hits: hits.hits.map((hit) => hit._source), + total: hits.total.value, + extras: extras, }; } } diff --git a/src/lib/api/contrib/invenio/InvenioResponseSerializer.js b/src/lib/api/contrib/invenio/InvenioResponseSerializer.js index a6404e03..4e8e0555 100644 --- a/src/lib/api/contrib/invenio/InvenioResponseSerializer.js +++ b/src/lib/api/contrib/invenio/InvenioResponseSerializer.js @@ -17,10 +17,12 @@ export class InvenioResponseSerializer { * @param {object} payload the backend response payload */ serialize(payload) { + const { aggregations, hits, ...extras } = payload; return { - aggregations: payload.aggregations || {}, - hits: payload.hits.hits, - total: payload.hits.total, + aggregations: aggregations || {}, + hits: hits.hits, + total: hits.total, + extras: extras, }; } } diff --git a/src/lib/components/ReactSearchKit/ReactSearchKit.js b/src/lib/components/ReactSearchKit/ReactSearchKit.js index 4633f2ff..308d66f0 100644 --- a/src/lib/components/ReactSearchKit/ReactSearchKit.js +++ b/src/lib/components/ReactSearchKit/ReactSearchKit.js @@ -26,6 +26,7 @@ export class ReactSearchKit extends Component { ? props.urlHandlerApi.customHandler || new UrlHandlerApi(props.urlHandlerApi.overrideConfig) : null, + updateQueryStateFromResponse: props.updateQueryStateFromResponse, searchOnInit: props.searchOnInit, initialQueryState: props.initialQueryState, }; @@ -97,6 +98,7 @@ ReactSearchKit.defaultProps = { eventListenerEnabled: false, overridableId: '', initialQueryState: {}, + updateQueryStateFromResponse: false, }; export default Overridable.component('ReactSearchKit', ReactSearchKit); diff --git a/src/lib/state/actions/query.js b/src/lib/state/actions/query.js index 84d6fa76..89ceb87b 100644 --- a/src/lib/state/actions/query.js +++ b/src/lib/state/actions/query.js @@ -27,8 +27,8 @@ import { RESULTS_UPDATE_LAYOUT, } from '../types'; -export const setInitialState = initialState => { - return dispatch => { +export const setInitialState = (initialState) => { + return (dispatch) => { dispatch({ type: SET_QUERY_COMPONENT_INITIAL_STATE, payload: initialState, @@ -36,16 +36,22 @@ export const setInitialState = initialState => { }; }; -export const onAppInitialized = searchOnInit => { - return dispatch => { +export const onAppInitialized = (searchOnInit) => { + return (dispatch, getState, config) => { if (searchOnInit) { - dispatch(executeQuery({ shouldUpdateUrlQueryString: false })); + dispatch( + executeQuery({ + shouldUpdateUrlQueryString: false, + shouldUpdateStateFromResponse: + config.updateQueryStateFromResponse || false, + }) + ); } }; }; -export const updateQueryString = queryString => { - return dispatch => { +export const updateQueryString = (queryString) => { + return (dispatch) => { dispatch({ type: SET_QUERY_STRING, payload: queryString, @@ -55,7 +61,7 @@ export const updateQueryString = queryString => { }; export const updateQuerySorting = (sortByValue, sortOrderValue) => { - return dispatch => { + return (dispatch) => { dispatch({ type: SET_QUERY_SORTING, payload: { sortBy: sortByValue, sortOrder: sortOrderValue }, @@ -64,8 +70,8 @@ export const updateQuerySorting = (sortByValue, sortOrderValue) => { }; }; -export const updateQuerySortBy = sortByValue => { - return dispatch => { +export const updateQuerySortBy = (sortByValue) => { + return (dispatch) => { dispatch({ type: SET_QUERY_SORT_BY, payload: sortByValue, @@ -74,29 +80,29 @@ export const updateQuerySortBy = sortByValue => { }; }; -export const updateQuerySortOrder = sortOrderValue => { - return dispatch => { +export const updateQuerySortOrder = (sortOrderValue) => { + return (dispatch) => { dispatch({ type: SET_QUERY_SORT_ORDER, payload: sortOrderValue }); dispatch(executeQuery()); }; }; -export const updateQueryPaginationPage = page => { - return dispatch => { +export const updateQueryPaginationPage = (page) => { + return (dispatch) => { dispatch({ type: SET_QUERY_PAGINATION_PAGE, payload: page }); dispatch(executeQuery()); }; }; -export const updateQueryPaginationSize = size => { - return dispatch => { +export const updateQueryPaginationSize = (size) => { + return (dispatch) => { dispatch({ type: SET_QUERY_PAGINATION_SIZE, payload: size }); dispatch(executeQuery()); }; }; -export const updateQueryFilters = filters => { - return dispatch => { +export const updateQueryFilters = (filters) => { + return (dispatch) => { dispatch({ type: SET_QUERY_FILTERS, payload: filters, @@ -105,7 +111,7 @@ export const updateQueryFilters = filters => { }; }; -export const updateResultsLayout = layout => { +export const updateResultsLayout = (layout) => { return async (dispatch, getState, config) => { const urlHandlerApi = config.urlHandlerApi; if (urlHandlerApi) { @@ -125,7 +131,7 @@ export const updateResultsLayout = layout => { }; export const resetQuery = () => { - return dispatch => { + return (dispatch) => { dispatch({ type: RESET_QUERY, }); @@ -133,12 +139,31 @@ export const resetQuery = () => { }; }; +export const updateQueryStateFromResponse = (responseState) => { + return (dispatch, getState, config) => { + let queryState = _cloneDeep(getState().query); + const urlHandlerApi = config.urlHandlerApi; + queryState = { + ...queryState, + ...responseState, + }; + dispatch({ + type: SET_QUERY_STATE, + payload: queryState, + }); + if (urlHandlerApi) { + urlHandlerApi.replace(queryState); + } + }; +}; + export const executeQuery = ({ shouldUpdateUrlQueryString = true, shouldReplaceUrlQueryString = false, + shouldUpdateStateFromResponse = false, } = {}) => { return async (dispatch, getState, config) => { - const queryState = _cloneDeep(getState().query); + let queryState = _cloneDeep(getState().query); const searchApi = config.searchApi; const urlHandlerApi = config.urlHandlerApi; @@ -153,12 +178,17 @@ export const executeQuery = ({ dispatch({ type: RESULTS_LOADING }); try { const response = await searchApi.search(queryState); + if (shouldUpdateStateFromResponse) { + dispatch(updateQueryStateFromResponse(response.extras)); + } + dispatch({ type: RESULTS_FETCH_SUCCESS, payload: { aggregations: response.aggregations, hits: response.hits, total: response.total, + extras: response.extras, }, }); } catch (reason) { @@ -168,8 +198,8 @@ export const executeQuery = ({ }; }; -export const updateSuggestions = suggestionString => { - return dispatch => { +export const updateSuggestions = (suggestionString) => { + return (dispatch) => { dispatch({ type: SET_SUGGESTION_STRING, payload: suggestionString, @@ -198,7 +228,7 @@ export const executeSuggestionQuery = () => { }; export const clearSuggestions = () => { - return dispatch => { + return (dispatch) => { dispatch({ type: CLEAR_QUERY_SUGGESTIONS, payload: { @@ -208,8 +238,8 @@ export const clearSuggestions = () => { }; }; -export const updateQueryState = queryState => { - return dispatch => { +export const updateQueryState = (queryState) => { + return (dispatch) => { dispatch({ type: SET_QUERY_STATE, payload: queryState, diff --git a/src/lib/state/reducers/results.js b/src/lib/state/reducers/results.js index eff7993e..c3216461 100644 --- a/src/lib/state/reducers/results.js +++ b/src/lib/state/reducers/results.js @@ -30,6 +30,7 @@ export default (state = {}, action) => { aggregations: action.payload.aggregations, hits: action.payload.hits, total: action.payload.total, + extras: action.payload.extras, }, error: {}, }; diff --git a/src/lib/state/selectors/query.js b/src/lib/state/selectors/query.js index cd7b42da..995b717f 100644 --- a/src/lib/state/selectors/query.js +++ b/src/lib/state/selectors/query.js @@ -48,7 +48,7 @@ function updateFilter(queryFilter, stateFilters) { * convert query and state to strings so they can be compared */ const strQuery = toString(queryFilter); - const strStateFilters = stateFilters.map(stateObjQuery => + const strStateFilters = stateFilters.map((stateObjQuery) => toString(stateObjQuery) ); @@ -67,7 +67,7 @@ function updateFilter(queryFilter, stateFilters) { * filtered = [] */ let anyRemoved = false; - const filteredStrStates = strStateFilters.filter(strStateFilter => { + const filteredStrStates = strStateFilters.filter((strStateFilter) => { const childFilterExists = startsWith(strStateFilter, strQuery); const parentFilterExists = startsWith(strQuery, strStateFilter); @@ -103,7 +103,7 @@ function updateFilter(queryFilter, stateFilters) { /** * convert back to lists */ - return filteredStrStates.map(strState => parse(strState)); + return filteredStrStates.map((strState) => parse(strState)); } export const updateQueryFilters = (queryFilter, stateFilters) => { @@ -120,14 +120,14 @@ export const updateQueryFilters = (queryFilter, stateFilters) => { let tempStateFilters = stateFilters; _forEach( queryFilter, - filter => (tempStateFilters = updateFilter(filter, tempStateFilters)) + (filter) => (tempStateFilters = updateFilter(filter, tempStateFilters)) ); return tempStateFilters; }; export const updateQueryState = (oldState, newState, storeKeys) => { let pickedState = _pick(newState, storeKeys); - if ('filters' in pickedState) { + if ('filters' in pickedState && !_isEmpty(pickedState.filters)) { pickedState['filters'] = updateQueryFilters( pickedState.filters, oldState.filters diff --git a/src/lib/store.js b/src/lib/store.js index eb0e90b1..ceceacb9 100644 --- a/src/lib/store.js +++ b/src/lib/store.js @@ -15,7 +15,7 @@ import { INITIAL_STORE_STATE } from './storeConfig'; export function configureStore(appConfig) { const initialQueryState = { - ...INITIAL_STORE_STATE, + ...INITIAL_STORE_STATE, ...appConfig.initialQueryState, }; @@ -25,6 +25,7 @@ export function configureStore(appConfig) { hits: [], total: 0, aggregations: {}, + extras: {}, }, error: {}, };