Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1493: Improve faceting and add facet filtering #1496

Merged
merged 6 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading