diff --git a/MapStore2 b/MapStore2 index 59ea05b935..d609157005 160000 --- a/MapStore2 +++ b/MapStore2 @@ -1 +1 @@ -Subproject commit 59ea05b9350d1ab245c338640a0937e5fabe4281 +Subproject commit d6091570052360eca994b8f4588b2450fd8bd78e diff --git a/configs/localConfig.json b/configs/localConfig.json index 5786848b94..effe1bd696 100644 --- a/configs/localConfig.json +++ b/configs/localConfig.json @@ -74,7 +74,7 @@ "path": "print.capabilities" }, { - "name": "usergroups", + "name": "usergroups", "path": "security.user.groups.group" } ], @@ -246,8 +246,32 @@ } } }, + "AutoResourceUpdateOptions": { + "urlsToReplace": { + "http://datigis.comune.fi.it/img": "https://datigis.comune.fi.it/img", + "http://geonetwork.comune.intranet/geonetwork": "https://geonetwork.comune.fi.it/geonetwork", + "http://geostories.comune.intranet/": "https://mapstore2.comune.fi.it", + "http://mappe.cittametropolitana.fi.it:443/geoserver": "https://mappe.cittametropolitana.fi.it/geoserver", + "http://opendata.comune.fi.it": "https://opendata.comune.fi.it", + "http://sr-vm378-sitgfn.comune.intranet:9080/geonetwork": "https://geonetwork.comune.fi.it/geonetwork", + "http://datigis.comune.fi.it/geonetwork": "https://geonetwork.comune.fi.it/geonetwork", + "http://geoserver.comune.intranet/geoserver": "https://datigis.comune.fi.it/geoserver", + "http://datigis.comune.intranet/geoserver": "https://datigis.comune.fi.it/geoserver", + "http://sr-vm380-sitgs1.comune.intranet:8080/geoserver": "https://datigis.comune.fi.it/geoserver", + "http://sr-vm380-sitgs1:8080/geoserver": "https://datigis.comune.fi.it/geoserver", + "http://datigis2.comune.intranet/geoserver": "https://datigis2.comune.fi.it/geoserver", + "http://tms.comune.fi.it/geowebcache": "https://tms.comune.fi.it/geowebcache", + "http://tms.comune.fi.it/tiles": "https://tms.comune.fi.it/tiles", + "http://tms.comune.intranet/geoserver": "https://datigis.comune.fi.it/geoserver/gwc/service", + "http://tms.comune.intranet/geowebcache_internal": "https://datigis.comune.fi.it/geoserver/gwc/service", + "http://3dtiles-cloud-cofi.comune.intranet": "https://3dtiles-cloud-cofi.comune.fi.it" + } + }, "plugins": { "mobile": [ + { + "name": "AutoResourceUpdate" + }, { "name": "Map", "cfg": { @@ -442,6 +466,9 @@ } }, "AutoMapUpdate", + { + "name": "AutoResourceUpdate" + }, "DrawerMenu", "Notifications", "BackgroundSelector", @@ -720,6 +747,9 @@ } ], "embedded": [ + { + "name": "AutoResourceUpdate" + }, "Details", { "name": "Map", @@ -933,6 +963,10 @@ "FeedbackMask" ], "dashboard": [ + + { + "name": "AutoResourceUpdate" + }, "Details", "AddWidgetDashboard", "MapConnectionDashboard", @@ -1078,6 +1112,9 @@ } ], "geostory-embedded": [ + { + "name": "AutoResourceUpdate" + }, "GeoStory", { "name": "GeoStoryNavigation", @@ -1093,6 +1130,9 @@ } ], "dashboard-embedded": [ + { + "name": "AutoResourceUpdate" + }, { "name": "Dashboard", "cfg": { @@ -1104,6 +1144,9 @@ } ], "geostory": [ + { + "name": "AutoResourceUpdate" + }, { "name": "OmniBar", "cfg": { diff --git a/js/actions/autoResourceUpdate.js b/js/actions/autoResourceUpdate.js new file mode 100644 index 0000000000..65b5fc639e --- /dev/null +++ b/js/actions/autoResourceUpdate.js @@ -0,0 +1,16 @@ +/* + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. +*/ + +export const START_UPDATING_RESOURCE = "AUTO_RESOURCE_UPDATE:START_UPDATING_RESOURCE"; + +export const startUpdatingResource = (options) => { + return { + type: START_UPDATING_RESOURCE, + options + }; +}; diff --git a/js/apiPlugins.js b/js/apiPlugins.js new file mode 100644 index 0000000000..b92a3ebafa --- /dev/null +++ b/js/apiPlugins.js @@ -0,0 +1,14 @@ + +import AutoResourceUpdate from './plugins/AutoResourceUpdate'; +import pluginsDef from '../MapStore2/web/client/product/apiPlugins'; +const { plugins, requires } = pluginsDef; + +export default { + plugins: { + ...plugins, + AutoResourceUpdatePlugin: AutoResourceUpdate + }, + requires: { + ...requires + } +}; diff --git a/js/apps/app.jsx b/js/apps/app.jsx index 0f2ce27d0f..9924687dd5 100644 --- a/js/apps/app.jsx +++ b/js/apps/app.jsx @@ -10,8 +10,9 @@ import { checkForMissingPlugins } from '@mapstore/utils/DebugUtils'; import main from '@mapstore/product/main'; import {setConfigProp, setLocalConfigurationFile} from '@mapstore/utils/ConfigUtils'; import appConfig from '@mapstore/product/appConfig'; -import plugins from '@mapstore/product/plugins'; +import plugins from '@js/plugins'; import '../../assets/css/mapstore2.css'; +import { manageAutoContextUpdateEpic } from '@js/epics/autoResourceUpdateContext'; /** * Add custom (overriding) translations with: @@ -52,6 +53,10 @@ setLocalConfigurationFile('configs/localConfig.json'); checkForMissingPlugins(plugins.plugins); +appConfig.appEpics = { + manageAutoContextUpdateEpic +}; + main({ ...appConfig, themeCfg: { diff --git a/js/apps/dashboardEmbedded.jsx b/js/apps/dashboardEmbedded.jsx index 661399bf57..36cd82c48d 100644 --- a/js/apps/dashboardEmbedded.jsx +++ b/js/apps/dashboardEmbedded.jsx @@ -13,7 +13,7 @@ import { import { loadVersion } from '@mapstore/actions/version'; import { triggerShowConnections } from '@mapstore/actions/dashboard'; import appConfigDashboardEmbedded from '@mapstore/product/appConfigDashboardEmbedded'; -import pluginsDashboardEmbedded from '@mapstore/product/pluginsDashboardEmbedded'; +import pluginsDashboardEmbedded from '@js/pluginsDashboardEmbedded'; import main from '@mapstore/product/main'; import url from 'url'; import '../../assets/css/mapstore2.css'; diff --git a/js/apps/embedded.jsx b/js/apps/embedded.jsx index 95b4f76499..db2ff53fdf 100644 --- a/js/apps/embedded.jsx +++ b/js/apps/embedded.jsx @@ -11,11 +11,11 @@ import { setLocalConfigurationFile } from '@mapstore/utils/ConfigUtils'; import appConfigEmbedded from '@mapstore/product/appConfigEmbedded'; -import apiPlugins from '@mapstore/product/apiPlugins'; import { loadVersion } from '@mapstore/actions/version'; import main from '@mapstore/product/main'; import '../../assets/css/mapstore2.css'; +import apiPlugins from '@js/apiPlugins'; /** * Add custom (overriding) translations with: * diff --git a/js/apps/geostoryEmbedded.jsx b/js/apps/geostoryEmbedded.jsx index 3d8b186375..1a8198fb63 100644 --- a/js/apps/geostoryEmbedded.jsx +++ b/js/apps/geostoryEmbedded.jsx @@ -11,7 +11,7 @@ import { setLocalConfigurationFile } from '@mapstore/utils/ConfigUtils'; import appConfigGeostoryEmbedded from '@mapstore/product/appConfigGeostoryEmbedded'; -import pluginsEmbedded from '@mapstore/product/pluginsGeostoryEmbedded'; +import pluginsEmbedded from '@js/pluginsGeostoryEmbedded'; import main from '@mapstore/product/main'; import { loadVersion } from '@mapstore/actions/version'; import '../../assets/css/mapstore2.css'; diff --git a/js/epics/autoResourceUpdate.js b/js/epics/autoResourceUpdate.js new file mode 100644 index 0000000000..2c84a11a65 --- /dev/null +++ b/js/epics/autoResourceUpdate.js @@ -0,0 +1,145 @@ +/* + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. +*/ +import Rx from 'rxjs'; +import { isEmpty } from 'lodash'; + +import { MAP_CONFIG_LOADED, MAP_INFO_LOADED } from '../../MapStore2/web/client/actions/config'; +import { warning } from '../../MapStore2/web/client/actions/notifications'; +import { updateCatalogServices } from '../../MapStore2/web/client/actions/catalog'; +import { replaceLayers } from '../../MapStore2/web/client/actions/layers'; +import { isUserAllowedSelectorCreator } from '../../MapStore2/web/client/selectors/security'; +import { DASHBOARD_LOADED, dashboardLoaded } from '../../MapStore2/web/client/actions/dashboard'; +import { SET_CURRENT_STORY, setCurrentStory } from '../../MapStore2/web/client/actions/geostory'; + +import { START_UPDATING_RESOURCE } from '@js/actions/autoResourceUpdate'; +import { getConfigProp } from '../../MapStore2/web/client/utils/ConfigUtils'; +import { migrateAllUrls } from '../utils/AutoResourceUpdateUtils'; + +/** + * Epics for update old map + * @name epics.autoresourceupdate + * @type {Object} + */ + + +/** + * When map has been loaded, it sends a notification if the version is less than 2 and users has write permission. + * @param {external:Observable} action$ manages `MAP_CONFIG_LOADED` and `MAP_INFO_LOADED`. + * @memberof epics.autoresourceupdate + * @return {external:Observable} + */ +export const manageAutoMapUpdateEpic = (action$, store) => + action$.ofType(START_UPDATING_RESOURCE) + .switchMap(() => + action$.ofType(MAP_CONFIG_LOADED) + .switchMap((mapConfigLoaded) => + action$.ofType(MAP_INFO_LOADED) + .take(1) + .switchMap(() => { + const state = store.getState(); + const options = getConfigProp('AutoResourceUpdateOptions'); + const showNotification = options?.showNotificationForRoles?.length && isUserAllowedSelectorCreator({ + allowedRoles: options.showNotificationForRoles + })(state); + const layers = mapConfigLoaded?.config?.map?.layers || []; + const newLayers = migrateAllUrls(layers, options.urlsToReplace); + const services = mapConfigLoaded?.config?.catalogServices?.services; + const newServices = migrateAllUrls(services, options.urlsToReplace); + let actions = []; + if (showNotification) { + // show only for allowed roles, do not show it by default + actions.push(warning({ + title: "notification.warning", + message: "notification.updateOldResource", + autoDismiss: options.autoDismiss, + position: "tc" + })); + } + actions.push(updateCatalogServices(newServices)); + actions.push(replaceLayers(newLayers)); + if (isEmpty(options)) { + return Rx.Observable.empty(); + } + return Rx.Observable.from(actions); + }) + )); + +/** + * When map has been loaded, it sends a notification if the version is less than 2 and users has write permission. + * @param {external:Observable} action$ manages `MAP_CONFIG_LOADED` and `MAP_INFO_LOADED`. + * @memberof epics.autoresourceupdate + * @return {external:Observable} + */ +export const manageAutoDashboardUpdateEpic = (action$, store) => + action$.ofType(START_UPDATING_RESOURCE) + .switchMap(() => + action$.ofType(DASHBOARD_LOADED) + .take(1) + .switchMap(({resource, data}) => { + const state = store.getState(); + const options = getConfigProp('AutoResourceUpdateOptions'); + const showNotification = options?.showNotificationForRoles?.length && isUserAllowedSelectorCreator({ + allowedRoles: options.showNotificationForRoles + })(state); + + const newData = migrateAllUrls(data, options.urlsToReplace); + let actions = []; + if (showNotification) { + // show only for allowed roles, do not show it by default + actions.push(warning({ + title: "notification.warning", + message: "notification.updateOldResource", + autoDismiss: options.autoDismiss, + position: "tc" + })); + } + actions.push(dashboardLoaded(resource, newData)); + if (isEmpty(options)) { + return Rx.Observable.empty(); + } + return Rx.Observable.from(actions); + }) + ); + + +/** + * when a story is loaded a check is done and all urls are replaced + * @param {external:Observable} action$ manages `START_UPDATING_RESOURCE` and `SET_CURRENT_STORY`. + * @memberof epics.autoresourceupdate + * @return {external:Observable} + */ +export const manageAutoGeostoryUpdateEpic = (action$, store) => + action$.ofType(START_UPDATING_RESOURCE) + .switchMap(() => + action$.ofType(SET_CURRENT_STORY) + .filter(({story}) => !isEmpty(story)) + .switchMap(({story}) => { + const state = store.getState(); + const options = getConfigProp('AutoResourceUpdateOptions'); + const showNotification = options?.showNotificationForRoles?.length && isUserAllowedSelectorCreator({ + allowedRoles: options.showNotificationForRoles + })(state); + + const newStory = migrateAllUrls(story, options.urlsToReplace); + let actions = []; + if (showNotification) { + // show only for allowed roles, do not show it by default + actions.push(warning({ + title: "notification.warning", + message: "notification.updateOldResource", + autoDismiss: options.autoDismiss, + position: "tc" + })); + } + actions.push(setCurrentStory(newStory)); + if (isEmpty(options)) { + return Rx.Observable.empty(); + } + return Rx.Observable.from(actions); + }) + ); diff --git a/js/epics/autoResourceUpdateContext.js b/js/epics/autoResourceUpdateContext.js new file mode 100644 index 0000000000..91fd9ea32a --- /dev/null +++ b/js/epics/autoResourceUpdateContext.js @@ -0,0 +1,59 @@ +/* + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. +*/ +import Rx from 'rxjs'; +import {isEmpty} from 'lodash'; + +import { LOAD_MAP_CONFIG, MAP_CONFIG_LOADED } from '../../MapStore2/web/client/actions/config'; +import { warning } from '../../MapStore2/web/client/actions/notifications'; +import { updateCatalogServices } from '../../MapStore2/web/client/actions/catalog'; +import { isUserAllowedSelectorCreator } from '../../MapStore2/web/client/selectors/security'; +import { replaceLayers } from '../../MapStore2/web/client/actions/layers'; + +import { getConfigProp } from '../../MapStore2/web/client/utils/ConfigUtils'; +import { migrateAllUrls } from '../utils/AutoResourceUpdateUtils'; + + +/** + * When map has been loaded, it sends a notification if the version is less than 2 and users has write permission. + * @param {external:Observable} action$ manages `MAP_CONFIG_LOADED` and `MAP_INFO_LOADED`. + * @memberof epics.autoresourceupdate + * @return {external:Observable} + */ +export const manageAutoContextUpdateEpic = (action$, store) => + action$.ofType(LOAD_MAP_CONFIG) + .switchMap(() => + action$.ofType(MAP_CONFIG_LOADED) + .take(1) + .switchMap((mapConfigLoaded) => { + const state = store.getState(); + const options = getConfigProp('AutoResourceUpdateOptions'); + const showNotification = options?.showNotificationForRoles?.length && isUserAllowedSelectorCreator({ + allowedRoles: options.showNotificationForRoles + })(state); + const layers = mapConfigLoaded?.config?.map?.layers || []; + const newLayers = migrateAllUrls(layers, options.urlsToReplace); + const services = mapConfigLoaded?.config?.catalogServices?.services; + const newServices = migrateAllUrls(services, options.urlsToReplace); + let actions = []; + if (showNotification) { + // show only for allowed roles, do not show it by default + actions.push(warning({ + title: "notification.warning", + message: "notification.updateOldResource", + autoDismiss: options.autoDismiss, + position: "tc" + })); + } + actions.push(updateCatalogServices(newServices)); + actions.push(replaceLayers(newLayers)); + if (isEmpty(options)) { + return Rx.Observable.empty(); + } + return Rx.Observable.from(actions); + }) + ); diff --git a/js/plugins.js b/js/plugins.js new file mode 100644 index 0000000000..3bec15f2b3 --- /dev/null +++ b/js/plugins.js @@ -0,0 +1,15 @@ + +import AutoResourceUpdate from './plugins/AutoResourceUpdate'; +import pluginsDef from '../MapStore2/web/client/product/plugins'; + +const { plugins, requires } = pluginsDef; + +export default { + plugins: { + ...plugins, + AutoResourceUpdatePlugin: AutoResourceUpdate + }, + requires: { + ...requires + } +}; diff --git a/js/plugins/AutoResourceUpdate.jsx b/js/plugins/AutoResourceUpdate.jsx new file mode 100644 index 0000000000..8dcee1fb83 --- /dev/null +++ b/js/plugins/AutoResourceUpdate.jsx @@ -0,0 +1,51 @@ +/* + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. +*/ + +import { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { createPlugin } from '@mapstore/utils/PluginsUtils'; + +import * as autoResourceUpdateEpic from '@js/epics/autoResourceUpdate'; +import { startUpdatingResource } from '@js/actions/autoResourceUpdate'; + +/** + * AutoResourceUpdate Plugin. + * It sends a notification to update resources only if showNotificationForRoles is defined and user match the role defined + * The notification will disappear after 10 seconds (See https://github.com/igorprado/react-notification-system for details) + * The updated resource is not automatically saved + * a generic configuration has been places in the root of locaConfig called "AutoResourceUpdateOptions". these are not plugin cfg + * @param {string[]} AutoResourceUpdateOptions.showNotificationForRoles the list of roles for which this notification must be visible, can be ["ALL","ADMIN","USER"], default is [] + * @param {number} AutoResourceUpdateOptions.autoDismiss seconds of duration of the notification popup + * @param {object} AutoResourceUpdateOptions.urlsToReplace list of key value pairs as object to replace the url + * @memberof plugins + */ +const AutoResourceUpdate = ({ + onStartUpdatingResource = () => {} +}) => { + useEffect(() => { + onStartUpdatingResource(); + }, []); + return null; +}; + +const AutoResourceUpdateConnected = connect(() => ({}), { + onStartUpdatingResource: startUpdatingResource +})(AutoResourceUpdate); + +export default createPlugin( + "AutoResourceUpdate", { + component: AutoResourceUpdateConnected, + epics: autoResourceUpdateEpic + }); + +AutoResourceUpdate.propTypes = { + showNotificationForRoles: PropTypes.array, + onStartUpdatingResource: PropTypes.func +}; diff --git a/js/pluginsDashboardEmbedded.js b/js/pluginsDashboardEmbedded.js new file mode 100644 index 0000000000..de2bb5be9b --- /dev/null +++ b/js/pluginsDashboardEmbedded.js @@ -0,0 +1,15 @@ + +import AutoResourceUpdate from './plugins/AutoResourceUpdate'; +import pluginsDef from '../MapStore2/web/client/product/pluginsDashboardEmbedded'; + +const { plugins, requires } = pluginsDef; + +export default { + plugins: { + ...plugins, + AutoResourceUpdatePlugin: AutoResourceUpdate + }, + requires: { + ...requires + } +}; diff --git a/js/pluginsGeostoryEmbedded.js b/js/pluginsGeostoryEmbedded.js new file mode 100644 index 0000000000..4825c7d193 --- /dev/null +++ b/js/pluginsGeostoryEmbedded.js @@ -0,0 +1,16 @@ + +import AutoResourceUpdate from './plugins/AutoResourceUpdate'; +import pluginsDef from '../MapStore2/web/client/product/pluginsGeostoryEmbedded'; + + +const { plugins, requires } = pluginsDef; + +export default { + plugins: { + ...plugins, + AutoResourceUpdatePlugin: AutoResourceUpdate + }, + requires: { + ...requires + } +}; diff --git a/js/utils/AutoResourceUpdateUtils.js b/js/utils/AutoResourceUpdateUtils.js new file mode 100644 index 0000000000..7494c205be --- /dev/null +++ b/js/utils/AutoResourceUpdateUtils.js @@ -0,0 +1,21 @@ +/* + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. +*/ + +/** + * if this function finds a segment of url it will replace all occurrences otherwise it will ignore old urls + * @param {object} objectToInspect js object to inspect for replacing strings + * @param {object} urlsToReplace the list of urls to replaces with key value pairs as object + * @returns + */ +export const migrateAllUrls = (objectToInspect, urlsToReplace) => { + let stringVar = JSON.stringify(objectToInspect); + Object.keys(urlsToReplace).forEach((url) => { + stringVar = stringVar.replaceAll(url, urlsToReplace[url]); + }); + return JSON.parse(stringVar); +}; diff --git a/translations/data.en-US.json b/translations/data.en-US.json index c8822c26a7..809cdee1bb 100644 --- a/translations/data.en-US.json +++ b/translations/data.en-US.json @@ -1,14 +1,17 @@ { - "locale": "en-US", - "messages": { - "home":{ - "shortDescription": "Data Resources and Territorial Information System of the Municipality of Florence
***The information contained in the cartography represents the best knowledge available to date of the managed infrastructures;
some may contain errors, some are indicative of the service and not exhaustive, others are subject to periodic update
(monthly, bimonthly or without a fixed frequency). For this information, you can access the metadata catalog or contact the SIT office.
", - "footerDescription": " " - }, - "streetView": { - "title": "Street", - "tooltip": "Open Street tool", - "emptyTitle": "Street" - } + "locale": "en-US", + "messages": { + "home": { + "shortDescription": "Data Resources and Territorial Information System of the Municipality of Florence
***The information contained in the cartography represents the best knowledge available to date of the managed infrastructures;
some may contain errors, some are indicative of the service and not exhaustive, others are subject to periodic update
(monthly, bimonthly or without a fixed frequency). For this information, you can access the metadata catalog or contact the SIT office.
", + "footerDescription": " " + }, + "streetView": { + "title": "Street", + "tooltip": "Open Street tool", + "emptyTitle": "Street" + }, + "notification": { + "updateOldResource": "This is an old resource which contains old urls, that will be updated accordingly" } + } } diff --git a/translations/data.it-IT.json b/translations/data.it-IT.json index 67ca71a966..b9f0c05a50 100644 --- a/translations/data.it-IT.json +++ b/translations/data.it-IT.json @@ -1,14 +1,17 @@ { - "locale": "it-IT", - "messages": { - "home":{ - "shortDescription": "Risorse Dati e Sistema Informativo Territoriale del Comune di Firenze
***Le informazioni contenute nella cartografia rappresentano la migliore conoscenza disponibile ad oggi delle infrastrutture gestite;
alcune possono contenere errori, alcune hanno carattere di mappatura indicativa del servizio e non esaustiva, altre sono soggette ad aggiornamento
periodico mensile o bimestrale o senza cadenza fissa. Per tali informazioni potete accedere al catalogo dei metadati o contattare l’ufficio SIT.
", - "footerDescription": " " - }, - "streetView": { - "title": "Street", - "tooltip": "Apri Street tool", - "emptyTitle": "Street" - } + "locale": "it-IT", + "messages": { + "home": { + "shortDescription": "Risorse Dati e Sistema Informativo Territoriale del Comune di Firenze
***Le informazioni contenute nella cartografia rappresentano la migliore conoscenza disponibile ad oggi delle infrastrutture gestite;
alcune possono contenere errori, alcune hanno carattere di mappatura indicativa del servizio e non esaustiva, altre sono soggette ad aggiornamento
periodico mensile o bimestrale o senza cadenza fissa. Per tali informazioni potete accedere al catalogo dei metadati o contattare l’ufficio SIT.
", + "footerDescription": " " + }, + "streetView": { + "title": "Street", + "tooltip": "Apri Street tool", + "emptyTitle": "Street" + }, + "notification": { + "updateOldResource": "Questa è una vecchia risorsa che contiene vecchi URL, che verranno aggiornati di conseguenza" } + } }