diff --git a/web/client/plugins/ResourcesCatalog/ResourceDetails.jsx b/web/client/plugins/ResourcesCatalog/ResourceDetails.jsx index c69ba78654..5484f0513a 100644 --- a/web/client/plugins/ResourcesCatalog/ResourceDetails.jsx +++ b/web/client/plugins/ResourcesCatalog/ResourceDetails.jsx @@ -127,7 +127,7 @@ function ResourceDetails({ "type": "boolean", "labelId": "resourcesCatalog.columnFeatured", "path": "attributes.featured", - "disableIf": "{!state('resourceCanEdit')}", + "disableIf": "{state('userrole') !== 'ADMIN'}", "editable": true } ] diff --git a/web/client/plugins/ResourcesCatalog/ResourcesFiltersForm.jsx b/web/client/plugins/ResourcesCatalog/ResourcesFiltersForm.jsx index 3506cf46bd..cd8847733d 100644 --- a/web/client/plugins/ResourcesCatalog/ResourcesFiltersForm.jsx +++ b/web/client/plugins/ResourcesCatalog/ResourcesFiltersForm.jsx @@ -81,14 +81,18 @@ function ResourcesFiltersForm({ { id: 'context', labelId: 'resourcesCatalog.contextsFilter', - type: 'filter', - disableIf: '{state("userrole") !== "ADMIN"}' + type: 'filter' } ] }, { type: 'divider' }, + { + type: 'select', + facet: "group", + disableIf: '{!state("userrole")}' + }, { type: 'select', facet: "context" diff --git a/web/client/plugins/ResourcesCatalog/api/__tests__/resources-test.js b/web/client/plugins/ResourcesCatalog/api/__tests__/resources-test.js index 2411bbe28d..18766dadb8 100644 --- a/web/client/plugins/ResourcesCatalog/api/__tests__/resources-test.js +++ b/web/client/plugins/ResourcesCatalog/api/__tests__/resources-test.js @@ -43,6 +43,9 @@ describe('resources api', () => { }, { "CATEGORY": { "operator": "EQUAL_TO", "name": "GEOSTORY" } + }, + { + "CATEGORY": { "operator": "EQUAL_TO", "name": "CONTEXT" } } ] } @@ -80,16 +83,62 @@ describe('resources api', () => { }); expect(json).toEqual({ "AND": { - "FIELD": { "field": "NAME", "operator": "ILIKE", "value": "%A%" }, + "FIELD": [ + { + "field": "NAME", + "operator": "ILIKE", + "value": "%A%" + }, + { + "field": "CREATION", + "operator": "GREATER_THAN_OR_EQUAL_TO", + "value": "2025-01-22T00:00:00" + }, + { + "field": "CREATION", + "operator": "LESS_THAN_OR_EQUAL_TO", + "value": "2025-01-24T23:59:59" + }, + { + "field": "LASTUPDATE", + "operator": "GREATER_THAN_OR_EQUAL_TO", + "value": "2025-01-22T00:00:00" + }, + { + "field": "LASTUPDATE", + "operator": "LESS_THAN_OR_EQUAL_TO", + "value": "2025-01-24T23:59:59" + } + ], "ATTRIBUTE": { "name": "featured", "operator": "EQUAL_TO", "type": "STRING", "value": "true" }, - "OR": { - "AND": { - "CATEGORY": { "operator": "EQUAL_TO", "name": "MAP" }, - "OR": { - "ATTRIBUTE": { "name": "context", "operator": "EQUAL_TO", "type": "STRING", "value": "contextName" } + "GROUP": { + "operator": 'IN', + "names": "group01" + }, + "OR": [ + { + "AND": { + "CATEGORY": { "operator": "EQUAL_TO", "name": "MAP" }, + "OR": { + "ATTRIBUTE": { "name": "context", "operator": "EQUAL_TO", "type": "STRING", "value": "contextName" } + } } + }, + { + "FIELD": [ + { + "field": "CREATOR", + "operator": "EQUAL_TO", + "value": "admin" + }, + { + "field": "CREATOR", + "operator": "EQUAL_TO", + "value": "creator" + } + ] } - } + ] } }); } catch (e) { @@ -104,13 +153,19 @@ describe('resources api', () => { }); requestResources({ params: { - page: 2, - pageSize: 24, - f: ['map', 'featured'], - q: 'A', - 'filter{ctx.in}': ['contextName'] + 'page': 2, + 'pageSize': 24, + 'f': ['map', 'featured', 'my-resources'], + 'q': 'A', + 'filter{ctx.in}': ['contextName'], + 'filter{group.in}': ['group01'], + 'filter{creator.in}': ['creator'], + 'filter{creation.gte}': '2025-01-22T00:00:00', + 'filter{creation.lte}': '2025-01-24T23:59:59', + 'filter{lastUpdate.gte}': '2025-01-22T00:00:00', + 'filter{lastUpdate.lte}': '2025-01-24T23:59:59' } - }) + }, { user: { name: 'admin' } }) .then((response) => { expect(response).toEqual({ total: 0, diff --git a/web/client/plugins/ResourcesCatalog/api/resources.js b/web/client/plugins/ResourcesCatalog/api/resources.js index 5e13aaca47..d9a95b726e 100644 --- a/web/client/plugins/ResourcesCatalog/api/resources.js +++ b/web/client/plugins/ResourcesCatalog/api/resources.js @@ -9,6 +9,7 @@ import { searchListByAttributes, getResource } from '../../../observables/geostore'; import { castArray } from 'lodash'; import isString from 'lodash/isString'; +import GeoStoreDAO from '../../../api/GeoStoreDAO'; const splitFilterValue = (value) => { const parts = value.split(':'); @@ -29,6 +30,7 @@ const getFilter = ({ const ctx = castArray(query['filter{ctx.in}'] || []); const categories = ['MAP', 'DASHBOARD', 'GEOSTORY', 'CONTEXT']; const creators = castArray(query['filter{creator.in}'] || []); + const groups = castArray(query['filter{group.in}'] || []); const categoriesFilters = categories.filter(category => f.includes(category.toLocaleLowerCase())); const associatedContextFilters = ctx.map((ctxValue) => { const { value } = splitFilterValue(ctxValue); @@ -76,10 +78,17 @@ const getFilter = ({ value: [true] }] : []) ], + ...(groups.length && { + GROUP: [ + { + operator: ['IN'], + names: groups + } + ] + }), OR: [ { AND: categories - .filter(name => name !== 'CONTEXT' || name === 'CONTEXT' && user?.role === 'ADMIN') .map(name => { return ( !categoriesFilters.length && !associatedContextFilters.length @@ -279,6 +288,44 @@ export const facets = [ }; }); } + }, + { + id: 'group', + type: 'select', + labelId: 'resourcesCatalog.groups', + key: 'filter{group.in}', + getLabelValue: (item) => { + return item.value; + }, + getFilterByField: (field, value) => { + return { label: value, value }; + }, + loadItems: ({ params, config }) => { + const { page, pageSize, q } = params; + return GeoStoreDAO.getGroups(q ? `*${q}*` : '*', { + ...config, + params: { + start: parseFloat(page) * pageSize, + limit: pageSize, + all: true + } + }) + .then((response) => { + const groups = castArray(response?.ExtGroupList?.Group).map((item) => { + return { + ...item, + filterValue: item.groupName, + value: item.groupName, + label: `${item.groupName}` + }; + }); + const totalCount = response?.ExtGroupList?.GroupCount; + return { + items: groups, + isNextPageAvailable: (page + 1) < (totalCount / pageSize) + }; + }); + } } ]; diff --git a/web/client/plugins/ResourcesCatalog/components/FilterGroup.jsx b/web/client/plugins/ResourcesCatalog/components/FilterGroup.jsx index 107ae9e15a..0669d61b88 100644 --- a/web/client/plugins/ResourcesCatalog/components/FilterGroup.jsx +++ b/web/client/plugins/ResourcesCatalog/components/FilterGroup.jsx @@ -31,7 +31,7 @@ const Title = ({ }; const FilterGroup = ({ - items, + items: itemsProp, loadItems, title, titleId, @@ -43,11 +43,13 @@ const FilterGroup = ({ }) => { const isMounted = useIsMounted(); - const [groupItems, setGroupItems] = useState(items); + const [groupItems, setGroupItems] = useState(itemsProp); const [loading, setLoading] = useState(false); + const shouldRequestItems = loadItems && typeof loadItems === 'function'; + useEffect(() => { - if (loadItems && typeof loadItems === 'function') { + if (shouldRequestItems) { if (!loading) { setLoading(true); loadItems({ page_size: 999999 }) @@ -57,7 +59,10 @@ const FilterGroup = ({ .finally(()=> isMounted(() => setLoading(false))); } } - }, [JSON.stringify(query)]); + }, [JSON.stringify(query), shouldRequestItems]); + + // avoid to use groupItems when not async to get the latest updated items + const items = shouldRequestItems ? groupItems : itemsProp; return ( @@ -69,7 +74,7 @@ const FilterGroup = ({ {loading ? - : !isEmpty(groupItems) ? content(groupItems) + : !isEmpty(items) ? content(items) : !loading ? : null } diff --git a/web/client/plugins/ResourcesCatalog/containers/ResourcePermissions.jsx b/web/client/plugins/ResourcesCatalog/containers/ResourcePermissions.jsx index 4827baee7c..bb5988f979 100644 --- a/web/client/plugins/ResourcesCatalog/containers/ResourcePermissions.jsx +++ b/web/client/plugins/ResourcesCatalog/containers/ResourcePermissions.jsx @@ -18,26 +18,18 @@ import Icon from '../components/Icon'; import Message from '../../../components/I18N/Message'; import useIsMounted from '../hooks/useIsMounted'; import Spinner from '../components/Spinner'; +import { castArray } from 'lodash'; function ResourcePermissions({ editing, - user, resource, onChange }) { - const [availableGroups, setGroups] = useState([]); const [loading, setLoading] = useState(false); const init = useRef(false); const isMounted = useIsMounted(); - useEffect(() => { - GeoStoreDAO.getAvailableGroups(user) - .then((response) => isMounted(() => { - setGroups(response); - })); - }, [user]); - useEffect(() => { if (resource?.permissions === undefined && !init.current) { init.current = true; @@ -128,11 +120,33 @@ function ResourcePermissions({ { id: 'group', labelId: 'resourcesCatalog.groups', - request: ({ q }) => { - return Promise.resolve(availableGroups.filter(group => (group?.groupName || '').toLowerCase().includes(q.toLowerCase()))); + request: ({ q, page: pageParam, pageSize }) => { + const page = pageParam - 1; + return GeoStoreDAO.getGroups(q ? `*${q}*` : '*', { + params: { + start: parseFloat(page) * pageSize, + limit: pageSize, + all: true + } + }) + .then((response) => { + const groups = castArray(response?.ExtGroupList?.Group).map((item) => { + return { + ...item, + filterValue: item.groupName, + value: item.groupName, + label: `${item.groupName}` + }; + }); + const totalCount = response?.ExtGroupList?.GroupCount; + return { + groups, + isNextPageAvailable: (page + 1) < (totalCount / pageSize) + }; + }); }, responseToEntries: ({ response, entries }) => { - return response.map((group) => { + return response.groups.map((group) => { const permissions = (entries || []).find(entry => entry.id === group.id)?.permissions; return { type: 'group', diff --git a/web/client/reducers/__tests__/context-test.js b/web/client/reducers/__tests__/context-test.js index 766e137b3e..ca71213e1e 100644 --- a/web/client/reducers/__tests__/context-test.js +++ b/web/client/reducers/__tests__/context-test.js @@ -28,7 +28,7 @@ describe('context reducer', () => { it('loadContext', () => { const CONTEXT = {}; const state = stateMocker(setContext(CONTEXT)); - expect(currentContextSelector(state)).toBe(CONTEXT); + expect(currentContextSelector(state)).toEqual(CONTEXT); }); it('setResource', () => { const RESOURCE = {}; diff --git a/web/client/selectors/__tests__/context-test.js b/web/client/selectors/__tests__/context-test.js index f442486505..f7b704d358 100644 --- a/web/client/selectors/__tests__/context-test.js +++ b/web/client/selectors/__tests__/context-test.js @@ -29,7 +29,7 @@ import CONTEXT_SHORT_RESOURCE from '../../test-resources/geostore/resources/reso const stateMocker = createStateMocker({context}); describe('context selectors', () => { it('currentContextSelector', () => { - expect(currentContextSelector(stateMocker(setContext(CONTEXT_DATA)))).toBe(CONTEXT_DATA); + expect(currentContextSelector(stateMocker(setContext(CONTEXT_DATA)))).toEqual(CONTEXT_DATA); }); it('contextMonitoredStateSelector', () => { expect(contextMonitoredStateSelector(stateMocker())).toBe('{}'); diff --git a/web/client/utils/ContextCreatorUtils.js b/web/client/utils/ContextCreatorUtils.js index 926e48adb0..c512b31be7 100644 --- a/web/client/utils/ContextCreatorUtils.js +++ b/web/client/utils/ContextCreatorUtils.js @@ -30,18 +30,20 @@ export const migrateContextConfiguration = (context) => { }; return { ...context, - plugins: Object.fromEntries(Object.keys(context.plugins) - .map((key) => { - const plugins = context.plugins[key]; - return [key, plugins.map((plugin) => { - if (changedPluginsNames[plugin.name]) { - return { - ...plugin, - name: changedPluginsNames[plugin.name] - }; - } - return plugin; - })]; - })) + ...(context?.plugins && { + plugins: Object.fromEntries(Object.keys(context.plugins) + .map((key) => { + const plugins = context.plugins[key]; + return [key, plugins.map((plugin) => { + if (changedPluginsNames[plugin.name]) { + return { + ...plugin, + name: changedPluginsNames[plugin.name] + }; + } + return plugin; + })]; + })) + }) }; };