Skip to content

Commit

Permalink
#1493: Improve faceting and add facet filtering (#1496)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsuren1 authored Jul 28, 2023
1 parent 596d6e3 commit e12195e
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 300 deletions.
19 changes: 17 additions & 2 deletions geonode_mapstore_client/client/js/actions/gnsearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand Down
233 changes: 63 additions & 170 deletions geonode_mapstore_client/client/js/api/geonode/v2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 };
};
Expand Down Expand Up @@ -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,
Expand All @@ -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']
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(() => []);
};
Expand Down Expand Up @@ -962,10 +859,6 @@ export default {
updateMap,
getMapByPk,
getMapsByPk,
getCategories,
getRegions,
getOwners,
getKeywords,
getCompactPermissionsByPk,
updateCompactPermissionsByPk,
deleteResource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ const Accordion = ({
identifier,
content,
loadItems,
items
items,
query
}) => {
const isMounted = useIsMounted();

Expand Down Expand Up @@ -73,7 +74,7 @@ const Accordion = ({
.finally(()=> isMounted(() => setLoading(false)));
}
}
}, [isExpanded]);
}, [isExpanded, query]);

return (
<div className={'gn-accordion'}>
Expand Down Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function DetailsInfoFields({ fields, formatHref }) {
{(values) => values.map((value, idx) => {
return field.href
? <a key={idx} href={field.href}>{value}</a>
: <a key={idx} href={value.href}>{value.value}</a>
: <a key={idx} href={value.href}>{value.value}</a>;
})}
</DetailsInfoField>
);
Expand Down
Loading

0 comments on commit e12195e

Please sign in to comment.