diff --git a/geonode_mapstore_client/client/js/actions/gnsearch.js b/geonode_mapstore_client/client/js/actions/gnsearch.js
index 1d59545335..6d8e056d90 100644
--- a/geonode_mapstore_client/client/js/actions/gnsearch.js
+++ b/geonode_mapstore_client/client/js/actions/gnsearch.js
@@ -19,6 +19,8 @@ export const INCREASE_TOTAL_COUNT = 'GEONODE_INCREASE_TOTAL_COUNT';
export const SET_SEARCH_CONFIG = 'GEONODE_SET_SEARCH_CONFIG';
export const GET_FACET_ITEMS = 'GEONODE:GET_FACET_ITEMS';
export const SET_FACET_ITEMS = 'GEONODE:SET_FACET_ITEMS';
+export const GET_FACET_FILTERS = 'GEONODE:GET_FACET_FILTERS';
+export const SET_FILTERS = "SET_FILTERS";
/**
* Actions for GeoNode resource featured items
@@ -112,9 +114,10 @@ export function setSearchConfig(config) {
}
-export function getFacetItems() {
+export function getFacetItems(query) {
return {
- type: GET_FACET_ITEMS
+ type: GET_FACET_ITEMS,
+ query
};
}
export function setFacetItems(facetItems) {
@@ -123,6 +126,18 @@ export function setFacetItems(facetItems) {
facetItems
};
}
+export function getFacetFilters(facets) {
+ return {
+ type: GET_FACET_FILTERS,
+ facets
+ };
+}
+
+export function setFilters(filters) {
+ return {type: SET_FILTERS,
+ filters
+ };
+}
export default {
SEARCH_RESOURCES,
diff --git a/geonode_mapstore_client/client/js/api/geonode/v2/index.js b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
index 6610906147..61b7762cab 100644
--- a/geonode_mapstore_client/client/js/api/geonode/v2/index.js
+++ b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
@@ -20,9 +20,12 @@ import isArray from 'lodash/isArray';
import isString from 'lodash/isString';
import isObject from 'lodash/isObject';
import castArray from 'lodash/castArray';
+import omit from 'lodash/omit';
import get from 'lodash/get';
+import pick from 'lodash/pick';
+import isEmpty from 'lodash/isEmpty';
+import isNil from 'lodash/isNil';
import { getUserInfo } from '@js/api/geonode/user';
-import { setFilterById } from '@js/utils/SearchUtils';
import { ResourceTypes, availableResourceTypes, setAvailableResourceTypes } from '@js/utils/ResourceUtils';
import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils';
import { mergeConfigsPatch } from '@mapstore/patcher';
@@ -59,20 +62,12 @@ const MAPS = 'maps';
const GEOAPPS = 'geoapps';
const USERS = 'users';
const RESOURCE_TYPES = 'resource_types';
-const OWNERS = 'owners';
-const REGIONS = 'regions';
-const CATEGORIES = 'categories';
-const KEYWORDS = 'keywords';
const GROUPS = 'groups';
const UPLOADS = 'uploads';
const STATUS = 'status';
const EXECUTIONREQUEST = 'exectionRequest';
const FACETS = 'facets';
-function addCountToLabel(name, count) {
- return `${name} (${count || 0})`;
-}
-
export const setEndpoints = (data) => {
endpoints = { ...endpoints, ...data };
};
@@ -118,7 +113,14 @@ function mergeCustomQuery(params, customQuery) {
}
return params;
}
-
+export const getQueryParams = (params, customFilters) => {
+ const customQuery = customFilters
+ .filter(({ id }) => castArray(params?.f ?? []).indexOf(id) !== -1)
+ .reduce((acc, filter) => mergeCustomQuery(acc, filter.query || {}), {}) || {};
+ return {
+ ...mergeCustomQuery(omit(params, "f"), customQuery)
+ };
+};
export const getResources = ({
q,
pageSize = 20,
@@ -128,13 +130,8 @@ export const getResources = ({
customFilters = [],
...params
}) => {
-
- const customQuery = customFilters
- .filter(({ id }) => castArray(f || []).indexOf(id) !== -1)
- .reduce((acc, filter) => mergeCustomQuery(acc, filter.query || {}), {}) || {};
- const _mergeCustomQueryParams = mergeCustomQuery(params, customQuery);
const _params = {
- ..._mergeCustomQueryParams,
+ ...getQueryParams({...params, f}, customFilters),
...(q && {
search: q,
search_fields: ['title', 'abstract']
@@ -618,140 +615,6 @@ export const getFeaturedResources = (page = 1, page_size = 4) => {
}).then(({data}) => data);
};
-export const getCategories = ({ q, includes, page, pageSize, config, ...params }, filterKey = 'categories') => {
- return axios.get(parseDevHostname(`${endpoints[CATEGORIES]}`), {
- ...config,
- params: {
- page_size: pageSize || 9999,
- page,
- ...params,
- ...(includes && {'filter{identifier.in}': includes}),
- ...(q && { 'filter{identifier.icontains}': q }),
- with_resources: "True"
- }
- })
- .then(({ data }) => {
- const results = (data?.categories || [])
- .map((result) => {
- const selectOption = {
- value: result.identifier,
- label: addCountToLabel(result.gn_description || result.gn_description_en, result.count)
- };
- const category = {
- ...result,
- selectOption
- };
- setFilterById(filterKey + result.identifier, category);
- return category;
- });
- return {
- results,
- total: data.total,
- isNextPageAvailable: !!data.links.next
- };
- });
-};
-
-export const getRegions = ({ q, includes, page, pageSize, config, ...params }, filterKey = 'regions') => {
- return axios.get(parseDevHostname(`${endpoints[REGIONS]}`), {
- ...config,
- params: {
- page_size: pageSize || 9999,
- page,
- ...params,
- ...(includes && {'filter{name.in}': includes}),
- ...(q && { 'filter{name.icontains}': q }),
- with_resources: "True"
- }
- })
- .then(({ data }) => {
- const results = (data?.regions || [])
- .map((result) => {
- const selectOption = {
- value: result.name,
- label: addCountToLabel(result.name, result.count)
- };
- const region = {
- ...result,
- selectOption
- };
- setFilterById(filterKey + result.name, region);
- return region;
- });
- return {
- results,
- total: data.total,
- isNextPageAvailable: !!data.links.next
- };
- });
-};
-
-export const getOwners = ({ q, includes, page, pageSize, config, ...params }, filterKey = 'owners') => {
- return axios.get(parseDevHostname(`${endpoints[OWNERS]}`), {
- ...config,
- params: {
- page_size: pageSize || 9999,
- page,
- ...params,
- ...(includes && {'filter{username.in}': includes}),
- ...(q && { 'filter{username.icontains}': q })
- }
- })
- .then(({ data }) => {
- const results = (data?.owners || [])
- .map((result) => {
- const selectOption = {
- value: result.username,
- label: addCountToLabel(result.username, result.count)
- };
- const owner = {
- ...result,
- selectOption
- };
- setFilterById(filterKey + result.username, owner);
- return owner;
- });
- return {
- results,
- total: data.total,
- isNextPageAvailable: !!data.links.next
- };
- });
-};
-
-export const getKeywords = ({ q, includes, page, pageSize, config, ...params }, filterKey = 'keywords') => {
- return axios.get(parseDevHostname(`${endpoints[KEYWORDS]}`), {
- ...config,
- params: {
- page_size: pageSize || 9999,
- page,
- ...params,
- ...(includes && {'filter{slug.in}': includes}),
- ...(q && { 'filter{slug.icontains}': q })
- }
- })
- .then(({ data }) => {
- const results = (data?.keywords || [])
- .map((result) => {
- const selectOption = {
- value: result.slug,
- label: addCountToLabel(result.slug, result.count)
- };
- const keyword = {
- ...result,
- selectOption
- };
- setFilterById(filterKey + result.slug, keyword);
- return keyword;
- });
- return {
- results,
- total: data.total,
- isNextPageAvailable: !!data.links.next
- };
- });
-};
-
export const getCompactPermissionsByPk = (pk) => {
return axios.get(parseDevHostname(`${endpoints[RESOURCES]}/${pk}/permissions`))
.then(({ data }) => data);
@@ -902,37 +765,71 @@ export const getResourceByTypeAndByPk = (type, pk) => {
}
};
-export const getFacetItemsByFacetName = ({ name: facetName, style, filterKey }, { config, ...params }) => {
- return axios.get(`${parseDevHostname(endpoints[FACETS])}/${facetName}`, { ...config, params }).then(({data}) => {
- const {page: _page = 0, items = [], total, page_size: size} = data?.topics ?? {};
+export const getFacetItemsByFacetName = ({ name: facetName, style, filterKey, filters, setFilters}, { config, ...params }, customFilters) => {
+ const updatedParams = getQueryParams(params, customFilters);
+ return axios.get(`${parseDevHostname(endpoints[FACETS])}/${facetName}`,
+ { ...config,
+ params: updatedParams,
+ paramsSerializer
+ }
+ ).then(({data}) => {
+ const {page: _page = 0, items: _items = [], total, page_size: size} = data?.topics ?? {};
const page = Number(_page);
const isNextPageAvailable = (Math.ceil(Number(total) / Number(size)) - (page + 1)) !== 0;
- return {
- page,
- isNextPageAvailable,
- items: items.map(({label, is_localized: isLocalized, key, count} = {})=> {
- const item = {
+
+ // Add filter values as item even when count is 0
+ const filterKeys = Object.keys(updatedParams)?.map(key => `${key}${updatedParams[key]}`)?.filter(param => param?.includes(data?.filter));
+ const filtersPresent = Object.values(pick(filters, filterKeys));
+
+ const items = isEmpty(_items) && !isEmpty(filtersPresent)
+ ? filtersPresent.map(item => ({
+ ...(item.labelId ? {labelId: item.labelId} : {label: item.label}),
+ type: "filter",
+ count: 0,
+ filterKey: item.filterKey ?? filterKey,
+ filterValue: isNil(item.filterValue) ? String(item.key) : String(item.filterValue),
+ style
+ }))
+ : _items.map(({label, is_localized: isLocalized, key, count} = {})=> {
+ return {
type: "filter",
- ...(isLocalized ? { label } : { labelId: label }),
+ ...(!isNil(isLocalized) && !isLocalized ? { labelId: label } : { label }), // TODO remove when api send isLocalized for all facets response
count,
filterKey,
filterValue: String(key),
style
};
- setFilterById(filterKey + key, item);
- return item;
- })
+ });
+
+ // Update filters
+ setFilters(items.map((item) => ({[item.filterKey + item.filterValue]: item})).reduce((f, c) => ({...f, ...c}), {}));
+
+ return {
+ page,
+ isNextPageAvailable,
+ items
};
});
};
-export const getFacetItems = () => {
+export const getFacetsByKey = (facet, filterParams) => {
return axios
- .get(parseDevHostname(endpoints[FACETS]))
- .then(({ data } = {}) =>
+ .get(parseDevHostname(endpoints[FACETS] + `/${facet}`), {params: {...filterParams}, paramsSerializer})
+ .then(({ data } = {}) => data?.topics);
+};
+
+export const getFacetItems = (customFilters) => {
+ return axios
+ .get(parseDevHostname(endpoints[FACETS]),
+ {
+ params: {
+ include_config: true
+ }
+ }
+ ).then(({ data } = {}) =>
data?.facets?.map((facet) => ({
...facet,
- loadItems: getFacetItemsByFacetName
+ loadItems: (...args) => getFacetItemsByFacetName(...args, customFilters)
})) || []
).catch(() => []);
};
@@ -962,10 +859,6 @@ export default {
updateMap,
getMapByPk,
getMapsByPk,
- getCategories,
- getRegions,
- getOwners,
- getKeywords,
getCompactPermissionsByPk,
updateCompactPermissionsByPk,
deleteResource,
diff --git a/geonode_mapstore_client/client/js/components/Accordion/Accordion.jsx b/geonode_mapstore_client/client/js/components/Accordion/Accordion.jsx
index 5123a33d2a..20fbae4785 100644
--- a/geonode_mapstore_client/client/js/components/Accordion/Accordion.jsx
+++ b/geonode_mapstore_client/client/js/components/Accordion/Accordion.jsx
@@ -45,7 +45,8 @@ const Accordion = ({
identifier,
content,
loadItems,
- items
+ items,
+ query
}) => {
const isMounted = useIsMounted();
@@ -73,7 +74,7 @@ const Accordion = ({
.finally(()=> isMounted(() => setLoading(false)));
}
}
- }, [isExpanded]);
+ }, [isExpanded, query]);
return (
@@ -103,7 +104,8 @@ Accordion.propTypes = {
identifier: PropTypes.string,
content: PropTypes.func,
loadItems: PropTypes.func,
- items: PropTypes.array
+ items: PropTypes.array,
+ query: PropTypes.object
};
Accordion.defaultProps = {
diff --git a/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsInfo.jsx b/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsInfo.jsx
index c6075ddf29..6697c16863 100644
--- a/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsInfo.jsx
+++ b/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsInfo.jsx
@@ -86,7 +86,7 @@ function DetailsInfoFields({ fields, formatHref }) {
{(values) => values.map((value, idx) => {
return field.href
?
{value}
- :
{value.value}
+ :
{value.value};
})}
);
diff --git a/geonode_mapstore_client/client/js/components/FiltersForm/FilterItems.jsx b/geonode_mapstore_client/client/js/components/FiltersForm/FilterItems.jsx
index 482ce59819..4515f1892d 100644
--- a/geonode_mapstore_client/client/js/components/FiltersForm/FilterItems.jsx
+++ b/geonode_mapstore_client/client/js/components/FiltersForm/FilterItems.jsx
@@ -15,7 +15,6 @@ import ReactSelect from 'react-select';
import Accordion from "@js/components/Accordion";
import SelectInfiniteScroll from '@js/components/SelectInfiniteScroll';
-import { getFilterLabelById, getFilterById } from '@js/utils/SearchUtils';
import localizedProps from '@mapstore/framework/components/misc/enhancers/localizedProps';
import withDebounceOnCallback from '@mapstore/framework/components/misc/enhancers/withDebounceOnCallback';
import { getMessageById } from '@mapstore/framework/utils/LocaleUtils';
@@ -80,12 +79,12 @@ function ExtentFilterWithDebounce({
}
function FilterItem({
id,
- suggestionsRequestTypes,
values,
onChange,
extentProps,
timeDebounce,
- field
+ field,
+ filters
}, { messages }) {
@@ -128,6 +127,7 @@ function FilterItem({
const getLabelValue = (item) => item.labelId
? `${getMessageById(messages, item.labelId)} (${item.count})`
: `${item.label || ''} (${item.count})`;
+ const getFilterById = (value) => filters?.[filterKey + value];
return (
{field.labelId ? getMessageById(messages, field.labelId) : field.label}
{
- const selectedFilter = getFilterById(filterKey, value);
+ const selectedFilter = getFilterById(value);
return {
value,
label: selectedFilter ? getLabelValue(selectedFilter) : value
@@ -150,6 +150,7 @@ function FilterItem({
}}
loadOptions={({ q, ...params }) => field.loadItems({
...params,
+ ...values, // filter queries
...(q && { topic_contains: q }),
page: params.page - 1
})
@@ -176,29 +177,22 @@ function FilterItem({
label,
placeholderId,
description,
- options,
- suggestionsRequestKey
+ options
} = field;
- const key = `${id}-${formId || suggestionsRequestKey}`;
- const filterKey = suggestionsRequestKey
- ? suggestionsRequestTypes[suggestionsRequestKey]?.filterKey
- : `filter{${formId}.in}`;
+ const key = `${id}-${formId}`;
+ const filterKey = `filter{${formId}.in}`;
- const currentValues = castArray(suggestionsRequestKey
- ? values[suggestionsRequestTypes[suggestionsRequestKey]?.filterKey] || []
- : values[filterKey] || []);
+ const currentValues = values[filterKey] || [];
- const optionsProp = suggestionsRequestKey
- ? { loadOptions: suggestionsRequestTypes[suggestionsRequestKey]?.loadOptions }
- : { options: options.map(option => ({ value: option, label: option })) };
- const Select = suggestionsRequestKey ? SelectInfiniteScroll : SelectSync;
+ const optionsProp = { options: options?.map(option => ({ value: option, label: option })) };
+ const getFilterLabelById = (value) => filters?.[filterKey + value]?.selectOption?.label || filters?.[filterKey + value]?.label;
return (
-
@@ -304,6 +298,7 @@ function FilterItem({
value={getFilterValue(field)}
onChange={onChangeFilterParent}>
{field.labelId ? getMessageById(messages, field.labelId) : field.label}
+ {!isNil(field.count) && {`(${field.count})`}}
{filterChild()}
@@ -312,15 +307,15 @@ function FilterItem({
if (field.type === 'accordion' && !field.facet && field.id) {
const key = `${id}-${field.id}`;
return ( field.loadItems({...params, ...values})}
items={field.items}
content={(accordionItems) => (
)
@@ -336,21 +331,26 @@ FilterItem.contextTypes = {
function FilterItems({ items, ...props }) {
return items.map((field, idx) =>
-
+ field.loadItems(...args, props.filters, props.setFilters)
+ }}
+ />
);
}
FilterItems.defaultProps = {
id: PropTypes.string,
items: PropTypes.array,
- suggestionsRequestTypes: PropTypes.object,
values: PropTypes.object,
onChange: PropTypes.func
};
FilterItems.defaultProps = {
items: [],
- suggestionsRequestTypes: {},
values: {},
onChange: () => {}
};
diff --git a/geonode_mapstore_client/client/js/components/FiltersForm/FiltersForm.jsx b/geonode_mapstore_client/client/js/components/FiltersForm/FiltersForm.jsx
index db947be4e7..e1395858ac 100644
--- a/geonode_mapstore_client/client/js/components/FiltersForm/FiltersForm.jsx
+++ b/geonode_mapstore_client/client/js/components/FiltersForm/FiltersForm.jsx
@@ -21,11 +21,11 @@ import { Glyphicon } from 'react-bootstrap';
/**
* FilterForm component allows to configure a list of field that can be used to apply filter on the page
- * @name FilterForm
+ * @name FiltersForm
* @memberof components
* @prop {string} id the thumbnail is scaled based on the following configuration
*/
-function FilterForm({
+function FiltersForm({
id,
style,
styleContainerForm,
@@ -36,9 +36,10 @@ function FilterForm({
onClose,
onClear,
extentProps,
- suggestionsRequestTypes,
timeDebounce,
- onGetFacets
+ onGetFacets,
+ filters,
+ setFilters
}) {
const [fields, setFields] = useState([]);
@@ -47,14 +48,14 @@ function FilterForm({
if (!isEqual(fieldsProp, prevFieldsProp) || !isEqual(facets, prevFacets)) {
setPrevFieldsProp(fieldsProp);
setPrevFacets(facets);
- setFields(updateFilterFormItemsWithFacet(fieldsProp, facets));
+ setFields(updateFilterFormItemsWithFacet({formItems: fieldsProp, facetItems: facets}));
}
useEffect(() => {
- if (filterFormItemsContainFacet(fieldsProp) && fieldsProp && onGetFacets) {
- onGetFacets();
+ if (fieldsProp && onGetFacets && filterFormItemsContainFacet(fieldsProp) && isEmpty(facets)) {
+ onGetFacets(query);
}
- }, []);
+ }, [facets]);
const handleFieldChange = (newParam) => {
onChange(newParam);
@@ -92,10 +93,11 @@ function FilterForm({
@@ -104,7 +106,7 @@ function FilterForm({
);
}
-FilterForm.defaultProps = {
+FiltersForm.defaultProps = {
id: PropTypes.string,
style: PropTypes.object,
styleContainerForm: PropTypes.object,
@@ -114,20 +116,18 @@ FilterForm.defaultProps = {
onClose: PropTypes.func,
onClear: PropTypes.func,
extentProps: PropTypes.object,
- suggestionsRequestTypes: PropTypes.object,
submitOnChangeField: PropTypes.bool,
timeDebounce: PropTypes.number,
formParams: PropTypes.object
};
-FilterForm.defaultProps = {
+FiltersForm.defaultProps = {
query: {},
fields: [],
onChange: () => {},
onClose: () => {},
onClear: () => {},
- suggestionsRequestTypes: {},
submitOnChangeField: true,
timeDebounce: 500,
formParams: {}
@@ -136,8 +136,9 @@ FilterForm.defaultProps = {
const arePropsEqual = (prevProps, nextProps) => {
return isEqual(prevProps.query, nextProps.query)
&& isEqual(prevProps.fields, nextProps.fields)
- && isEqual(prevProps.facets, nextProps.facets);
+ && isEqual(prevProps.facets, nextProps.facets)
+ && isEqual(prevProps.filters, nextProps.filters);
};
-export default memo(FilterForm, arePropsEqual);
+export default memo(FiltersForm, arePropsEqual);
diff --git a/geonode_mapstore_client/client/js/components/FiltersForm/__tests__/FilterItems-test.jsx b/geonode_mapstore_client/client/js/components/FiltersForm/__tests__/FilterItems-test.jsx
index 92ab624053..d6446445f0 100644
--- a/geonode_mapstore_client/client/js/components/FiltersForm/__tests__/FilterItems-test.jsx
+++ b/geonode_mapstore_client/client/js/components/FiltersForm/__tests__/FilterItems-test.jsx
@@ -49,12 +49,10 @@ describe('FilterItems component', () => {
{
"labelId": "gnhome.resourceTypes",
"placeholderId": "gnhome.resourceTypesPlaceholder",
- "type": "select",
- "suggestionsRequestKey": "resourceTypes"
+ "type": "select"
}
];
- const suggestionsRequestTypes = { resourceTypes: { loadOptions: () => new Promise(resolve => resolve([])) }};
- ReactDOM.render( , document.getElementById("container"));
+ ReactDOM.render( , document.getElementById("container"));
const filterItemsSelectNode = document.querySelector('.Select');
expect(filterItemsSelectNode).toBeTruthy();
});
@@ -113,6 +111,7 @@ describe('FilterItems component', () => {
"type": "accordion",
"id": "accordion",
"labelId": "gnhome.accordion",
+ "loadItems": () => Promise.resolve(),
"items": [
{
"type": "filter",
@@ -141,6 +140,7 @@ describe('FilterItems component', () => {
"type": "accordion",
"id": "accordion",
"labelId": "gnhome.accordion",
+ "loadItems": () => Promise.resolve(),
"items": [
{
"type": "filter",
diff --git a/geonode_mapstore_client/client/js/epics/__tests__/gnsearch-test.js b/geonode_mapstore_client/client/js/epics/__tests__/gnsearch-test.js
index fcf197430c..1a78efae22 100644
--- a/geonode_mapstore_client/client/js/epics/__tests__/gnsearch-test.js
+++ b/geonode_mapstore_client/client/js/epics/__tests__/gnsearch-test.js
@@ -15,7 +15,7 @@ import {
} from '@js/epics/gnsearch';
import {
getFacetItems,
- SET_FACET_ITEMS
+ GET_FACET_FILTERS
} from '@js/actions/gnsearch';
let mockAxios;
@@ -47,7 +47,7 @@ describe('gnsearch epics', () => {
try {
expect(actions.map(({ type }) => type))
.toEqual([
- SET_FACET_ITEMS
+ GET_FACET_FILTERS
]);
} catch (e) {
done(e);
diff --git a/geonode_mapstore_client/client/js/epics/gnsearch.js b/geonode_mapstore_client/client/js/epics/gnsearch.js
index c246882036..b2187e5563 100644
--- a/geonode_mapstore_client/client/js/epics/gnsearch.js
+++ b/geonode_mapstore_client/client/js/epics/gnsearch.js
@@ -8,8 +8,10 @@
import { Observable } from 'rxjs';
import isEqual from 'lodash/isEqual';
+import isEmpty from 'lodash/isEmpty';
import isArray from 'lodash/isArray';
import isNil from 'lodash/isNil';
+import pick from 'lodash/pick';
import {
getResources,
getFeaturedResources,
@@ -27,7 +29,10 @@ import {
UPDATE_FEATURED_RESOURCES,
requestResource,
GET_FACET_ITEMS,
- setFacetItems
+ setFacetItems,
+ getFacetFilters,
+ GET_FACET_FILTERS,
+ setFilters
} from '@js/actions/gnsearch';
import {
resourceLoading,
@@ -54,6 +59,8 @@ import { getResourceData } from '@js/selectors/resource';
import uuid from 'uuid';
import { matchPath } from 'react-router-dom';
import { CATALOGUE_ROUTES } from '@js/utils/AppRoutesUtils';
+import { getFacetsByKey, getQueryParams } from '@js/api/geonode/v2/index';
+import { getFacetsItems } from '@js/selectors/search';
const UPDATE_RESOURCES_REQUEST = 'GEONODE_SEARCH:UPDATE_RESOURCES_REQUEST';
const updateResourcesRequest = (payload, reset) => ({
@@ -352,20 +359,59 @@ export const gnWatchStopCopyProcessOnSearch = (action$, store) =>
});
});
+const isKeyPresent = (filterKey, filterValue) =>
+ filterKey === (typeof filterKey === "string" ? filterValue : Number(filterValue));
+/**
+ * Set facet filter from topic items based on the query applied
+ */
+export const gnSetFacetFilter = (action$, {getState = () => {}}) =>
+ action$.ofType(GET_FACET_FILTERS, LOCATION_CHANGE)
+ .filter(({facets} = {}) => !isEmpty(facets) || getFacetsItems(getState()))
+ .switchMap(({facets: facetsItems} = {})=> {
+ const customFilters = getCustomMenuFilters(getState());
+ const location = getState()?.router?.location;
+ const { query } = url.parse((location?.search || ''), true);
+ const stateFacetItems = getFacetsItems(getState());
+
+ const facets = facetsItems || stateFacetItems;
+ const topicQuery = pick(query, Object.keys(query).filter(q => facets.map(f => f.filter).includes(q)));
+ const facetNames = facets
+ ?.filter(facet => topicQuery[facet.filter])
+ ?.map(facet => ({facet: facet.name, key: topicQuery[facet.filter]})) ?? [];
+ const queries = {...getQueryParams(query, customFilters), include_topics: true};
+
+ return Observable.forkJoin(
+ facetNames.map(({facet, key} = {}) => Observable.defer(() => getFacetsByKey(facet, {...queries, key})))
+ ).switchMap((topics) => {
+ let filters = {};
+ const updatedTopics = (topics ?? [])?.reduce((a, t) => t?.items?.concat(a), []);
+ const facetFilters = facets?.map((facet) => ({filter: facet.filter, value: query?.[facet.filter]}))?.filter(f => !isEmpty(f.value));
+
+ facetFilters.forEach(({filter, value} = {}) => {
+ updatedTopics?.forEach((item) => {
+ const itemObj = isKeyPresent(item.key, value) && item;
+ if (!isEmpty(itemObj)) {
+ filters[filter + itemObj.key] = {...itemObj, count: itemObj.count || 0};
+ }
+ });
+ });
+ return Observable.of(setFilters(filters));
+ }).concat(!isEmpty(stateFacetItems) ? Observable.empty() : Observable.of(setFacetItems(facets)));
+ });
+
/**
* Get facet filter items
*/
-export const gnGetFacetItems = (action$) =>
+export const gnGetFacetItems = (action$, {getState = () => {}}) =>
action$.ofType(GET_FACET_ITEMS)
- .switchMap(() =>
- Observable.defer(() =>
- getFacetItems()
- ).switchMap((facetItems) =>
- Observable.of(
- setFacetItems(facetItems)
- )
- )
- );
+ .switchMap(() => {
+ const customFilters = getCustomMenuFilters(getState());
+ return Observable.defer(() =>
+ getFacetItems(customFilters)
+ ).switchMap((facets = []) =>
+ Observable.of(getFacetFilters(facets))
+ );
+ });
export default {
gnsSearchResourcesEpic,
@@ -374,5 +420,6 @@ export default {
getFeaturedResourcesEpic,
gnWatchStopCopyProcessOnSearch,
gnsRequestResourceOnLocationChange,
- gnGetFacetItems
+ gnGetFacetItems,
+ gnSetFacetFilter
};
diff --git a/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx b/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx
index eadadeb57a..fc49cd1288 100644
--- a/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx
+++ b/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx
@@ -31,7 +31,7 @@ import { withResizeDetector } from 'react-resize-detector';
import { userSelector } from '@mapstore/framework/selectors/security';
import ConnectedCardGrid from '@js/plugins/resourcesgrid/ConnectedCardGrid';
import { getTotalResources, getFacetsItems } from '@js/selectors/search';
-import { searchResources, setSearchConfig, getFacetItems } from '@js/actions/gnsearch';
+import { searchResources, setSearchConfig, getFacetItems, setFilters as setFiltersAction } from '@js/actions/gnsearch';
import gnsearch from '@js/reducers/gnsearch';
import gnresource from '@js/reducers/gnresource';
@@ -46,7 +46,6 @@ import { processingDownload } from '@js/selectors/resourceservice';
import {resourceHasPermission} from '@js/utils/ResourceUtils';
import {downloadResource, setFavoriteResource} from '@js/actions/gnresource';
import FiltersForm from '@js/components/FiltersForm';
-import {getCategories, getRegions, getOwners, getKeywords} from '@js/api/geonode/v2';
import usePluginItems from '@js/hooks/usePluginItems';
import { ProcessTypes } from '@js/utils/ResourceServiceUtils';
import { replace } from 'connected-react-router';
@@ -56,25 +55,6 @@ import useLocalStorage from '@js/hooks/useLocalStorage';
import MainLoader from '@js/components/MainLoader';
import detailViewerEpics from '@js/epics/detailviewer';
-const suggestionsRequestTypes = {
- categories: {
- filterKey: 'filter{category.identifier.in}',
- loadOptions: params => getCategories(params, 'filter{category.identifier.in}')
- },
- keywords: {
- filterKey: 'filter{keywords.slug.in}',
- loadOptions: params => getKeywords(params, 'filter{keywords.slug.in}')
- },
- regions: {
- filterKey: 'filter{regions.name.in}',
- loadOptions: params => getRegions(params, 'filter{regions.name.in}')
- },
- owners: {
- filterKey: 'filter{owner.username.in}',
- loadOptions: params => getOwners(params, 'filter{owner.username.in}')
- }
-};
-
const ConnectedDetailsPanel = connect(
createSelector([
state => state?.gnresource?.loading || false,
@@ -392,32 +372,24 @@ function ResourcesGrid({
disableIf: '{!state("user")}'
},
{
- labelId: 'gnhome.categories',
- placeholderId: 'gnhome.categoriesPlaceholder',
type: 'select',
- suggestionsRequestKey: 'categories'
+ facet: "category"
},
{
- labelId: 'gnhome.keywords',
- placeholderId: 'gnhome.keywordsPlaceholder',
type: 'select',
- suggestionsRequestKey: 'keywords'
+ facet: "keyword"
},
{
- labelId: 'gnhome.regions',
- placeholderId: 'gnhome.regionsPlaceholder',
type: 'select',
- suggestionsRequestKey: 'regions'
+ facet: 'place'
},
{
- labelId: 'gnhome.owners',
- placeholderId: 'gnhome.ownersPlaceholder',
type: 'select',
- suggestionsRequestKey: 'owners'
+ facet: 'user'
},
{
type: "accordion",
- style: "facet",
+ style: "facet", // style can be facet or filter (checkbox)
facet: "thesaurus"
},
{
@@ -461,7 +433,9 @@ function ResourcesGrid({
enableGeoNodeCardsMenuItems,
detailsTabs = [],
onGetFacets,
- facets
+ facets,
+ filters,
+ setFilters
}, context) {
const [_cardLayoutStyleState, setCardLayoutStyle] = useLocalStorage('layoutCardsStyle', defaultCardLayoutStyle);
@@ -661,12 +635,13 @@ function ResourcesGrid({
fields={parsedConfig.filtersFormItems}
facets={facets}
extentProps={parsedConfig.extent}
- suggestionsRequestTypes={suggestionsRequestTypes}
query={query}
onChange={handleUpdate}
onClose={handleShowFilterForm.bind(null, false)}
onClear={handleClear}
onGetFacets={onGetFacets}
+ filters={filters}
+ setFilters={setFilters}
/>}
@@ -812,8 +787,9 @@ const ResourcesGridPlugin = connect(
state => state?.gnresource?.data || null,
state => getMonitoredState(state, getConfigProp('monitorState')),
state => state?.gnsearch?.error,
- getFacetsItems
- ], (params, user, totalResources, loading, location, resource, monitoredState, error, facets) => ({
+ getFacetsItems,
+ state => state?.gnsearch?.filters
+ ], (params, user, totalResources, loading, location, resource, monitoredState, error, facets, filters) => ({
params,
user,
totalResources,
@@ -822,13 +798,15 @@ const ResourcesGridPlugin = connect(
resource,
monitoredState,
error,
- facets
+ facets,
+ filters
})),
{
onSearch: searchResources,
onInit: setSearchConfig,
onReplaceLocation: replace,
- onGetFacets: getFacetItems
+ onGetFacets: getFacetItems,
+ setFilters: setFiltersAction
}
)(withResizeDetector(ResourcesGrid));
diff --git a/geonode_mapstore_client/client/js/reducers/gnsearch.js b/geonode_mapstore_client/client/js/reducers/gnsearch.js
index 2138a20611..112fbf968a 100644
--- a/geonode_mapstore_client/client/js/reducers/gnsearch.js
+++ b/geonode_mapstore_client/client/js/reducers/gnsearch.js
@@ -17,7 +17,8 @@ import {
REDUCE_TOTAL_COUNT,
INCREASE_TOTAL_COUNT,
SET_SEARCH_CONFIG,
- SET_FACET_ITEMS
+ SET_FACET_ITEMS,
+ SET_FILTERS
} from '@js/actions/gnsearch';
import { UPDATE_SINGLE_RESOURCE } from '@js/actions/gnresource';
@@ -126,6 +127,11 @@ function gnsearch(state = defaultState, action) {
...state,
facetItems: action.facetItems
};
+ case SET_FILTERS:
+ return {
+ ...state,
+ filters: {...state.filters, ...action.filters}
+ };
default:
return state;
}
diff --git a/geonode_mapstore_client/client/js/utils/SearchUtils.js b/geonode_mapstore_client/client/js/utils/SearchUtils.js
index c5d21d71a1..94159b6815 100644
--- a/geonode_mapstore_client/client/js/utils/SearchUtils.js
+++ b/geonode_mapstore_client/client/js/utils/SearchUtils.js
@@ -11,14 +11,6 @@ import castArray from 'lodash/castArray';
import omit from 'lodash/omit';
import uuid from 'uuid/v1';
-let filters = {};
-
-export const setFilterById = (id, value) => {
- filters[id] = value;
-};
-export const getFilterLabelById = (filterKey = '', id) => filters?.[filterKey + id]?.selectOption?.label || filters?.[filterKey + id]?.label;
-export const getFilterById = (filterKey = '', id) => filters?.[filterKey + id];
-
export const hashLocationToHref = ({
location,
pathname,
@@ -85,7 +77,7 @@ export const filterFormItemsContainFacet = (formItems) => {
return formItems.some(formItem => formItem.items ? filterFormItemsContainFacet(formItem.items) : !!formItem.facet);
};
-export const updateFilterFormItemsWithFacet = (formItems, facetItems) => {
+export const updateFilterFormItemsWithFacet = ({formItems, facetItems}) => {
return formItems.reduce((acc, formItem) => {
if (!!formItem.facet) {
const filteredFacetItems = (facetItems || [])
@@ -94,16 +86,18 @@ export const updateFilterFormItemsWithFacet = (formItems, facetItems) => {
return [
...acc,
...filteredFacetItems
- .map(({ name, key, label, is_localized: isLocalized, loadItems } = {}) => {
+ .map(({ name, key, config, filter: filterKey, label, is_localized: isLocalized, loadItems } = {}) => {
+ const style = config.style || formItem.style;
+ const type = config.type || formItem.type;
return {
uuid: uuid(),
name,
key,
id: name,
- type: formItem.type,
- style: formItem.style,
+ type,
+ style,
...(isLocalized ? { labelId: label } : { label }),
- loadItems: (params) => loadItems({ name, style: formItem.style, filterKey: key }, params)
+ loadItems: (params, filters, setFilters) => loadItems({ name, style, filterKey, filters, setFilters }, params)
};
})
];
@@ -114,7 +108,7 @@ export const updateFilterFormItemsWithFacet = (formItems, facetItems) => {
{
...formItem,
uuid: formItem.uuid || uuid(),
- items: updateFilterFormItemsWithFacet(formItem.items, facetItems)
+ items: updateFilterFormItemsWithFacet({formItems: formItem.items, facetItems})
}
];
}
diff --git a/geonode_mapstore_client/client/js/utils/__tests__/SearchUtils-test.js b/geonode_mapstore_client/client/js/utils/__tests__/SearchUtils-test.js
index e77959be47..f26bfb789c 100644
--- a/geonode_mapstore_client/client/js/utils/__tests__/SearchUtils-test.js
+++ b/geonode_mapstore_client/client/js/utils/__tests__/SearchUtils-test.js
@@ -22,18 +22,18 @@ describe('Test Resource Utils', () => {
describe('Test updateFilterFormItemsWithFacet', () => {
it('test with no facet item in filter form items', () => {
const formItems = [{name: "1"}, {name: "2"}];
- const items = updateFilterFormItemsWithFacet(formItems);
+ const items = updateFilterFormItemsWithFacet({formItems});
expect(items[0].name).toEqual(formItems[0].name);
expect(items[1].name).toEqual(formItems[1].name);
});
it('test with facet item and filter form items', () => {
const formItems = [{name: "1"}, {style: "facet", type: "accordion", facet: "thesaurus"}];
- const facetItems = {name: "some-name", key: "filterkey", label: "label1", type: "thesaurus"};
- const items = updateFilterFormItemsWithFacet(formItems, [facetItems]);
+ const facetItems = [{name: "some-name", key: "filterkey", label: "label1", type: "thesaurus", config: {style: "facet"}}];
+ const items = updateFilterFormItemsWithFacet({formItems, facetItems});
expect(items.length).toBe(2);
- expect(items[1].name).toBe(facetItems.name);
- expect(items[1].key).toBe(facetItems.key);
- expect(items[1].id).toBe(facetItems.name);
+ expect(items[1].name).toBe(facetItems[0].name);
+ expect(items[1].key).toBe(facetItems[0].key);
+ expect(items[1].id).toBe(facetItems[0].name);
expect(items[1].type).toBe("accordion");
expect(items[1].style).toBe("facet");
expect(items[1].label).toBe("label1");
@@ -41,18 +41,19 @@ describe('Test Resource Utils', () => {
});
it('test with facet item by no matching facet', () => {
const formItems = [{name: "1"}, {style: "facet", type: "accordion", facet: "thesaurus"}];
- const facetItems = {name: "some-name", key: "filterkey", label: "label1", type: "owner"};
- const items = updateFilterFormItemsWithFacet(formItems, [facetItems]);
+ const facetItems = [{name: "some-name", key: "filterkey", label: "label1", type: "owner", config: {style: "facet"}}];
+ const items = updateFilterFormItemsWithFacet({formItems, facetItems});
expect(items.length).toBe(1);
expect(items[0].name).toEqual(formItems[0].name);
});
it('test with nested facet item', () => {
+ const config = {style: "facet"};
const formItems = [{ type: "group", items: [{style: "facet", type: "accordion", facet: "thesaurus"}] }];
const facetItems = [
- {name: "some-name", key: "filterkey", label: "label1", type: "thesaurus"},
- {name: "some-name-2", key: "filterkey", label: "label2", type: "thesaurus"}
+ {name: "some-name", key: "filterkey", label: "label1", type: "thesaurus", config},
+ {name: "some-name-2", key: "filterkey", label: "label2", type: "thesaurus", config}
];
- const items = updateFilterFormItemsWithFacet(formItems, facetItems);
+ const items = updateFilterFormItemsWithFacet({formItems, facetItems});
expect(items.length).toBe(1);
expect(items[0].items.length).toBe(2);
});
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_filter-form.less b/geonode_mapstore_client/client/themes/geonode/less/_filter-form.less
index 87a6ef50ed..546e6167ed 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_filter-form.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_filter-form.less
@@ -256,6 +256,15 @@
overflow-y: auto;
.form-group {
padding-top: 0.6rem;
+ .checkbox {
+ label {
+ display: flex;
+ justify-content: space-between;
+ .facet-count {
+ margin-right: 0.625rem;
+ }
+ }
+ }
}
.gn-facet-wrapper {
padding: 0.25rem 0;