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

#7422: StyleEditor plugin must not rely on the workspace prefix inside the style names returned by GetCapabilities #7423

Merged
merged 5 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions web/client/api/geoserver/Styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion web/client/components/styleeditor/StyleList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const StyleList = ({
}>
<SideGrid
size="sm"
onItemClick={({ name }) => onSelect({ style: defaultStyle === name ? '' : name }, true)}
onItemClick={({ name }) => onSelect({ style: name }, true)}
items={availableStyles
.filter(({name = '', title = '', _abstract = '', metadata = {} }) => !filterText
|| filterText && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});


Expand Down
4 changes: 3 additions & 1 deletion web/client/configs/localConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down
104 changes: 104 additions & 0 deletions web/client/epics/__tests__/styleeditor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {

import {
REMOVE_ADDITIONAL_LAYER,
UPDATE_ADDITIONAL_LAYER,
UPDATE_OPTIONS_BY_OWNER
} from '../../actions/additionallayers';

Expand Down Expand Up @@ -1378,4 +1379,107 @@ 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: 'notLayerWorkspace:style_03',
workspace: 'notLayerWorkspace'
}]
}
}}];
});

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' }
]
});
} catch (e) {
done(e);
}
done();
};

testEpic(
toggleStyleEditorEpic,
NUMBER_OF_ACTIONS,
toggleStyleEditor(undefined, true),
results,
state);

});

});
167 changes: 108 additions & 59 deletions web/client/epics/styleeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -206,73 +239,90 @@ 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 { workspace: layerWorkspace } = getNameParts(layer.name);
const stylesConfig = layerConfig?.styles?.style || [];
const layerConfigAvailableStyles = uniqBy([
layerConfig.defaultStyle,
...stylesConfig
], 'name')
// show only styles included in the same workspace
.filter((style) => style?.name && style?.workspace === layerWorkspace);
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.
Expand Down Expand Up @@ -711,4 +761,3 @@ export default {
deleteStyleEpic,
setDefaultStyleEpic
};