feat: allow to configure the default label keys per instance (#2291)

fix #2289

Let's add "nom" in OSM France and ANCT instances, then, to make users
experience smoother (a lot of imported data contains a "nom" column, and
no "name" nor "title").
This commit is contained in:
Yohan Boniface 2024-12-05 17:35:48 +01:00 committed by GitHub
commit 2c601e483d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 72 additions and 27 deletions

View file

@ -175,11 +175,6 @@ UMAP_EXTRA_URLS = {
} }
``` ```
#### UMAP_KEEP_VERSIONS
How many datalayer versions to keep. 10 by default.
#### UMAP_DEFAULT_EDIT_STATUS #### UMAP_DEFAULT_EDIT_STATUS
Define the map default edit status. Define the map default edit status.
@ -270,6 +265,16 @@ Available importers:
- `communesfr`: download French communes boundaries, from https://geo.api.gouv.fr/ - `communesfr`: download French communes boundaries, from https://geo.api.gouv.fr/
- `datasets`: define URLs you want to promote to users, with a `name` and a `format` - `datasets`: define URLs you want to promote to users, with a `name` and a `format`
#### UMAP_KEEP_VERSIONS
How many datalayer versions to keep. 10 by default.
#### UMAP_LABEL_KEYS
List of properties to consider as "Feature label" (to show in popup or in browser).
UMAP_LABEL_KEYS = ["name", "title"]
#### UMAP_MAPS_PER_PAGE #### UMAP_MAPS_PER_PAGE
How many maps to show in maps list, like search or home page. How many maps to show in maps list, like search or home page.

View file

@ -273,6 +273,7 @@ UMAP_HOME_FEED = "latest"
UMAP_IMPORTERS = {} UMAP_IMPORTERS = {}
UMAP_HOST_INFOS = {} UMAP_HOST_INFOS = {}
UMAP_PURGATORY_ROOT = "/tmp/umappurgatory" UMAP_PURGATORY_ROOT = "/tmp/umappurgatory"
UMAP_LABEL_KEYS = ["name", "title"]
UMAP_READONLY = env("UMAP_READONLY", default=False) UMAP_READONLY = env("UMAP_READONLY", default=False)
UMAP_GZIP = True UMAP_GZIP = True

View file

@ -165,7 +165,9 @@ class Feature {
} }
getSlug() { getSlug() {
return this.properties[this._umap.getProperty('slugKey') || 'name'] || '' return (
this.properties[this._umap.getProperty('slugKey') || U.DEFAULT_LABEL_KEY] || ''
)
} }
getPermalink() { getPermalink() {
@ -234,15 +236,23 @@ class Feature {
container.appendChild(builder.build()) container.appendChild(builder.build())
const properties = [] const properties = []
let labelKeyFound = undefined
for (const property of this.datalayer._propertiesIndex) { for (const property of this.datalayer._propertiesIndex) {
if (['name', 'description'].includes(property)) { if (!labelKeyFound && U.LABEL_KEYS.includes(property)) {
labelKeyFound = property
continue
}
if (property === 'description') {
continue continue
} }
properties.push([`properties.${property}`, { label: property }]) properties.push([`properties.${property}`, { label: property }])
} }
// We always want name and description for now (properties management to come) // We always want name and description for now (properties management to come)
properties.unshift('properties.description') properties.unshift('properties.description')
properties.unshift('properties.name') if (!labelKeyFound) {
labelKeyFound = U.DEFAULT_LABEL_KEY
}
properties.unshift([`properties.${labelKeyFound}`, { label: labelKeyFound }])
builder = new U.FormBuilder(this, properties, { builder = new U.FormBuilder(this, properties, {
id: 'umap-feature-properties', id: 'umap-feature-properties',
}) })
@ -255,7 +265,7 @@ class Feature {
this.getAdvancedEditActions(advancedActions) this.getAdvancedEditActions(advancedActions)
const onLoad = this._umap.editPanel.open({ content: container }) const onLoad = this._umap.editPanel.open({ content: container })
onLoad.then(() => { onLoad.then(() => {
builder.helpers['properties.name'].input.focus() builder.helpers[`properties.${labelKeyFound}`].input.focus()
}) })
this._umap.editedFeature = this this._umap.editedFeature = this
if (!this.ui.isOnScreen(this._umap._leafletMap.getBounds())) this.zoomTo(event) if (!this.ui.isOnScreen(this._umap._leafletMap.getBounds())) this.zoomTo(event)
@ -316,19 +326,21 @@ class Feature {
endEdit() {} endEdit() {}
getDisplayName(fallback) { getDisplayName() {
const key = this.getOption('labelKey') || 'name' const keys = U.LABEL_KEYS.slice() // Copy.
const labelKey = this.getOption('labelKey')
// Variables mode. // Variables mode.
if (Utils.hasVar(key)) { if (labelKey) {
return Utils.greedyTemplate(key, this.extendedProperties()) if (Utils.hasVar(labelKey)) {
return Utils.greedyTemplate(labelKey, this.extendedProperties())
}
keys.unshift(labelKey)
} }
// Simple mode. for (const key of keys) {
return ( const value = this.properties[key]
this.properties[key] || if (value) return value
this.properties.title || }
fallback || return this.datalayer.getName()
this.datalayer.getName()
)
} }
hasPopupFooter() { hasPopupFooter() {

View file

@ -289,7 +289,7 @@ export class DataLayer extends ServerStored {
reindex() { reindex() {
const features = Object.values(this._features) const features = Object.values(this._features)
Utils.sortFeatures(features, this._umap.getProperty('sortKey'), U.lang) this.sortFeatures(features)
this._index = features.map((feature) => stamp(feature)) this._index = features.map((feature) => stamp(feature))
} }
@ -450,6 +450,11 @@ export class DataLayer extends ServerStored {
} }
} }
sortFeatures(collection) {
const sortKeys = this._umap.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
return Utils.sortFeatures(collection, sortKeys, U.lang)
}
makeFeatures(geojson = {}, sync = true) { makeFeatures(geojson = {}, sync = true) {
if (geojson.type === 'Feature' || geojson.coordinates) { if (geojson.type === 'Feature' || geojson.coordinates) {
geojson = [geojson] geojson = [geojson]
@ -458,7 +463,7 @@ export class DataLayer extends ServerStored {
? geojson ? geojson
: geojson.features || geojson.geometries : geojson.features || geojson.geometries
if (!collection) return if (!collection) return
Utils.sortFeatures(collection, this._umap.getProperty('sortKey'), U.lang) this.sortFeatures(collection)
for (const feature of collection) { for (const feature of collection) {
this.makeFeature(feature, sync) this.makeFeature(feature, sync)
} }

View file

@ -115,7 +115,9 @@ class Table extends TitleMixin(PopupTemplate) {
const table = document.createElement('table') const table = document.createElement('table')
for (const key in feature.properties) { for (const key in feature.properties) {
if (typeof feature.properties[key] === 'object' || key === 'name') continue if (typeof feature.properties[key] === 'object' || U.LABEL_KEYS.includes(key)) {
continue
}
table.appendChild(this.makeRow(feature, key)) table.appendChild(this.makeRow(feature, key))
} }
return table return table

View file

@ -75,7 +75,7 @@ const FeatureMixin = {
resetTooltip: function () { resetTooltip: function () {
if (!this.feature.hasGeom()) return if (!this.feature.hasGeom()) return
const displayName = this.feature.getDisplayName(null) const displayName = this.feature.getDisplayName()
let showLabel = this.feature.getOption('showLabel') let showLabel = this.feature.getOption('showLabel')
const oldLabelHover = this.feature.getOption('labelHover') const oldLabelHover = this.feature.getOption('labelHover')

View file

@ -105,7 +105,7 @@ export default class TableEditor extends WithTemplate {
resetProperties() { resetProperties() {
this.properties = this.datalayer._propertiesIndex this.properties = this.datalayer._propertiesIndex
if (this.properties.length === 0) { if (this.properties.length === 0) {
this.properties = ['name', 'description'] this.properties = [U.DEFAULT_LABEL_KEY, 'description']
} }
} }

View file

@ -71,6 +71,10 @@ export default class Umap extends ServerStored {
// To be used in javascript APIs // To be used in javascript APIs
if (geojson.properties.lang) U.lang = geojson.properties.lang if (geojson.properties.lang) U.lang = geojson.properties.lang
// Make it available to utils, without needing a reference to `Umap`.
U.LABEL_KEYS = geojson.properties.defaultLabelKeys || []
U.DEFAULT_LABEL_KEY = U.LABEL_KEYS[0] || 'name'
this.setPropertiesFromQueryString() this.setPropertiesFromQueryString()
// Needed for actions labels // Needed for actions labels

View file

@ -292,7 +292,7 @@ export function naturalSort(a, b, lang) {
} }
export function sortFeatures(features, sortKey, lang) { export function sortFeatures(features, sortKey, lang) {
const sortKeys = (sortKey || 'name').split(',') const sortKeys = sortKey.split(',')
const sort = (a, b, i) => { const sort = (a, b, i) => {
let sortKey = sortKeys[i] let sortKey = sortKeys[i]

View file

@ -15,7 +15,12 @@ DATALAYER_DATA = {
"features": [ "features": [
{ {
"type": "Feature", "type": "Feature",
"properties": {"name": "one point in france", "foo": "point", "bar": "one"}, "properties": {
"name": "one point in france",
"foo": "point",
"bar": "one",
"label": "this is label one",
},
"geometry": {"type": "Point", "coordinates": [3.339844, 46.920255]}, "geometry": {"type": "Point", "coordinates": [3.339844, 46.920255]},
}, },
{ {
@ -24,6 +29,7 @@ DATALAYER_DATA = {
"name": "one polygon in greenland", "name": "one polygon in greenland",
"foo": "polygon", "foo": "polygon",
"bar": "two", "bar": "two",
"label": "this is label two",
}, },
"geometry": { "geometry": {
"type": "Polygon", "type": "Polygon",
@ -44,6 +50,7 @@ DATALAYER_DATA = {
"name": "one line in new zeland", "name": "one line in new zeland",
"foo": "line", "foo": "line",
"bar": "three", "bar": "three",
"label": "this is label three",
}, },
"geometry": { "geometry": {
"type": "LineString", "type": "LineString",
@ -476,3 +483,11 @@ def test_main_toolbox_toggle_all_layers(live_server, map, page):
# Should hidden again all layers # Should hidden again all layers
expect(page.locator(".datalayer.off")).to_have_count(3) expect(page.locator(".datalayer.off")).to_have_count(3)
expect(markers).to_have_count(0) expect(markers).to_have_count(0)
def test_honour_the_label_fields_settings(live_server, map, page, bootstrap, settings):
settings.UMAP_LABEL_KEYS = ["label", "name"]
page.goto(f"{live_server.url}{map.get_absolute_url()}")
expect(page.locator(".panel").get_by_text("this is label one")).to_be_visible()
expect(page.locator(".panel").get_by_text("this is label two")).to_be_visible()
expect(page.locator(".panel").get_by_text("this is label three")).to_be_visible()

View file

@ -609,6 +609,7 @@ class MapDetailMixin(SessionMixin):
"websocketEnabled": settings.WEBSOCKET_ENABLED, "websocketEnabled": settings.WEBSOCKET_ENABLED,
"websocketURI": settings.WEBSOCKET_FRONT_URI, "websocketURI": settings.WEBSOCKET_FRONT_URI,
"importers": settings.UMAP_IMPORTERS, "importers": settings.UMAP_IMPORTERS,
"defaultLabelKeys": settings.UMAP_LABEL_KEYS,
} }
created = bool(getattr(self, "object", None)) created = bool(getattr(self, "object", None))
if (created and self.object.owner) or (not created and not user.is_anonymous): if (created and self.object.owner) or (not created and not user.is_anonymous):