diff --git a/web/client/api/geoserver/Styles.js b/web/client/api/geoserver/Styles.js index 1db3afb5cb..9b914b5a35 100644 --- a/web/client/api/geoserver/Styles.js +++ b/web/client/api/geoserver/Styles.js @@ -223,8 +223,8 @@ export const getStylesInfo = ({baseUrl: geoserverBaseUrl, styles = []}) => { if (!styles || styles.length === 0) { resolve([]); } else { - styles.forEach(({name}, idx) => - axios.get(getStyleBaseUrl({...getNameParts(name), geoserverBaseUrl})) + styles.forEach(({ name, href }, idx) => + axios.get(href || getStyleBaseUrl({...getNameParts(name), geoserverBaseUrl})) .then(({data}) => { responses[idx] = assign({}, styles[idx], data && data.style && { ...data.style, diff --git a/web/client/components/styleeditor/StyleList.jsx b/web/client/components/styleeditor/StyleList.jsx index a97b101d19..4b5bc07716 100644 --- a/web/client/components/styleeditor/StyleList.jsx +++ b/web/client/components/styleeditor/StyleList.jsx @@ -78,7 +78,7 @@ const StyleList = ({ }> onSelect({ style: defaultStyle === name ? '' : name }, true)} + onItemClick={({ name }) => onSelect({ style: name }, true)} items={availableStyles .filter(({name = '', title = '', _abstract = '', metadata = {} }) => !filterText || filterText && ( diff --git a/web/client/components/styleeditor/__tests__/StyleList-test.jsx b/web/client/components/styleeditor/__tests__/StyleList-test.jsx index 784f5537a5..a0882f2bc3 100644 --- a/web/client/components/styleeditor/__tests__/StyleList-test.jsx +++ b/web/client/components/styleeditor/__tests__/StyleList-test.jsx @@ -159,7 +159,7 @@ describe('test StyleList module component', () => { TestUtils.Simulate.click(cards[0]); - expect(spyOnSelect).toHaveBeenCalledWith({style: ''}, true); + expect(spyOnSelect).toHaveBeenCalledWith({ style: 'point' }, true); }); diff --git a/web/client/configs/localConfig.json b/web/client/configs/localConfig.json index 359f6e2a40..055f5bfc30 100644 --- a/web/client/configs/localConfig.json +++ b/web/client/configs/localConfig.json @@ -8,7 +8,9 @@ "https://nominatim.openstreetmap.org", "http://cloudsdi.geo-solutions.it", "https://gs-stable.geo-solutions.it/geoserver", - "https://gs-stable.geo-solutions.it:443/geoserver" + "https://gs-stable.geo-solutions.it:443/geoserver", + "http://gs-stable.geo-solutions.it/geoserver", + "http://gs-stable.geo-solutions.it:443/geoserver" ] }, "geoStoreUrl": "rest/geostore/", diff --git a/web/client/epics/__tests__/styleeditor-test.js b/web/client/epics/__tests__/styleeditor-test.js index 447cad61f3..cc69895b83 100644 --- a/web/client/epics/__tests__/styleeditor-test.js +++ b/web/client/epics/__tests__/styleeditor-test.js @@ -23,6 +23,7 @@ import { import { REMOVE_ADDITIONAL_LAYER, + UPDATE_ADDITIONAL_LAYER, UPDATE_OPTIONS_BY_OWNER } from '../../actions/additionallayers'; @@ -1378,4 +1379,108 @@ describe('Test styleeditor epics, with mock axios', () => { }); + it('toggleStyleEditorEpic: test request via GeoServer rest api', (done) => { + + mockAxios.onGet(/\/manifest/).reply(() => { + return [ 200, { about: { resource: [{ '@name': 'gt-css-2.16' }]} }]; + }); + + mockAxios.onGet(/\/version/).reply(() => { + return [ 200, { about: { resource: [{ '@name': 'GeoServer', version: '2.16' }] } }]; + }); + + mockAxios.onGet(/\/fonts/).reply(() => { + return [ 200, { fonts: ['Arial'] }]; + }); + + mockAxios.onGet(/\/rest\/layers/).reply(() => { + return [ 200, { layer: { + defaultStyle: { + name: 'layerWorkspace:style_01', + workspace: 'layerWorkspace' + }, + styles: { + style: [{ + name: 'layerWorkspace:style_01', + workspace: 'layerWorkspace' + }, { + name: 'layerWorkspace:style_02', + workspace: 'layerWorkspace' + }, { + name: 'style_03', + workspace: '' + }] + } + }}]; + }); + + const state = { + layers: { + flat: [ + { + id: 'layerId', + name: 'layerWorkspace:layerName', + url: 'protocol://style-editor/geoserver/' + } + ], + selected: [ + 'layerId' + ], + settings: { + options: { + opacity: 1 + } + } + } + }; + const NUMBER_OF_ACTIONS = 5; + + const results = (actions) => { + try { + const [ + loadingStyleAction, + resetStyle, + initStyleServiceAction, + updateAdditionalLayerAction, + updateSettingsParamsAction + ] = actions; + + expect(resetStyle.type).toBe(RESET_STYLE_EDITOR); + expect(loadingStyleAction.type).toBe(LOADING_STYLE); + expect(initStyleServiceAction.type).toBe(INIT_STYLE_SERVICE); + expect(initStyleServiceAction.service).toEqual({ + baseUrl: 'protocol://style-editor/geoserver/', + version: '2.16', + formats: [ 'css', 'sld' ], + availableUrls: [], + fonts: ['Arial'], + classificationMethods: { + vector: [ 'equalInterval', 'quantile', 'jenks' ], + raster: [ 'equalInterval', 'quantile', 'jenks' ] + } + }); + expect(updateAdditionalLayerAction.type).toBe(UPDATE_ADDITIONAL_LAYER); + expect(updateSettingsParamsAction.type).toBe(UPDATE_SETTINGS_PARAMS); + expect(updateSettingsParamsAction.newParams).toEqual({ + availableStyles: [ + { name: 'layerWorkspace:style_01', workspace: 'layerWorkspace' }, + { name: 'layerWorkspace:style_02', workspace: 'layerWorkspace' }, + { name: 'style_03', workspace: '' } + ] + }); + } catch (e) { + done(e); + } + done(); + }; + + testEpic( + toggleStyleEditorEpic, + NUMBER_OF_ACTIONS, + toggleStyleEditor(undefined, true), + results, + state); + + }); + }); diff --git a/web/client/epics/styleeditor.js b/web/client/epics/styleeditor.js index 05e019584c..717407e24a 100644 --- a/web/client/epics/styleeditor.js +++ b/web/client/epics/styleeditor.js @@ -8,7 +8,7 @@ import Rx from 'rxjs'; -import { get, head, isArray, template } from 'lodash'; +import { get, head, isArray, template, uniqBy } from 'lodash'; import { success, error } from '../actions/notifications'; import { UPDATE_NODE, updateNode, updateSettingsParams } from '../actions/layers'; import { updateAdditionalLayer, removeAdditionalLayer, updateOptionsByOwner } from '../actions/additionallayers'; @@ -174,6 +174,39 @@ const updateLayerSettingsObservable = (action$, store, filter = () => true, star .takeUntil(action$.ofType(LOADED_STYLE)) ); + +function getAvailableStylesFromLayerCapabilities(layer, reset) { + if (!reset && layer.availableStyles) { + return Rx.Observable.of( + updateSettingsParams({ availableStyles: layer.availableStyles }), + loadedStyle() + ); + } + return getLayerCapabilities(layer) + .switchMap((capabilities) => { + const layerCapabilities = formatCapabitiliesOptions(capabilities); + if (!layerCapabilities.availableStyles) { + return Rx.Observable.of( + errorStyle('availableStyles', { status: 401 }), + loadedStyle() + ); + } + + return Rx.Observable.of( + updateSettingsParams({ availableStyles: layerCapabilities.availableStyles }), + updateNode(layer.id, 'layer', { ...layerCapabilities }), + loadedStyle() + ); + + }) + .catch((err) => { + const errorType = err.message.indexOf("could not be unmarshalled") !== -1 ? "parsingCapabilities" : "global"; + return Rx.Observable.of(errorStyle(errorType, err), loadedStyle()); + }) + .startWith(loadingStyle('global')); +} + + /** * Epics for Style Editor * @name epics.styleeditor @@ -206,73 +239,87 @@ export const toggleStyleEditorEpic = (action$, store) => const geoserverName = findGeoServerName(layer); if (!geoserverName) { - if (layer.availableStyles) { - return Rx.Observable.of( - updateSettingsParams({ availableStyles: layer.availableStyles }), - loadedStyle() - ); - } - return getLayerCapabilities(layer) - .switchMap((capabilities) => { - const layerCapabilities = formatCapabitiliesOptions(capabilities); - if (!layerCapabilities.availableStyles) { - return Rx.Observable.of( - errorStyle('availableStyles', { status: 401 }), - loadedStyle() - ); - } - - return Rx.Observable.of( - updateSettingsParams({ availableStyles: layerCapabilities.availableStyles }), - updateNode(layer.id, 'layer', { ...layerCapabilities }), - loadedStyle() - ); - - }).startWith(loadingStyle('global')); + return getAvailableStylesFromLayerCapabilities(layer); } const layerUrl = layer.url.split(geoserverName); const baseUrl = `${layerUrl[0]}${geoserverName}`; const lastStyleService = styleServiceSelector(state); - return Rx.Observable - .defer(() => updateStyleService({ + return Rx.Observable.concat( + Rx.Observable.of( + loadingStyle('global'), + resetStyleEditor() + ), + Rx.Observable.defer(() => updateStyleService({ baseUrl, styleService: lastStyleService })) - .switchMap((styleService) => { - const initialAction = [ initStyleService(styleService) ]; - return getLayerCapabilities(layer) - .switchMap((capabilities) => { - const layerCapabilities = formatCapabitiliesOptions(capabilities); - if (!layerCapabilities.availableStyles) { - return Rx.Observable.of( - errorStyle('availableStyles', { status: 401 }), - loadedStyle() - ); - } - const setAdditionalLayers = (availableStyles = []) => - Rx.Observable.of( - updateAdditionalLayer(layer.id, STYLE_OWNER_NAME, 'override', {}), - updateSettingsParams({ availableStyles }), - updateNode(layer.id, 'layer', {...layerCapabilities, availableStyles}), - loadedStyle() - ); - return Rx.Observable.defer(() => - StylesAPI.getStylesInfo({ - baseUrl, - styles: layerCapabilities && layerCapabilities.availableStyles || [] - }) + .switchMap((styleService) => { + return Rx.Observable.concat( + Rx.Observable.of(initStyleService(styleService)), + Rx.Observable.defer(() => + // use rest API to get the correct name and workspace of the styles + LayersAPI.getLayer(baseUrl + 'rest/', layer.name) ) - .switchMap(availableStyles => setAdditionalLayers(availableStyles)); - }) - .startWith(...initialAction) - .catch((err) => { - const errorType = err.message.indexOf("could not be unmarshalled") !== -1 ? "parsingCapabilities" : "global"; - return Rx.Observable.of(errorStyle(errorType, err), loadedStyle()); - }); - }) - .startWith(loadingStyle('global'), resetStyleEditor()); + .switchMap((layerConfig) => { + const stylesConfig = layerConfig?.styles?.style || []; + const layerConfigAvailableStyles = uniqBy([ + layerConfig.defaultStyle, + ...stylesConfig + ], 'name'); + if (layerConfigAvailableStyles.length === 0) { + return Rx.Observable.of( + errorStyle('availableStyles', { status: 401 }), + loadedStyle() + ); + } + + return Rx.Observable.defer(() => + Promise.all([ + StylesAPI.getStylesInfo({ + baseUrl, + styles: layerConfigAvailableStyles + }), + getLayerCapabilities(layer) + .toPromise() + .then(cap => cap) + .catch(() => null) + ]) + ) + .switchMap(([availableStylesRest, capabilities]) => { + const layerCapabilities = capabilities && formatCapabitiliesOptions(capabilities); + const availableStylesCap = (layerCapabilities?.availableStyles || []) + .map((style) => ({ ...style, ...getNameParts(style.name) })) + .filter(({ name } = {}) => name); + + // get title information from capabilities + const availableStyles = availableStylesCap.length > 0 + ? availableStylesRest.map(restStyle => { + const parts = getNameParts(restStyle.name); + const { name, workspace, ...capStyle} = availableStylesCap.find(style => style.name === parts.name) || {}; + if (capStyle) { + return { ...capStyle, ...restStyle }; + } + return restStyle; + }) + : availableStylesRest; + + return Rx.Observable.of( + updateAdditionalLayer(layer.id, STYLE_OWNER_NAME, 'override', {}), + updateSettingsParams({ availableStyles }), + updateNode(layer.id, 'layer', { availableStyles }), + loadedStyle() + ); + }); + }) + .catch(() => { + // fallback to get capabilities to get list of styles + return getAvailableStylesFromLayerCapabilities(layer, true); + }) + ); + }) + ); }); /** * Gets every `UPDATE_STATUS` event. @@ -711,4 +758,3 @@ export default { deleteStyleEpic, setDefaultStyleEpic }; -